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
54 private static final String REPOSITORY_NAME = "Repository";
55
56
57
58 private static long id;
59
60
61 private Map repositories;
62
63
64 private Map classes;
65
66
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
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
261
262
263
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
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
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
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 }