/* * Constellation - An open source and standard compliant SDI * http://www.constellation-sdi.org * * Copyright 2014 Geomatys. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.constellation.ws.embedded; import org.apache.sis.util.logging.Logging; import org.constellation.configuration.AppProperty; import org.constellation.ws.rs.CstlApplication; import org.constellation.ws.rs.jackson.JacksonFeature; import org.geotoolkit.console.CommandLine; import org.geotoolkit.console.Option; import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ServerProperties; import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature; import javax.ws.rs.ProcessingException; import javax.ws.rs.core.UriBuilder; import javax.xml.ws.Endpoint; import java.io.IOException; import java.net.URI; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import org.springframework.core.env.AbstractEnvironment; /** * An Abstract class to run the web service in an embedded container. * <p> * For now, the services are run only using the Grizzly web server. In the * future, we may look into working on the embedded Glassfish system. * </p> * <p> * Classes wishing to run an embedded service should extend this class. The * responsibilities of each extending class is to: * <ol> * <li>Call this constructor with the arguments of the main method. * <pre> * super(args); * </pre> * </li> * <li>For JAX-RS, REST based services, add parameters to the configuration * {@code Map}. * <pre> * map.put("com.sun.jersey.config.property.packages", "org.constellation.map.ws.rs"); * map.put("com.sun.jersey.config.property.packages", "org.constellation.coverage.ws.rs"); * map.put(TODO: get providers line from Cedric); * </pre> * </li> * <li>For JAX-WS, SOAP based services, set the reference of the Service * Endpoint Interface. * <pre> * serviceInstanceSOAP = new org.constellation.sos.ws.soap.SOSService(); * </pre> * </li> * <li>Add a simple main, such as: * <pre> * public static void main(String[] args) { * * EXTENDOR sei = new EXTENDOR(args); * if (useFacadeREST){ * sei.runREST(); * } else { * sei.runSOAP(); * } * * System.exit(0); * * } * </pre> * </li> * </ol> * </p> * * @author Adrian Custer (Geomatys) * @since 0.3 * */ public class CstlEmbeddedService extends CommandLine { /** * Logger for this service. */ private static final Logger LOGGER = Logging.getLogger("org.constellation.ws.embedded"); // THESE ARE INJECTED BY THE CommandLine CLASS // TODO: these default values clobber main's args; fixed in Geotidy @Option protected Boolean useFacadeREST = true; //Default value @Option protected String host = "localhost";//Default value @Option protected Integer port = 9090; //Default value @Option protected Integer portsoap = 9191; @Option public Integer duration = 20 * 60 * 1000; //minutes*seconds*millseconds; set to <=0 to last until <enter> public boolean findAvailablePort = false; public Integer currentPort; public boolean ready = false; private URI uri; public String uriSuffix; final URI uriSoap; final DateFormat f = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); /* *********************************************************************** * CONCRETE CLASSES MUST: (see below) * ********************************************************************** */ //FOR REST, COMPLETE THIS PARAMETER MAP // Grizzly is used by the REST service. Extending classes need to add the // package(s) containing the service(s) they desire to run and the // providers on which those services depend, e.g. : // map.put("com.sun.jersey.config.property.packages", "org.constellation.map.ws.rs"); // map.put("com.sun.jersey.config.property.packages", "org.constellation.coverage.ws.rs"); // map.put(TODO: get providers line from Cedric); // below we add in one of the properties needed by all services. protected Map<String, Object> grizzlyWebContainerProperties = new HashMap<>(); //FOR SOAP, DEFINE THIS REFERENCE: public final Map<String, Object> serviceInstanceSOAP = new HashMap<>(); private HttpServer threadSelector; final List<Endpoint> eps = new ArrayList<>(); //INCLUDE THIS MAIN. // public static void main(String[] args) { // // EXTENDOR sei = new EXTENDOR(args); // if (useFacadeREST){ // sei.runREST(); // } else { // sei.runSOAP(); // } // // System.exit(0); // // } /* *********************************************************************** * CONCRETE CLASSES MUST HAVE: (see above) * ********************************************************************** */ public CstlEmbeddedService(final String[] args) { this(args, (String)null); } /** * Constructor which passes the arguments for processing to the * CommandLine parent and sets the URI. * By default, only start the WMS and WCS services. * <p> * Extending classes using the REST facade should */ public CstlEmbeddedService(final String[] args, String uriSuffix) { this(args, new String[] { "org.constellation.rest.api", "org.constellation.map.ws.rs", "org.constellation.coverage.ws.rs", "org.constellation.wfs.ws.rs", "org.constellation.wps.ws.rs", "org.constellation.sos.ws.rs", "org.constellation.sos.ws.rs.provider", "org.constellation.configuration.ws.rs", "org.constellation.metadata.ws.rs", "org.constellation.metadata.ws.rs.provider", "org.constellation.ws.rs.provider" }, uriSuffix); } public CstlEmbeddedService(final int port, final String[] args, final String[] providerPackages) { this(args, providerPackages); this.port = port; final String base = "http://" + host + "/"; this.uri = UriBuilder.fromUri(base).port(port).build(); } public CstlEmbeddedService(final String[] args, final String[] providerPackages) { this(args, providerPackages, null); } /** * Constructor which passes the arguments for processing to the * CommandLine parent and sets the URI. * <p> * Extending classes using the REST facade should * * @param args The command line arguments. * @param providerPackages The packages for providers to start. * @param uriSuffix */ public CstlEmbeddedService(final String[] args, final String[] providerPackages, final String uriSuffix) { super(null, args); final StringBuilder sb = new StringBuilder(); final int length = providerPackages.length; for (int i=0; i<length; i++) { final String pack = providerPackages[i]; sb.append(pack); if (i != length - 1) { sb.append(';'); } } grizzlyWebContainerProperties.put(ServerProperties.PROVIDER_PACKAGES, sb.toString()); this.uriSuffix = uriSuffix; final String base; if (uriSuffix == null) { base = "http://" + host + "/"; } else { base = "http://" + host + "/" + uriSuffix + "/"; } uri = UriBuilder.fromUri(base).port(port).build(); uriSoap = UriBuilder.fromUri(base).port(portsoap).build(); } private HttpServer buildThreadSelector() { HttpServer threadSelector = null; final String base = "http://" + host + "/"; URI currentUri = uri; currentPort = port; boolean working = false; while (!working) { working = true; try { final ResourceConfig config = ResourceConfig.forApplication(new CstlApplication()); System.setProperty(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, "standard"); System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "standard"); System.setProperty(AppProperty.CSTL_URL.getKey(), currentUri.toString()); System.setProperty(AppProperty.CSTL_SERVICE_URL.getKey(), currentUri.toString()); // grizzlyWebContainerProperties.put(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, "standard"); // grizzlyWebContainerProperties.put(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "standard"); config.addProperties(grizzlyWebContainerProperties); config.register(JacksonFeature.class); config.register(MultiPartFeature.class); config.register(RolesAllowedDynamicFeature.class); //ApplicationHandler handler = new ApplicationHandler(config); threadSelector = GrizzlyHttpServerFactory.createHttpServer(currentUri, config, true); } catch (ProcessingException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); if (findAvailablePort) { working = false; currentPort++; currentUri = UriBuilder.fromUri(base).port(currentPort).build(); } } } uri = currentUri; return threadSelector; } /** * Should be called by the {@code main()} method for any web service wishing * to implement a JAX-RS REST facade. */ public void runREST() { grizzlyWebContainerProperties.put("com.sun.jersey.config.property.resourceConfigClass", "com.sun.jersey.api.core.PackagesResourceConfig"); LOGGER.log(Level.INFO, "Starting grizzly server at: {0}", f.format(new Date())); threadSelector = buildThreadSelector(); ready = true; LOGGER.log(Level.INFO, "Started Grizzly application server for: {0}", uri); LOGGER.log(Level.INFO, "The service definition file can be found at: {0}application.wadl", uri); stayAlive(); shutdown(); LOGGER.log(Level.INFO, "*Stopped grizzly server at: {0}.", f.format(new Date())); } /** * Should be called by the {@code main()} method for any web service wishing * to implement a JAX-WS SOAP facade. */ public void runSOAP() { if (serviceInstanceSOAP.isEmpty()) { LOGGER.info("The SOAP Service Endpoint Instance was never defined."); System.exit(0); } LOGGER.log(Level.INFO, "Starting jax-ws server at: {0}", f.format(new Date())); eps.clear(); for (Entry<String, Object> instance : serviceInstanceSOAP.entrySet()) { final String service = uriSoap.toString() + instance.getKey(); Endpoint ep = Endpoint.create(instance.getValue()); ep.publish(service); eps.add(ep); LOGGER.log(Level.INFO, "Started jax-ws application server for: {0}", service); LOGGER.log(Level.INFO, "The service definition file can be found at: {0}?wsdl", service); } ready = true; stayAlive(); shutdown(); LOGGER.log(Level.INFO, "*Stopped jax-ws server at: {0}.", f.format(new Date())); } /** * Should be called by the {@code main()} method for any web service wishing * to implement a JAX-RS REST facade. */ public void runAll() { /*grizzlyWebContainerProperties.put("com.sun.jersey.config.property.resourceConfigClass", "com.sun.jersey.api.core.PackagesResourceConfig");*/ LOGGER.log(Level.INFO, "Starting grizzly server at: {0}", f.format(new Date())); threadSelector = buildThreadSelector(); LOGGER.log(Level.INFO, "Started Grizzly application server for: {0}", uri); LOGGER.log(Level.INFO, "The service definition file can be found at: {0}application.wadl", uri); eps.clear(); for (Entry<String, Object> instance : serviceInstanceSOAP.entrySet()) { final String service = uriSoap.toString() + instance.getKey(); final Endpoint ep = Endpoint.create(instance.getValue()); ep.publish(service); eps.add(ep); LOGGER.log(Level.INFO, "Started jax-ws application server for: {0}", service); LOGGER.log(Level.INFO, "The service definition file can be found at: {0}?wsdl", service); } ready = true; stayAlive(); LOGGER.log(Level.INFO, "*grizzly shutdown in progress"); shutdown(); LOGGER.log(Level.INFO, "*Stopped grizzly server at: {0}.", f.format(new Date())); } /** * Will keep either of the services alive either for the number of * milliseconds defined in the command-line parameter {@code duration} if * that value is greater than zero or until the user presses the * {@code <ENTER>} (or {@code <RETURN>}) key of the keyboard depending if * the duration time is greater than zero. * */ public void stayAlive() { if (duration > 0) { //Survive 'duration' milliseconds LOGGER.log(Level.INFO, " Service will stop in {0} minutes.", duration / (60 * 1000)); try { Thread.sleep(duration); } catch (InterruptedException iex) { LOGGER.fine("The grizzly thread has received an interrupted request."); LOGGER.log(Level.INFO, "*Interrupted grizzly server at: {0}.", f.format(new Date())); } } else { //Listen and wait for <ENTER> LOGGER.info(" Hit <ENTER> to stop the service."); try { System.in.read(); } catch (IOException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } } } public static void main(String[] args) { new CstlEmbeddedService(args).runSOAP(); } public void shutdown() { LOGGER.log(Level.INFO, "*grizzly shutdown in progress"); if (threadSelector != null) { threadSelector.shutdownNow(); } for (Endpoint ep : eps) { ep.stop(); } } }