001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.client.builder;
018    
019    import java.io.File;
020    import java.io.IOException;
021    import java.net.URI;
022    import java.net.URISyntaxException;
023    import java.net.URL;
024    import java.util.ArrayList;
025    import java.util.Collection;
026    import java.util.Collections;
027    import java.util.HashMap;
028    import java.util.LinkedList;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.StringTokenizer;
032    import java.util.LinkedHashSet;
033    import java.util.jar.Attributes;
034    import java.util.jar.JarFile;
035    import java.util.jar.Manifest;
036    import java.util.zip.ZipEntry;
037    
038    import org.apache.commons.logging.Log;
039    import org.apache.commons.logging.LogFactory;
040    import org.apache.geronimo.client.AppClientContainer;
041    import org.apache.geronimo.client.StaticJndiContextPlugin;
042    import org.apache.geronimo.common.DeploymentException;
043    import org.apache.geronimo.deployment.ClassPathList;
044    import org.apache.geronimo.deployment.DeploymentContext;
045    import org.apache.geronimo.deployment.ModuleIDBuilder;
046    import org.apache.geronimo.deployment.NamespaceDrivenBuilder;
047    import org.apache.geronimo.deployment.NamespaceDrivenBuilderCollection;
048    import org.apache.geronimo.deployment.ModuleList;
049    import org.apache.geronimo.deployment.service.EnvironmentBuilder;
050    import org.apache.geronimo.deployment.service.GBeanBuilder;
051    import org.apache.geronimo.deployment.util.DeploymentUtil;
052    import org.apache.geronimo.deployment.util.NestedJarFile;
053    import org.apache.geronimo.deployment.xbeans.EnvironmentType;
054    import org.apache.geronimo.deployment.xbeans.PatternType;
055    import org.apache.geronimo.deployment.xmlbeans.XmlBeansUtil;
056    import org.apache.geronimo.gbean.AbstractName;
057    import org.apache.geronimo.gbean.AbstractNameQuery;
058    import org.apache.geronimo.gbean.GBeanData;
059    import org.apache.geronimo.gbean.GBeanInfo;
060    import org.apache.geronimo.gbean.GBeanInfoBuilder;
061    import org.apache.geronimo.gbean.SingleElementCollection;
062    import org.apache.geronimo.j2ee.deployment.AppClientModule;
063    import org.apache.geronimo.j2ee.deployment.ConnectorModule;
064    import org.apache.geronimo.j2ee.deployment.CorbaGBeanNameSource;
065    import org.apache.geronimo.j2ee.deployment.EARContext;
066    import org.apache.geronimo.j2ee.deployment.Module;
067    import org.apache.geronimo.j2ee.deployment.ModuleBuilder;
068    import org.apache.geronimo.j2ee.deployment.ModuleBuilderExtension;
069    import org.apache.geronimo.j2ee.deployment.NamingBuilder;
070    import org.apache.geronimo.j2ee.deployment.NamingBuilderCollection;
071    import org.apache.geronimo.j2ee.deployment.annotation.AnnotatedApplicationClient;
072    import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
073    import org.apache.geronimo.j2ee.management.impl.J2EEAppClientModuleImpl;
074    import org.apache.geronimo.j2ee.ApplicationInfo;
075    import org.apache.geronimo.kernel.GBeanAlreadyExistsException;
076    import org.apache.geronimo.kernel.Naming;
077    import org.apache.geronimo.kernel.config.Configuration;
078    import org.apache.geronimo.kernel.config.ConfigurationAlreadyExistsException;
079    import org.apache.geronimo.kernel.config.ConfigurationModuleType;
080    import org.apache.geronimo.kernel.config.ConfigurationStore;
081    import org.apache.geronimo.kernel.repository.Artifact;
082    import org.apache.geronimo.kernel.repository.Environment;
083    import org.apache.geronimo.kernel.repository.Repository;
084    import org.apache.geronimo.kernel.repository.ArtifactResolver;
085    import org.apache.geronimo.kernel.repository.MissingDependencyException;
086    import org.apache.geronimo.schema.SchemaConversionUtils;
087    import org.apache.geronimo.security.deploy.SubjectInfo;
088    import org.apache.geronimo.security.deployment.SecurityConfiguration;
089    import org.apache.geronimo.xbeans.geronimo.client.GerApplicationClientDocument;
090    import org.apache.geronimo.xbeans.geronimo.client.GerApplicationClientType;
091    import org.apache.geronimo.xbeans.geronimo.client.GerResourceType;
092    import org.apache.geronimo.xbeans.geronimo.naming.GerAbstractNamingEntryDocument;
093    import org.apache.geronimo.xbeans.geronimo.security.GerSubjectInfoType;
094    import org.apache.geronimo.xbeans.javaee.ApplicationClientDocument;
095    import org.apache.geronimo.xbeans.javaee.ApplicationClientType;
096    import org.apache.geronimo.xbeans.javaee.FullyQualifiedClassType;
097    import org.apache.xbean.finder.ClassFinder;
098    import org.apache.xmlbeans.XmlCursor;
099    import org.apache.xmlbeans.XmlException;
100    import org.apache.xmlbeans.XmlObject;
101    
102    
103    /**
104     * @version $Rev:385232 $ $Date: 2007-07-31 05:39:53 -0400 (Tue, 31 Jul 2007) $
105     */
106    public class AppClientModuleBuilder implements ModuleBuilder, CorbaGBeanNameSource {
107        private static final Log log = LogFactory.getLog(AppClientModuleBuilder.class);
108        private static final String LINE_SEP = System.getProperty("line.separator");
109        private static final String GERAPPCLIENT_NAMESPACE = GerApplicationClientDocument.type.getDocumentElementName().getNamespaceURI();
110    
111        private final Environment defaultClientEnvironment;
112        private final Environment defaultServerEnvironment;
113        private final AbstractNameQuery corbaGBeanObjectName;
114    
115        private final AbstractNameQuery transactionManagerObjectName;
116        private final AbstractNameQuery connectionTrackerObjectName;
117        private final AbstractNameQuery credentialStoreName;
118        private final SingleElementCollection connectorModuleBuilder;
119        private final NamespaceDrivenBuilderCollection serviceBuilder;
120        private final NamingBuilderCollection namingBuilders;
121        private final Collection<ModuleBuilderExtension> moduleBuilderExtensions;
122    
123        private final Collection<Repository> repositories;
124    
125        private final ArtifactResolver clientArtifactResolver;
126    
127        public AppClientModuleBuilder(Environment defaultClientEnvironment,
128                                      Environment defaultServerEnvironment,
129                                      AbstractNameQuery transactionManagerObjectName,
130                                      AbstractNameQuery connectionTrackerObjectName,
131                                      AbstractNameQuery corbaGBeanObjectName,
132                                      AbstractNameQuery credentialStoreName,
133                                      Collection<Repository> repositories,
134                                      ModuleBuilder connectorModuleBuilder,
135                                      NamespaceDrivenBuilder serviceBuilder,
136                                      Collection<NamingBuilder> namingBuilders,
137                                      Collection<ModuleBuilderExtension> moduleBuilderExtensions,
138                                      ArtifactResolver clientArtifactResolver) {
139            this(defaultClientEnvironment,
140                    defaultServerEnvironment,
141                    transactionManagerObjectName,
142                    connectionTrackerObjectName,
143                    corbaGBeanObjectName,
144                    credentialStoreName, repositories, new SingleElementCollection<ModuleBuilder>(connectorModuleBuilder),
145                    serviceBuilder == null ? Collections.EMPTY_SET : Collections.singleton(serviceBuilder),
146                    namingBuilders == null ? Collections.EMPTY_SET : namingBuilders,
147                    moduleBuilderExtensions,
148                    clientArtifactResolver);
149        }
150    
151        public AppClientModuleBuilder(AbstractNameQuery transactionManagerObjectName,
152                                      AbstractNameQuery connectionTrackerObjectName,
153                                      AbstractNameQuery corbaGBeanObjectName,
154                                      AbstractNameQuery credentialStoreName,
155                                      Collection<Repository> repositories,
156                                      Collection<ModuleBuilder> connectorModuleBuilder,
157                                      Collection<NamespaceDrivenBuilder> serviceBuilder,
158                                      Collection<NamingBuilder> namingBuilders,
159                                      Collection<ModuleBuilderExtension> moduleBuilderExtensions,
160                                      ArtifactResolver clientArtifactResolver,
161                                      Environment defaultClientEnvironment,
162                                      Environment defaultServerEnvironment
163        ) {
164            this(defaultClientEnvironment,
165                    defaultServerEnvironment,
166                    transactionManagerObjectName,
167                    connectionTrackerObjectName,
168                    corbaGBeanObjectName,
169                    credentialStoreName, repositories,
170                    new SingleElementCollection<ModuleBuilder>(connectorModuleBuilder),
171                    serviceBuilder,
172                    namingBuilders,
173                    moduleBuilderExtensions,
174                    clientArtifactResolver);
175        }
176    
177        private AppClientModuleBuilder(Environment defaultClientEnvironment,
178                                       Environment defaultServerEnvironment,
179                                       AbstractNameQuery transactionManagerObjectName,
180                                       AbstractNameQuery connectionTrackerObjectName,
181                                       AbstractNameQuery corbaGBeanObjectName,
182                                       AbstractNameQuery credentialStoreName,
183                                       Collection<Repository> repositories,
184                                       SingleElementCollection<ModuleBuilder> connectorModuleBuilder,
185                                       Collection<NamespaceDrivenBuilder> serviceBuilder,
186                                       Collection<NamingBuilder> namingBuilders,
187                                       Collection<ModuleBuilderExtension> moduleBuilderExtensions,
188                                       ArtifactResolver clientArtifactResolver) {
189            this.defaultClientEnvironment = defaultClientEnvironment;
190            this.defaultServerEnvironment = defaultServerEnvironment;
191            this.corbaGBeanObjectName = corbaGBeanObjectName;
192            this.transactionManagerObjectName = transactionManagerObjectName;
193            this.connectionTrackerObjectName = connectionTrackerObjectName;
194            this.credentialStoreName = credentialStoreName;
195            this.repositories = repositories;
196            this.connectorModuleBuilder = connectorModuleBuilder;
197            this.serviceBuilder = new NamespaceDrivenBuilderCollection(serviceBuilder, GBeanBuilder.SERVICE_QNAME);
198            this.namingBuilders = new NamingBuilderCollection(namingBuilders, GerAbstractNamingEntryDocument.type.getDocumentElementName());
199            this.moduleBuilderExtensions = moduleBuilderExtensions;
200            this.clientArtifactResolver = clientArtifactResolver;
201        }
202    
203    
204        public AbstractNameQuery getCorbaGBeanName() {
205            return corbaGBeanObjectName;
206        }
207    
208        private ModuleBuilder getConnectorModuleBuilder() {
209            return (ModuleBuilder) connectorModuleBuilder.getElement();
210        }
211    
212        public Module createModule(File plan, JarFile moduleFile, Naming naming, ModuleIDBuilder idBuilder) throws DeploymentException {
213            return createModule(plan, moduleFile, "app-client", null, null, null, naming, idBuilder);
214        }
215    
216        public Module createModule(Object plan, JarFile moduleFile, String targetPath, URL specDDUrl, Environment environment, Object moduleContextInfo, AbstractName earName, Naming naming, ModuleIDBuilder idBuilder) throws DeploymentException {
217            return createModule(plan, moduleFile, targetPath, specDDUrl, environment, earName, naming, idBuilder);
218        }
219    
220        private Module createModule(Object plan, JarFile moduleFile, String targetPath, URL specDDUrl, Environment earEnvironment, AbstractName earName, Naming naming, ModuleIDBuilder idBuilder) throws DeploymentException {
221            assert moduleFile != null : "moduleFile is null";
222            assert targetPath != null : "targetPath is null";
223            assert !targetPath.endsWith("/") : "targetPath must not end with a '/'";
224            assert (earName == null) == (earEnvironment == null) : "if earName is not null you must supply earEnvironment as well";
225    
226            boolean standAlone = earEnvironment == null;
227    
228            // get the app client main class
229            String mainClass;
230            try {
231                Manifest manifest = moduleFile.getManifest();
232                if (manifest == null) {
233                    throw new DeploymentException("App client module jar does not contain a manifest: " + moduleFile.getName());
234                }
235                mainClass = manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
236                if (mainClass == null) {
237                    //not an app client
238                    return null;
239                }
240                String classPath = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
241                if (standAlone && classPath != null) {
242                    throw new DeploymentException("Manifest class path entry is not allowed in a standalone jar (JAVAEE 5 Section 8.2)");
243                }
244            } catch (IOException e) {
245                throw new DeploymentException("Could not get manifest from app client module: " + moduleFile.getName(), e);
246            }
247    
248            String specDD;
249            ApplicationClientType appClient = null;
250            try {
251                if (specDDUrl == null) {
252                    specDDUrl = DeploymentUtil.createJarURL(moduleFile, "META-INF/application-client.xml");
253                }
254    
255                // read in the entire specDD as a string, we need this for getDeploymentDescriptor
256                // on the J2ee management object
257                specDD = DeploymentUtil.readAll(specDDUrl);
258            } catch (Exception e) {
259                //construct a default spec dd
260                ApplicationClientDocument appClientDoc = ApplicationClientDocument.Factory.newInstance();
261                appClientDoc.addNewApplicationClient();
262                appClient = appClientDoc.getApplicationClient();
263                specDD = appClientDoc.xmlText();
264            }
265    
266            if (appClient == null) {
267                //we found application-client.xml, if it won't parse it's an error.
268                try {
269                    // parse it
270                    XmlObject xmlObject = XmlBeansUtil.parse(specDD);
271                    ApplicationClientDocument appClientDoc = convertToApplicationClientSchema(xmlObject);
272                    appClient = appClientDoc.getApplicationClient();
273                } catch (XmlException e) {
274                    throw new DeploymentException("Unable to parse application-client.xml", e);
275                }
276            }
277    
278            // parse vendor dd
279            GerApplicationClientType gerAppClient = getGeronimoAppClient(plan, moduleFile, standAlone, targetPath, appClient, earEnvironment);
280    
281    
282            EnvironmentType clientEnvironmentType = gerAppClient.getClientEnvironment();
283            Environment clientEnvironment = EnvironmentBuilder.buildEnvironment(clientEnvironmentType, defaultClientEnvironment);
284            if (standAlone) {
285                String name = new File(moduleFile.getName()).getName();
286                idBuilder.resolve(clientEnvironment, name + "_" + name, "jar");
287            } else {
288                Artifact earConfigId = earEnvironment.getConfigId();
289                idBuilder.resolve(clientEnvironment, earConfigId.getArtifactId() + "_" + targetPath, "jar");
290            }
291            EnvironmentType serverEnvironmentType = gerAppClient.getServerEnvironment();
292            Environment serverEnvironment = EnvironmentBuilder.buildEnvironment(serverEnvironmentType, defaultServerEnvironment);
293            if (!standAlone) {
294                EnvironmentBuilder.mergeEnvironments(earEnvironment, serverEnvironment);
295                serverEnvironment = earEnvironment;
296                if (!serverEnvironment.getConfigId().isResolved()) {
297                    throw new IllegalStateException("Server environment module ID should be fully resolved (not " + serverEnvironment.getConfigId() + ")");
298                }
299            } else {
300                idBuilder.resolve(serverEnvironment, new File(moduleFile.getName()).getName(), "jar");
301            }
302    
303            if (earName == null) {
304                earName = naming.createRootName(serverEnvironment.getConfigId(), NameFactory.NULL, NameFactory.J2EE_APPLICATION);
305            }
306    
307            //always use the artifactId of the app client as the name component of the module name (on the server).
308            AbstractName moduleName = naming.createChildName(earName, clientEnvironment.getConfigId().toString(), NameFactory.APP_CLIENT_MODULE);
309            AbstractName clientBaseName = naming.createRootName(clientEnvironment.getConfigId(), clientEnvironment.getConfigId().toString(), NameFactory.J2EE_APPLICATION);
310    
311            //start installing the resource adapters in the client.
312            Collection<ConnectorModule> resourceModules = new ArrayList<ConnectorModule>();
313            GerResourceType[] resources = gerAppClient.getResourceArray();
314            for (GerResourceType resource : resources) {
315                String path;
316                JarFile connectorFile;
317                if (resource.isSetExternalRar()) {
318                    PatternType externalRar = resource.getExternalRar();
319                    String groupId = trim(externalRar.getGroupId());
320                    String artifactId = trim(externalRar.getArtifactId());
321                    String version = trim(externalRar.getVersion());
322                    String type = trim(externalRar.getType());
323                    Artifact artifact = new Artifact(groupId, artifactId, version, type);
324                    try {
325                        artifact = clientArtifactResolver.resolveInClassLoader(artifact);
326                    } catch (MissingDependencyException e) {
327                        throw new DeploymentException("Could not resolve external rar location in repository: " + artifact, e);
328                    }
329                    File file = null;
330                    for (Repository repository : repositories) {
331                        if (repository.contains(artifact)) {
332                            file = repository.getLocation(artifact);
333                            break;
334                        }
335                    }
336                    if (file == null) {
337                        throw new DeploymentException("Missing external rar in repositories: " + artifact);
338                    }
339                    try {
340                        connectorFile = new JarFile(file);
341                    } catch (IOException e) {
342                        throw new DeploymentException("Could not access external rar contents for artifact: " + artifact, e);
343                    }
344                    path = artifact.toString();
345                } else {
346                    path = resource.getInternalRar();
347                    try {
348                        connectorFile = new NestedJarFile(moduleFile, path);
349                    } catch (IOException e) {
350                        throw new DeploymentException("Could not locate connector inside ear", e);
351                    }
352                }
353                XmlObject connectorPlan = resource.getConnector();
354                ConnectorModule connectorModule = (ConnectorModule) getConnectorModuleBuilder().createModule(connectorPlan, connectorFile, path, null, clientEnvironment, null, clientBaseName, naming, idBuilder);
355                resourceModules.add(connectorModule);
356            }
357    
358            // Create the AnnotatedApp interface for the AppClientModule
359            AnnotatedApplicationClient annotatedApplicationClient = new AnnotatedApplicationClient(appClient, mainClass);
360    
361            AppClientModule module = new AppClientModule(standAlone, moduleName, clientBaseName, serverEnvironment, clientEnvironment, moduleFile, targetPath, appClient, mainClass, gerAppClient, specDD, resourceModules, annotatedApplicationClient);
362            for (ModuleBuilderExtension mbe : moduleBuilderExtensions) {
363                mbe.createModule(module, plan, moduleFile, targetPath, specDDUrl, clientEnvironment, null, earName, naming, idBuilder);
364            }
365            if (standAlone) {
366                ApplicationInfo appInfo = new ApplicationInfo(ConfigurationModuleType.CAR,
367                        serverEnvironment,
368                        earName,
369                        null,
370                        null,
371                        null,
372                        new LinkedHashSet<Module>(Collections.singleton(module)),
373                        new ModuleList(),
374                        null);
375                return appInfo;
376            } else {
377                return module;
378            }
379        }
380    
381        private String trim(String s) {
382            if (s == null) {
383                return null;
384            }
385            return s.trim();
386        }
387    
388        GerApplicationClientType getGeronimoAppClient(Object plan, JarFile moduleFile, boolean standAlone, String targetPath, ApplicationClientType appClient, Environment environment) throws DeploymentException {
389            GerApplicationClientType gerAppClient;
390            XmlObject rawPlan = null;
391            try {
392                // load the geronimo-application-client.xml from either the supplied plan or from the earFile
393                try {
394                    if (plan instanceof XmlObject) {
395                        rawPlan = (XmlObject) plan;
396                    } else {
397                        if (plan != null) {
398                            rawPlan = XmlBeansUtil.parse((File) plan);
399                        } else {
400                            URL path = DeploymentUtil.createJarURL(moduleFile, "META-INF/geronimo-application-client.xml");
401                            rawPlan = XmlBeansUtil.parse(path, getClass().getClassLoader());
402                        }
403                    }
404                } catch (IOException e) {
405                    //exception means we create default
406                }
407    
408                // if we got one extract the validate it otherwise create a default one
409                if (rawPlan != null) {
410                    gerAppClient = (GerApplicationClientType) SchemaConversionUtils.fixGeronimoSchema(rawPlan, GerApplicationClientDocument.type.getDocumentElementName(), GerApplicationClientType.type);
411                } else {
412                    String path;
413                    if (standAlone) {
414                        // default configId is based on the moduleFile name
415                        path = new File(moduleFile.getName()).getName();
416                    } else {
417                        // default configId is based on the module uri from the application.xml
418                        path = targetPath;
419                    }
420                    gerAppClient = createDefaultPlan(path, appClient, standAlone, environment);
421                }
422            } catch (XmlException e) {
423                throw new DeploymentException("Unable to parse application plan", e);
424            }
425            return gerAppClient;
426        }
427    
428        private GerApplicationClientType createDefaultPlan(String name, ApplicationClientType appClient, boolean standAlone, Environment environment) {
429            String id = appClient == null ? null : appClient.getId();
430            if (id == null) {
431                id = name;
432                if (id.endsWith(".jar")) {
433                    id = id.substring(0, id.length() - 4);
434                }
435                if (id.endsWith("/")) {
436                    id = id.substring(0, id.length() - 1);
437                }
438            }
439    
440            GerApplicationClientType geronimoAppClient = GerApplicationClientType.Factory.newInstance();
441            EnvironmentType clientEnvironmentType = geronimoAppClient.addNewClientEnvironment();
442            EnvironmentType serverEnvironmentType = geronimoAppClient.addNewServerEnvironment();
443            //TODO configid fill in environment with configids
444            // set the parentId and configId
445    //        if (standAlone) {
446    //            geronimoAppClient.setClientConfigId(id);
447    //            geronimoAppClient.setConfigId(id + "/server");
448    //        } else {
449    //            geronimoAppClient.setClientConfigId(earConfigId.getPath() + "/" + id);
450    //             not used but we need to have a value
451    //            geronimoAppClient.setConfigId(id);
452    //        }
453            return geronimoAppClient;
454        }
455    
456        static ApplicationClientDocument convertToApplicationClientSchema(XmlObject xmlObject) throws XmlException {
457            if (ApplicationClientDocument.type.equals(xmlObject.schemaType())) {
458                ApplicationClientType appClient = ((ApplicationClientDocument) xmlObject).getApplicationClient();
459                if ("5.0".equals(appClient.getVersion())) {
460                    appClient.setVersion("5");
461                }
462                XmlBeansUtil.validateDD(xmlObject);
463                return (ApplicationClientDocument) xmlObject;
464            }
465            XmlCursor cursor = xmlObject.newCursor();
466            XmlCursor moveable = xmlObject.newCursor();
467            String schemaLocationURL = "http://java.sun.com/xml/ns/javaee/application-client_5.xsd";
468            String version = "5";
469            try {
470                cursor.toStartDoc();
471                cursor.toFirstChild();
472                if ("http://java.sun.com/xml/ns/j2ee".equals(cursor.getName().getNamespaceURI())
473                        || "http://java.sun.com/xml/ns/javaee".equals(cursor.getName().getNamespaceURI())) {
474                    SchemaConversionUtils.convertSchemaVersion(cursor, SchemaConversionUtils.JAVAEE_NAMESPACE, schemaLocationURL, version);
475                    XmlObject result = xmlObject.changeType(ApplicationClientDocument.type);
476                    XmlBeansUtil.validateDD(result);
477                    return (ApplicationClientDocument) result;
478                }
479    
480                // otherwise assume DTD
481                SchemaConversionUtils.convertToSchema(cursor, SchemaConversionUtils.JAVAEE_NAMESPACE, schemaLocationURL, version);
482                cursor.toStartDoc();
483                cursor.toChild(SchemaConversionUtils.JAVAEE_NAMESPACE, "application-client");
484                cursor.toFirstChild();
485                SchemaConversionUtils.convertToDescriptionGroup(SchemaConversionUtils.JAVAEE_NAMESPACE, cursor, moveable);
486            } finally {
487                cursor.dispose();
488                moveable.dispose();
489            }
490            XmlObject result = xmlObject.changeType(ApplicationClientDocument.type);
491            if (result != null) {
492                XmlBeansUtil.validateDD(result);
493                return (ApplicationClientDocument) result;
494            }
495            XmlBeansUtil.validateDD(xmlObject);
496            return (ApplicationClientDocument) xmlObject;
497    
498        }
499    
500        public void installModule(JarFile earFile, EARContext earContext, Module module, Collection configurationStores, ConfigurationStore targetConfigurationStore, Collection repositories) throws DeploymentException {
501            // extract the app client jar file into a standalone packed jar file and add the contents to the output
502            //This duplicates the copy in the app client's own configuration, made below.
503            //this should really only be done if there's a manifest classpath reference to the app client jar by another
504            //javaee module.
505            JarFile moduleFile = module.getModuleFile();
506            try {
507                earContext.addIncludeAsPackedJar(URI.create(module.getTargetPath()), moduleFile);
508            } catch (IOException e) {
509                throw new DeploymentException("Unable to copy app client module jar into configuration: " + moduleFile.getName(), e);
510            }
511            AppClientModule appClientModule = (AppClientModule) module;
512            appClientModule.setEarFile(earFile);
513            //create the ear context for the app client.
514            Environment clientEnvironment = appClientModule.getEnvironment();
515    //        if (!appClientModule.isStandAlone() || clientEnvironment.getConfigId() == null) {
516    //            Artifact earConfigId = earContext.getConfigID();
517    //            Artifact configId = new Artifact(earConfigId.getGroupId(), earConfigId.getArtifactId() + "_" + module.getTargetPath(), earConfigId.getVersion(), "car");
518    //            clientEnvironment.setConfigId(configId);
519    //        }
520    
521            File appClientDir;
522            try {
523                appClientDir = targetConfigurationStore.createNewConfigurationDir(clientEnvironment.getConfigId());
524            } catch (ConfigurationAlreadyExistsException e) {
525                throw new DeploymentException("Unable to create configuration directory for " + clientEnvironment.getConfigId(), e);
526            }
527    
528            // construct the app client deployment context... this is the same class used by the ear context
529            EARContext appClientDeploymentContext;
530            try {
531    
532                appClientDeploymentContext = new EARContext(appClientDir,
533                        null,
534                        clientEnvironment,
535                        ConfigurationModuleType.CAR,
536                        earContext.getNaming(),
537                        earContext.getConfigurationManager(),
538                        null, //no server name needed on client
539                        appClientModule.getAppClientName(),
540                        transactionManagerObjectName,
541                        connectionTrackerObjectName,
542                        null,
543                        null,
544                        corbaGBeanObjectName,
545                        earContext.getMessageDestinations());
546                appClientModule.setEarContext(appClientDeploymentContext);
547                appClientModule.setRootEarContext(appClientDeploymentContext);
548    
549                try {
550                    appClientDeploymentContext.addIncludeAsPackedJar(URI.create(module.getTargetPath()), moduleFile);
551                } catch (IOException e) {
552                    throw new DeploymentException("Unable to copy app client module jar into configuration: " + moduleFile.getName(), e);
553                }
554                ClassPathList libClasspath = (ClassPathList) earContext.getGeneralData().get(ClassPathList.class);
555                if (libClasspath != null) {
556                    for (String libEntryPath : libClasspath) {
557                        try {
558                            NestedJarFile library = new NestedJarFile(earFile, libEntryPath);
559                            appClientDeploymentContext.addIncludeAsPackedJar(URI.create(libEntryPath), library);
560                        } catch (IOException e) {
561                            throw new DeploymentException("Could not add to app client library classpath: " + libEntryPath, e);
562                        }
563                    }
564                }
565            } catch (DeploymentException e) {
566                cleanupAppClientDir(appClientDir);
567                throw e;
568            }
569            for (ConnectorModule connectorModule : appClientModule.getResourceModules()) {
570                getConnectorModuleBuilder().installModule(connectorModule.getModuleFile(), appClientDeploymentContext, connectorModule, configurationStores, targetConfigurationStore, repositories);
571            }
572    
573            for (ModuleBuilderExtension mbe : moduleBuilderExtensions) {
574                mbe.installModule(module.getModuleFile(), appClientDeploymentContext, module, configurationStores, targetConfigurationStore, repositories);
575            }
576        }
577    
578        public void initContext(EARContext earContext, Module clientModule, ClassLoader cl) throws DeploymentException {
579            namingBuilders.buildEnvironment(clientModule.getSpecDD(), clientModule.getVendorDD(), ((AppClientModule) clientModule).getEnvironment());
580    
581            AppClientModule appClientModule = ((AppClientModule) clientModule);
582            for (ConnectorModule connectorModule : appClientModule.getResourceModules()) {
583                getConnectorModuleBuilder().initContext(appClientModule.getEarContext(), connectorModule, cl);
584            }
585            for (ModuleBuilderExtension mbe : moduleBuilderExtensions) {
586                mbe.initContext(earContext, clientModule, cl);
587            }
588        }
589    
590        public void addGBeans(EARContext earContext, Module module, ClassLoader earClassLoader, Collection repositories) throws DeploymentException {
591    
592            AppClientModule appClientModule = (AppClientModule) module;
593            JarFile moduleFile = module.getModuleFile();
594    
595            ApplicationClientType appClient = (ApplicationClientType) appClientModule.getSpecDD();
596            GerApplicationClientType geronimoAppClient = (GerApplicationClientType) appClientModule.getVendorDD();
597    
598            // generate the object name for the app client
599            AbstractName appClientModuleName = appClientModule.getModuleName();
600    
601            // create a gbean for the app client module and add it to the ear
602            GBeanData appClientModuleGBeanData = new GBeanData(appClientModuleName, J2EEAppClientModuleImpl.GBEAN_INFO);
603            try {
604                appClientModuleGBeanData.setReferencePattern("J2EEServer", earContext.getServerName());
605                if (!module.isStandAlone()) {
606                    appClientModuleGBeanData.setReferencePattern("J2EEApplication", earContext.getModuleName());
607                }
608    
609            } catch (Exception e) {
610                throw new DeploymentException("Unable to initialize AppClientModule GBean", e);
611            }
612            try {
613                earContext.addGBean(appClientModuleGBeanData);
614            } catch (GBeanAlreadyExistsException e) {
615                throw new DeploymentException("Could not add application client module gbean to configuration", e);
616            }
617    
618            EARContext appClientDeploymentContext = appClientModule.getEarContext();
619            //Share the ejb info with the ear.
620            //TODO this might be too much, but I don't want to impose a dependency on geronimo-openejb to get
621            //EjbModuleBuilder.EarData.class
622            Map<Object, Object> generalData = earContext.getGeneralData();
623            for (Map.Entry<Object, Object> entry : generalData.entrySet()) {
624                Object key = entry.getKey();
625                if (key instanceof Class && ((Class) key).getName().equals("org.apache.geronimo.openejb.deployment.EjbModuleBuilder$EarData")) {
626                    appClientDeploymentContext.getGeneralData().put(key, entry.getValue());
627                    break;
628                }
629            }
630    
631            // Create a Module ID Builder defaulting to similar settings to use for any children we create
632            ModuleIDBuilder idBuilder = new ModuleIDBuilder();
633            idBuilder.setDefaultGroup(appClientModule.getEnvironment().getConfigId().getGroupId());
634            idBuilder.setDefaultVersion(appClientModule.getEnvironment().getConfigId().getVersion());
635            try {
636                try {
637    
638                    //register the message destinations in the app client ear context.
639                    namingBuilders.initContext(appClient, geronimoAppClient, appClientModule);
640                    // extract the client Jar file into a standalone packed jar file and add the contents to the output
641                    URI moduleBase = new URI(appClientModule.getTargetPath());
642                    try {
643                        appClientDeploymentContext.addIncludeAsPackedJar(moduleBase, moduleFile);
644                    } catch (IOException e) {
645                        throw new DeploymentException("Unable to copy app client module jar into configuration: " + moduleFile.getName(), e);
646                    }
647    
648                    // add manifest class path entries to the app client context
649                    addManifestClassPath(appClientDeploymentContext, appClientModule.getEarFile(), moduleFile, moduleBase);
650    
651                    // get the classloader
652                    ClassLoader appClientClassLoader = appClientDeploymentContext.getClassLoader();
653    
654                    // pop in all the gbeans declared in the geronimo app client file
655                    if (geronimoAppClient != null) {
656                        serviceBuilder.build(geronimoAppClient, appClientDeploymentContext, appClientDeploymentContext);
657                        //deploy the resource adapters specified in the geronimo-application.xml
658    
659                        for (ConnectorModule connectorModule : appClientModule.getResourceModules()) {
660                            getConnectorModuleBuilder().addGBeans(appClientDeploymentContext, connectorModule, appClientClassLoader, repositories);
661                        }
662                    }
663    
664                    //Holder may be loaded in the "client" module classloader here, whereas
665                    //NamingBuilder.INJECTION_KEY.get(buildingContext) returns a Holder loaded in the j2ee-server classloader.
666                    Object holder;
667                    // add the app client static jndi provider
668                    //TODO track resource ref shared and app managed security
669                    AbstractName jndiContextName = earContext.getNaming().createChildName(appClientDeploymentContext.getModuleName(), "StaticJndiContext", "StaticJndiContext");
670                    GBeanData jndiContextGBeanData = new GBeanData(jndiContextName, StaticJndiContextPlugin.GBEAN_INFO);
671                    try {
672                        Map<NamingBuilder.Key, Object> buildingContext = new HashMap<NamingBuilder.Key, Object>();
673                        buildingContext.put(NamingBuilder.GBEAN_NAME_KEY, jndiContextName);
674                        Configuration localConfiguration = appClientDeploymentContext.getConfiguration();
675                        Configuration remoteConfiguration = earContext.getConfiguration();
676    
677                        if (!appClient.getMetadataComplete()) {
678                            // Create a classfinder and populate it for the naming builder(s). The absence of a
679                            // classFinder in the module will convey whether metadata-complete is set
680                            // (or not)
681                            appClientModule.setClassFinder(createAppClientClassFinder(appClient, appClientModule));
682                        }
683    
684                        namingBuilders.buildNaming(appClient, geronimoAppClient, appClientModule, buildingContext);
685    
686                        if (!appClient.getMetadataComplete()) {
687                            appClient.setMetadataComplete(true);
688                            module.setOriginalSpecDD(module.getSpecDD().toString());
689                        }
690    
691                        appClientModuleGBeanData.setAttribute("deploymentDescriptor", appClientModule.getOriginalSpecDD());
692                        holder = NamingBuilder.INJECTION_KEY.get(buildingContext);
693                        jndiContextGBeanData.setAttribute("context", NamingBuilder.JNDI_KEY.get(buildingContext));
694                    } catch (DeploymentException e) {
695                        throw e;
696                    } catch (Exception e) {
697                        throw new DeploymentException("Unable to construct jndi context for AppClientModule GBean " +
698                                appClientModule.getName(), e);
699                    }
700                    appClientDeploymentContext.addGBean(jndiContextGBeanData);
701    
702                    // finally add the app client container
703                    AbstractName appClientContainerName = appClientDeploymentContext.getModuleName();
704                    GBeanData appClientContainerGBeanData = new GBeanData(appClientContainerName, AppClientContainer.GBEAN_INFO);
705                    try {
706                        appClientContainerGBeanData.setAttribute("mainClassName", appClientModule.getMainClassName());
707                        appClientContainerGBeanData.setAttribute("appClientModuleName", appClientModuleName);
708                        String callbackHandlerClassName = null;
709                        if (appClient.isSetCallbackHandler()) {
710                            callbackHandlerClassName = appClient.getCallbackHandler().getStringValue().trim();
711                        }
712                        if (geronimoAppClient.isSetCallbackHandler()) {
713                            callbackHandlerClassName = geronimoAppClient.getCallbackHandler().trim();
714                        }
715                        String realmName = null;
716                        if (geronimoAppClient.isSetRealmName()) {
717                            realmName = geronimoAppClient.getRealmName().trim();
718                        }
719                        if (callbackHandlerClassName != null && realmName == null) {
720                            throw new DeploymentException("You must specify a realm name with the callback handler");
721                        }
722                        if (realmName != null) {
723                            appClientContainerGBeanData.setAttribute("realmName", realmName);
724                            appClientContainerGBeanData.setAttribute("callbackHandlerClassName", callbackHandlerClassName);
725                        } else if (geronimoAppClient.isSetDefaultSubject()) {
726                            GerSubjectInfoType subjectInfoType = geronimoAppClient.getDefaultSubject();
727                            SubjectInfo subjectInfo = buildSubjectInfo(subjectInfoType);
728                            appClientContainerGBeanData.setAttribute("defaultSubject", subjectInfo);
729                            appClientContainerGBeanData.setReferencePattern("CredentialStore", credentialStoreName);
730                        } else if (earContext.getSecurityConfiguration() != null) {
731                            //beware a linkage error if we cast this to SubjectInfo
732                            String realm = ((SecurityConfiguration) earContext.getSecurityConfiguration()).getDefaultSubjectRealm();
733                            String id = ((SecurityConfiguration) earContext.getSecurityConfiguration()).getDefaultSubjectId();
734                            if (realm != null) {
735                                SubjectInfo subjectInfo = new SubjectInfo(realm, id);
736                                appClientContainerGBeanData.setAttribute("defaultSubject", subjectInfo);
737                                appClientContainerGBeanData.setReferencePattern("CredentialStore", credentialStoreName);
738                            }
739                        }
740                        appClientContainerGBeanData.setReferencePattern("JNDIContext", jndiContextName);
741                        appClientContainerGBeanData.setAttribute("holder", holder);
742    
743                    } catch (Exception e) {
744                        throw new DeploymentException("Unable to initialize AppClientModule GBean", e);
745                    }
746                    appClientDeploymentContext.addGBean(appClientContainerGBeanData);
747    
748                    //TODO this may definitely not be the best place for this!
749                    for (ModuleBuilderExtension mbe : moduleBuilderExtensions) {
750                        mbe.addGBeans(appClientDeploymentContext, appClientModule, appClientClassLoader, repositories);
751                    }
752    
753                    // get the configuration data
754                    earContext.addAdditionalDeployment(appClientDeploymentContext.getConfigurationData());
755                } finally {
756                    if (appClientDeploymentContext != null) {
757                        try {
758                            appClientDeploymentContext.close();
759                        } catch (IOException e) {
760                            //nothing we can do
761                        }
762                    }
763                }
764    
765            } catch (Throwable e) {
766                File appClientDir = appClientDeploymentContext.getBaseDir();
767                cleanupAppClientDir(appClientDir);
768                if (e instanceof Error) {
769                    throw (Error) e;
770                } else if (e instanceof DeploymentException) {
771                    throw (DeploymentException) e;
772                } else if (e instanceof Exception) {
773                    throw new DeploymentException(e);
774                }
775                throw new Error(e);
776            }
777        }
778    
779    
780        private ClassFinder createAppClientClassFinder(ApplicationClientType appClient, AppClientModule appClientModule) throws DeploymentException {
781    
782            //------------------------------------------------------------------------------------
783            // Find the list of classes from the application-client.xml we want to search for
784            // annotations in
785            //------------------------------------------------------------------------------------
786            List<Class> classes = new ArrayList<Class>();
787    
788            // Get the classloader from the module's EARContext
789            ClassLoader classLoader = appClientModule.getEarContext().getClassLoader();
790    
791            // Get the main class from the module
792            String mainClass = appClientModule.getMainClassName();
793            Class<?> mainClas;
794            try {
795                mainClas = classLoader.loadClass(mainClass);
796            }
797            catch (ClassNotFoundException e) {
798                throw new DeploymentException("AppClientModuleBuilder: Could not load main class: " + mainClass, e);
799            }
800            while (mainClas != null && mainClas != Object.class) {
801                classes.add(mainClas);
802                mainClas = mainClas.getSuperclass();
803            }
804    
805            // Get the callback-handler from the deployment descriptor
806            if (appClient.isSetCallbackHandler()) {
807                FullyQualifiedClassType cls = appClient.getCallbackHandler();
808                Class<?> clas;
809                try {
810                    clas = classLoader.loadClass(cls.getStringValue().trim());
811                }
812                catch (ClassNotFoundException e) {
813                    throw new DeploymentException("AppClientModuleBuilder: Could not load callback-handler class: " + cls.getStringValue(), e);
814                }
815                classes.add(clas);
816            }
817    
818            return new ClassFinder(classes);
819        }
820    
821        private SubjectInfo buildSubjectInfo(GerSubjectInfoType defaultSubject) {
822            String realmName = defaultSubject.getRealm().trim();
823            String id = defaultSubject.getId().trim();
824            return new SubjectInfo(realmName, id);
825        }
826    
827        public String getSchemaNamespace() {
828            return GERAPPCLIENT_NAMESPACE;
829        }
830    
831        public void addManifestClassPath(DeploymentContext deploymentContext, JarFile earFile, JarFile jarFile, URI jarFileLocation) throws DeploymentException {
832            Manifest manifest;
833            try {
834                manifest = jarFile.getManifest();
835            } catch (IOException e) {
836                throw new DeploymentException("Could not read manifest: " + jarFileLocation, e);
837            }
838    
839            if (manifest == null) {
840                return;
841            }
842            String manifestClassPath = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
843            if (manifestClassPath == null) {
844                return;
845            }
846    
847            for (StringTokenizer tokenizer = new StringTokenizer(manifestClassPath, " "); tokenizer.hasMoreTokens();) {
848                String path = tokenizer.nextToken();
849    
850                URI pathUri;
851                try {
852                    pathUri = new URI(path);
853                } catch (URISyntaxException e) {
854                    throw new DeploymentException("Invalid manifest classpath entry: jarFile=" + jarFileLocation + ", path=" + path, e);
855                }
856    
857                if (!pathUri.getPath().endsWith(".jar")) {
858                    throw new DeploymentException("Manifest class path entries must end with the .jar extension (JAVAEE 5 Section 8.2): jarFile=" + jarFileLocation + ", path=" + path);
859                }
860                if (pathUri.isAbsolute()) {
861                    throw new DeploymentException("Manifest class path entries must be relative (JAVAEE 5 Section 8.2): jarFile=" + jarFileLocation + ", path=" + path);
862                }
863    
864                // determine the target file
865                URI classPathJarLocation = jarFileLocation.resolve(pathUri);
866                File classPathFile = deploymentContext.getTargetFile(classPathJarLocation);
867    
868                // we only recuse if the path entry is not already in the output context
869                // this will work for all current cases, but may not work in the future
870                if (!classPathFile.exists()) {
871                    // check if the path exists in the earFile
872                    ZipEntry entry = earFile.getEntry(classPathJarLocation.getPath());
873                    if (entry == null) {
874                        throw new DeploymentException("Cound not find manifest class path entry: jarFile=" + jarFileLocation + ", path=" + path);
875                    }
876    
877                    try {
878                        // copy the file into the output context
879                        deploymentContext.addFile(classPathJarLocation, earFile, entry);
880                    } catch (IOException e) {
881                        throw new DeploymentException("Cound not copy manifest class path entry into configuration: jarFile=" + jarFileLocation + ", path=" + path, e);
882                    }
883    
884                    JarFile classPathJarFile;
885                    try {
886                        classPathJarFile = new JarFile(classPathFile);
887                    } catch (IOException e) {
888                        throw new DeploymentException("Manifest class path entries must be a valid jar file (JAVAEE 5 Section 8.2): jarFile=" + jarFileLocation + ", path=" + path, e);
889                    }
890    
891                    // add the client jars of this class path jar
892                    addManifestClassPath(deploymentContext, earFile, classPathJarFile, classPathJarLocation);
893                }
894            }
895        }
896    
897        private boolean cleanupAppClientDir(File configurationDir) {
898            LinkedList<String> cannotBeDeletedList = new LinkedList<String>();
899    
900            if (!DeploymentUtil.recursiveDelete(configurationDir, cannotBeDeletedList)) {
901                // Output a message to help user track down file problem
902                log.warn("Unable to delete " + cannotBeDeletedList.size() +
903                        " files while recursively deleting directory "
904                        + configurationDir.getAbsolutePath() + LINE_SEP +
905                        "The first file that could not be deleted was:" + LINE_SEP + "  " +
906                        (!cannotBeDeletedList.isEmpty() ? cannotBeDeletedList.getFirst() : ""));
907                return false;
908            }
909            return true;
910        }
911    
912        public static final GBeanInfo GBEAN_INFO;
913    
914        static {
915            GBeanInfoBuilder infoBuilder = GBeanInfoBuilder.createStatic(AppClientModuleBuilder.class, NameFactory.MODULE_BUILDER);
916            infoBuilder.addAttribute("defaultClientEnvironment", Environment.class, true, true);
917            infoBuilder.addAttribute("defaultServerEnvironment", Environment.class, true, true);
918            infoBuilder.addAttribute("transactionManagerObjectName", AbstractNameQuery.class, true);
919            infoBuilder.addAttribute("connectionTrackerObjectName", AbstractNameQuery.class, true);
920            infoBuilder.addAttribute("corbaGBeanObjectName", AbstractNameQuery.class, true);
921            infoBuilder.addAttribute("credentialStoreName", AbstractNameQuery.class, true);
922            infoBuilder.addReference("Repositories", Repository.class, "Repository");
923            infoBuilder.addReference("ConnectorModuleBuilder", ModuleBuilder.class, NameFactory.MODULE_BUILDER);
924            infoBuilder.addReference("ServiceBuilders", NamespaceDrivenBuilder.class, NameFactory.MODULE_BUILDER);
925            infoBuilder.addReference("NamingBuilders", NamingBuilder.class, NameFactory.MODULE_BUILDER);
926            infoBuilder.addReference("ModuleBuilderExtensions", ModuleBuilderExtension.class, NameFactory.MODULE_BUILDER);
927            infoBuilder.addReference("ClientArtifactResolver", ArtifactResolver.class, "ArtifactResolver");
928    
929            infoBuilder.addInterface(ModuleBuilder.class);
930    
931            infoBuilder.setConstructor(new String[]{"transactionManagerObjectName",
932                    "connectionTrackerObjectName",
933                    "corbaGBeanObjectName",
934                    "credentialStoreName",
935                    "Repositories",
936                    "ConnectorModuleBuilder",
937                    "ServiceBuilders",
938                    "NamingBuilders",
939                    "ModuleBuilderExtensions",
940                    "ClientArtifactResolver",
941                    "defaultClientEnvironment",
942                    "defaultServerEnvironment",
943            });
944    
945            GBEAN_INFO = infoBuilder.getBeanInfo();
946        }
947    
948        public static GBeanInfo getGBeanInfo() {
949            return GBEAN_INFO;
950        }
951    
952    }