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