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 }