org.glassfish.appclient.server.core.jws.JWSAdapterManager Maven / Gradle / Ivy
Show all versions of payara-embedded-web Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2014 Oracle and/or its affiliates. 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_1_1.html
* or packager/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 packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [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.
*/
// Portions Copyright 2018-2022 Payara Foundation and/or its affiliates
package org.glassfish.appclient.server.core.jws;
import com.sun.enterprise.config.serverbeans.Config;
import com.sun.enterprise.deployment.ApplicationClientDescriptor;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.api.container.EndpointRegistrationException;
import org.glassfish.api.container.RequestDispatcher;
import org.glassfish.api.deployment.DeploymentContext;
import org.glassfish.appclient.server.core.AppClientDeployer;
import org.glassfish.appclient.server.core.AppClientServerApplication;
import org.glassfish.appclient.server.core.jws.servedcontent.ASJarSigner;
import org.glassfish.appclient.server.core.jws.servedcontent.AutoSignedContent;
import org.glassfish.appclient.server.core.jws.servedcontent.DynamicContent;
import org.glassfish.appclient.server.core.jws.servedcontent.SimpleDynamicContentImpl;
import org.glassfish.appclient.server.core.jws.servedcontent.StaticContent;
import org.glassfish.enterprise.iiop.api.GlassFishORBFactory;
import org.glassfish.hk2.api.PostConstruct;
import org.glassfish.internal.api.ServerContext;
import org.glassfish.logging.annotation.LogMessageInfo;
import org.glassfish.orb.admin.config.IiopService;
import org.jvnet.hk2.annotations.Service;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
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;
/**
* Handles all management of the HTTP adapters created to support Java Web
* Start launches of app clients.
*
* @author tjquinn
*/
@Service
@Singleton
public class JWSAdapterManager implements PostConstruct {
private final static String SIGNING_ALIAS_PROPERTY_NAME = "jar-signing-alias";
private final static String DEFAULT_SIGNING_ALIAS = "s1as";
private final static String MANIFEST_APP_NAME_FOR_SYSTEM_FILES = "GlassFish";
@Inject
private ServerEnvironment serverEnv;
@Inject
private ServerContext serverContext;
@Inject
private RequestDispatcher requestDispatcher;
@Inject
private ASJarSigner jarSigner;
@Inject @Named(ServerEnvironment.DEFAULT_INSTANCE_NAME)
private Config config;
@Inject
AppClientDeployer appClientDeployer;
@Inject
private GlassFishORBFactory orbFactory;
private static final String LINE_SEP = System.getProperty("line.separator");
private static final List DO_NOT_SERVE_LIST =
Collections.EMPTY_LIST; //Arrays.asList("glassfish/modules/jaxb-osgi.jar");
private static final String JWS_SIGNED_SYSTEM_JARS_ROOT = "java-web-start/___system";
private static final String JWS_SIGNED_DOMAIN_JARS_ROOT = "java-web-start/___domain";
private static final String JAVA_WEB_START_CONTEXT_ROOT_PROPERTY_NAME =
"javaWebStartContextRoot";
/**
* maps "(aliasName)/(systemJarRelativePath)" to AutoSignedContent for
* the system JAR as signed by the cert linked to the alias
*/
private final Map appLevelSignedSystemContent =
new HashMap();
private URI installRootURI = null;
private AppClientHTTPAdapter systemAdapter = null;
private Logger logger = null;
private IiopService iiopService;
private final HashMap> contributingAppClients =
new HashMap>();
private final ConcurrentHashMap httpAdapters = new
ConcurrentHashMap();
private URI umbrellaRootURI = null;
private File umbrellaRoot = null;
private File systemLevelSignedJARsRoot = null;
private File domainLevelSignedJARsRoot = null;
@LogMessageInfo(
message = "Error starting the adapter to serve static system-level content",
cause = "An unexpected internal system error occurred",
action = "Please consult the exception stack trace")
public static final String ERROR_STARTING_SYSTEM_ADAPTER = "AS-ACDEPL-00105";
@Override
public synchronized void postConstruct() {
installRootURI = serverContext.getInstallRoot().toURI();
logger = Logger.getLogger(JavaWebStartInfo.APPCLIENT_SERVER_MAIN_LOGGER, JavaWebStartInfo.APPCLIENT_SERVER_LOGMESSAGE_RESOURCE);
iiopService = config.getExtensionByType(IiopService.class);
umbrellaRoot = new File(installRootURI).getParentFile();
umbrellaRootURI = umbrellaRoot.toURI();
systemLevelSignedJARsRoot = new File(serverEnv.getInstanceRoot(), JWS_SIGNED_SYSTEM_JARS_ROOT);
domainLevelSignedJARsRoot = new File(serverEnv.getInstanceRoot(), JWS_SIGNED_DOMAIN_JARS_ROOT);
}
public static String signingAlias(final DeploymentContext dc) {
return chooseAlias(dc);
}
synchronized File rootForSignedFilesInDomain() {
return domainLevelSignedJARsRoot;
}
/**
* Adds more static content to the content served by the system adapter.
* @param lookupURI
* @param content
*/
void addStaticSystemContent(final String lookupURI, StaticContent newContent) throws IOException {
AppClientHTTPAdapter adapter = systemAdapter();
if (adapter != null) {
adapter.addContentIfAbsent(lookupURI, newContent);
}
}
private static String chooseAlias(final DeploymentContext dc) {
final String userSpecifiedAlias;
return ((userSpecifiedAlias = extractUserProvidedAlias(dc)) != null)
? userSpecifiedAlias : DEFAULT_SIGNING_ALIAS;
}
private static String extractUserProvidedAlias(final DeploymentContext dc) {
return dc.getAppProps().getProperty(SIGNING_ALIAS_PROPERTY_NAME);
}
private synchronized AppClientHTTPAdapter startSystemContentAdapter() {
try {
AppClientHTTPAdapter sysAdapter = new AppClientHTTPAdapter(
NamingConventions.JWSAPPCLIENT_SYSTEM_PREFIX,
new Properties(),
serverEnv.getInstanceRoot(),
new File(installRootURI),
iiopService,
orbFactory);
requestDispatcher.registerEndpoint(
NamingConventions.JWSAPPCLIENT_SYSTEM_PREFIX,
sysAdapter,
null);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Registered system content adapter serving {0}", sysAdapter);
}
return sysAdapter;
} catch (Exception e) {
logger.log(Level.SEVERE, ERROR_STARTING_SYSTEM_ADAPTER, e);
return null;
}
}
void addContentIfAbsent(final Map staticContent,
final Map dynamicContent) throws IOException {
AppClientHTTPAdapter adapter = systemAdapter();
if (adapter != null) {
adapter.addContentIfAbsent(staticContent, dynamicContent);
}
}
/**
* Records the need for signed copies of the GlassFish system JARs for the
* specified signing alias.
*
* @param systemJARRelativeURIs populated with the relative URIs for the added content
* @param signingAlias alias to use in signing the JARs
* @return map from the key by which the content will be stored to the content for that key
* @throws IOException
*/
Map addStaticSystemContent(
final List systemJARRelativeURIs,
final String signingAlias) throws IOException {
/*
* This method builds the static content for a given signing alias to
* be served by the "system" Grizzly
* adapter which serves files from the installation, as opposed to
* files from the domain or files from a specific app.
*/
Map result = new HashMap();
File gfClientJAR = gfClientJAR();
final String classPathExpr = getGFClientModuleClassPath(gfClientJAR);
final URI gfClientJARURI = gfClientJAR.toURI();
result.put(systemPath(gfClientJARURI, signingAlias),
systemJarSignedContent(gfClientJAR, signingAlias));
if (classPathExpr != null) {
for (String classPathElement : classPathExpr.split(" ")) {
final URI uri = gfClientJARURI.resolve(classPathElement);
final String systemPath = systemPath(uri, signingAlias);
/*
* There may be elements in the class path which do not exist
* on some platforms. So make sure the file exists before we offer
* to serve it.
*/
final File candidateFile = new File(uri);
final String relativeSystemPath = relativeSystemPath(uri);
if (candidateFile.exists() &&
( ! candidateFile.isDirectory()) && ( ! DO_NOT_SERVE_LIST.contains(relativeSystemPath))) {
result.put(systemPath,
systemJarSignedContent(candidateFile, signingAlias));
systemJARRelativeURIs.add(relativeSystemPath(uri));
}
}
}
return result;
}
File gfClientJAR() {
return new File(
libDir(),
"gf-client.jar");
}
File gfClientModuleJAR() {
return new File(
modulesDir(),
"gf-client-module.jar");
}
private synchronized File modulesDir() {
return new File(new File(installRootURI), "modules");
}
private synchronized File libDir() {
return new File(new File(installRootURI), "lib");
}
private AutoSignedContent systemJarSignedContent (
final File unsignedFile,
final String signingAlias) throws FileNotFoundException {
final String relativeURI = relativeSystemPath(unsignedFile.toURI());
final File signedFile = new File(signedSystemContentAliasDir(signingAlias),
relativeURI);
return new AutoSignedContent(unsignedFile, signedFile, signingAlias, jarSigner, relativeURI,
MANIFEST_APP_NAME_FOR_SYSTEM_FILES);
}
Map addDynamicSystemContent(final List systemJARRelativeURIs,
final String signingAlias) throws IOException {
final Map result = new HashMap();
final String template = JavaWebStartInfo.textFromURL(
"/org/glassfish/appclient/server/core/jws/templates/systemJarsDocumentTemplate.jnlp");
final StringBuilder sb = new StringBuilder();
for (String relativeURIString : systemJARRelativeURIs) {
sb.append(" ").append(LINE_SEP);
}
final Properties p = new Properties();
p.setProperty("system.jars", sb.toString());
final String replacedText = Util.replaceTokens(template, p);
result.put(NamingConventions.systemJNLPURI(signingAlias),
new SimpleDynamicContentImpl(replacedText, "jnlp"));
return result;
}
private String systemPath(final URI systemFileURI) {
return //NamingConventions.JWSAPPCLIENT_SYSTEM_PREFIX + "/" +
relativeSystemPath(systemFileURI);
}
String systemPath(final URI systemFileURI, final String signingAlias) {
return signingAlias + "/" + relativeSystemPath(systemFileURI);
}
String systemPathInClientJNLP(final URI systemFileURI, final String signingAlias) {
return "${request.scheme}://${request.host}:${request.port}" +
NamingConventions.JWSAPPCLIENT_SYSTEM_PREFIX + "/" +
signingAlias + "/" + relativeSystemPath(systemFileURI);
}
private synchronized String relativeSystemPath(final URI systemFileURI) {
return umbrellaRootURI.relativize(systemFileURI).getPath();
}
/**
* Adds content on behalf of a single app client to the HTTP adapters
* for the client and/or the containing EAR.
*
* This method always creates at least one adapter for the client - one to
* receive requests for the user-friendly context path. (This is the
* path either assigned by the developer or defaulted by the server based
* on the application name and, for clients nested within an EAR, the
* URI to the app client within the EAR.) This adapter will only serve the
* main generated JNLP document. All other accesses to files that can be
* downloaded will use the "user-unfriendly" context path.
*
* For stand-alone
* app clients it also creates an adapter for the "user-unfriendly" context
* path. This serves all content other than the main JNLP document.
*
* For nested app clients this method will either create or reuse an
* adapter which serves content for the EAR. This includes content specific
* to the app client being added as well as for library JARs, etc. that
* could be common to more than one app client.
*
* @param appName
* @param contributor
* @param tokens
* @param staticContent
* @param dynamicContent
* @throws EndpointRegistrationException
* @throws IOException
*/
public synchronized void addContentForAppClient(
final String appName,
final String clientURIWithinEAR,
final AppClientServerApplication contributor, final Properties tokens,
final Map staticContent,
final Map dynamicContent) throws EndpointRegistrationException, IOException {
AppClientHTTPAdapter appAdapter = httpAdapters.get(appName);
if (appAdapter == null) {
/*
* For a stand-alone app client, this is the adapter that will
* serve all the content (except for the main JNLP docuemnt which the
* user-friendly adapter will typically serve). For a nested
* app client, the app adapter is the EAR-level adapter.
*/
appAdapter = addAppAdapter(appName, staticContent, dynamicContent,
tokens, contributor);
} else {
/*
* This must be the 2nd through n-th nested app client in an EAR
* because the app adapter already exists.
*/
appAdapter.addContentIfAbsent(staticContent, dynamicContent);
}
/*
* Add a new adapter for this client's user-friendly context root.
*/
AppClientHTTPAdapter userFriendlyAppAdapter =
addAdapterForUserFriendlyContextRoot(staticContent, dynamicContent,
tokens, contributor);
appClientDeployer.recordContextRoot(appName, clientURIWithinEAR, userFriendlyAppAdapter.contextRoot());
logger.log(Level.FINE, "Registered at context roots {0},{1}",
new Object[]{appAdapter.contextRoot(), userFriendlyAppAdapter.contextRoot()});
addContributorToAppLevelAdapter(appName, contributor);
}
private synchronized AppClientHTTPAdapter createAndRegisterAdapter(
final String contextRoot,
final Map staticContent,
final Map dynamicContent,
final Properties tokens,
final AppClientServerApplication contributor) throws IOException, EndpointRegistrationException {
final AppClientHTTPAdapter adapter = new AppClientHTTPAdapter(
contextRoot, staticContent,
dynamicContent, tokens,
serverEnv.getInstanceRoot(),
new File(installRootURI),
iiopService,
orbFactory);
requestDispatcher.registerEndpoint(
contextRoot,
adapter,
null);
return adapter;
}
private synchronized AppClientHTTPAdapter addAdapterForUserFriendlyContextRoot(
final Map staticContent,
final Map dynamicContent,
final Properties tokens,
final AppClientServerApplication contributor) throws IOException, EndpointRegistrationException {
final String ufContextRoot = userFriendlyContextRoot(contributor);
return createAndRegisterAdapter(ufContextRoot, staticContent, dynamicContent, tokens, contributor);
}
private synchronized AppClientHTTPAdapter systemAdapter() {
if (systemAdapter == null) {
systemAdapter = startSystemContentAdapter();
}
return systemAdapter;
}
private synchronized AppClientHTTPAdapter addAppAdapter(
final String appName,
final Map staticContent,
final Map dynamicContent,
final Properties tokens,
final AppClientServerApplication contributor) throws IOException, EndpointRegistrationException {
systemAdapter(); // Make sure it is started
final String contextRoot = NamingConventions.contextRootForAppAdapter(appName);
final AppClientHTTPAdapter adapter = createAndRegisterAdapter(
contextRoot, staticContent,
dynamicContent, tokens, contributor);
httpAdapters.put(appName, adapter);
return adapter;
}
public static String userFriendlyContextRoot(final AppClientServerApplication contributor) {
return userFriendlyContextRoot(contributor.getDescriptor(),
contributor.dc().getAppProps());
}
public static String userFriendlyContextRoot(
final ApplicationClientDescriptor acDesc, final Properties p) {
String ufContextRoot = NamingConventions.defaultUserFriendlyContextRoot(
acDesc);
/*
* See if the context root setting has one for this client. The
* format of the property setting is:
*
* Stand-alone client deployment: javaWebStartContextRoot=contextRoot
* Nested in EAR: javaWebStartContextRoot.uri-to-client-without-.jar=contextRoot
*
* There can be multiple such javaWebStartContextRoot.xxx properties, one for
* each nested app client in the EAR.
*/
String overridingContextRoot = null;
if (acDesc.getApplication().isVirtual()) {
/*
* Stand-alone case.
*/
overridingContextRoot = p.getProperty(JAVA_WEB_START_CONTEXT_ROOT_PROPERTY_NAME);
} else {
/*
* Nested app clients case.
*/
final String uriToNestedClient = NamingConventions.uriToNestedClient(
acDesc);
overridingContextRoot = p.getProperty(
JAVA_WEB_START_CONTEXT_ROOT_PROPERTY_NAME + "." + uriToNestedClient);
}
if (overridingContextRoot != null) {
ufContextRoot = overridingContextRoot;
}
/*
* Grizzly wants the context root to start with a slash.
*/
if ( ! ufContextRoot.startsWith("/")) {
ufContextRoot = "/" + ufContextRoot;
}
return ufContextRoot;
}
public synchronized AutoSignedContent appLevelSignedSystemContent(
final String relativePathToSystemJar,
final String alias) throws FileNotFoundException {
/*
* The key to the map is also the subpath to the file within the
* domain's repository which holds signed system JARs.
*/
final String key = keyToAppLevelSignedSystemContentMap(relativePathToSystemJar, alias);
AutoSignedContent result = appLevelSignedSystemContent.get(key);
if (result == null) {
final File unsignedFile = new File(umbrellaRoot, relativePathToSystemJar);
final File signedFile = new File(systemLevelSignedJARsRoot, key);
result = new AutoSignedContent(unsignedFile, signedFile, alias, jarSigner, relativePathToSystemJar,
MANIFEST_APP_NAME_FOR_SYSTEM_FILES);
appLevelSignedSystemContent.put(key, result);
}
return result;
}
private static String keyToAppLevelSignedSystemContentMap(
final String relativePathToSystemJar,
final String alias) {
return alias + "/" + relativePathToSystemJar;
}
synchronized File signedSystemContentAliasDir(final String alias) {
return new File(systemLevelSignedJARsRoot, alias);
}
public String contextRootForAppAdapter(final String appName) {
final AppClientHTTPAdapter adapter = httpAdapters.get(appName);
if (adapter != null) {
return adapter.contextRoot();
} else {
return null;
}
}
private synchronized void addContributorToAppLevelAdapter(
final String appName,
final AppClientServerApplication contributor) {
/*
* Record that the calling app client server app has contributed content
* to the Grizzly adapter.
*/
Set contributorsToAppLevelAdapter = contributingAppClients.get(appName);
if (contributorsToAppLevelAdapter == null) {
contributorsToAppLevelAdapter = new HashSet();
contributingAppClients.put(appName, contributorsToAppLevelAdapter);
}
contributorsToAppLevelAdapter.add(contributor);
}
public synchronized void removeContentForAppClient(final String appName,
final String clientURIWithinEAR,
final AppClientServerApplication contributor) throws EndpointRegistrationException {
/*
* Remove the adapter for the user-friendly context root.
*/
removeAdapter(userFriendlyContextRoot(contributor));
removeContributorToAppLevelAdapter(appName, contributor);
appClientDeployer.removeContextRoot(appName, clientURIWithinEAR);
}
private synchronized void removeContributorToAppLevelAdapter(
final String appName,
final AppClientServerApplication contributor) throws EndpointRegistrationException {
/*
* If this is the last contributor for this app-level adapter then
* remove the app-level adapter also.
*/
final Set contributorsToAppLevelAdapter = contributingAppClients.get(appName);
if (contributorsToAppLevelAdapter == null) {
return;
}
contributorsToAppLevelAdapter.remove(contributor);
if (contributorsToAppLevelAdapter.isEmpty()) {
contributingAppClients.remove(appName);
removeAdapter(NamingConventions.contextRootForAppAdapter(appName));
httpAdapters.remove(appName);
}
}
private synchronized void removeAdapter(final String contextRoot)
throws EndpointRegistrationException {
requestDispatcher.unregisterEndpoint(contextRoot);
}
private String getGFClientModuleClassPath(final File gfClientJAR) throws IOException {
final JarFile jf = new JarFile(gfClientJAR);
try {
final Manifest mf = jf.getManifest();
Attributes mainAttrs = mf.getMainAttributes();
return mainAttrs.getValue(Attributes.Name.CLASS_PATH);
} finally
{
jf.close();
}
}
}