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