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.CompositeConverter;
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 = "resource:";
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 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 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 instantiate(Property[] properties) throws ConfigurationException {
201                    Hashtable ret = new Hashtable();
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                                                                    CompositeConverter
211                                                                            .getDefaultConverter()
212                                                                            .convert(
213                                                                                            properties[i].getStringValue(), 
214                                                                                            Class.forName(properties[i].getType()), true));
215                                            } catch (ClassNotFoundException e) {
216                                                    throw new ConfigurationException("Cannot instantiate property of type "+properties[i].getType()+" from value "+properties[i].getStringValue());
217                                            }
218                                    }
219                            }
220                    }
221                    return ret;
222            }
223    
224            public void start() throws ConfigurationException {
225                    timer = new Timer();
226                    if (definition.getStartInBackground()) {
227                            new Thread(definition.getName()+" startup thread") {
228                                    public void run() {
229                                            try {
230                                                    super.start();
231                                            } catch (Exception e) {
232                                                    logger.log(Level.SEVERE, "Failed to start JMS Adapter "+definition.getName()+": "+e, e);
233                                            }
234                                    }
235                            }.start();
236                    } else {
237                            super.start();
238                    }
239    
240            }
241            
242            public void stop() throws ConfigurationException {
243                    if (timer!=null) {
244                            timer.cancel();
245                    }
246                    super.stop();
247            }
248    
249            private SimpleContext aliases = new SimpleContext();
250    
251            public void remove(String name) {
252                    aliases.remove(name);           
253            }
254    
255            public void set(String name, Object value) {
256                    aliases.set(name, value);               
257            }
258            
259            /**
260             * Returns service. Unwraps wrappers.
261             */
262            public Object get(String name) {                
263                    Object ret = super.get(name);
264                    if (ret==null) {
265                            ret = aliases.get(name);
266                    }
267                    return ret instanceof Wrapper ? ((Wrapper) ret).getMaster() : ret;
268            }
269    
270            /**
271             * The first and only argument is definition URL.
272             * If URL starts with "resource:" then definition is loaded from classpath resource.
273             * If the first argument is not present then definition is read from System.in
274             * Adapter is stopped by shutdown hook on JVM termination (Ctrl-C).
275             * @param args
276             */
277            public static void main(String[] args) {
278                    System.out.println("Usage: java <options> biz.hammurapi.jms.adapter.JmsAdapter [<definition URL>]");
279                    
280                    final long start=System.currentTimeMillis();
281                    
282                    XmlObject parsed;
283                    try {
284                            if (args.length==0) {
285                                    parsed = XmlObject.Factory.parse(System.in);
286                            } else if (args[0].startsWith(RESOURCE_PREFIX)) {
287                                    InputStream resourceStream = JmsAdapter.class.getClassLoader().getResourceAsStream(
288                                                    args[0].substring(RESOURCE_PREFIX.length()));
289                                    parsed = XmlObject.Factory.parse(resourceStream);
290                            } else {
291                                    parsed = XmlObject.Factory.parse(new URL(args[0]));
292                            }
293                    } catch (Exception e) {
294                            System.err.println("Could not load definition: "+e);
295                            e.printStackTrace();    
296                            System.exit(3);
297                            return; // Stupid, but needed for proper compilation.
298                    }
299                    
300                    try {
301                            if (parsed instanceof JmsAdapterDocument) {
302                                    ArrayList validationErrors = new ArrayList();
303                                    XmlOptions validationOptions = new XmlOptions();
304                                    validationOptions.setErrorListener(validationErrors);
305                                    if (!parsed.validate(validationOptions)) {
306                                            System.err.println("Invalid adapter definition:");
307                                        Iterator iter = validationErrors.iterator();
308                                        while (iter.hasNext()) {
309                                            System.err.println("\t>> " + iter.next());
310                                        }
311                                            
312                                            System.exit(1);
313                                    }
314                                    biz.hammurapi.jms.adapter.definition.JmsAdapter definition = ((JmsAdapterDocument) parsed).getJmsAdapter();
315                                    final JmsAdapter adapter = new JmsAdapter(definition);
316                                    System.out.println("Adapter started in "+((System.currentTimeMillis()-start)/1000)+" seconds");
317                                    Runtime.getRuntime().addShutdownHook(
318                                                    new Thread() {
319                                                            public void run() {
320                                                                    try {                                                   
321                                                                            adapter.stop();
322                                                                    } catch (ConfigurationException e) {
323                                                                            System.err.println("Could not properly stop JMS adapter.");
324                                                                            e.printStackTrace();
325                                                                    }
326                                                                    System.out.println("Total execution time: "+((System.currentTimeMillis()-start)/1000)+" sec.");                                                 
327                                                            }
328                                                    });                     
329                                    
330                                    adapter.start();
331                            } else {
332                                    System.out.println("Invalid adapter definition.");
333                                    System.exit(2);
334                            }
335                    } catch (Exception e) {
336                            System.err.println("Could not start adapter: "+e);
337                            e.printStackTrace();    
338                            System.exit(4);                 
339                    }
340                    
341            }
342            
343            public static boolean isBlank(String str) {
344                    return str==null || str.trim().length()==0;
345            }
346    
347            /**
348             * @return Adapter XML definition.
349             */
350            public biz.hammurapi.jms.adapter.definition.JmsAdapter getDefinition() {
351                    return definition;
352            }
353    
354            /**
355             * Adapter is always naming root. Owner is available through 'owner' alias.
356             */
357            public void setOwner(Object owner) {
358                    aliases.set("owner", owner);
359            }
360    
361            /**
362             * Executes job in the current thread if there is no default worker.
363             */
364            public boolean post(Runnable job) {
365                    if (defaultWorker==null) {
366                            job.run();
367                            return true;
368                    }
369                    
370                    return defaultWorker.post(job);
371            }
372            
373    }