/******************************************************************************* * Copyright 2014 Miami-Dade County * * 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.sharegov.cirm; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.net.URI; import java.net.URL; import java.net.URLDecoder; import java.util.Vector; import java.util.logging.Level; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.Variant.VariantListBuilder; import javax.ws.rs.ext.RuntimeDelegate; import mjson.Json; import org.restlet.Application; import org.restlet.Component; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.Server; import org.restlet.data.MediaType; import org.restlet.data.Protocol; import org.restlet.data.Status; import org.restlet.engine.application.Encoder; import org.restlet.engine.local.DirectoryServerResource; import org.restlet.ext.jaxrs.JaxRsApplication; import org.restlet.representation.Representation; import org.restlet.resource.Directory; import org.restlet.resource.ServerResource; import org.restlet.routing.Filter; import org.restlet.routing.Redirector; import org.restlet.routing.Router; import org.restlet.routing.Template; import org.restlet.service.EncoderService; import org.sharegov.cirm.legacy.ActivityManager; import org.sharegov.cirm.legacy.MessageManager; import org.sharegov.cirm.legacy.ServiceCaseManager; import org.sharegov.cirm.owl.CachedReasoner; import org.sharegov.cirm.rdb.RelationalOWLMapper; import org.sharegov.cirm.rest.MainRestApplication; import org.sharegov.cirm.rest.OntoAdmin; import org.sharegov.cirm.utils.AdaptiveClassLoader; import org.sharegov.cirm.utils.GenUtils; import org.sharegov.cirm.utils.ThreadLocalStopwatch; import org.sharegov.cirm.utils.UploadToCloudServerResource; /** * Starts up OpenCirm. * * @author unknown, Thomas Hilpold * */ public class StartUp extends ServerResource { public final static String PRODUCTION_MODE_IDENTIFIER = "http://www.miamidade.gov/ontology#ProdConfigSet"; public final static String MODE_CONFIG_PARAM = "ontologyConfigSet"; public final static Json DEFAULT_CONFIG = Json.object() .set("workingDir", "C:/work/opencirm") .set("mainApplication", "http://www.miamidade.gov/ontology#CIRMApplication") .set("port", 8182) .set("ignorePasswords", true) .set("ssl-port", 8183) .set("ssl", true) .set("keystore", "cirm.jks") .set("storePass", "password") .set("keyPass", "password") .set("defaultOntologyIRI", "http://www.miamidade.gov/cirm/legacy") //.set("ontologyConfigSet", "http://www.miamidade.gov/ontology#ProdConfigSet") .set("ontologyConfigSet", "http://www.miamidade.gov/ontology#TestConfigSet") //.set("ontologyConfigSet", "http://www.miamidade.gov/ontology#DevConfigSet") //.set("ontologyConfigSet", "http://www.miamidade.gov/ontology#LocalConfigSetXE") //.set("ontologyConfigSet", "http://www.miamidade.gov/ontology#LocalConfigSet") .set("nameBase", "http://www.miamidade.gov/ontology" ) .set("stopExpansionConditionIRI", Json.array( "http://www.miamidade.gov/cirm/legacy#providedBy", "http://www.miamidade.gov/cirm/legacy#hasChoiceValue" )) .set("metaDatabaseLocation", "c:/temp/dbConf") .set("allClientsExempt", true) .set("network", Json.object( "user", "cirmservice_production", "password","cirmsprod", "serverUrl","s0144818", "ontoServer","ontology_server_production")) .set("ontologyPrefixes", Json.object( "legacy:", "http://www.miamidade.gov/cirm/legacy#", "mdc:", "http://www.miamidade.gov/ontology#", ":", "http://www.miamidade.gov/ontology#" )) .set("cachedReasonerPopulate", false) .set("startDepartmentIntegration", "x.x.xxx") .set("isConfigMode", false); private final static StartupHttpInitializer HTTP_INIT = new StartupHttpInitializer(); /** * Switch for stress testing. If true, disables most external calls and dbg output. */ public static boolean STRESS_TEST_CONFIG = false; public static Level LOGGING_LEVEL = Level.INFO; private volatile static Json config = DEFAULT_CONFIG; private static Component server = null; private static Component redirectServer = null; private static PaddedJSONFilter jsonpFilter = null; private static Encoder encoder = null; private static boolean productionMode = false; /** * Just for registration */ private static volatile RequestScopeFilter requestScopeFilter = null; public static Component getServer() { return server; } public static PaddedJSONFilter getJsonpFilter() { return jsonpFilter; } /** * Gets the global json configuration. * @return */ public static Json getConfig() { return config; } public static void setConfig(Json configObject) { if (server != null) throw new IllegalStateException("Server was already created. Setting new config prevented."); config = configObject; } /** * Returns true if and only if the http/s server is fully initialized and ready to serve requests. * * @return true, or false if the server was not started or is initializing */ public static boolean isServerStarted() { return server != null && server.isStarted(); } /** * Check if this instance running in production mode. * @return false if any test or dev mode */ public static boolean isProductionMode() { return productionMode; } private static void setProductionMode(boolean prodMode) { productionMode = prodMode; } public static class CirmServerResource extends DirectoryServerResource { public Representation handle() { try { URI uri = new URI(this.getTargetUri()); String localFilename = URLDecoder.decode(uri.getRawPath(), "UTF-8"); File expectedParent = new File(config.at("workingDir").asString() + "/src"); File thefile = new File(localFilename).getCanonicalFile(); boolean grantaccess = false; while (thefile.getParentFile() != null && !grantaccess) { if (thefile.getParentFile().equals(expectedParent)) grantaccess = true; thefile = thefile.getParentFile(); } if (!grantaccess) { this.setStatus(Status.CLIENT_ERROR_BAD_REQUEST); return null; } } catch (Exception ex) { ex.printStackTrace(); return null; } Representation result = super.handle(); if (result != null) { // System.out.println("Handle: " + result.getMediaType() + " " + // result.getLocationRef()); if (result.getLocationRef().toString().contains("manifest.appcache")) result.setMediaType(new MediaType("text/cache-manifest")); } return result; } } public static Directory configureDirResource(Directory dir) { dir.setListingAllowed(false); dir.setModifiable(false); dir.setTargetClass(CirmServerResource.class); return dir; } public static Restlet createRestServicesApp() { final JaxRsApplication app = new JaxRsApplication(server.getContext().createChildContext()); EncoderService encoderService = new EncoderService(); encoderService.setEnabled(true); //app.setEncoderService(encoderService); app.getLogger().setLevel(LOGGING_LEVEL); try { Vector<File> v = new Vector<File>(); v.add(new File(new File(config.at("workingDir").asString()), "hotdeploy")); AdaptiveClassLoader loader = new AdaptiveClassLoader(v, true); MainRestApplication main = new MainRestApplication(loader); main.configure(OWL.individual(config.at("mainApplication").asString())); app.add(main); } catch (Exception ex) { throw new RuntimeException(ex); } // Set filters. HttpMethodOverrideFilter methodFilter = new HttpMethodOverrideFilter(app.getContext()); methodFilter.setNext(app); jsonpFilter = new PaddedJSONFilter(app.getContext()); jsonpFilter.setNext(methodFilter); // TrafficMonitor trafficMonitor = new TrafficMonitor(app.getContext()); // trafficMonitor.setNext(jsonpFilter); requestScopeFilter = new RequestScopeFilter(); requestScopeFilter.setNext(getJsonpFilter()); encoder = new Encoder(app.getContext(), true, true, encoderService); encoder.setNext(requestScopeFilter); //set the form param fix as the last filter in the chain. Filter formParamFix = new FormParamAdditionalDecodeFilter(app.getContext()); formParamFix.setNext(encoder); return formParamFix; } static URL selfUrl() { try { String hostname = java.net.InetAddress.getLocalHost().getHostName(); boolean ssl = config.is("ssl", true); int port = ssl ? config.at("ssl-port").asInteger() : config.at("port").asInteger(); return new URL( ( ssl ? "https://" : "http://") + hostname + ":" + port); } catch (Exception ex) { throw new RuntimeException(ex); } } /** * Disable most external calls and dbg output during stresstesting. */ public static void stressTestConfig() { ActivityManager.USE_TIME_MACHINE = false; MessageManager.DISABLE_SEND = true; // LegacyEmulator.SEND_BO_TO_INTERFACE = false; // this is triggered by events now, so the event should be removed from ontology CachedReasoner.DBG_CACHE_MISS = false; // GisClient.USE_GIS_SERVICE = false; // GisClient.DBG = false; } public static void main(String[] args) throws Exception { if (STRESS_TEST_CONFIG) stressTestConfig(); if( (args.length > 0) ) { setConfig(Json.read(GenUtils.readTextFile(new File(args[0])))); } boolean productionMode = config.at(MODE_CONFIG_PARAM).asString().equals(PRODUCTION_MODE_IDENTIFIER); setProductionMode(productionMode); System.out.println("Using config " + config.toString()); if (isProductionMode()) { ThreadLocalStopwatch.now("************************* 311Hub PRODUCTION MODE *************************"); } else { ThreadLocalStopwatch.now("------------------------- 311Hub TEST OR DEV MODE: " + config.at(MODE_CONFIG_PARAM).asString() + "-------------------------"); } try { ConfigSet.getInstance(); }catch(Throwable t) { throw new RuntimeException(t); } configureServer(); final Restlet restApplication = createRestServicesApp(); final Context childCtx = server.getContext().createChildContext(); Application javaScriptApplication = new Application(childCtx) { @Override public Restlet createInboundRoot() { return configureDirResource(new Directory(childCtx.createChildContext(), "file:///" + config.at("workingDir").asString() + "/src/javascript/")); } }; //server.getDefaultHost().attach("/javascript", application); Application resourcesApplication = new Application(childCtx) { @Override public Restlet createInboundRoot() { return configureDirResource(new Directory(childCtx.createChildContext(), "file:///" + config.at("workingDir").asString() + "/src/resources/")); } }; //server.getDefaultHost().attach("/resources", application); // Restlet to upload file from client to server Application uploadApplication = new Application(server.getContext().createChildContext()) { public Restlet createInboundRoot() { Router router = new Router(); // Attach the resource. router.attachDefault(UploadToCloudServerResource.class); return router; } }; //server.getDefaultHost().attach("/upload", application); // Restlet to serve uploaded files from server to client Application uploadedApplication = new Application(childCtx) { @Override public Restlet createInboundRoot() { return configureDirResource(new Directory(childCtx.createChildContext(), "file:///" + config.at("workingDir").asString() + "/src/uploaded/")); } }; //server.getDefaultHost().attach("/uploaded", application); Application healthcheckApplication = new Application(childCtx) { @Override public Restlet createInboundRoot() { return configureDirResource(new Directory(childCtx.createChildContext(), "file:///" + config.at("workingDir").asString() + "/src/html/healthcheck.htm")); } }; //server.getDefaultHost().attach("/healthcheck.htm", application); Application htmlApplication = new Application(server.getContext().createChildContext()) { @Override public Restlet createInboundRoot() { Directory dir = new Directory(getContext().createChildContext(), "file:///" + config.at("workingDir").asString() + "/src/html/"); dir.setIndexName("startup.html"); return configureDirResource(dir); } }; final Router router = new Router(server.getContext().createChildContext()); router.setDefaultMatchingMode(Template.MODE_STARTS_WITH); router.attach("/healthcheck.htm", healthcheckApplication).setMatchingMode(Template.MODE_EQUALS); router.attach("/", htmlApplication).setMatchingMode(Template.MODE_STARTS_WITH); router.attach("/go", htmlApplication).setMatchingMode(Template.MODE_STARTS_WITH); router.attach("/html", htmlApplication).setMatchingMode(Template.MODE_STARTS_WITH); router.attach("/javascript", javaScriptApplication).setMatchingMode(Template.MODE_STARTS_WITH); router.attach("/resources", resourcesApplication).setMatchingMode(Template.MODE_STARTS_WITH); router.attach("/upload", uploadApplication).setMatchingMode(Template.MODE_STARTS_WITH); router.attach("/uploaded", uploadedApplication).setMatchingMode(Template.MODE_STARTS_WITH); if (config.is("startTest", true)) { Application testApplication = new Application(server.getContext().createChildContext()) { @Override public Restlet createInboundRoot() { Directory dir = new Directory(getContext().createChildContext(), "file:///" + config.at("workingDir").asString() + "/src/test/"); dir.setIndexName("testsuite.html"); return configureDirResource(dir); } }; router.attach("/test", testApplication).setMatchingMode(Template.MODE_STARTS_WITH); } router.setRoutingMode(Router.MODE_BEST_MATCH); final Restlet topRestlet = new Restlet() { @Override public void handle(Request request, Response response) { if (request.getResourceRef().getPath().equals("/") || request.getResourceRef().getPath().startsWith("/images") || request.getResourceRef().getPath().equals("/healthcheck.htm") || request.getResourceRef().getPath().startsWith("/go") || request.getResourceRef().getPath().startsWith("/favicon.ico") || request.getResourceRef().getPath().startsWith("/html") || request.getResourceRef().getPath().startsWith("/test") || request.getResourceRef().getPath().startsWith("/javascript") || request.getResourceRef().getPath().startsWith("/resources") || request.getResourceRef().getPath().startsWith("/upload")) { //System.out.println("FILE REQUEST : " + request);// + request.getResourceRef().getPath()); router.handle(request, response); } else { //System.out.println("REST SERVICE : " + request.getResourceRef().getPath()); RequestScopeFilter.set("clientInfo", request.getClientInfo()); restApplication.handle(request, response); } } }; StartupUtils.disableCertificateValidation(); attachToServer(topRestlet); RelationalOWLMapper.getInstance(); if (config.has("cachedReasonerPopulate") && config.is("cachedReasonerPopulate", true)) { OntoAdmin oa = new OntoAdmin(); oa.populateIndividualSerialEntityCache(); oa.cachedReasonerQ1Populate(); } try { if (config.has("isConfigMode") && config.at("isConfigMode").asBoolean()){ System.out.println("Create Configuration Server cache..."); ServiceCaseManager.getInstance(); System.out.println("Done creating Configuration Server cache."); } server.start(); if (redirectServer != null) { redirectServer.start(); } HTTP_INIT.initialize(); } catch (Exception e) { System.err.println("ERROR ON STARTUP - EXITING"); throw new RuntimeException(e); } } static void commandLineStart(Component server) { try { BufferedReader stdReader = new BufferedReader(new InputStreamReader(System.in)); while (true) { System.out.print("\n>"); String line = stdReader.readLine(); System.out.println("Command: " + line); if ("stop".equals(line.trim())) { server.stop(); } else if ("start".equals(line.trim())) server.start(); else if ("exit".equals(line.trim())) { server.stop(); } } } catch (Exception ex) { ex.printStackTrace(System.err); System.exit(-1); } } static void set311RuntimeDelegate() { final RuntimeDelegate currentDelegate = RuntimeDelegate.getInstance(); RuntimeDelegate.setInstance(new RuntimeDelegate() { @Override public <T> T createEndpoint( javax.ws.rs.core.Application application, Class<T> endpointType) throws IllegalArgumentException, UnsupportedOperationException { System.out.println("Endpoint: " + endpointType.getName()); return currentDelegate.createEndpoint(application, endpointType); } @Override public <T> HeaderDelegate<T> createHeaderDelegate(Class<T> type) { System.out.println("Header: " + type.getName()); return currentDelegate.createHeaderDelegate(type); } @Override public ResponseBuilder createResponseBuilder() { System.out.println("create response builder"); return currentDelegate.createResponseBuilder(); } @Override public UriBuilder createUriBuilder() { System.out.println("create URI builder"); return currentDelegate.createUriBuilder(); } @Override public VariantListBuilder createVariantListBuilder() { System.out.println("create variant list builder"); return currentDelegate.createVariantListBuilder(); } }); } private static void configureServer() { server = new Component(); if (config.is("ssl", true)) { configureSSLServer(server); redirectServer = new Component(); Server http = redirectServer.getServers().add(Protocol.HTTP, config.at("port").asInteger()); http.getContext().getParameters().add("ioMaxIdleTimeMs", "" + 24*60*60*1000); } else { Server http = server.getServers().add(Protocol.HTTP, config.at("port").asInteger()); // this is Jetty configuration parameter to allow for very long // server processing (i.e. in debugging mode) with no timeout, otherwise // the requests get retried http.getContext().getParameters().add("ioMaxIdleTimeMs", "" + 24*60*60*1000); } // Configure client protocols server.getClients().add(Protocol.HTTP); server.getClients().add(Protocol.FILE); server.getLogger().setLevel(Level.WARNING); //WARNING } private static void attachToServer(Restlet topRestlet) { if (config.is("ssl", true)) { configureRedirectToSSL(redirectServer, topRestlet); } server.getDefaultHost().attach(topRestlet); } /** * Configures the SSL server. * */ private static void configureSSLServer(Component sslServer) { final int sslport = config.at("ssl-port").asInteger(); final Server httpsServer = sslServer.getServers().add(Protocol.HTTPS, sslport); httpsServer.getContext().getParameters().add("hostname", selfUrl().getHost()); httpsServer.getContext().getParameters().add("keystorePath", config.at("workingDir").asString() + "/conf/" + config.at("keystore").asString()); httpsServer.getContext().getParameters().add("keystorePassword", config.at("storePass").asString()); httpsServer.getContext().getParameters().add("keyPassword", config.at("keyPass").asString()); httpsServer.getContext().getParameters().add("ioMaxIdleTimeMs", "" + 24*60*60*1000); configureSSLCipherSuites(httpsServer); //hilpold server.getServers().add(Protocol.HTTP, config.at("port").asInteger()); } /** * Configures HTTP > HTTPS redirector. * * @param topRestlet */ private static void configureRedirectToSSL(Component httpServer, final Restlet topRestlet) { URL url = selfUrl(); final Redirector redirector = new Redirector(httpServer.getContext().createChildContext(), url.toString(), Redirector.MODE_CLIENT_FOUND); httpServer.getDefaultHost().attach(new Restlet() { @Override public void handle(Request request, Response response) { if (request.getProtocol().equals(Protocol.HTTP)) { redirector.handle(request, response); } else topRestlet.handle(request, response); } }); } /** * Enables strong and disables weak cipher suites for a restlet server. * * @param s a restlet server. */ private static void configureSSLCipherSuites(Server s) { //printSSLCipherSuites(); Context ctx = s.getContext(); ctx.getParameters().add("disabledCipherSuites", StartupUtils.getWeakSSLCipherSuitesParamString()); ctx.getParameters().add("enabledCipherSuites", StartupUtils.getStrongSSLCipherSuitesParamString()); } }