View Javadoc

1   /************************************************************************
2    * Copyright (c) 2000-2006 The Apache Software Foundation.             *
3    * All rights reserved.                                                *
4    * ------------------------------------------------------------------- *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you *
6    * may not use this file except in compliance with the License. You    *
7    * may obtain a copy of the License at:                                *
8    *                                                                     *
9    *     http://www.apache.org/licenses/LICENSE-2.0                      *
10   *                                                                     *
11   * Unless required by applicable law or agreed to in writing, software *
12   * distributed under the License is distributed on an "AS IS" BASIS,   *
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or     *
14   * implied.  See the License for the specific language governing       *
15   * permissions and limitations under the License.                      *
16   ***********************************************************************/
17  
18  package org.apache.james.core;
19  
20  import org.apache.avalon.cornerstone.services.store.Store;
21  import org.apache.avalon.framework.activity.Initializable;
22  import org.apache.avalon.framework.component.Composable;
23  import org.apache.avalon.framework.service.DefaultServiceManager;
24  import org.apache.avalon.framework.service.ServiceManager;
25  import org.apache.avalon.framework.service.ServiceException;
26  import org.apache.avalon.framework.service.Serviceable;
27  import org.apache.avalon.framework.configuration.Configurable;
28  import org.apache.avalon.framework.configuration.Configuration;
29  import org.apache.avalon.framework.configuration.ConfigurationException;
30  import org.apache.avalon.framework.configuration.DefaultConfiguration;
31  import org.apache.avalon.framework.container.ContainerUtil;
32  import org.apache.avalon.framework.context.Context;
33  import org.apache.avalon.framework.context.ContextException;
34  import org.apache.avalon.framework.context.Contextualizable;
35  import org.apache.avalon.framework.logger.AbstractLogEnabled;
36  import org.apache.avalon.framework.logger.LogEnabled;
37  import org.apache.commons.collections.ReferenceMap;
38  
39  import java.util.HashMap;
40  import java.util.Map;
41  
42  /***
43   * Provides a registry of mail repositories. A mail repository is uniquely
44   * identified by its destinationURL, type and model.
45   *
46   */
47  public class AvalonMailStore
48      extends AbstractLogEnabled
49      implements Contextualizable, Serviceable, Configurable, Initializable, Store {
50  
51      // Prefix for repository names
52      private static final String REPOSITORY_NAME = "Repository";
53  
54      // Static variable used to name individual repositories.  Should only
55      // be accessed when a lock on the AvalonMailStore.class is held
56      private static long id;
57  
58      // map of [destinationURL + type]->Repository
59      private Map repositories;
60  
61      // map of [protocol(destinationURL) + type ]->classname of repository;
62      private Map classes;
63  
64      // map of [protocol(destinationURL) + type ]->default config for repository.
65      private Map defaultConfigs;
66  
67      /***
68       * The Avalon context used by the instance
69       */
70      protected Context                context;
71  
72      /***
73       * The Avalon configuration used by the instance
74       */
75      protected Configuration          configuration;
76  
77      /***
78       * The Avalon component manager used by the instance
79       */
80      protected ServiceManager       m_manager;
81  
82      /***
83       * @see org.apache.avalon.framework.context.Contextualizable#contextualize(Context)
84       */
85      public void contextualize(final Context context)
86              throws ContextException {
87          this.context = context;
88      }
89  
90      /***
91       * @see org.apache.avalon.framework.service.Servicable#service(ServiceManager)
92       */
93      public void service( final ServiceManager manager )
94          throws ServiceException
95      {
96          DefaultServiceManager def_manager = new DefaultServiceManager(manager);
97          def_manager.put(Store.ROLE, this);
98          m_manager = def_manager;
99      }
100 
101 
102     /***
103      * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
104      */
105     public void configure( final Configuration configuration )
106         throws ConfigurationException
107     {
108         this.configuration = configuration;
109     }
110 
111     /***
112      * @see org.apache.avalon.framework.activity.Initializable#initialize()
113      */
114     public void initialize()
115         throws Exception {
116 
117         getLogger().info("JamesMailStore init...");
118         repositories = new ReferenceMap();
119         classes = new HashMap();
120         defaultConfigs = new HashMap();
121         Configuration[] registeredClasses
122             = configuration.getChild("repositories").getChildren("repository");
123         for ( int i = 0; i < registeredClasses.length; i++ )
124         {
125             registerRepository(registeredClasses[i]);
126         }
127 
128     }
129 
130     /***
131      * <p>Registers a new mail repository type in the mail store's
132      * registry based upon a passed in <code>Configuration</code> object.</p>
133      *
134      * <p>This is presumably synchronized to prevent corruption of the
135      * internal registry.</p>
136      *
137      * @param repConf the Configuration object used to register the
138      *                repository
139      *
140      * @throws ConfigurationException if an error occurs accessing the
141      *                                Configuration object
142      */
143     public synchronized void registerRepository(Configuration repConf)
144         throws ConfigurationException {
145         String className = repConf.getAttribute("class");
146         boolean infoEnabled = getLogger().isInfoEnabled();
147         Configuration[] protocols
148             = repConf.getChild("protocols").getChildren("protocol");
149         Configuration[] types = repConf.getChild("types").getChildren("type");
150         for ( int i = 0; i < protocols.length; i++ )
151         {
152             String protocol = protocols[i].getValue();
153 
154             // Get the default configuration for these protocol/type combinations.
155             Configuration defConf = repConf.getChild("config");
156 
157             for ( int j = 0; j < types.length; j++ )
158             {
159                 String type = types[j].getValue();
160                 String key = protocol + type ;
161                 if (infoEnabled) {
162                     StringBuffer infoBuffer =
163                         new StringBuffer(128)
164                             .append("Registering Repository instance of class ")
165                             .append(className)
166                             .append(" to handle ")
167                             .append(protocol)
168                             .append(" protocol requests for repositories of type ")
169                             .append(type);
170                     getLogger().info(infoBuffer.toString());
171                 }
172                 if (classes.get(key) != null) {
173                     throw new ConfigurationException("The combination of protocol and type comprise a unique key for repositories.  This constraint has been violated.  Please check your repository configuration.");
174                 }
175                 classes.put(key, className);
176                 if (defConf != null) {
177                     defaultConfigs.put(key, defConf);
178                 }
179             }
180         }
181 
182     }
183 
184     /***
185      * This method accept a Configuration object as hint and return the
186      * corresponding MailRepository.
187      * The Configuration must be in the form of:
188      * <repository destinationURL="[URL of this mail repository]"
189      *             type="[repository type ex. OBJECT or STREAM or MAIL etc.]"
190      *             model="[repository model ex. PERSISTENT or CACHE etc.]">
191      *   [addition configuration]
192      * </repository>
193      *
194      * @param hint the Configuration object used to look up the repository
195      *
196      * @return the selected repository
197      *
198      * @throws ServiceException if any error occurs while parsing the 
199      *                            Configuration or retrieving the 
200      *                            MailRepository
201      */
202     public synchronized Object select(Object hint) throws ServiceException {
203         Configuration repConf = null;
204         try {
205             repConf = (Configuration) hint;
206         } catch (ClassCastException cce) {
207             throw new ServiceException("",
208                 "hint is of the wrong type. Must be a Configuration", 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                 "Malformed configuration has no destinationURL attribute", ce);
223         }
224 
225         try
226         {
227             String type = repConf.getAttribute("type");
228             String repID = destination + type;
229             Object reply = repositories.get(repID);
230             StringBuffer logBuffer = null;
231             if (reply != null) {
232                 if (getLogger().isDebugEnabled()) {
233                     logBuffer =
234                         new StringBuffer(128)
235                                 .append("obtained repository: ")
236                                 .append(repID)
237                                 .append(",")
238                                 .append(reply.getClass());
239                     getLogger().debug(logBuffer.toString());
240                 }
241                 return reply;
242             } else {
243                 String key = protocol + type;
244                 String repClass = (String) classes.get( key );
245 
246                 if (getLogger().isDebugEnabled()) {
247                     logBuffer =
248                         new StringBuffer(128)
249                                 .append("obtained repository: ")
250                                 .append(repClass)
251                                 .append(" to handle: ")
252                                 .append(protocol)
253                                 .append(",")
254                                 .append(type);
255                     getLogger().debug( logBuffer.toString() );
256                 }
257 
258                 // If default values have been set, create a new repository
259                 // configuration element using the default values
260                 // and the values in the selector.
261                 // If no default values, just use the selector.
262                 Configuration config;
263                 Configuration defConf = (Configuration)defaultConfigs.get(key);
264                 if ( defConf == null) {
265                     config = repConf;
266                 }
267                 else {
268                     config = new DefaultConfiguration(repConf.getName(),
269                                                       repConf.getLocation());
270                     copyConfig(defConf, (DefaultConfiguration)config);
271                     copyConfig(repConf, (DefaultConfiguration)config);
272                 }
273 
274                 try {
275                     reply = this.getClass().getClassLoader().loadClass(repClass).newInstance();
276                     if (reply instanceof LogEnabled) {
277                        setupLogger(reply);
278                     }
279                     ContainerUtil.contextualize(reply,context);
280                     ContainerUtil.service(reply,m_manager);
281 
282                     if (reply instanceof Composable) {
283                         final String error = "no implementation in place to support Composable";
284                         getLogger().error( error );
285                         throw new IllegalArgumentException( error );
286                     }
287                     
288                     ContainerUtil.configure(reply,config);
289                     ContainerUtil.initialize(reply);
290 
291                     repositories.put(repID, reply);
292                     if (getLogger().isInfoEnabled()) {
293                         logBuffer =
294                             new StringBuffer(128)
295                                 .append("added repository: ")
296                                 .append(repID)
297                                 .append("->")
298                                 .append(repClass);
299                         getLogger().info(logBuffer.toString());
300                     }
301                     return reply;
302                 } catch (Exception e) {
303                     if (getLogger().isWarnEnabled()) {
304                         getLogger().warn( "Exception while creating repository:" +
305                                           e.getMessage(), e );
306                     }
307                     throw new
308                         ServiceException("", "Cannot find or init repository",
309                                            e);
310                 }
311             }
312         } catch( final ConfigurationException ce ) {
313             throw new ServiceException("", "Malformed configuration", ce );
314         }
315     }
316 
317     /***
318      * <p>Returns a new name for a repository.</p>
319      *
320      * <p>Synchronized on the AvalonMailStore.class object to ensure
321      * against duplication of the repository name</p>
322      *
323      * @return a new repository name
324      */
325     public static final String getName() {
326         synchronized (AvalonMailStore.class) {
327             return REPOSITORY_NAME + id++;
328         }
329     }
330 
331     /***
332      * Returns whether the mail store has a repository corresponding to
333      * the passed in hint.
334      *
335      * @param hint the Configuration object used to look up the repository
336      *
337      * @return whether the mail store has a repository corresponding to this hint
338      */
339     public boolean isSelectable( Object hint ) {
340         Object comp = null;
341         try {
342             comp = select(hint);
343         } catch(ServiceException ex) {
344             if (getLogger().isErrorEnabled()) {
345                 getLogger().error("Exception AvalonMailStore.hasComponent-" + ex.toString());
346             }
347         }
348         return (comp != null);
349     }
350 
351     /***
352      * Copies values from one config into another, overwriting duplicate attributes
353      * and merging children.
354      *
355      * @param fromConfig the Configuration to be copied
356      * @param toConfig the Configuration to which data is being copied
357      */
358     private void copyConfig(Configuration fromConfig, DefaultConfiguration toConfig)
359     {
360         // Copy attributes
361         String[] attrs = fromConfig.getAttributeNames();
362         for ( int i = 0; i < attrs.length; i++ ) {
363             String attrName = attrs[i];
364             String attrValue = fromConfig.getAttribute(attrName, null);
365             toConfig.setAttribute(attrName, attrValue);
366         }
367 
368         // Copy children
369         Configuration[] children = fromConfig.getChildren();
370         for ( int i = 0; i < children.length; i++ ) {
371             Configuration child = children[i];
372             String childName = child.getName();
373             Configuration existingChild = toConfig.getChild(childName, false);
374             if ( existingChild == null ) {
375                 toConfig.addChild(child);
376             }
377             else {
378                 copyConfig(child, (DefaultConfiguration)existingChild);
379             }
380         }
381 
382         // Copy value
383         String val = fromConfig.getValue(null);
384         if ( val != null ) {
385             toConfig.setValue(val);
386         }
387     }
388 
389     /***
390      * Return the <code>Component</code> when you are finished with it.  In this
391      * implementation it does nothing
392      *
393      * @param component The Component we are releasing.
394      */
395     public void release(Object component) {}
396 }