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