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 }