001    package biz.hammurapi.jms.adapter;
002    
003    import java.io.InputStream;
004    import java.lang.reflect.Constructor;
005    import java.net.URL;
006    import java.util.ArrayList;
007    import java.util.Hashtable;
008    import java.util.Iterator;
009    import java.util.Map;
010    import java.util.Timer;
011    import java.util.logging.Level;
012    import java.util.logging.Logger;
013    
014    import org.apache.xmlbeans.XmlObject;
015    import org.apache.xmlbeans.XmlOptions;
016    
017    import biz.hammurapi.config.ConfigurationException;
018    import biz.hammurapi.config.DomConfigFactory;
019    import biz.hammurapi.config.GenericContainer;
020    import biz.hammurapi.config.MapContext;
021    import biz.hammurapi.config.MutableContext;
022    import biz.hammurapi.config.SimpleContext;
023    import biz.hammurapi.config.Wrapper;
024    import biz.hammurapi.convert.ConvertingService;
025    import biz.hammurapi.jms.adapter.definition.GenericService;
026    import biz.hammurapi.jms.adapter.definition.JmsAdapterDocument;
027    import biz.hammurapi.jms.adapter.definition.NamedObjectSpecification;
028    import biz.hammurapi.jms.adapter.definition.ObjectSpecification;
029    import biz.hammurapi.jms.adapter.definition.Property;
030    import biz.hammurapi.metrics.MeasurementConsumer;
031    import biz.hammurapi.util.Worker;
032    
033    public class JmsAdapter extends GenericContainer implements MutableContext, Worker {
034            
035            private static final String RESOURCE_PREFIX = biz.hammurapi.config.DomConfigFactory.RESOURCE_PREFIX;
036    
037            private static final Logger logger = Logger.getLogger(JmsAdapter.class.getName());
038            
039            private Timer timer;
040            private biz.hammurapi.jms.adapter.definition.JmsAdapter definition;
041    
042            private Worker defaultWorker;
043            
044            /**
045             * Sets default worker. Overrides default worker read from
046             * configuration. This method is used by JCA wrapper.
047             * @param defaultWorker
048             */
049            public void setDefaultWorker(Worker defaultWorker) {
050                    this.defaultWorker = defaultWorker;
051            }
052            
053            public Timer getTimer() {
054                    return timer;
055            }
056            
057            /**
058             * Instantiates JMS adapter from the definition.
059             * @param definition
060             */
061            public JmsAdapter(biz.hammurapi.jms.adapter.definition.JmsAdapter definition) throws ConfigurationException {
062                    this.definition = definition;
063                    
064                    // Measurement consumer
065                    if (definition.getMeasurementConsumer()!=null) {
066                            setMeasurementConsumer((MeasurementConsumer) instantiate(definition.getMeasurementConsumer()));
067                    }
068                    
069                    // Bind types
070                    NamedObjectSpecification[] bindTypesArray = definition.getBindTypeArray();
071                    GenericContainer bindTypes=new GenericContainer();
072                    addComponent("bind-types", bindTypes);
073                    for (int i=0; i<bindTypesArray.length; ++i) {
074                            bindTypes.addComponent(bindTypesArray[i].getName(), instantiate(bindTypesArray[i]));
075                    }
076                    
077                    // Workers
078                    biz.hammurapi.jms.adapter.definition.Worker[] workerArray = definition.getWorkerArray();
079                    GenericContainer workers=new GenericContainer();
080                    addComponent("workers", workers);
081                    for (int i=0; i<workerArray.length; ++i) {
082                            Object worker = instantiate(workerArray[i]);
083                            workers.addComponent(workerArray[i].getName(), worker);
084                            if (workerArray[i].getDefault()) {
085                                    defaultWorker = (Worker) worker;
086                            }
087                    }
088                    
089                    // Generic services
090                    GenericService[] serviceArray = definition.getServiceArray();
091                    GenericContainer services=new GenericContainer();
092                    addComponent("services", services);
093                    for (int i=0; i<serviceArray.length; ++i) {
094                            Object service = instantiate(serviceArray[i]);
095                            services.addComponent(serviceArray[i].getName(), service);
096                            for (int j=0; j<serviceArray[i].getAliasArray().length; ++j) {
097                                    set(serviceArray[i].getAliasArray()[j], service);
098                            }
099                    }
100                    
101                    // Contexts
102                    biz.hammurapi.jms.adapter.definition.Context[] contextArray = definition.getContextArray();
103                    GenericContainer contexts=new GenericContainer();
104                    addComponent("contexts", contexts);
105                    for (int i=0; i<contextArray.length; ++i) {
106                            contexts.addComponent(contextArray[i].getName(), new JndiContext(this, contextArray[i]));
107                    }
108                    
109                    // Factory connections
110                    biz.hammurapi.jms.adapter.definition.FactoryConnection[] connectionArray = definition.getConnectionArray();
111                    GenericContainer connections=new GenericContainer();
112                    addComponent("connections", connections);
113                    for (int i=0; i<connectionArray.length; ++i) {
114                            connections.addComponent(connectionArray[i].getName(), new FactoryConnection(this, connectionArray[i]));
115                    }
116                    
117                    
118            }
119            
120            /**
121             * Instantiates object from specifications and configures it by injecting properties.
122             * @param objSpec
123             * @return
124             * @throws ConfigurationException
125             */
126            public static Object instantiate(ObjectSpecification objSpec) throws ConfigurationException {
127                    try {
128                            Class clazz = Class.forName(objSpec.getType());
129                            Constructor candidate = null;
130                            Constructor[] ca = clazz.getConstructors();
131                            for (int i = 0; i<ca.length; ++i) {
132                                    Class[] parameterTypes = ca[i].getParameterTypes();
133                                    if (parameterTypes.length==1 
134                                                    && parameterTypes[0].isInstance(objSpec) 
135                                                    && (candidate==null || candidate.getParameterTypes()[0].isAssignableFrom(parameterTypes[0]))) {
136                                            candidate = ca[i];
137                                    }
138                            }
139                            
140                            if (candidate!=null) {
141                                    return candidate.newInstance(new Object[] {objSpec});
142                            }
143                            
144                            Object ret = clazz.newInstance();
145                            Map<String, Object> properties = instantiate(objSpec.getPropertyArray());
146                            if (!properties.isEmpty()) {
147                                    DomConfigFactory.inject(ret, new MapContext(properties));
148                            }
149                            return ret;
150                    } catch (ConfigurationException e) {
151                            throw e;
152                    } catch (Exception e) {
153                            throw new ConfigurationException("Could not instantiate object from specification: "+e, e);
154                    }
155            }
156    
157            /**
158             * Instantiates object from specifications and configures it by injecting properties.
159             * @param objSpec
160             * @return
161             * @throws ConfigurationException
162             */
163            public static Object instantiate(NamedObjectSpecification objSpec) throws ConfigurationException {
164                    try {
165                            Class clazz = Class.forName(objSpec.getType());
166                            Constructor candidate = null;
167                            Constructor[] ca = clazz.getConstructors();
168                            for (int i = 0; i<ca.length; ++i) {
169                                    Class[] parameterTypes = ca[i].getParameterTypes();
170                                    if (parameterTypes.length==1 
171                                                    && parameterTypes[0].isInstance(objSpec) 
172                                                    && (candidate==null || candidate.getParameterTypes()[0].isAssignableFrom(parameterTypes[0]))) {
173                                            candidate = ca[i];
174                                    }
175                            }
176                            
177                            if (candidate!=null) {
178                                    return candidate.newInstance(new Object[] {objSpec});
179                            }
180                            
181                            Object ret = clazz.newInstance();
182                            Map<String, Object> properties = instantiate(objSpec.getPropertyArray());
183                            if (!properties.isEmpty()) {
184                                    DomConfigFactory.inject(ret, new MapContext(properties));
185                            }
186                            return ret;
187                    } catch (ConfigurationException e) {
188                            throw e;
189                    } catch (Exception e) {
190                            throw new ConfigurationException("Could not instantiate object from specification: "+e, e);
191                    }
192            }
193    
194            /**
195             * Instantiates properties from XML defintion
196             * @param property
197             * @return
198             * @throws ConfigurationException
199             */
200            public static Hashtable<String, Object> instantiate(Property[] properties) throws ConfigurationException {
201                    Hashtable<String, Object> ret = new Hashtable<String, Object>();
202                    if (properties!=null) {
203                            for (int i=0; i<properties.length; ++i) {
204                                    if (properties[i].getType()==null || properties[i].getType().trim().length()==0) {
205                                            ret.put(properties[i].getName(), properties[i].getStringValue());
206                                    } else {                                
207                                            try {
208                                                    ret.put(
209                                                                    properties[i].getName(), 
210                                                                    ConvertingService.convert(
211                                                                                            properties[i].getStringValue(), 
212                                                                                            Class.forName(properties[i].getType())));
213                                            } catch (ClassNotFoundException e) {
214                                                    throw new ConfigurationException("Cannot instantiate property of type "+properties[i].getType()+" from value "+properties[i].getStringValue());
215                                            }
216                                    }
217                            }
218                    }
219                    return ret;
220            }
221    
222            public void start() throws ConfigurationException {
223                    timer = new Timer();
224                    if (definition.getStartInBackground()) {
225                            new Thread(definition.getName()+" startup thread") {
226                                    public void run() {
227                                            try {
228                                                    super.start();
229                                            } catch (Exception e) {
230                                                    logger.log(Level.SEVERE, "Failed to start JMS Adapter "+definition.getName()+": "+e, e);
231                                            }
232                                    }
233                            }.start();
234                    } else {
235                            super.start();
236                    }
237    
238            }
239            
240            public void stop() throws ConfigurationException {
241                    if (timer!=null) {
242                            timer.cancel();
243                    }
244                    super.stop();
245            }
246    
247            private SimpleContext aliases = new SimpleContext();
248    
249            public boolean remove(String name) {
250                    return aliases.remove(name);                    
251            }
252    
253            public void set(String name, Object value) {
254                    aliases.set(name, value);               
255            }
256            
257            /**
258             * Returns service. Unwraps wrappers.
259             */
260            public Object get(String name) {                
261                    Object ret = super.get(name);
262                    if (ret==null) {
263                            ret = aliases.get(name);
264                    }
265                    return ret instanceof Wrapper ? ((Wrapper) ret).getMaster() : ret;
266            }
267    
268            /**
269             * The first and only argument is definition URL.
270             * If URL starts with biz.hammurapi.config.DomConfigFactory.RESOURCE_PREFIX then definition is loaded from classpath resource.
271             * If the first argument is not present then definition is read from System.in
272             * Adapter is stopped by shutdown hook on JVM termination (Ctrl-C).
273             * @param args
274             */
275            public static void main(String[] args) {
276                    System.out.println("Usage: java <options> biz.hammurapi.jms.adapter.JmsAdapter [<definition URL>]");
277                    
278                    final long start=System.currentTimeMillis();
279                    
280                    XmlObject parsed;
281                    try {
282                            if (args.length==0) {
283                                    parsed = XmlObject.Factory.parse(System.in);
284                            } else if (args[0].startsWith(RESOURCE_PREFIX)) {
285                                    InputStream resourceStream = JmsAdapter.class.getClassLoader().getResourceAsStream(
286                                                    args[0].substring(RESOURCE_PREFIX.length()));
287                                    parsed = XmlObject.Factory.parse(resourceStream);
288                            } else {
289                                    parsed = XmlObject.Factory.parse(new URL(args[0]));
290                            }
291                    } catch (Exception e) {
292                            System.err.println("Could not load definition: "+e);
293                            e.printStackTrace();    
294                            System.exit(3);
295                            return; // Stupid, but needed for proper compilation.
296                    }
297                    
298                    try {
299                            if (parsed instanceof JmsAdapterDocument) {
300                                    ArrayList validationErrors = new ArrayList();
301                                    XmlOptions validationOptions = new XmlOptions();
302                                    validationOptions.setErrorListener(validationErrors);
303                                    if (!parsed.validate(validationOptions)) {
304                                            System.err.println("Invalid adapter definition:");
305                                        Iterator iter = validationErrors.iterator();
306                                        while (iter.hasNext()) {
307                                            System.err.println("\t>> " + iter.next());
308                                        }
309                                            
310                                            System.exit(1);
311                                    }
312                                    biz.hammurapi.jms.adapter.definition.JmsAdapter definition = ((JmsAdapterDocument) parsed).getJmsAdapter();
313                                    final JmsAdapter adapter = new JmsAdapter(definition);
314                                    System.out.println("Adapter started in "+((System.currentTimeMillis()-start)/1000)+" seconds");
315                                    Runtime.getRuntime().addShutdownHook(
316                                                    new Thread() {
317                                                            public void run() {
318                                                                    try {                                                   
319                                                                            adapter.stop();
320                                                                    } catch (ConfigurationException e) {
321                                                                            System.err.println("Could not properly stop JMS adapter.");
322                                                                            e.printStackTrace();
323                                                                    }
324                                                                    System.out.println("Total execution time: "+((System.currentTimeMillis()-start)/1000)+" sec.");                                                 
325                                                            }
326                                                    });                     
327                                    
328                                    adapter.start();
329                            } else {
330                                    System.out.println("Invalid adapter definition.");
331                                    System.exit(2);
332                            }
333                    } catch (Exception e) {
334                            System.err.println("Could not start adapter: "+e);
335                            e.printStackTrace();    
336                            System.exit(4);                 
337                    }
338                    
339            }
340            
341            public static boolean isBlank(String str) {
342                    return str==null || str.trim().length()==0;
343            }
344    
345            /**
346             * @return Adapter XML definition.
347             */
348            public biz.hammurapi.jms.adapter.definition.JmsAdapter getDefinition() {
349                    return definition;
350            }
351    
352            /**
353             * Adapter is always naming root. Owner is available through 'owner' alias.
354             */
355            public void setOwner(Object owner) {
356                    aliases.set("owner", owner);
357            }
358    
359            /**
360             * Executes job in the current thread if there is no default worker.
361             */
362            public boolean post(Runnable job) {
363                    if (defaultWorker==null) {
364                            job.run();
365                            return true;
366                    }
367                    
368                    return defaultWorker.post(job);
369            }
370            
371    }