package com.perforce.hws; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.perforce.hws.core.p4base.AuthError; import com.perforce.hws.core.p4base.P4BaseException; import com.perforce.hws.core.p4base.ResultMap; import com.perforce.hws.filters.CustomRequestFilter; import com.perforce.hws.filters.EnableCORSFilter; import com.perforce.hws.filters.HttpRequestFactoryFilter; import com.perforce.hws.filters.JsonContentTypeFilter; import com.perforce.hws.filters.PlatformVersionFilter; import com.perforce.hws.filters.SettingsFilter; import com.perforce.hws.filters.VersionFilter; import com.perforce.hws.route.VersionRoute; import com.perforce.hws.spark.api.HwsApi; import com.perforce.hws.utils.ApiPathUtils; import com.perforce.hws.utils.HWSSettings; import com.perforce.hws.utils.StatusChecks; import com.perforce.hws.utils.UnknownServerException; import com.perforce.hws.utils.UrlBuilderMethods; import com.perforce.p4java.exception.MessageSeverityCode; import org.apache.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import spark.Service; import spark.servlet.SparkApplication; import javax.script.ScriptException; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.UnsupportedCharsetException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static com.perforce.hws.utils.ApiPath.Hws; import static com.perforce.hws.utils.ApiPath.Version; /** * Configures the Spark/Jetty web server and connects up all our routers and * middleware.<p/> */ public class HelixWebServices implements UrlBuilderMethods, StatusChecks, SparkApplication, ApiPathUtils { private static final Logger LOGGER = LoggerFactory.getLogger(HelixWebServices.class); /** * The hws settings. */ private HWSSettings hwsSettings = new HWSSettings(); /** * The gson instance for converting json to java and * vice-versa. */ private static Gson gson; /** * The http request factory filter. */ private HttpRequestFactoryFilter httpRequestFactoryFilter = new HttpRequestFactoryFilter(); /** * The hws plugin Manager */ private HwsPluginManager pluginManager; /** * By default, create a single HelixWebServices instance. * * @param args the arguments * @throws Exception the exception */ public static void main(final String[] args) throws Exception { try { HelixWebServices helixWebServices = new HelixWebServices(); helixWebServices.init(); LOGGER.info("Helix Web Services started"); } catch (Exception e) { LOGGER.error("Shutting down system due to exception", e); throw e; } } /** * Generally just reads configuration from external * sources and preps the instance as well * as we know. * <p> * If you haven't set up environent or system properties, * don't worry, just edit the HWSSettings * via the getSettings() method and associate it to * whatever configuration system you use. */ private HelixWebServices() { gson = new GsonBuilder(). setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").create(); LOGGER.info("Initializing HelixWebServices..."); initializeSettings(); LOGGER.info("HelixWebServices initialized."); LOGGER.info("Initializing Plugins..."); initializePlugins(); LOGGER.info("Plugins initialized."); } /** * Load settings and apply overrides from both the environment * and the configuration file. */ private void initializeSettings() { try { hwsSettings.overrideFromEnvironment(); String path = System.getenv("HWS_CONFIG_PATH"); if (path != null) { hwsSettings.setSystemConfigPath(path); } path = System.getProperty("HWS_CONFIG_PATH"); if (path != null) { hwsSettings.setSystemConfigPath(path); } hwsSettings.overrideFromSystemConfig(); // Double check that the settings has at least one p4d // server configured, or throw a warning if (hwsSettings.getP4dConfigIds().isEmpty()) { LOGGER.warn( "No p4d configurations found, you likely have misconfigured a " + "P4DCONFIGDIR, or, you do not have any p4d configuration files"); } } catch (IOException io) { throw new UncheckedIOException(io); } } /** * Load plugins into HelixWebServices. */ private void initializePlugins() { pluginManager = HwsPluginManager.getInstance(); try { pluginManager.loadPlugins(); } catch (IOException io) { throw new UncheckedIOException(io); } } /** * This used to initialize the application while running inside of another web * server. */ @Override public void init() { try { Service service = Service.ignite(); initializeServiceFromSettings(service); //initializeStaticFileLocation(service); addBeforeFilters(service); addRoutes("", service); addAfterFilters(service); addExceptionHandlers(service); // setUser(service); // Log any status errors on startup. isStatusOk(hwsSettings); } catch (IOException io) { throw new UncheckedIOException(io); } catch (ScriptException se) { throw new IllegalStateException(se); } } /** * Some settings, like "is this HTTPS" or what port to listen to, * can be initialized from the HWS settings. * <p> * If you don't want to use these settings, then don't call this method. * * @param service the service */ private void initializeServiceFromSettings(final Service service) { if (hwsSettings.isEnableHttps()) { LOGGER.info("setting secure server: keystore {}, truststore {}", hwsSettings.getKeystoreFile(), hwsSettings.getTruststoreFile()); service.secure(hwsSettings.getKeystoreFile(), hwsSettings.getKeystorePassword(), hwsSettings.getTruststoreFile(), hwsSettings.getTruststorePassword()); } service.port(hwsSettings.getHwsPort()); } /** * Configure the service's static file location, * which basically points out where we've stored online documentation. * * @param service the service */ private void initializeStaticFileLocation(final Service service) { service.staticFileLocation("/publicsite"); } /** * Adds the before filters. * * @param service the service * @throws IOException Signals that an I/O exception has occurred. * @throws ScriptException the script exception */ private void addBeforeFilters(final Service service) throws IOException, ScriptException { service.before(new VersionFilter()); service.before(new SettingsFilter(hwsSettings)); service.before(new PlatformVersionFilter(hwsSettings)); if (hwsSettings.getRequestFilterPath() != null) { service.before(new CustomRequestFilter(hwsSettings)); } service.before(httpRequestFactoryFilter); } /** * Adds routes for the configured contexts. Currently known contexts * are: * <p> * - Basic api routes * - Internal HWS routes * - Plugin Server routes * * @param service the service */ private void addRoutes(final String basePath, final Service service) { addDefaultRoutes(service); addHwsRoutes(basePath, service); pluginManager.addRoutes(basePath, service); } /** * Adds the default routes. * * @param service the service */ private void addDefaultRoutes(final Service service) { //-------------------------------------------------------------------- // Methods that really don't belong to a platform version //-------------------------------------------------------------------- service.get("/", (request, response) -> { response.redirect(apiPathTo(Hws, Version)); return null; }); // This should remain a simple page that people who are lost can find // their way to something reasonable. service.get(apiPathTo(Hws, Version), new VersionRoute(hwsSettings)); // service.get(apiPathTo(P4d, Version), new PerforceVersionRoute(hwsSettings)); } /** * Adds the hws routes. * * @param service the service */ private void addHwsRoutes(final String basePath, final Service service) { //-------------------------------------------------------------------- // Generic methods not tied to a specific server //-------------------------------------------------------------------- // These methods should all precede with a platform version. new HwsApi(basePath, service); } /** * Adds filters applied to the response after the main processing * is complete. * <p> * Current filters are: * <p> * - CORS filters, for resource sharing * - Content type of the response body * * @param service the service */ private void addAfterFilters(final Service service) { service.after(new EnableCORSFilter(hwsSettings)); service.after(new JsonContentTypeFilter()); } /** * Adds the exception handlers. * <p> * Conditions handled are: * <p> * - Authentication error * - Unknown perforce server * - Unsupported encoding * - Other exception, reported as server error * * @param service the service */ public static void addExceptionHandlers(final Service service) { // Any exception handler you add here should call cleanUpOrLog. service.exception(AuthError.class, (e, request, response) -> { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Authentication Failed", e); } response.status(HttpStatus.SC_FORBIDDEN); response.body(""); }); service.exception(P4BaseException.class, (p4e, request, response) -> { LOGGER.error("Perforce exception", p4e); ResultMap p4Results = ((P4BaseException) p4e).getResultMap(); List<Map<String, String>> responses = p4Results.getErrors().stream().map(err -> { Map<String, String> m = new HashMap<>(); m.put("MessageCode", err.getUniqueCode()); m.put("MessageText", p4e.getLocalizedMessage()); switch (err.getSeverity()) { default: case MessageSeverityCode.E_EMPTY: m.put("MessageSeverity", "EMPTY"); break; case MessageSeverityCode.E_INFO: m.put("MessageSeverity", "INFO"); break; case MessageSeverityCode.E_WARN: m.put("MessageSeverity", "WARN"); break; case MessageSeverityCode.E_FAILED: m.put("MessageSeverity", "ERROR"); break; case MessageSeverityCode.E_FATAL: m.put("MessageSeverity", "FATAL"); break; } return m; }).collect(Collectors.toList()); // Our after filter may not get hit, so force json response.header("Content-Type", "application/json"); // Set return status for the perforce command, improve maybe? switch (p4Results.getMaxSeverity()) { case MessageSeverityCode.E_FAILED: response.status(HttpStatus.SC_BAD_REQUEST); break; default: response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR); break; } // Set the body if (responses.size() > 1) { response.body(gson.toJson(responses)); } else { response.body(gson.toJson(responses.get(0))); } }); service.exception(UnknownServerException.class, (use, request, response) -> { String serverId = ((UnknownServerException) use).getServerId(); LOGGER.error("Unknown server " + serverId, use); // Our after filter may not get hit. response.header("Content-Type", "application/json"); // Set up the response for this error response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR); Map<String, String> map = new HashMap<>(); map.put("MessageCode", "0"); map.put("MessageSeverity", "ERROR"); map.put("MessageText", "Unknown server ID [" + serverId + "]"); response.body(gson.toJson(map)); }); service.exception(UnsupportedCharsetException.class, (uce, request, response) -> { // A charset exception shouldn't be caused by any kind // of user input. Map<String, String> map = new HashMap<>(); map.put("MessageCode", "0"); map.put("MessageSeverity", "FATAL"); map.put("MessageText", "Server has a misconfigured charset"); // Our after filter may not get hit. response.header("Content-Type", "application/json"); // Set up the response for this error response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR); response.body(gson.toJson(map)); }); service.exception(Exception.class, (e, request, response) -> { LOGGER.error("Unhandled exception", e); // Our after filter may not get hit. response.header("Content-Type", "application/json"); response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR); response.body(""); }); } /** * Gets the json to/from java object converter. * * @return the gson */ public Gson getGson() { return gson; } /* (non-Javadoc) * @see com.perforce.hws.server.ApiPathUtils#getSettings() */ @Override public HWSSettings getSettings() { return hwsSettings; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#6 | 20138 | Paul Allen | Refactor module :api to :core | ||
#5 | 20095 | Paul Allen |
Functional test API with Perforce Server and Spark Server. - Basic User/Login test. |
||
#4 | 20043 | Paul Allen | Refactor /p4d to /api/p4 and smart mustache code to detect authMethod and POST. | ||
#3 | 19993 | Paul Allen | Basic init for HWS | ||
#2 | 19987 | Paul Allen | Added remaining classes and updated initilisation. | ||
#1 | 19886 | Paul Allen | HWS basic plugin design |