001: /**
002: * EasyBeans
003: * Copyright (C) 2006 Bull S.A.S.
004: * Contact: easybeans@ow2.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: ClientContainer.java 1970 2007-10-16 11:49:25Z benoitf $
023: * --------------------------------------------------------------------------
024: */package org.ow2.easybeans.client;
025:
026: import static org.ow2.easybeans.util.url.URLUtils.fileToURL2;
027:
028: import java.io.File;
029: import java.lang.reflect.Field;
030: import java.lang.reflect.InvocationTargetException;
031: import java.lang.reflect.Method;
032: import java.net.URL;
033: import java.net.URLClassLoader;
034: import java.util.ArrayList;
035: import java.util.Arrays;
036: import java.util.HashMap;
037: import java.util.List;
038: import java.util.Map;
039: import java.util.StringTokenizer;
040: import java.util.jar.Attributes;
041: import java.util.jar.JarFile;
042: import java.util.jar.Manifest;
043:
044: import javax.naming.Context;
045: import javax.naming.InitialContext;
046: import javax.naming.LinkRef;
047: import javax.naming.NamingException;
048:
049: import org.objectweb.carol.util.configuration.ConfigurationRepository;
050: import org.ow2.easybeans.client.xml.ApplicationClient;
051: import org.ow2.easybeans.client.xml.ApplicationClientLoader;
052: import org.ow2.easybeans.deployment.Deployment;
053: import org.ow2.easybeans.deployment.resolver.JNDIResolver;
054: import org.ow2.easybeans.deployment.xml.struct.common.EJBRef;
055: import org.ow2.easybeans.deployment.xml.struct.common.InjectionTarget;
056: import org.ow2.easybeans.enhancer.EnhancerException;
057: import org.ow2.easybeans.loader.EasyBeansClassLoader;
058: import org.ow2.easybeans.naming.NamingManager;
059: import org.ow2.easybeans.util.files.FileUtils;
060: import org.ow2.easybeans.util.files.FileUtilsException;
061: import org.ow2.easybeans.util.url.URLUtilsException;
062: import org.ow2.util.ee.deploy.api.archive.IArchive;
063: import org.ow2.util.ee.deploy.impl.archive.ArchiveManager;
064: import org.ow2.util.log.Log;
065: import org.ow2.util.log.LogFactory;
066:
067: /**
068: * Defines the class use for the client container This class analyze the ear or
069: * the jar client and launch the client.
070: * @author Florent Benoit
071: */
072: public final class ClientContainer {
073:
074: /**
075: * Folder to create in tmp folder.
076: */
077: private static final String DEFAULT_FOLDER = "EasyBeans-Deployer";
078:
079: /**
080: * Main class to use to launch the application client.
081: */
082: private String mainClass = null;
083:
084: /**
085: * Temporary directory.
086: */
087: private String tmpDir = null;
088:
089: /**
090: * Classpath for the application client.
091: */
092: private String classpath = null;
093:
094: /**
095: * Arguments used by the client.
096: */
097: private String[] args = null;
098:
099: /**
100: * Extra Arguments.
101: */
102: private List<String> appArgs = null;
103:
104: /**
105: * URLs resolved in the case of the extension mechanism in the Ear case.
106: */
107: private URL[] extensionsURLs = null;
108:
109: /**
110: * Logger.
111: */
112: private Log logger = LogFactory.getLog(ClientContainer.class);
113:
114: /**
115: * Reference to a JNDIResolver.
116: */
117: private JNDIResolver jndiResolver = null;
118:
119: /**
120: * Jar client to use (if many).
121: */
122: private String jarClient = null;
123:
124: /**
125: * XML Struct representing application-client.xml file.
126: */
127: private ApplicationClient applicationClient = null;
128:
129: /**
130: * Constructor for a Client container.
131: * @param args the arguments of the instance of the client container
132: */
133: private ClientContainer(final String[] args) {
134: this .args = args;
135:
136: appArgs = new ArrayList<String>();
137: }
138:
139: /**
140: * Main method of the Client container.
141: * @param args the arguments of the client container
142: */
143: public static void main(final String[] args) {
144: // Retrieve command line parameters
145: ClientContainer cc = new ClientContainer(args);
146:
147: try {
148: cc.start();
149: } catch (InvocationTargetException ite) {
150: Throwable t = ite.getTargetException();
151: String message = t.getMessage();
152: if (t instanceof Error) {
153: System.err.println("There was the following error : "
154: + message);
155: } else if (t instanceof Exception) {
156: System.err
157: .println("There was the following exception : "
158: + message);
159: }
160: t.printStackTrace(System.err);
161: } catch (Exception e) {
162: System.err.println("There was the following exception : "
163: + e.getMessage());
164: e.printStackTrace();
165: System.exit(-1);
166: }
167: }
168:
169: /**
170: * Start the client container.
171: * @throws Exception if it fails
172: */
173: private void start() throws Exception {
174: analyzeArgs();
175:
176: // Get the filename
177: String userArg = null;
178: String fileName = null;
179: boolean fileMode = true;
180:
181: try {
182: userArg = appArgs.get(0);
183: } catch (IndexOutOfBoundsException ioobe) {
184: usage();
185: throw new ClientContainerException(
186: "You haven't specify a jar, an ear file or class name as argument."
187: + "See the Usage.");
188: }
189:
190: String className = null;
191: // Test if this is an ear or a jar file else it must be a class name
192: if (!(userArg.toLowerCase().endsWith(".jar") || userArg
193: .toLowerCase().endsWith(".ear"))) {
194: className = userArg;
195: fileMode = false;
196: } else {
197: fileMode = true;
198: fileName = userArg;
199: }
200:
201: // Build file and test if it exists
202: File clientJarFile = null;
203: if (fileMode) {
204: File argFile = new File(fileName);
205:
206: // Unpack and analyze EAR file if it is an ear
207: if (fileName.toLowerCase().endsWith(".ear")) {
208: clientJarFile = extractAndAnalyzeEar(argFile);
209: } else {
210: // Client jar is the given file
211: clientJarFile = argFile;
212: }
213: }
214:
215: // Carol initialisation (property)
216: ConfigurationRepository.init();
217: System
218: .setProperty(Context.INITIAL_CONTEXT_FACTORY,
219: "org.objectweb.carol.jndi.spi.MultiOrbInitialContextFactory");
220: System.setProperty(Context.URL_PKG_PREFIXES,
221: "org.ow2.easybeans.naming.pkg");
222:
223: // Extract Main-Class to use in the jar from the manifest
224: if (fileMode) {
225: Manifest manifest = new JarFile(clientJarFile)
226: .getManifest();
227:
228: if (manifest == null) {
229: throw new ClientContainerException(
230: "No manifest was found inside the file"
231: + clientJarFile);
232: }
233:
234: // Extract attributes
235: Attributes attributes = manifest.getMainAttributes();
236:
237: if (attributes == null) {
238: throw new ClientContainerException(
239: "No attributes were found in the manifest of the file '"
240: + clientJarFile + "'.");
241: }
242: mainClass = attributes.getValue(Attributes.Name.MAIN_CLASS);
243: } else {
244: mainClass = className;
245: }
246:
247: // Invoke the client if there is no need of XML parsing
248: if (!fileMode) {
249: ClassLoader clientCL = new URLClassLoader(
250: getUserClasspathUrls());
251: Thread.currentThread().setContextClassLoader(clientCL);
252: invokeClient();
253: return;
254: }
255:
256: if (mainClass == null || mainClass.length() == 0) {
257: throw new ClientContainerException(
258: "No main class was found inside the Manifest of the file '"
259: + clientJarFile
260: + "'. This attribute is required to launch the application client.");
261: }
262:
263: logger.info("Using Main-Class : {0}", mainClass);
264:
265: // Convert file to URL
266: URL clientJarURL = null;
267:
268: try {
269: clientJarURL = fileToURL2(clientJarFile);
270: } catch (URLUtilsException uue) {
271: throw new ClientContainerException(
272: "Error when building an URL with the file '"
273: + clientJarFile + "'.", uue);
274: }
275:
276: // Parse xml file (if any)
277: applicationClient = ApplicationClientLoader
278: .loadApplicationClient(clientJarFile);
279:
280: // Build the urls for the classloader
281: URL[] urlsClient = null;
282:
283: // URLs for the classloader
284: if (extensionsURLs != null) {
285: // There were URLs with the extension mechanism in the EAR
286: urlsClient = new URL[extensionsURLs.length + 1];
287:
288: for (int i = 0; i < extensionsURLs.length; i++) {
289: urlsClient[i] = extensionsURLs[i];
290:
291: logger.debug("Adding " + extensionsURLs[i]
292: + " to the urls of the client");
293: }
294:
295: urlsClient[extensionsURLs.length] = clientJarURL;
296: } else {
297: logger.debug("Only one url for urls of client");
298:
299: // No extension or jar case.
300: urlsClient = new URL[1];
301: urlsClient[0] = clientJarURL;
302: }
303:
304: // Build classloader
305: logger.debug("Building classloader with urls {0}", Arrays
306: .asList(urlsClient));
307: EasyBeansClassLoader clientClassloader = new EasyBeansClassLoader(
308: urlsClient, Thread.currentThread()
309: .getContextClassLoader());
310: Thread.currentThread().setContextClassLoader(clientClassloader);
311:
312: // Build environment of the client.
313: buildENC();
314:
315: // Start client
316: invokeClient();
317:
318: }
319:
320: /**
321: * Start the client on its main class with the thread class loader.
322: * @throws ClassNotFoundException if class is not found
323: * @throws NoSuchMethodException if method (main) is not found
324: * @throws IllegalAccessException if access is illegal
325: * @throws InvocationTargetException if invocation failed
326: * @throws EnhancerException if enhancement fails.
327: */
328: private void invokeClient() throws ClassNotFoundException,
329: NoSuchMethodException, IllegalAccessException,
330: InvocationTargetException, EnhancerException {
331: ClassLoader clientClassloader = Thread.currentThread()
332: .getContextClassLoader();
333:
334: if (logger.isDebugEnabled()) {
335: if (clientClassloader instanceof URLClassLoader) {
336: URLClassLoader urlClassLoader = (URLClassLoader) clientClassloader;
337: URL[] urls = urlClassLoader.getURLs();
338: logger.debug("URLs of the classloader :");
339: for (int u = 0; u < urls.length; u++) {
340: logger.debug("URL[" + u + "] = " + urls[u]);
341: }
342: }
343: }
344:
345: // Need to enhance client class (for injected resources).
346: List<String> classesToEnhance = new ArrayList<String>();
347: classesToEnhance.add(mainClass.replace(".", "/"));
348: Map<String, Object> map = new HashMap<String, Object>();
349: map.put(JNDIResolver.NAME, jndiResolver);
350: ClientEnhancer.enhance(Thread.currentThread()
351: .getContextClassLoader(), classesToEnhance, map);
352:
353: // Invoke client
354: // Launch the "class_to_run" by using our classloader.
355: Class clazz = clientClassloader.loadClass(mainClass);
356: Class[] argList = new Class[] { args.getClass() };
357:
358: // ejb-ref injection code
359: if (applicationClient != null) {
360: for (EJBRef ejbRef : applicationClient.getEJBRefs()) {
361: List<InjectionTarget> injectionTargetList = ejbRef
362: .getInjectionTargetList();
363: if (injectionTargetList != null) {
364: for (InjectionTarget injectionTarget : injectionTargetList) {
365: // get target name
366: String targetName = injectionTarget
367: .getTargetName();
368: try {
369: Field field = clazz
370: .getDeclaredField(targetName);
371: boolean access = field.isAccessible();
372: try {
373: if (!access) {
374: field.setAccessible(true);
375: }
376: // ejb-link
377: String ejbLink = ejbRef.getEjbLink();
378: // interface
379: String itfName = ejbRef.getRemote();
380:
381: // Gets the jndi name
382: String jndiName = jndiResolver
383: .getJndiNameInterface(itfName,
384: ejbLink);
385: if (jndiName != null) {
386: Object value = null;
387: try {
388: value = new InitialContext()
389: .lookup(jndiName);
390: } catch (NamingException e) {
391: logger.error(
392: "Unable to lookup the name '"
393: + jndiName
394: + "'.", e);
395: }
396: if (value != null) {
397: field.set(applicationClient,
398: value);
399: } else {
400: logger
401: .error("No value found in Initial Context for the name '"
402: + jndiName
403: + "'.");
404: }
405: } else {
406: logger
407: .error("No JNDI name found for interface '"
408: + itfName + "'.");
409: }
410: } finally {
411: if (!access) {
412: field.setAccessible(false);
413: }
414: }
415:
416: } catch (SecurityException e) {
417: logger
418: .error(
419: "Cannot perform injection on the client",
420: e);
421: } catch (NoSuchFieldException e) {
422: logger
423: .error(
424: "Cannot perform injection on the client",
425: e);
426: }
427: }
428: }
429: }
430:
431: }
432:
433: // First, call injection code
434: Method injectedMeth = clazz.getMethod("injectedByEasyBeans",
435: new Class[] {});
436: try {
437: injectedMeth.invoke(null, new Object[] {});
438: } catch (Exception e) {
439: logger.error("Cannot perform injection on the client", e);
440: }
441:
442: // Then, call lifecycle method
443: Method lifeCycleMeth = clazz.getMethod(
444: "easyBeansLifeCyclePostConstruct", new Class[] {});
445: try {
446: lifeCycleMeth.invoke(null, new Object[] {});
447: } catch (Exception e) {
448: logger.error("Cannot perform lifecycle init on the client",
449: e);
450: }
451:
452: Method meth = clazz.getMethod("main", argList);
453:
454: // Remove name of the file from arguments
455: String[] newArgs = new String[appArgs.size() - 1];
456: String txtArgs = "";
457:
458: for (int i = 0; i < newArgs.length; i++) {
459: newArgs[i] = appArgs.get(i + 1);
460: txtArgs += (newArgs[i] + " ");
461: }
462:
463: logger
464: .info("Starting the application client with the arguments '"
465: + txtArgs + "'.");
466:
467: logger.info("Starting client...");
468:
469: meth.invoke(null, new Object[] { newArgs });
470:
471: logger.debug("End of main method");
472:
473: }
474:
475: /**
476: * Analyze arguments and extract parameters for the client container.
477: * @throws Exception if there is an error when analyzing arguments
478: */
479: private void analyzeArgs() throws Exception {
480: for (int argn = 0; argn < args.length; argn++) {
481: String arg = args[argn];
482:
483: try {
484: if (arg.equals("-tmpDir")) {
485: tmpDir = args[++argn];
486:
487: continue;
488: }
489:
490: if (arg.equals("-jarClient")) {
491: jarClient = args[++argn];
492:
493: continue;
494: }
495:
496: if (arg.equals("-cp")) {
497: classpath = args[++argn];
498: continue;
499: }
500:
501: if (arg.equals("--help") || arg.equals("-help")
502: || arg.equals("-h") || arg.equals("-?")) {
503: usage();
504: System.exit(1);
505: }
506:
507: // Add argument to the application arguments
508: appArgs.add(arg);
509: } catch (ArrayIndexOutOfBoundsException aioobe) {
510: // The next argument is not in the array
511: throw new ClientContainerException(
512: "A required parameter was missing after the argument"
513: + arg);
514: }
515: }
516: }
517:
518: /**
519: * Print the usage of this client.
520: */
521: private void usage() {
522: System.out.println("Usage of this client :");
523: System.out
524: .println("-------------------------------------------------------------------");
525: System.out
526: .println("java -jar client.jar <client.jar|app.ear|className> [options]");
527: System.out
528: .println("-------------------------------------------------------------------");
529: System.out
530: .println(" -jarClient : Specify the client jar to use of the ear if many.");
531: System.out
532: .println(" -tmpDir : Specify the temp directory where unpack the ear.");
533: System.out
534: .println(" -cp : Specify the classpath to use for the jar client.");
535: System.out
536: .println("-------------------------------------------------------------------");
537: System.out.println(" --help : Display this help.");
538: System.out.println(" -help : Display this help.");
539: System.out.println(" -h : Display this help.");
540: System.out.println(" -? : Display this help.");
541: System.out
542: .println("-------------------------------------------------------------------");
543: }
544:
545: /**
546: * Extract the client of an ear and analyze ear too.
547: * @param earFile ear to be analyzed
548: * @return the file of the client which was extracted
549: * @throws Exception if the analyze and/or extract fails
550: */
551: private File extractAndAnalyzeEar(final File earFile)
552: throws Exception {
553:
554: URL earUrl = null;
555:
556: try {
557: earUrl = fileToURL2(earFile);
558: } catch (URLUtilsException uue) {
559: throw new ClientContainerException(
560: "Can not build an url with the filename '"
561: + earFile + "'.", uue);
562: }
563:
564: // Create classLoader
565: URL[] arrURL = new URL[1];
566: arrURL[0] = earUrl;
567:
568: // Temporary directory
569: String tempDir = null;
570:
571: if (tmpDir != null) {
572: // use specific directory
573: tempDir = tmpDir;
574: logger.debug("Use your specified temp directory '"
575: + tempDir + "'.");
576: } else {
577: // use default
578: tempDir = System.getProperty("java.io.tmpdir");
579: }
580:
581: logger.debug("Using temp directory {0}", tempDir);
582:
583: // Can we write to ?
584: File tmpFileDir = new File(tempDir);
585:
586: if (!tmpFileDir.exists() || !tmpFileDir.isDirectory()) {
587: throw new ClientContainerException("The temp directory '"
588: + tempDir
589: + "' doesn't exist or is not a directory.");
590: }
591:
592: if (!tmpFileDir.canWrite()) {
593: throw new ClientContainerException(
594: "Can not write to the temporary directory '"
595: + tempDir + "'.");
596: }
597:
598: logger.info("Ear file = {0}", earFile);
599: // Unpack the ear file and get the unpacked dir
600: JarFile earJarFile = new JarFile(earFile);
601: File rootFolder = new File(System.getProperty("java.io.tmpdir")
602: + File.separator + System.getProperty("user.name")
603: + File.separator + DEFAULT_FOLDER);
604: rootFolder.mkdirs();
605:
606: File tmpFolder = new File(rootFolder, "TMP");
607: tmpFolder.mkdirs();
608: File file = new File(tmpFolder, earFile.getName() + ".new");
609:
610: try {
611: FileUtils.unpack(earJarFile, file);
612: } catch (FileUtilsException e) {
613: throw new ClientContainerException("Cannot unpack '"
614: + earJarFile + "'.", e);
615: }
616:
617: File fClient = null;
618:
619: jndiResolver = new JNDIResolver();
620:
621: File clientFile = null;
622: File[] files = file.listFiles();
623: if (files != null) {
624: if (jarClient != null) {
625: int f = 0;
626: File ff = null;
627: boolean found = false;
628:
629: while (f < files.length && !found) {
630: ff = files[f];
631:
632: if (ff.getPath().endsWith(jarClient)) {
633: found = true;
634: clientFile = ff;
635: logger
636: .info(
637: "Found a matching client with the name {0}",
638: ff);
639: }
640:
641: f++;
642: }
643:
644: if (!found) {
645: throw new ClientContainerException(
646: "No client with the name '" + jarClient
647: + "' was found in this Ear file");
648: }
649: }
650:
651: for (File f : files) {
652: if (f.getName().toLowerCase().endsWith("_client.jar")
653: && clientFile == null) {
654: clientFile = f;
655: } else if (f.getName().toLowerCase().endsWith(".ear")) {
656: // ear, ignore
657: continue;
658: } else {
659: IArchive archive = ArchiveManager.getInstance()
660: .getArchive(f);
661: Deployment dep = new Deployment(archive);
662: dep.analyze();
663: jndiResolver.addDeployment(dep);
664: }
665: }
666: }
667:
668: logger.debug("Resolver =" + jndiResolver);
669:
670: if (clientFile == null) {
671: throw new IllegalStateException("No client found");
672: }
673:
674: // Take found or first found
675: fClient = clientFile;
676:
677: logger.info("Use the application client '" + fClient
678: + "' of the Ear '" + earUrl + "'.");
679:
680: return fClient;
681: }
682:
683: /**
684: * Gets the URL of user classpath (can be empty).
685: * @return URL of user classpath (-cp arg)
686: */
687: private URL[] getUserClasspathUrls() {
688: if (classpath == null) {
689: return new URL[0];
690: }
691: String sep = File.pathSeparator;
692: List<URL> clUser = new ArrayList<URL>();
693: StringTokenizer tokenizer = new StringTokenizer(classpath, sep);
694: while (tokenizer.hasMoreTokens()) {
695: File file = new File(tokenizer.nextToken());
696: try {
697: clUser.add(fileToURL2(file));
698: } catch (URLUtilsException uue) {
699: logger.warn("Cannot transform to URL the file : '"
700: + file + "'", uue);
701: }
702: }
703: return clUser.toArray(new URL[0]);
704: }
705:
706: /**
707: * Build ENC environment used by this client.
708: * @throws ClientContainerException if the ENC environment is not built.
709: */
710: private void buildENC() throws ClientContainerException {
711:
712: // Get NamingManager reference
713: NamingManager namingManager = null;
714: try {
715: namingManager = NamingManager.getInstance();
716: } catch (NamingException e) {
717: throw new ClientContainerException(
718: "Cannot get a reference on the naming manager.", e);
719: }
720:
721: Context javaCtx = null;
722: // create env
723: try {
724: javaCtx = namingManager.createEnvironmentContext("client");
725: } catch (NamingException e) {
726: throw new ClientContainerException(
727: "Cannot build an environment context", e);
728: }
729:
730: Context envCtx;
731: try {
732: envCtx = javaCtx.createSubcontext("comp/env");
733: } catch (NamingException e) {
734: throw new ClientContainerException(
735: "Cannot create subcontext comp/env", e);
736: }
737:
738: // register environment for this client.
739: namingManager.setClientContainerComponentContext(javaCtx);
740:
741: // add ejb-ref
742: if (applicationClient != null) {
743: for (EJBRef ejbRef : applicationClient.getEJBRefs()) {
744: //get name
745: String ejbRefName = ejbRef.getEjbRefName();
746:
747: // ejb-link
748: String ejbLink = ejbRef.getEjbLink();
749:
750: // interface
751: String home = ejbRef.getHome();
752: String remote = ejbRef.getRemote();
753: String itfName = null;
754: if (home != null) {
755: itfName = home;
756: } else {
757: itfName = remote;
758: }
759:
760: // Resolve reference
761: String jndiName = jndiResolver.getJndiNameInterface(
762: itfName, ejbLink);
763:
764: // bind reference
765: try {
766: logger
767: .debug(
768: "Binding ejb-ref-name {0} with jndi-name {1}",
769: ejbRefName, jndiName);
770: envCtx.bind(ejbRefName, new LinkRef(jndiName));
771: } catch (NamingException e) {
772: throw new ClientContainerException(
773: "Cannot bind name '" + ejbRefName
774: + "' in java:comp/env", e);
775: }
776: }
777: }
778: }
779:
780: }
|