All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.appclient.server.core.jws.JWSAdapterManager Maven / Gradle / Ivy

There is a newer version: 6.2024.6
Show newest version
/*
 * 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(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy