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

org.glassfish.embed.AppServer Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2008 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 org.glassfish.embed;

import com.sun.enterprise.deploy.shared.ArchiveFactory;
import com.sun.enterprise.module.Module;
import com.sun.enterprise.module.bootstrap.BootException;
import com.sun.enterprise.module.bootstrap.Main;
import com.sun.enterprise.module.bootstrap.StartupContext;
import com.sun.enterprise.module.impl.ClassLoaderProxy;
import com.sun.enterprise.module.impl.ModulesRegistryImpl;
import com.sun.enterprise.security.SecuritySniffer;
import com.sun.enterprise.universal.glassfish.SystemPropertyConstants;
import com.sun.enterprise.util.io.FileUtils;
import com.sun.enterprise.v3.admin.adapter.AdminConsoleAdapter;
import com.sun.enterprise.v3.server.APIClassLoaderServiceImpl;
import com.sun.enterprise.v3.server.ApplicationLifecycle;
import com.sun.enterprise.v3.server.DomainXml;
import com.sun.enterprise.v3.server.DomainXmlPersistence;
import com.sun.enterprise.v3.server.SnifferManager;
import com.sun.enterprise.v3.services.impl.LogManagerService;
import com.sun.enterprise.web.WebDeployer;
import com.sun.hk2.component.ExistingSingletonInhabitant;
import com.sun.hk2.component.InhabitantsParser;
import com.sun.web.security.RealmAdapter;
import com.sun.web.server.DecoratorForJ2EEInstanceListener;
import org.glassfish.api.Startup;
import org.glassfish.api.admin.ParameterNames;
import org.glassfish.api.container.Sniffer;
import org.glassfish.api.deployment.archive.ArchiveHandler;
import org.glassfish.api.deployment.archive.ReadableArchive;
import org.glassfish.deployment.autodeploy.AutoDeployService;
import org.glassfish.deployment.common.DeploymentContextImpl;
import org.glassfish.embed.impl.EmbeddedAPIClassLoaderServiceImpl;
import org.glassfish.embed.impl.EmbeddedApplicationLifecycle;
import org.glassfish.embed.impl.EmbeddedDomainXml;
import org.glassfish.embed.impl.EmbeddedServerEnvironment;
import org.glassfish.embed.impl.EmbeddedWebDeployer;
import org.glassfish.embed.impl.EntityResolverImpl;
import org.glassfish.embed.impl.ProxyModuleDefinition;
import org.glassfish.embed.impl.ScatteredWarHandler;
import org.glassfish.embed.impl.SilentActionReport;
import org.glassfish.internal.api.Init;
import org.glassfish.internal.data.ApplicationInfo;
import org.glassfish.server.ServerEnvironmentImpl;
import org.glassfish.web.WebEntityResolver;
import org.jvnet.hk2.component.Habitat;
import org.jvnet.hk2.component.Inhabitant;
import org.jvnet.hk2.component.Inhabitants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Entry point to the embedded GlassFish Server.
 * 

*

