View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.config.bootstrap;
8   
9   import org.mule.api.MuleContext;
10  import org.mule.api.context.MuleContextAware;
11  import org.mule.api.lifecycle.Initialisable;
12  import org.mule.api.lifecycle.InitialisationException;
13  import org.mule.api.registry.MuleRegistry;
14  import org.mule.api.registry.ObjectProcessor;
15  import org.mule.api.registry.RegistrationException;
16  import org.mule.api.registry.Registry;
17  import org.mule.api.transformer.DiscoverableTransformer;
18  import org.mule.api.transformer.Transformer;
19  import org.mule.api.util.StreamCloser;
20  import org.mule.config.i18n.CoreMessages;
21  import org.mule.transformer.types.DataTypeFactory;
22  import org.mule.util.ClassUtils;
23  import org.mule.util.ExceptionUtils;
24  import org.mule.util.PropertiesUtils;
25  import org.mule.util.UUID;
26  
27  import java.lang.reflect.InvocationTargetException;
28  import java.net.URL;
29  import java.util.Enumeration;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Properties;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  /**
39   * This object will load objects defined in a file called <code>registry-bootstrap.properties</code> into the local registry.
40   * This allows modules and transports to make certain objects available by default.  The most common use case is for a
41   * module or transport to load stateless transformers into the registry.
42   * For this file to be located it must be present in the modules META-INF directory under
43   * <pre>META-INF/services/org/mule/config/</pre>
44   * <p/>
45   * The format of this file is a simple key / value pair. i.e.
46   * <pre>
47   * myobject=org.foo.MyObject
48   * </pre>
49   * Will register an instance of MyObject with a key of 'myobject'. If you don't care about the object name and want to
50   * ensure that the ojbect gets a unique name you can use -
51   * <pre>
52   * object.1=org.foo.MyObject
53   * object.2=org.bar.MyObject
54   * </pre>
55   * or
56   * <pre>
57   * myFoo=org.foo.MyObject
58   * myBar=org.bar.MyObject
59   * </pre>
60   * Loading transformers has a slightly different notation since you can define the 'returnClass' with optional mime type, and 'name'of
61   * the transformer as parameters i.e.
62   * <pre>
63   * transformer.1=org.mule.transport.jms.transformers.JMSMessageToObject,returnClass=byte[]
64   * transformer.2=org.mule.transport.jms.transformers.JMSMessageToObject,returnClass=java.lang.String:text/xml, name=JMSMessageToString
65   * transformer.3=org.mule.transport.jms.transformers.JMSMessageToObject,returnClass=java.util.Hashtable)
66   * </pre>
67   * Note that the key used for transformers must be 'transformer.x' where 'x' is a sequential number.  The transformer name will be
68   * automatically generated as JMSMessageToXXX where XXX is the return class name i.e. JMSMessageToString unless a 'name'
69   * parameter is specified. If no 'returnClass' is specified the default in the transformer will be used.
70   * <p/>
71   * Note that all objects defined have to have a default constructor. They can implement injection interfaces such as
72   * {@link org.mule.api.context.MuleContextAware} and lifecycle interfaces such as {@link org.mule.api.lifecycle.Initialisable}.
73   */
74  public class SimpleRegistryBootstrap implements Initialisable, MuleContextAware
75  {
76      public static final String SERVICE_PATH = "META-INF/services/org/mule/config/";
77  
78      public static final String REGISTRY_PROPERTIES = "registry-bootstrap.properties";
79  
80      public String TRANSFORMER_KEY = ".transformer.";
81      public String OBJECT_KEY = ".object.";
82  
83      protected final transient Log logger = LogFactory.getLog(getClass());
84  
85      protected MuleContext context;
86  
87      /** {@inheritDoc} */
88      public void setMuleContext(MuleContext context)
89      {
90          this.context = context;
91      }
92  
93      /** {@inheritDoc} */
94      public void initialise() throws InitialisationException
95      {
96          Enumeration<?> e = ClassUtils.getResources(SERVICE_PATH + REGISTRY_PROPERTIES, getClass());
97          List<Properties> bootstraps = new LinkedList<Properties>();
98  
99          // load ALL of the bootstrap files first
100         while (e.hasMoreElements())
101         {
102             try
103             {
104                 URL url = (URL) e.nextElement();
105                 if (logger.isDebugEnabled())
106                 {
107                     logger.debug("Reading bootstrap file: " + url.toString());
108                 }
109                 Properties p = new Properties();
110                 p.load(url.openStream());
111                 bootstraps.add(p);
112             }
113             catch (Exception e1)
114             {
115                 throw new InitialisationException(e1, this);
116             }
117         }
118 
119         // ... and only then merge and process them
120         int objectCounter = 1;
121         int transformerCounter = 1;
122         Properties transformers = new Properties();
123         Properties namedObjects = new Properties();
124         Properties unnamedObjects = new Properties();
125 
126         for (Properties bootstrap : bootstraps)
127         {
128             for (Map.Entry entry : bootstrap.entrySet())
129             {
130                 final String key = (String) entry.getKey();
131                 if (key.contains(OBJECT_KEY))
132                 {
133                     String newKey = key.substring(0, key.lastIndexOf(".")) + objectCounter++;
134                     unnamedObjects.put(newKey, entry.getValue());
135                 }
136                 else if (key.contains(TRANSFORMER_KEY))
137                 {
138                     String newKey = key.substring(0, key.lastIndexOf(".")) + transformerCounter++;
139                     transformers.put(newKey, entry.getValue());
140                 }
141                 else
142                 {
143                     // we allow arbitrary keys in the registry-bootstrap.properties but since we're
144                     // aggregating multiple files here we must make sure that the keys are unique
145 //                    if (accumulatedProps.getProperty(key) != null)
146 //                    {
147 //                        throw new IllegalStateException(
148 //                                "more than one registry-bootstrap.properties file contains a key " + key);
149 //                    }
150 //                    else
151                     {
152                         namedObjects.put(key, entry.getValue());
153                     }
154                 }
155             }
156         }
157 
158         try
159         {
160             registerUnnamedObjects(unnamedObjects, context.getRegistry());
161             registerTransformers(transformers, context.getRegistry());
162             registerObjects(namedObjects, context.getRegistry());
163         }
164         catch (Exception e1)
165         {
166             throw new InitialisationException(e1, this);
167         }
168     }
169 
170     private void registerTransformers(Properties props, MuleRegistry registry) throws Exception
171     {
172         String transString;
173         String name = null;
174         String returnClassString;
175         boolean optional = false;
176 
177         for (Map.Entry<Object, Object> entry : props.entrySet())
178         {
179             transString = (String)entry.getValue();
180             // reset
181             Class<?> returnClass = null;
182             returnClassString = null;
183             int x = transString.indexOf(",");
184             if (x > -1)
185             {
186                 Properties p = PropertiesUtils.getPropertiesFromString(transString.substring(x + 1), ',');
187                 name = p.getProperty("name", null);
188                 returnClassString = p.getProperty("returnClass", null);
189                 optional = p.containsKey("optional");
190             }
191 
192             final String transClass = (x == -1 ? transString : transString.substring(0, x));
193             try
194             {
195                 String mime = null;
196                 if (returnClassString != null)
197                 {
198                     int i = returnClassString.indexOf(":");
199                     if(i > -1)
200                     {
201                         mime = returnClassString.substring(i + 1);
202                         returnClassString = returnClassString.substring(0, i);
203                     }
204                     if (returnClassString.equals("byte[]"))
205                     {
206                         returnClass = byte[].class;
207                     }
208                     else
209                     {
210                         returnClass = ClassUtils.loadClass(returnClassString, getClass());
211                     }
212                 }
213                 Transformer trans = (Transformer) ClassUtils.instanciateClass(transClass);
214                 if (!(trans instanceof DiscoverableTransformer))
215                 {
216                     throw new RegistrationException(CoreMessages.transformerNotImplementDiscoverable(trans));
217                 }
218                 if (returnClass != null)
219                 {
220                     trans.setReturnDataType(DataTypeFactory.create(returnClass, mime));
221                 }
222                 if (name != null)
223                 {
224                     trans.setName(name);
225                 }
226                 else
227                 {
228                     //This will generate a default name for the transformer
229                     name = trans.getName();
230                     //We then prefix the name to ensure there is less chance of conflict if the user registers
231                     // the transformer with the same name
232                     trans.setName("_" + name);
233                 }
234                 registry.registerTransformer(trans);
235             }
236             catch (InvocationTargetException itex)
237             {
238                 Throwable cause = ExceptionUtils.getCause(itex);
239                 if (cause instanceof NoClassDefFoundError && optional)
240                 {
241                     if (logger.isDebugEnabled())
242                     {
243                         logger.debug("Ignoring optional transformer: " + transClass);
244                     }
245                 }
246                 else
247                 {
248                     throw new Exception(cause);
249                 }
250             }
251             catch (NoClassDefFoundError ncdfe)
252             {
253                 if (optional)
254                 {
255                     if (logger.isDebugEnabled())
256                     {
257                         logger.debug("Ignoring optional transformer: " + transClass);
258                     }
259                 }
260                 else
261                 {
262                     throw ncdfe;
263                 }
264             }
265             catch (ClassNotFoundException cnfe)
266             {
267                 if (optional)
268                 {
269                     if (logger.isDebugEnabled())
270                     {
271                         logger.debug("Ignoring optional transformer: " + transClass);
272                     }
273                 }
274                 else
275                 {
276                     throw cnfe;
277                 }
278             }
279 
280             name = null;
281             returnClass = null;
282         }
283     }
284 
285     private void registerObjects(Properties props, Registry registry) throws Exception
286     {
287         for (Map.Entry<Object, Object> entry : props.entrySet())
288         {
289             registerObject((String)entry.getKey(), (String)entry.getValue(), registry);
290         }
291         props.clear();
292     }
293 
294     private void registerUnnamedObjects(Properties props, Registry registry) throws Exception
295     {
296         for (Map.Entry<Object, Object> entry : props.entrySet())
297         {
298             final String key = String.format("%s#%s", entry.getKey(), UUID.getUUID());
299             registerObject(key, (String) entry.getValue(), registry);
300         }
301         props.clear();
302     }
303 
304     private void registerObject(String key, String value, Registry registry) throws Exception
305     {
306         boolean optional = false;
307         String className = null;
308 
309         try
310         {
311             int x = value.indexOf(",");
312             if (x > -1)
313             {
314                 Properties p = PropertiesUtils.getPropertiesFromString(value.substring(x + 1), ',');
315                 optional = p.containsKey("optional");
316                 className = value.substring(0, x);
317             }
318             else
319             {
320                 className = value;
321             }
322             Object o = ClassUtils.instanciateClass(className);
323             Class<?> meta = Object.class;
324 
325             if (o instanceof ObjectProcessor)
326             {
327                 meta = ObjectProcessor.class;
328             }
329             else if (o instanceof StreamCloser)
330             {
331                 meta = StreamCloser.class;
332             }
333             else if (o instanceof BootstrapObjectFactory)
334             {
335                 o = ((BootstrapObjectFactory)o).create();
336             }
337             registry.registerObject(key, o, meta);
338         }
339         catch (InvocationTargetException itex)
340         {
341             Throwable cause = ExceptionUtils.getCause(itex);
342             if (cause instanceof NoClassDefFoundError && optional)
343             {
344                 if (logger.isDebugEnabled())
345                 {
346                     logger.debug("Ignoring optional object: " + className);
347                 }
348             }
349             else
350             {
351                 throw new Exception(cause);
352             }
353         }
354         catch (NoClassDefFoundError ncdfe)
355         {
356             if (optional)
357             {
358                 if (logger.isDebugEnabled())
359                 {
360                     logger.debug("Ignoring optional object: " + className);
361                 }
362             }
363             else
364             {
365                 throw ncdfe;
366             }
367         }
368         catch (ClassNotFoundException cnfe)
369         {
370             if (optional)
371             {
372                 if (logger.isDebugEnabled())
373                 {
374                     logger.debug("Ignoring optional object: " + className);
375                 }
376             }
377             else
378             {
379                 throw cnfe;
380             }
381         }
382     }
383 }