/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.enterprise.appclient;
import com.sun.corba.ee.impl.codegen.Attribute;
import com.sun.enterprise.deployment.Application;
import com.sun.enterprise.deployment.ApplicationClientDescriptor;
import com.sun.enterprise.deployment.archivist.Archivist;
import com.sun.enterprise.deployment.archivist.ApplicationArchivist;
import com.sun.enterprise.deployment.backend.J2EEModuleExploder;
import com.sun.enterprise.deployment.BundleDescriptor;
import com.sun.enterprise.deployment.deploy.shared.AbstractArchive;
import com.sun.enterprise.deployment.deploy.shared.FileArchive;
import com.sun.enterprise.deployment.RootDeploymentDescriptor;
import com.sun.enterprise.deployment.util.ModuleDescriptor;
import com.sun.enterprise.loader.EJBClassLoader;
import com.sun.enterprise.util.io.FileUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.deploy.shared.ModuleType;
import javax.print.attribute.AttributeSet;
import org.xml.sax.SAXParseException;
/**
* Represents an app client that is nested inside an enterprise app.
*
* Note that this could be either an undeployed ear that contains one or more
* embedded app clients or the generated jar file from the back-end that
* intentionally resembles an application archive because of other files that
* had to be packaged with the app client.
*
* @author tjquinn
*/
public class NestedAppClientInfo extends AppClientInfo {
private Application appDesc = null;
/** which of possibly several app clients in the app the user chose */
private ApplicationClientDescriptor selectedAppClientDescriptor = null;
/** display name specified (if any) on the command line to use in selecting the desired client main class */
private String displayNameFromCommandLine;
public NestedAppClientInfo(
boolean isJWS, Logger logger, File archive,
Archivist archivist, String mainClassFromCommandLine,
String displayNameFromCommandLine) {
super(isJWS, logger, archive, archivist, mainClassFromCommandLine);
this.displayNameFromCommandLine = displayNameFromCommandLine;
}
/**
*Reports which app client embedded in the application archive is the
*one the user has selected using either the main class or display name
*arguments from the command line.
*@return the app client descriptor for the user-selected app client
*/
protected ApplicationClientDescriptor getAppClient(Archivist archivist) {
if (selectedAppClientDescriptor != null) {
return selectedAppClientDescriptor;
}
Application app = Application.class.cast(archivist.getDescriptor());
/*
*There could be one or more app clients embedded in the enterprise app
*in the archive. Choose which one to run based on the user's
*command-line input.
*/
Set<ApplicationClientDescriptor> embeddedAppClients =
(Set<ApplicationClientDescriptor>)
app.getApplicationClientDescriptors();
/*
*Make sure the application module contains at least one app client.
*/
if (embeddedAppClients.size() == 0) {
throw new IllegalArgumentException(
localStrings.getString("appclient.noEmbeddedAppClients"));
}
/*
*If there is exactly one app client in the ear, then that's the app
*client to use.
*/
if (embeddedAppClients.size() == 1) {
selectedAppClientDescriptor = useFirstEmbeddedAppClient(
embeddedAppClients, mainClassFromCommandLine);
} else {
selectedAppClientDescriptor = chooseFromEmbeddedAppClients(
embeddedAppClients, mainClassFromCommandLine,
displayNameFromCommandLine);
/*
*Make sure that we've selected an app client.
*/
if (selectedAppClientDescriptor == null) {
if (mainClassFromCommandLine != null) {
throw new IllegalArgumentException(localStrings.getString("appclient.noMatchingClientUsingMainClass", mainClassFromCommandLine));
} else {
throw new IllegalArgumentException(localStrings.getString("appclient.noMatchingClientUsingDisplayName", displayNameFromCommandLine));
}
}
}
return selectedAppClientDescriptor;
}
private ApplicationClientDescriptor chooseFromEmbeddedAppClients(
Set<ApplicationClientDescriptor> embeddedAppClients,
String mainClassFromCommandLine,
String displayNameFromCommandLine) {
ApplicationClientDescriptor result = null;
/*
*There are at least two app clients embedded in the ear.
*
*To remain compatible with earlier releases the logic below
*exits the loop immediately upon finding a matching app client
*using the user-provided main class name. If there are other
*app clients with the same main class those are ignored.
*
*On the other hand, if the user specified the target display name
*then the logic below makes sure that exactly one app client
*has that display name.
*
*/
for (ApplicationClientDescriptor candidate : embeddedAppClients) {
/*
*If the user specified a main class name, use that value to
*match against the candiate.
*/
if (mainClassFromCommandLine != null) {
if (candidate.getMainClassName().equals(mainClassFromCommandLine)) {
result = candidate;
/*
*Because the main class name is used as the criteria,
*exit the loop as soon as one matching app client if found.
*/
break;
}
} else {
/*
*We know at this point that the user provided a display name.
*/
if (candidate.getName().equals(displayNameFromCommandLine)) {
/*
*Make sure no other candidate already matched the
*target display name.
*/
if (result == null) {
result = candidate;
/*
*Because the display name is used as the matching
*criteria, continue the loop to make sure there are
*not multiple app clients with the same display name
*/
} else {
throw new IllegalArgumentException(localStrings.getString("appclient.duplicate_display_name", displayNameFromCommandLine));
}
}
}
}
return result;
}
private ApplicationClientDescriptor useFirstEmbeddedAppClient(Set<ApplicationClientDescriptor> embeddedAppClients, String mainClassNameFromCommandLine) {
ApplicationClientDescriptor result = null;
/*
*If the size is 1 then there is sure to be a non-null .next.
*Still, may as well be sure.
*/
Iterator<ApplicationClientDescriptor> it = embeddedAppClients.iterator();
if ( ! it.hasNext()) {
throw new IllegalStateException(localStrings.getString("appclient.unexpectedEndOfEmbeddedClients"));
}
result = embeddedAppClients.iterator().next();
/*
*If, in addition, the user specified a main class on the command
*line, then use the user's class name as the main class name, rather
*than the class specified by the Main-Class attribute in the
*app client archive. This allows the user to override the Main-Class
*setting in the app client's manifest.
*/
if (mainClassNameFromCommandLine != null) {
result.setMainClassName(mainClassNameFromCommandLine);
}
return result;
}
/**
*Expands the contents of the source archive into a temporary
*directory, using the same format as backend server expansions.
*@param file an archive file to be expanded
*@return an opened FileArchive for the expanded directory archive
*@exception IOException in case of errors during the expansion
*/
protected AbstractArchive expand(File file)
throws IOException, Exception {
File tmpDir = createTmpArchiveDir(file);
_logger.fine("Expanding original archive " + file.getAbsolutePath() +
" into " + tmpDir.getAbsolutePath());
// first explode the top level jar
J2EEModuleExploder.explodeJar(file, tmpDir);
// now we need to load the application standard deployment descriptor.
FileArchive appArchive = new FileArchive();
appArchive.open(tmpDir.getAbsolutePath());
ApplicationArchivist archivist = new ApplicationArchivist();
if (archivist.hasStandardDeploymentDescriptor(appArchive)) {
appDesc = (Application)
archivist.readStandardDeploymentDescriptor(appArchive);
} else {
appDesc = Application.createApplication(appArchive,true);
}
// explode the sub modules, skipping the ones that do not exist since
// the generated appclient jar files do not contain web content
Iterator<ModuleDescriptor> bundles = appDesc.getModules();
while (bundles.hasNext()) {
ModuleDescriptor bundle = bundles.next();
String moduleName = bundle.getArchiveUri();
File srcArchive = new File(tmpDir, moduleName);
if (srcArchive.exists()) {
String massagedModuleName =
FileUtils.makeFriendlyFilename(moduleName);
File moduleDir =
new File(tmpDir, massagedModuleName);
J2EEModuleExploder.explodeJar(srcArchive, moduleDir);
// delete the original module file
srcArchive.delete();
}
}
/*
*Leave the new archive open so the caller can use it directly.
*/
return appArchive;
}
/**
* Construct the classpaths. The classpaths constructed here is
* slightly different from the backend. It does not process any
* web module. The paths included are:
* 1. all the module root directory (since expansion is needed)
* 2. all the .jar files found in the archive
*/
protected List<String> getClassPaths(AbstractArchive archive) {
List<String> paths = new ArrayList();
String appRoot = archive.getArchiveUri();
paths.add(appRoot);
if (appDesc != null) {
//add all module roots
Iterator<ModuleDescriptor> bundles = appDesc.getModules();
while (bundles.hasNext()) {
ModuleDescriptor bundle = bundles.next();
String moduleRoot = appRoot + File.separator +
FileUtils.makeFriendlyFilename(bundle.getArchiveUri());
paths.add(moduleRoot);
// Because the app client submodule - like any submodule - is
// expanded into a directory, the normal manifest processing
// done for JARs will not be applied to it when this directory is added to
// the class path of the EJBClassLoader. So we need to explicitly
// add the Class-Path elements from the manifest to the class path now.
File manifestFile = new File(moduleRoot, JarFile.MANIFEST_NAME);
if ( ! manifestFile.exists()) {
continue;
}
Manifest mf = null;
InputStream manifestIS = null;
try {
manifestIS = new FileInputStream(manifestFile);
mf = new Manifest(manifestIS);
Attributes mainAttrs = mf.getMainAttributes();
if (mainAttrs != null) {
String classPathString = mainAttrs.getValue(Attributes.Name.CLASS_PATH);
if (classPathString != null) {
URI appRootURI = new File(appRoot).toURI();
String bundleURIString = bundle.getArchiveUri();
int lastSlash = bundleURIString.lastIndexOf("/");
String parentURIString;
if (lastSlash >=0) {
parentURIString = bundleURIString.substring(0, lastSlash);
} else {
parentURIString = "";
}
String parentDirPath = new File(appRootURI.resolve(parentURIString)).getAbsolutePath();
for (String classPathElement : classPathString.split(" ")) {
paths.add(parentDirPath + File.separator + classPathElement.replace("/",File.separator));
}
}
}
} catch (Exception e) {
throw new RuntimeException(localStrings.getString("appclient.cannotOpenModuleManifest", bundle.getArchiveUri()), e);
} finally {
if (manifestIS != null) {
try {
manifestIS.close();
} catch (IOException ioe) {
throw new RuntimeException(localStrings.getString("appclient.cannotCloseModuleManifest", bundle.getArchiveUri()), ioe);
}
}
}
}
} else {
//@@@ read it from the archive
//shouldn't ever be here though since the appDesc should have been
//initialized when expand() is called.
}
//add all jar files
for (Enumeration en = archive.entries(); en.hasMoreElements(); ) {
String entryName = (String) en.nextElement();
if (entryName.endsWith(".jar")) {
String entry = appRoot + File.separator + entryName;
paths.add(entry);
}
}
return paths;
}
protected String getAppClientRoot(
AbstractArchive archive, ApplicationClientDescriptor descriptor) {
String appRoot = archive.getArchiveUri();
String moduleUri = descriptor.getModuleDescriptor().getArchiveUri();
String moduleRoot = appRoot + File.separator +
FileUtils.makeFriendlyFilename(moduleUri);
return moduleRoot;
}
}
|