* TODO: the way this is done today is that the embedded API wraps the ugliness * of the underlying GFv3 internal abstractions, but ideally, it should be the * other way around — this should be the native interface inside GlassFish, * and application server launcher and CLI commands should be the client of this * API. This is how all the other sensible containers do it, like Tomcat and Jetty. * * @author Kohsuke Kawaguchi */ public class AppServer { /** * As of April 2008, several key configurations like HTTP listener * creation cannot be done once GFv3 starts running. *

* We hide this from the client of this API by laziyl starting * the server, and this flag remembers which state we are in. */ private boolean started; protected /*almost final*/ Habitat habitat; /** * Work around until the live HTTP listener support comes back. */ private Document domainXml; /** * To navigate around {@link #domainXml}. */ private final XPath xpath = XPathFactory.newInstance().newXPath(); protected URL domainXmlUrl; protected URL defaultWebXml; // key components inside GlassFish. We access them all the time, // so we might just as well keep them here for ease of access. protected /*almost final*/ ApplicationLifecycle appLife; protected /*almost final*/ SnifferManager snifMan; protected /*almost final*/ ArchiveFactory archiveFactory; protected /*almost final*/ ServerEnvironmentImpl env; protected AppServer(URL domainXmlUrl, boolean start) throws EmbeddedException { this.domainXmlUrl = domainXmlUrl; if (this.domainXmlUrl == null) { // if not defined get the default one this.domainXmlUrl = getClass().getResource("/org/glassfish/embed/domain.xml"); if (this.domainXmlUrl == null) { throw new AssertionError("domain.xml is missing from resources"); } } if (start) { start(); } } /** * Starts an empty do-nothing GlassFish v3. *

*

* In particular, no HTTP listener is configured out of the box, so you'd have to add * some programatically via {@link #createHttpListener(int)} and {@link #createVirtualServer(GFHttpListener)}. */ public AppServer(URL domainXmlUrl) throws EmbeddedException { this(domainXmlUrl, true); } /** * Starts GlassFish v3 with minimalistic configuration that involves * single HTTP listener listening on the given port. */ public AppServer(int httpPort) throws EmbeddedException { this(null, false); try { domainXml = parseDefaultDomainXml(); } catch (IOException e) { throw new EmbeddedException(e); } catch (SAXException e) { throw new EmbeddedException(e); } catch (ParserConfigurationException e) { throw new EmbeddedException(e); } createVirtualServer(createHttpListener(httpPort)); start(); } private Document parseDefaultDomainXml() throws ParserConfigurationException, IOException, SAXException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // dbf.setNamespaceAware(true); // domain.xml doesn't use namespace //return dbf.newDocumentBuilder().parse(getClass().getResource("/org/glassfish/embed/domain.xml").toExternalForm()); return dbf.newDocumentBuilder().parse(domainXmlUrl.toExternalForm()); } /** * @return the domainXml URL. */ public URL getDomainXml() { return domainXmlUrl; } public void setDefaultWebXml(URL url) { this.defaultWebXml = url; } public URL getDefaultWebXml() { return defaultWebXml; } protected URL getDomainXML() { return domainXmlUrl; } /** * Sets the overall logging level for the Server. */ public static void setLogLevel(Level level) { Logger.getLogger("javax.enterprise").setLevel(level); } /** * Tweaks the 'recipe' --- for embedded use, we'd like GFv3 to behave a little bit * differently from normal stand-alone use. */ protected InhabitantsParser decorateInhabitantsParser(InhabitantsParser parser) { // registering the server using the base class and not the current instance class // (GlassFish server may be extended by the user) parser.habitat.add(new ExistingSingletonInhabitant(AppServer.class, this)); // register scattered web handler before normal WarHandler kicks in. Inhabitant swh = Inhabitants.create(new ScatteredWarHandler()); parser.habitat.add(swh); parser.habitat.addIndex(swh, ArchiveHandler.class.getName(), null); // we don't want GFv3 to reconfigure all the loggers parser.drop(LogManagerService.class); // we don't need admin CLI support. // TODO: admin CLI should be really moved to a separate class parser.drop(AdminConsoleAdapter.class); // don't care about auto-deploy either try { Class.forName("org.glassfish.deployment.autodeploy.AutoDeployService"); parser.drop(AutoDeployService.class); } catch (Exception e) { // ignore. It may not be available } //TODO: workaround for a bug parser.replace(ApplicationLifecycle.class, EmbeddedApplicationLifecycle.class); parser.replace(APIClassLoaderServiceImpl.class, EmbeddedAPIClassLoaderServiceImpl.class); // we don't really parse domain.xml from disk parser.replace(DomainXml.class, EmbeddedDomainXml.class); // ... and we don't persist it either. parser.replace(DomainXmlPersistence.class, EmbeddedDomainXml.class); // we provide our own ServerEnvironment parser.replace(ServerEnvironmentImpl.class, EmbeddedServerEnvironment.class); {// adjustment for webtier only bundle parser.drop(DecoratorForJ2EEInstanceListener.class); // in the webtier-only bundle, these components don't exist to begin with. try { // security code needs a whole lot more work to work in the modular environment. // disabling it for now. parser.drop(SecuritySniffer.class); // WebContainer has a bug in how it looks up Realm, but this should work around that. parser.drop(RealmAdapter.class); } catch (LinkageError e) { // maybe we are running in the webtier only bundle } } // override the location of default-web.xml parser.replace(WebDeployer.class, EmbeddedWebDeployer.class); // override the location of cached DTDs and schemas parser.replace(WebEntityResolver.class, EntityResolverImpl.class); return parser; } protected File createTempDir() throws IOException { File dir = File.createTempFile("glassfish", "embedded"); dir.delete(); dir.mkdirs(); return dir; } public EmbeddedVirtualServer createVirtualServer(final EmbeddedHttpListener listener) { // the following live update code doesn't work yet due to the missing functionality in the webtier. if (started) throw new IllegalStateException(); DomBuilder db = onHttpService(); db.element("virtual-server") .attribute("id", "server") .attribute("http-listeners", "http-listener-1") .attribute("hosts", "${com.sun.aas.hostName}") // ??? .attribute("log-file", "") .element("property") .attribute("name", "docroot") .attribute("value", "."); /** * Write domain.xml to a temporary file. UGLY UGLY UGLY. */ try { File domainFile = File.createTempFile("domain", "xml"); domainFile.deleteOnExit(); Transformer t = TransformerFactory.newInstance().newTransformer(); t.transform(new DOMSource(this.domainXml), new StreamResult(domainFile)); domainXmlUrl = domainFile.toURI().toURL(); } catch (IOException e) { throw new EmbeddedException("Failed to write domain XML", e); } catch (TransformerException e) { throw new EmbeddedException("Failed to write domain XML", e); } return new EmbeddedVirtualServer(null); // try { // Configs configs = habitat.getComponent(Configs.class); // // HttpService httpService = configs.getConfig().get(0).getHttpService(); // return (GFVirtualServer) ConfigSupport.apply(new SingleConfigCode() { // public Object run(HttpService param) throws PropertyVetoException, TransactionFailure { // VirtualServer vs = ConfigSupport.createChildOf(param, VirtualServer.class); // vs.setId("server"); // vs.setHttpListeners(listener.core.getId()); // vs.setHosts("${com.sun.aas.hostName}"); //// vs.setDefaultWebModule("no-such-module"); // // Property property = // ConfigSupport.createChildOf(vs, Property.class); // property.setName("docroot"); // property.setValue("."); // vs.getProperty().add(property); // // // param.getVirtualServer().add(vs); // return new GFVirtualServer(vs); // } // }, httpService); // } catch(TransactionFailure e) { // throw new GFException(e); // } } public EmbeddedHttpListener createHttpListener(final int listenerPort) { // the following live update code doesn't work yet due to the missing functionality in the webtier. if (started) throw new IllegalStateException(); onHttpService().element("http-listener") //hardcoding to http-listner-1 should not be a requirment, but the id is used to find the right Inhabitant .attribute("id", "http-listener-1") .attribute("address", "0.0.0.0") .attribute("port", listenerPort) .attribute("default-virtual-server", "server") .attribute("server-name", "") .attribute("enabled", true); return new EmbeddedHttpListener(String.valueOf(listenerPort), null); // try { // Configs configs = habitat.getComponent(Configs.class); // // HttpService httpService = configs.getConfig().get(0).getHttpService(); // return (GFHttpListener)ConfigSupport.apply(new SingleConfigCode() { // public Object run(HttpService param) throws PropertyVetoException, TransactionFailure { // HttpListener newListener = ConfigSupport.createChildOf(param, HttpListener.class); // newListener.setId("http-listener-"+listenerPort); // newListener.setAddress("127.0.0.1"); // newListener.setPort(String.valueOf(listenerPort)); // newListener.setDefaultVirtualServer("server"); // newListener.setEnabled("true"); // // param.getHttpListener().add(newListener); // return new GFHttpListener(newListener); // } // }, httpService); // } catch(TransactionFailure e) { // throw new GFException(e); // } } private DomBuilder onHttpService() { try { return new DomBuilder((Element) xpath.evaluate("//http-service", domainXml, XPathConstants.NODE)); } catch (XPathExpressionException e) { throw new AssertionError(e); // impossible } } /** * Starts the server if hasn't done so already. Necessary to work around the live HTTP listener update. */ private void start() { if (started) return; started = true; try { final Module[] proxyMod = new Module[1]; // ANONYMOUS CLASS HERE!! ModulesRegistryImpl modulesRegistry = new ModulesRegistryImpl(null) { public Module find(Class clazz) { Module m = super.find(clazz); if (m == null) return proxyMod[0]; return m; } }; proxyMod[0] = modulesRegistry.add(new ProxyModuleDefinition(getClass().getClassLoader())); StartupContext startupContext = new StartupContext(createTempDir(), new String[0]); // ANONYMOUS CLASS HERE!! Main main = new Main() { @Override protected InhabitantsParser createInhabitantsParser(Habitat habitat1) { return decorateInhabitantsParser(super.createInhabitantsParser(habitat1)); } }; habitat = main.launch(modulesRegistry, startupContext); appLife = habitat.getComponent(ApplicationLifecycle.class); snifMan = habitat.getComponent(SnifferManager.class); archiveFactory = habitat.getComponent(ArchiveFactory.class); env = habitat.getComponent(ServerEnvironmentImpl.class); } catch (IOException e) { throw new EmbeddedException(e); } catch (BootException e) { throw new EmbeddedException(e); } } /** * Deploys WAR/EAR/RAR/etc to this Server. * * @return always non-null. Represents the deployed application. * @throws GFException General error that represents a deployment failure. * @throws IOException If the given archive reports {@link IOException} from one of its methods, * that exception will be passed through. */ public App deploy(File archive) throws IOException { start(); ReadableArchive a = archiveFactory.openArchive(archive); if (!archive.isDirectory()) { // explode (if I don't, WarHandler won't work) ArchiveHandler h = appLife.getArchiveHandler(a); File tmpDir = new File(a.getName()); FileUtils.whack(tmpDir); tmpDir.mkdirs(); h.expand(a, archiveFactory.createArchive(tmpDir)); a.close(); a = archiveFactory.openArchive(tmpDir); } return deploy(a); } /** * Deploys a {@link ReadableArchive} to this Server. *

*

* This overloaded version of the deploy method is for advanced users. * It allows the caller to deploy an application in a non-standard layout. *

*

* The deployment uses the {@link ReadableArchive#getName() archive name} * as the context path. * * @return The object that represents a deployed application. * Never null. * @throws GFException General error that represents a deployment failure. * @throws IOException If the given archive reports {@link IOException} from one of its methods, * that exception will be passed through. */ public App deploy(ReadableArchive a) throws IOException { return deploy(a, null); } /** * Deploys a {@link ReadableArchive} to this Server. *

*

* This overloaded version of the deploy method is for advanced users. * It allows you specifying additional parameters to be passed to the deploy command * * @param a * @param params * @return * @throws IOException */ public App deploy(ReadableArchive a, Properties params) throws IOException { start(); ArchiveHandler h = appLife.getArchiveHandler(a); // now prepare sniffers //is this required? ClassLoader parentCL = createSnifferParentCL(null, snifMan.getSniffers()); ClassLoader cl = h.getClassLoader(parentCL, a); Collection activeSniffers = snifMan.getSniffers(a, cl); // TODO: we need to stop this totally type-unsafe way of passing parameters if (params == null) { params = new Properties(); } params.put(ParameterNames.NAME, a.getName()); params.put(ParameterNames.ENABLED, "true"); final DeploymentContextImpl deploymentContext = new DeploymentContextImpl(Logger.getAnonymousLogger(), a, params, env); SilentActionReport r = new SilentActionReport(); ApplicationInfo appInfo = appLife.deploy(activeSniffers, deploymentContext, r); r.check(); return new App(this, appInfo, deploymentContext); } /** * Sets up a parent classloader that will be used to create a temporary application * class loader to load classes from the archive before the Deployers are available. * Sniffer.handles() method takes a class loader as a parameter and this class loader * needs to be able to load any class the sniffer load themselves. * * @param parent parent class loader for this class loader * @param sniffers sniffer instances * @return a class loader with visibility on all classes loadable by classloaders. */ public ClassLoader createSnifferParentCL(ClassLoader parent, Collection sniffers) { // Use the sniffers class loaders as the delegates to the parent class loader. // This will allow any class loadable by the sniffer (therefore visible to the sniffer // class loader) to be also loadable by the archive's class loader. ClassLoaderProxy cl = new ClassLoaderProxy(new URL[0], parent); for (Sniffer sniffer : sniffers) { cl.addDelegate(sniffer.getClass().getClassLoader()); } return cl; } /** * Convenience method to deploy a scattered war archive on a given virtual server * and using the specified context root. * * @param war the scattered war * @param contextRoot the context root to use * @param virtualServer the virtual server ID */ public App deployWar(ScatteredWar war, String contextRoot, String virtualServer) throws IOException { Properties params = new Properties(); if (virtualServer == null) { virtualServer = "server"; } params.put(ParameterNames.VIRTUAL_SERVERS, virtualServer); if (contextRoot != null) { params.put(ParameterNames.CONTEXT_ROOT, contextRoot); } return deploy(war, params); } /** * Convenience method to deploy a scattered war archive on the default virtual server. * * @param war the archive * @param contextRoot the context root to use * @throws IOException */ public App deployWar(ScatteredWar war, String contextRoot) throws IOException { return deployWar(war, contextRoot, null); } /** * Convenience method to deploy a scattered war archive on the default virtual server * (as defined by the embedded domain.xml) and using the root "/" context. * * @param war the scattered war */ public App deployWar(ScatteredWar war) throws IOException { return deployWar(war, null, null); } /** * Stops the running server. */ public void stop() { if (!started) return; for (Inhabitant svc : habitat.getInhabitants(Startup.class)) { svc.release(); } for (Inhabitant svc : habitat.getInhabitants(Init.class)) { svc.release(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy