001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.core;
019:
020: import org.apache.avalon.cornerstone.services.store.Store;
021: import org.apache.avalon.framework.activity.Initializable;
022: import org.apache.avalon.framework.component.Composable;
023: import org.apache.avalon.framework.service.DefaultServiceManager;
024: import org.apache.avalon.framework.service.ServiceManager;
025: import org.apache.avalon.framework.service.ServiceException;
026: import org.apache.avalon.framework.service.Serviceable;
027: import org.apache.avalon.framework.configuration.Configurable;
028: import org.apache.avalon.framework.configuration.Configuration;
029: import org.apache.avalon.framework.configuration.ConfigurationException;
030: import org.apache.avalon.framework.configuration.DefaultConfiguration;
031: import org.apache.avalon.framework.container.ContainerUtil;
032: import org.apache.avalon.framework.context.Context;
033: import org.apache.avalon.framework.context.ContextException;
034: import org.apache.avalon.framework.context.Contextualizable;
035: import org.apache.avalon.framework.logger.AbstractLogEnabled;
036: import org.apache.avalon.framework.logger.LogEnabled;
037: import org.apache.commons.collections.ReferenceMap;
038:
039: import java.util.HashMap;
040: import java.util.Map;
041:
042: /**
043: * Provides a registry of mail repositories. A mail repository is uniquely
044: * identified by its destinationURL, type and model.
045: *
046: */
047: public class AvalonMailStore extends AbstractLogEnabled implements
048: Contextualizable, Serviceable, Configurable, Initializable,
049: Store {
050:
051: // Prefix for repository names
052: private static final String REPOSITORY_NAME = "Repository";
053:
054: // Static variable used to name individual repositories. Should only
055: // be accessed when a lock on the AvalonMailStore.class is held
056: private static long id;
057:
058: // map of [destinationURL + type]->Repository
059: private Map repositories;
060:
061: // map of [protocol(destinationURL) + type ]->classname of repository;
062: private Map classes;
063:
064: // map of [protocol(destinationURL) + type ]->default config for repository.
065: private Map defaultConfigs;
066:
067: /**
068: * The Avalon context used by the instance
069: */
070: protected Context context;
071:
072: /**
073: * The Avalon configuration used by the instance
074: */
075: protected Configuration configuration;
076:
077: /**
078: * The Avalon component manager used by the instance
079: */
080: protected ServiceManager m_manager;
081:
082: /**
083: * @see org.apache.avalon.framework.context.Contextualizable#contextualize(Context)
084: */
085: public void contextualize(final Context context)
086: throws ContextException {
087: this .context = context;
088: }
089:
090: /**
091: * @see org.apache.avalon.framework.service.Servicable#service(ServiceManager)
092: */
093: public void service(final ServiceManager manager)
094: throws ServiceException {
095: DefaultServiceManager def_manager = new DefaultServiceManager(
096: manager);
097: def_manager.put(Store.ROLE, this );
098: m_manager = def_manager;
099: }
100:
101: /**
102: * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
103: */
104: public void configure(final Configuration configuration)
105: throws ConfigurationException {
106: this .configuration = configuration;
107: }
108:
109: /**
110: * @see org.apache.avalon.framework.activity.Initializable#initialize()
111: */
112: public void initialize() throws Exception {
113:
114: getLogger().info("JamesMailStore init...");
115: repositories = new ReferenceMap();
116: classes = new HashMap();
117: defaultConfigs = new HashMap();
118: Configuration[] registeredClasses = configuration.getChild(
119: "repositories").getChildren("repository");
120: for (int i = 0; i < registeredClasses.length; i++) {
121: registerRepository(registeredClasses[i]);
122: }
123:
124: }
125:
126: /**
127: * <p>Registers a new mail repository type in the mail store's
128: * registry based upon a passed in <code>Configuration</code> object.</p>
129: *
130: * <p>This is presumably synchronized to prevent corruption of the
131: * internal registry.</p>
132: *
133: * @param repConf the Configuration object used to register the
134: * repository
135: *
136: * @throws ConfigurationException if an error occurs accessing the
137: * Configuration object
138: */
139: public synchronized void registerRepository(Configuration repConf)
140: throws ConfigurationException {
141: String className = repConf.getAttribute("class");
142: boolean infoEnabled = getLogger().isInfoEnabled();
143: Configuration[] protocols = repConf.getChild("protocols")
144: .getChildren("protocol");
145: Configuration[] types = repConf.getChild("types").getChildren(
146: "type");
147: for (int i = 0; i < protocols.length; i++) {
148: String protocol = protocols[i].getValue();
149:
150: // Get the default configuration for these protocol/type combinations.
151: Configuration defConf = repConf.getChild("config");
152:
153: for (int j = 0; j < types.length; j++) {
154: String type = types[j].getValue();
155: String key = protocol + type;
156: if (infoEnabled) {
157: StringBuffer infoBuffer = new StringBuffer(128)
158: .append(
159: "Registering Repository instance of class ")
160: .append(className)
161: .append(" to handle ")
162: .append(protocol)
163: .append(
164: " protocol requests for repositories of type ")
165: .append(type);
166: getLogger().info(infoBuffer.toString());
167: }
168: if (classes.get(key) != null) {
169: throw new ConfigurationException(
170: "The combination of protocol and type comprise a unique key for repositories. This constraint has been violated. Please check your repository configuration.");
171: }
172: classes.put(key, className);
173: if (defConf != null) {
174: defaultConfigs.put(key, defConf);
175: }
176: }
177: }
178:
179: }
180:
181: /**
182: * This method accept a Configuration object as hint and return the
183: * corresponding MailRepository.
184: * The Configuration must be in the form of:
185: * <repository destinationURL="[URL of this mail repository]"
186: * type="[repository type ex. OBJECT or STREAM or MAIL etc.]"
187: * model="[repository model ex. PERSISTENT or CACHE etc.]">
188: * [addition configuration]
189: * </repository>
190: *
191: * @param hint the Configuration object used to look up the repository
192: *
193: * @return the selected repository
194: *
195: * @throws ServiceException if any error occurs while parsing the
196: * Configuration or retrieving the
197: * MailRepository
198: */
199: public synchronized Object select(Object hint)
200: throws ServiceException {
201: Configuration repConf = null;
202: try {
203: repConf = (Configuration) hint;
204: } catch (ClassCastException cce) {
205: throw new ServiceException(
206: "",
207: "hint is of the wrong type. Must be a Configuration",
208: cce);
209: }
210: String destination = null;
211: String protocol = null;
212: try {
213: destination = repConf.getAttribute("destinationURL");
214: int idx = destination.indexOf(':');
215: if (idx == -1)
216: throw new ServiceException("",
217: "destination is malformed. Must be a valid URL: "
218: + destination);
219: protocol = destination.substring(0, idx);
220: } catch (ConfigurationException ce) {
221: throw new ServiceException(
222: "",
223: "Malformed configuration has no destinationURL attribute",
224: ce);
225: }
226:
227: try {
228: String type = repConf.getAttribute("type");
229: String repID = destination + type;
230: Object reply = repositories.get(repID);
231: StringBuffer logBuffer = null;
232: if (reply != null) {
233: if (getLogger().isDebugEnabled()) {
234: logBuffer = new StringBuffer(128).append(
235: "obtained repository: ").append(repID)
236: .append(",").append(reply.getClass());
237: getLogger().debug(logBuffer.toString());
238: }
239: return reply;
240: } else {
241: String key = protocol + type;
242: String repClass = (String) classes.get(key);
243:
244: if (getLogger().isDebugEnabled()) {
245: logBuffer = new StringBuffer(128).append(
246: "obtained repository: ").append(repClass)
247: .append(" to handle: ").append(protocol)
248: .append(",").append(type);
249: getLogger().debug(logBuffer.toString());
250: }
251:
252: // If default values have been set, create a new repository
253: // configuration element using the default values
254: // and the values in the selector.
255: // If no default values, just use the selector.
256: Configuration config;
257: Configuration defConf = (Configuration) defaultConfigs
258: .get(key);
259: if (defConf == null) {
260: config = repConf;
261: } else {
262: config = new DefaultConfiguration(
263: repConf.getName(), repConf.getLocation());
264: copyConfig(defConf, (DefaultConfiguration) config);
265: copyConfig(repConf, (DefaultConfiguration) config);
266: }
267:
268: try {
269: reply = this .getClass().getClassLoader().loadClass(
270: repClass).newInstance();
271: if (reply instanceof LogEnabled) {
272: setupLogger(reply);
273: }
274: ContainerUtil.contextualize(reply, context);
275: ContainerUtil.service(reply, m_manager);
276:
277: if (reply instanceof Composable) {
278: final String error = "no implementation in place to support Composable";
279: getLogger().error(error);
280: throw new IllegalArgumentException(error);
281: }
282:
283: ContainerUtil.configure(reply, config);
284: ContainerUtil.initialize(reply);
285:
286: repositories.put(repID, reply);
287: if (getLogger().isInfoEnabled()) {
288: logBuffer = new StringBuffer(128).append(
289: "added repository: ").append(repID)
290: .append("->").append(repClass);
291: getLogger().info(logBuffer.toString());
292: }
293: return reply;
294: } catch (Exception e) {
295: if (getLogger().isWarnEnabled()) {
296: getLogger().warn(
297: "Exception while creating repository:"
298: + e.getMessage(), e);
299: }
300: throw new ServiceException("",
301: "Cannot find or init repository", e);
302: }
303: }
304: } catch (final ConfigurationException ce) {
305: throw new ServiceException("", "Malformed configuration",
306: ce);
307: }
308: }
309:
310: /**
311: * <p>Returns a new name for a repository.</p>
312: *
313: * <p>Synchronized on the AvalonMailStore.class object to ensure
314: * against duplication of the repository name</p>
315: *
316: * @return a new repository name
317: */
318: public static final String getName() {
319: synchronized (AvalonMailStore.class) {
320: return REPOSITORY_NAME + id++;
321: }
322: }
323:
324: /**
325: * Returns whether the mail store has a repository corresponding to
326: * the passed in hint.
327: *
328: * @param hint the Configuration object used to look up the repository
329: *
330: * @return whether the mail store has a repository corresponding to this hint
331: */
332: public boolean isSelectable(Object hint) {
333: Object comp = null;
334: try {
335: comp = select(hint);
336: } catch (ServiceException ex) {
337: if (getLogger().isErrorEnabled()) {
338: getLogger().error(
339: "Exception AvalonMailStore.hasComponent-"
340: + ex.toString());
341: }
342: }
343: return (comp != null);
344: }
345:
346: /**
347: * Copies values from one config into another, overwriting duplicate attributes
348: * and merging children.
349: *
350: * @param fromConfig the Configuration to be copied
351: * @param toConfig the Configuration to which data is being copied
352: */
353: private void copyConfig(Configuration fromConfig,
354: DefaultConfiguration toConfig) {
355: // Copy attributes
356: String[] attrs = fromConfig.getAttributeNames();
357: for (int i = 0; i < attrs.length; i++) {
358: String attrName = attrs[i];
359: String attrValue = fromConfig.getAttribute(attrName, null);
360: toConfig.setAttribute(attrName, attrValue);
361: }
362:
363: // Copy children
364: Configuration[] children = fromConfig.getChildren();
365: for (int i = 0; i < children.length; i++) {
366: Configuration child = children[i];
367: String childName = child.getName();
368: Configuration existingChild = toConfig.getChild(childName,
369: false);
370: if (existingChild == null) {
371: toConfig.addChild(child);
372: } else {
373: copyConfig(child, (DefaultConfiguration) existingChild);
374: }
375: }
376:
377: // Copy value
378: String val = fromConfig.getValue(null);
379: if (val != null) {
380: toConfig.setValue(val);
381: }
382: }
383:
384: /**
385: * Return the <code>Component</code> when you are finished with it. In this
386: * implementation it does nothing
387: *
388: * @param component The Component we are releasing.
389: */
390: public void release(Object component) {
391: }
392: }
|