View Javadoc

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