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

com.techempower.gemini.path.PathDispatcher Maven / Gradle / Ivy

There is a newer version: 3.3.14
Show newest version
/*******************************************************************************
 * Copyright (c) 2018, TechEmpower, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name TechEmpower, Inc. nor the names of its
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL TECHEMPOWER, INC. BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/
package com.techempower.gemini.path;

import java.util.*;

import com.techempower.gemini.*;
import com.techempower.gemini.exceptionhandler.*;
import com.techempower.gemini.prehandler.*;
import com.techempower.log.*;

/**
 * A Dispatcher implementation that directs requests based on the first path
 * segment ("directory") in the URI and provides the remaining path segments 
 * as parameters to the handlers.
 *   

* Example usage: *

 *   final PathDispatcher.Configuration<TempestContext> configuration = 
 *       new PathDispatcher.Configuration<>();
 *
 *   // Add handlers.
 *   configuration.add("git", new GitHandler(this))
 *                .add("task", new TaskHandler(this))
 *                .add("test", new PerformanceTest(this));
 *
 *   // Add listeners.
 *   configuration.add(getMonitor().getListener());
 *       
 *   // Add exception handlers.
 *   configuration.add(new BasicExceptionHandler(this))
 *                .add(new NotificationExceptionHandler(this));
 * 
 *   // Set a default handler.
 *   configuration.setDefault(new HomeHandler(this));
 *   
 *   // Construct and return the PathDispatcher.
 *   return new PathDispatcher<>(this, configuration);
 * 
*/ public class PathDispatcher implements Dispatcher { // // Member variables. // private final A application; private final ComponentLog log; private final Map> handlers; private final PathHandler defaultHandler; private final PathHandler notImplementedHandler; private final PathHandler rootHandler; private final ExceptionHandler[] exceptionHandlers; private final Prehandler[] prehandlers; private final DispatchListener[] listeners; // // Member methods. // /** * Constructor. * * @param application The application reference. * @param configuration A Configuration providing references to the desired * PathHandlers, ExceptionHandlers, etc. */ public PathDispatcher(A application, Configuration configuration) { this.application = application; this.log = application.getLog("disp"); this.handlers = new HashMap<>(configuration.handlers); this.exceptionHandlers = configuration.exceptionHandlers.toArray( new ExceptionHandler[configuration.exceptionHandlers.size()]); this.prehandlers = configuration.prehandlers.toArray( new Prehandler[configuration.prehandlers.size()]); this.listeners = configuration.listeners.toArray( new DispatchListener[configuration.listeners.size()]); if (configuration.defaultHandler != null) { defaultHandler = configuration.defaultHandler; } else { defaultHandler = new FourZeroFourHandler<>(); } this.notImplementedHandler = new NotImplementedHandler<>(); rootHandler = configuration.rootHandler; if (exceptionHandlers.length == 0) { throw new IllegalArgumentException("PathDispatcher must be configured with at least one ExceptionHandler."); } } /** * Gets a Handler given a root path segment as a key. E.g., * dispatcher.get("task") * * @param rootPathSegment The root-level path segment; e.g., "task" */ public PathHandler get(String rootPathSegment) { return handlers.get(rootPathSegment); } /** * Gets a set of the root path segments that the Dispatcher handles. */ public Set getRootPathSegments() { return handlers.keySet(); } /** * Gets the Application reference. */ protected A getApplication() { return application; } /** * Gets the ComponentLog reference. */ protected ComponentLog getLog() { return log; } /** * Send the request to all prehandlers. */ protected boolean prehandle(C context) { final Prehandler[] thePrehandlers = prehandlers; for (Prehandler p : thePrehandlers) { if (p.prehandle(context)) { return true; } } return false; } @Override public boolean dispatch(Context plainContext) { boolean success = false; // Surround all logic with a try-catch so that we can send the request to // our ExceptionHandlers if anything goes wrong. try { // Cast the provided Context to a C. @SuppressWarnings("unchecked") final C context = (C)plainContext; // Convert the request URI into path segments. final PathSegments segments = new PathSegments(context.getRequestUri()); // Make these references available thread-locally. RequestReferences.set(context, segments); // Notify listeners. notifyListenersDispatchStarting(plainContext, segments.getUriFromRoot()); // Find the associated Handler. PathHandler handler = null; if (segments.getCount() > 0) { handler = get(segments.get(0)); // If we've found a Handler to use, we have consumed the first path // segment. if (handler != null) { segments.increaseOffset(); } } // Use the root handler when the segment count is 0. else if (rootHandler != null) { handler = rootHandler; } // Use the default handler if nothing else was provided. if (handler == null) { // The HTTP method for the request is not listed in the HTTPMethod enum, // so we are unable to handle the request and simply return a 501. if (plainContext.getRequestMethod() == null) { handler = notImplementedHandler; } else { handler = defaultHandler; } } // Send the request to all Prehandlers. success = prehandle(context); // Proceed to normal Handlers if the Prehandlers did not fully handle // the request. if (!success) { try { // Do preliminary processing. The prehandle method may fully handle // the request, and if it does so, it will return true. success = handler.prehandle(segments, context); // Proceed to the handle method if the prehandle method did not fully // handle the request on its own. if (!success) { // Dispatch. success = handler.handle(segments, context); } } finally { // Do wrap-up processing even if the request was not handled correctly. handler.posthandle(segments, context); } } // If the handler we selected did not successfully handle the request // and it's NOT the default handler, let's ask the default handler to // handle the request. if ( (!success) && (handler != defaultHandler) ) { try { // Result of prehandler is ignored because the default handler is // expected to handle any request. For the default handler, we'll // reset the PathSegments offset to 0. success = defaultHandler.prehandle(segments.offset(0), context); if (!success) { success = defaultHandler.handle(segments, context); } } finally { defaultHandler.posthandle(segments, context); } } } catch (Throwable exc) { dispatchException(plainContext, exc, null); } finally { RequestReferences.remove(); } return success; } @Override public void dispatchComplete(Context context) { notifyListenersDispatchComplete(context); } @Override public void dispatchException(Context context, Throwable exception, String description) { if (exception == null) { log.log("dispatchException called with a null reference.", LogLevel.ALERT); return; } try { final ExceptionHandler[] theHandlers = exceptionHandlers; for (ExceptionHandler handler : theHandlers) { if (description != null) { handler.handleException(context, exception, description); } else { handler.handleException(context, exception); } } } catch (Exception exc) { // In the especially worrisome case that we've encountered an exception // while attempting to handle another exception, we'll give up on the // request at this point and just write the exception to the log. log.log("Exception encountered while processing earlier " + exception, LogLevel.ALERT, exc); } } /** * Notify the listeners that a dispatch is starting. */ protected void notifyListenersDispatchStarting(Context context, String command) { final DispatchListener[] theListeners = listeners; for (DispatchListener listener : theListeners) { listener.dispatchStarting(this, context, command); } } /** * Notify the listeners that a dispatch is complete. */ protected void notifyListenersDispatchComplete(Context context) { final DispatchListener[] theListeners = listeners; for (DispatchListener listener : theListeners) { listener.dispatchComplete(this, context); } } /** * Notify the listeners that a rendering has started. */ @Override public void renderStarting(Context context, String jspName) { final DispatchListener[] theListeners = listeners; for (DispatchListener listener : theListeners) { listener.renderStarting(this, jspName); } } /** * Notify the listeners that a JSP has been completed. */ @Override public void renderComplete(Context context) { final DispatchListener[] theListeners = listeners; for (DispatchListener listener : theListeners) { listener.renderComplete(this, context); } } /** * A class used for the initial configuration of a PathDispatcher. */ public static class Configuration { private PathHandler defaultHandler; private PathHandler rootHandler; private final Map> handlers; private final List exceptionHandlers; private final List prehandlers; private final List listeners; /** * Constructor. */ public Configuration() { handlers = new HashMap<>(); exceptionHandlers = new ArrayList<>(); prehandlers = new ArrayList<>(); listeners = new ArrayList<>(); } /** * Add a Handler with a specified root path segment to this Configuration. */ public Configuration add(String rootPathSegment, PathHandler handler) { if (handler instanceof UriAware) { ((UriAware)handler).setBaseUri("/" + rootPathSegment); } handlers.put(rootPathSegment, handler); return this; } /** * Adds an ExceptionHandler. */ public Configuration add(ExceptionHandler exceptionHandler) { exceptionHandlers.add(exceptionHandler); return this; } /** * Adds a Prehandler. */ public Configuration add(Prehandler prehandler) { prehandlers.add(prehandler); return this; } /** * Adds a DispatchListener. */ public Configuration add(DispatchListener listener) { listeners.add(listener); return this; } /** * Set the default handler. */ public Configuration setDefault(PathHandler handler) { defaultHandler = handler; return this; } /** * Set the root handler. */ public Configuration setRootHandler(PathHandler handler) { rootHandler = handler; return this; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy