| Engine.java |
/*
* $Id: Engine.java,v 1.134 2013/01/18 14:26:45 agoubard Exp $
*
* See the COPYRIGHT file for redistribution and use restrictions.
*/
package org.xins.server;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.Utils;
import org.xins.common.collections.InvalidPropertyValueException;
import org.xins.common.collections.MissingRequiredPropertyException;
import org.xins.common.io.IOReader;
import org.xins.common.manageable.InitializationException;
import org.xins.common.spec.APISpec;
import org.xins.common.spec.EntityNotFoundException;
import org.xins.common.spec.InvalidSpecificationException;
import org.xins.common.spec.ParameterSpec;
import org.xins.common.text.TextUtils;
/**
* XINS server engine. The engine is a delegate of the {@link APIServlet} that
* is responsible for initialization and request handling.
*
* @version $Revision: 1.134 $ $Date: 2013/01/18 14:26:45 $
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
* @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
* @author <a href="mailto:mees.witteman@orange-ftgroup.com">Mees Witteman</a>
*/
final class Engine {
/**
* Property used to start JMX.
*/
private static final String JMX_PROPERTY = "org.xins.server.jmx";
/**
* The state machine for this engine. Never <code>null</code>.
*/
private final EngineStateMachine _stateMachine = new EngineStateMachine();
/**
* The starter of this engine. Never <code>null</code>.
*/
private final EngineStarter _starter;
/**
* The stored servlet configuration object. Never <code>null</code>.
*/
private final ServletConfig _servletConfig;
/**
* The API that this engine forwards requests to. Never <code>null</code>.
*/
private final API _api;
/**
* The name of the API. Never <code>null</code>.
*/
private String _apiName;
/**
* The manager for the runtime configuration file. Never <code>null</code>.
*/
private final ConfigManager _configManager;
/**
* The manager for the calling conventions. This field can be and initially
* is <code>null</code>. This field is initialized by {@link #bootstrapAPI()}.
*/
private CallingConventionManager _conventionManager;
/**
* The manager for the interceptors. This field can be and initially
* is <code>null</code>. This field is initialized by {@link #bootstrapAPI()}.
*/
private InterceptorManager _interceptorManager;
/**
* The SMD (Simple Method Description) of this API. This value is <code>null</code>
* until the meta function <i>_SMD</i> is called.
*/
private String _smd;
/**
* Constructs a new <code>Engine</code> object.
*
* @param config
* the {@link ServletConfig} object which contains build-time properties
* for this servlet, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>config == null</code>.
*
* @throws ServletException
* if the engine could not be constructed.
*/
Engine(ServletConfig config)
throws IllegalArgumentException, ServletException {
// Check preconditions
MandatoryArgumentChecker.check("config", config);
// Construct the EngineStarter
_starter = new EngineStarter(config);
// Determine the name of the API
_apiName = _starter.determineAPIName();
// Construct a configuration manager and store the servlet configuration
_configManager = new ConfigManager(this, config);
_servletConfig = config;
// Proceed to first actual stage
_stateMachine.setState(EngineState.BOOTSTRAPPING_FRAMEWORK);
// Read configuration details
_configManager.determineConfigFile();
_configManager.readRuntimeProperties();
if (!_configManager.propertiesRead()) {
_stateMachine.setState(EngineState.FRAMEWORK_BOOTSTRAP_FAILED);
throw new ServletException();
}
// Log boot messages
_starter.logBootMessages();
// Construct and bootstrap the API
_stateMachine.setState(EngineState.CONSTRUCTING_API);
try {
_api = _starter.constructAPI();
} catch (ServletException se) {
_stateMachine.setState(EngineState.API_CONSTRUCTION_FAILED);
throw se;
}
boolean bootstrapped = bootstrapAPI();
if (!bootstrapped) {
throw new ServletException();
}
// Done bootstrapping the framework
Log.log_3225(Library.getVersion());
// Initialize the configuration manager
_configManager.init();
// Check post-conditions
if (_api == null) {
throw Utils.logProgrammingError("_api == null");
} else if (_apiName == null) {
throw Utils.logProgrammingError("_apiName == null");
}
// Engine started
long startTime = System.currentTimeMillis() - _starter.getStartedTime();
Log.log_3446(_apiName, (int) startTime);
}
/**
* Bootstraps the API. The following steps will be performed:
*
* <ul>
* <li>load the Logdoc, if available;
* <li>bootstrap the API;
* <li>construct and bootstrap the calling conventions;
* <li>link the engine to the API;
* <li>construct and bootstrap a context ID generator;
* <li>perform JMX initialization.
* </ul>
*
* @return
* <code>true</code> if the bootstrapping of the API succeeded,
* <code>false</code> if it failed.
*/
private boolean bootstrapAPI() {
// Proceed to next stage
_stateMachine.setState(EngineState.BOOTSTRAPPING_API);
// Make the API have a link to this Engine
_api.setEngine(this);
Map<String, String> bootProps;
try {
// Load the Logdoc if available
_starter.loadLogdoc();
// Actually bootstrap the API
bootProps = _starter.bootstrap(_api);
// Handle any failures
} catch (ServletException se) {
_stateMachine.setState(EngineState.API_BOOTSTRAP_FAILED);
return false;
}
// Create the calling convention manager
_conventionManager = new CallingConventionManager(_api);
// Bootstrap the calling convention manager
try {
_conventionManager.bootstrap(bootProps);
// Missing required property
} catch (MissingRequiredPropertyException exception) {
Log.log_3209(exception.getPropertyName(), exception.getDetail());
return false;
// Invalid property value
} catch (InvalidPropertyValueException exception) {
Log.log_3210(exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
return false;
// Other bootstrap error
} catch (Throwable exception) {
Log.log_3211(exception);
return false;
}
// Create the interceptor manager
_interceptorManager = new InterceptorManager();
_interceptorManager.setApi(_api);
try {
_interceptorManager.bootstrap(bootProps);
} catch (Exception ex) {
return false;
}
// Perform JMX initialization if asked
String enableJmx = _configManager.getRuntimeProperties().get(JMX_PROPERTY);
if ("true".equals(enableJmx)) {
_starter.registerMBean(_api);
} else if (enableJmx != null && !enableJmx.equals("false")) {
Log.log_3251(enableJmx);
return false;
}
// Succeeded
return true;
}
/**
* Initializes the API using the current runtime settings. This method
* should be called whenever the runtime properties changed.
*
* @return
* <code>true</code> if the initialization succeeded, otherwise
* <code>false</code>.
*/
boolean initAPI() {
_stateMachine.setState(EngineState.INITIALIZING_API);
// Determine the locale for logging
boolean localeInitialized = _configManager.determineLogLocale();
if (!localeInitialized) {
_stateMachine.setState(EngineState.API_INITIALIZATION_FAILED);
return false;
}
// Check that the runtime properties were correct
if (!_configManager.propertiesRead()) {
_stateMachine.setState(EngineState.API_INITIALIZATION_FAILED);
return false;
}
// Determine the current runtime properties
Map<String, String> properties = _configManager.getRuntimeProperties();
// Determine at what level should the stack traces be displayed
String stackTraceAtMessageLevel = properties.get(ConfigManager.LOG_STACK_TRACE_AT_MESSAGE_LEVEL);
if ("true".equals(stackTraceAtMessageLevel)) {
org.znerd.logdoc.Library.setStackTraceAtMessageLevel(true);
} else if ("false".equals(stackTraceAtMessageLevel)) {
org.znerd.logdoc.Library.setStackTraceAtMessageLevel(false);
} else if (stackTraceAtMessageLevel != null) {
// XXX: Report this error in some way
_stateMachine.setState(EngineState.API_INITIALIZATION_FAILED);
return false;
}
boolean succeeded = false;
try {
// Initialize the API
_api.init(properties);
// Initialize the default calling convention for this API
_conventionManager.init(properties);
// Initialize the interceptors
_interceptorManager.init(properties);
succeeded = true;
// Missing required property
} catch (MissingRequiredPropertyException exception) {
Log.log_3411(exception.getPropertyName(), exception.getDetail());
// Invalid property value
} catch (InvalidPropertyValueException exception) {
Log.log_3412(exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
// Initialization of API failed for some other reason
} catch (InitializationException exception) {
Log.log_3413(exception);
// Other error
} catch (Throwable exception) {
Log.log_3414(exception);
// Always leave the object in a well-known state
} finally {
if (succeeded) {
_stateMachine.setState(EngineState.READY);
} else {
_stateMachine.setState(EngineState.API_INITIALIZATION_FAILED);
}
}
return succeeded;
}
/**
* Handles a request to this servlet (wrapper method). If any of the
* arguments is <code>null</code>, then the behaviour of this method is
* undefined.
*
* @param request
* the servlet request, should not be <code>null</code>.
*
* @param response
* the servlet response, should not be <code>null</code>.
*
* @throws IOException
* in case of an I/O error.
*/
void service(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// Set the correct character encoding for the request
if (request.getCharacterEncoding() == null) {
request.setCharacterEncoding("UTF-8");
}
// Handle the request
try {
doService(request, response);
// Catch and log all exceptions
} catch (Throwable exception) {
Log.log_3003(exception);
}
}
/**
* Handles a request to this servlet (implementation method). If any of the
* arguments is <code>null</code>, then the behaviour of this method is
* undefined.
*
* <p>This method is called from the corresponding wrapper method,
* {@link #service(HttpServletRequest,HttpServletResponse)}.
*
* @param request
* the servlet request, should not be <code>null</code>.
*
* @param response
* the servlet response, should not be <code>null</code>.
*
* @throws IOException
* in case of an I/O error.
*/
private void doService(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Determine current time
long start = System.currentTimeMillis();
String method = request.getMethod();
String path = request.getRequestURI();
_interceptorManager.beginRequest(request);
// If the current state is not usable, then return an error immediately
EngineState state = _stateMachine.getState();
if (! state.allowsInvocations()) {
handleUnusableState(state, request, response);
// Support the HTTP method "OPTIONS"
} else if ("OPTIONS".equals(method)) {
if ("*".equals(path)) {
handleOptions(null, request, response);
} else {
delegateToCC(start, request, response);
}
// The request should be handled by a calling convention
} else {
delegateToCC(start, request, response);
}
_interceptorManager.endRequest(request, response);
}
/**
* Handles an unprocessable request (low-level function). The response is
* filled for the request.
*
* @param request
* the HTTP request, cannot be <code>null</code>.
*
* @param response
* the HTTP request, cannot be <code>null</code>.
*
* @param statusCode
* the HTTP status code to return.
*
* @param reason
* explanation, can be <code>null</code>.
*
* @param exception
* the exception thrown, can be <code>null</code>.
*
* @throws IOException
* in case of an I/O error.
*/
private void handleUnprocessableRequest(HttpServletRequest request,
HttpServletResponse response,
int statusCode,
String reason,
Throwable exception)
throws IOException {
// Log
Log.log_3523(exception,
request.getRemoteAddr(),
request.getMethod(),
request.getRequestURI(),
request.getQueryString(),
statusCode,
reason);
}
/**
* Handles a request that comes in while function invocations are currently
* not allowed.
*
* @param state
* the current state, cannot be <code>null</code>.
*
* @param request
* the HTTP request, cannot be <code>null</code>.
*
* @param response
* the HTTP response to fill, cannot be <code>null</code>.
*
* @throws IOException
* in case of an I/O error.
*/
private void handleUnusableState(EngineState state,
HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Log and respond
int statusCode = state.isError()
? HttpServletResponse.SC_INTERNAL_SERVER_ERROR
: HttpServletResponse.SC_SERVICE_UNAVAILABLE;
String reason = "XINS/Java Server Framework engine state \""
+ state
+ "\" does not allow incoming requests.";
handleUnprocessableRequest(request, response, statusCode, reason, null);
response.sendError(statusCode);
}
/**
* Delegates the specified incoming request to the appropriate
* <code>CallingConvention</code>. The request may either be a function
* invocation or an <em>OPTIONS</em> request.
*
* @param start
* timestamp indicating when the call was received by the framework, in
* milliseconds since the
* <a href="http://en.wikipedia.org/wiki/Unix_Epoch">UNIX Epoch</a>.
*
* @param request
* the servlet request, should not be <code>null</code>.
*
* @param response
* the servlet response, should not be <code>null</code>.
*
* @throws IOException
* in case of an I/O error.
*/
private void delegateToCC(long start,
HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Determine the calling convention to use
CallingConvention cc = determineCC(request, response);
// If it is null, then there was an error. This error will have been
// handled completely, including logging and response output.
if (cc != null) {
// Handle OPTIONS calls separately
String method = request.getMethod();
if ("OPTIONS".equals(method)) {
handleOptions(cc, request, response);
// Non-OPTIONS requests are function invocations
} else {
invokeFunction(start, cc, request, response);
}
}
}
/**
* Determines which calling convention should be used for the specified
* request. In case of an error, an error response will be produced and
* sent to the client.
*
* @param request
* the HTTP request for which to determine the calling convention to use
* cannot be <code>null</code>.
*
* @param response
* the HTTP response, cannot be <code>null</code>.
*
* @return
* the {@link CallingConvention} to use, or <code>null</code> if the
* calling convention to use could not be determined.
*
* @throws IOException
* in case of an I/O error.
*/
private final CallingConvention determineCC(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Determine the calling convention; if an existing calling convention
// is specified in the request, then use that, otherwise use the default
// calling convention for this engine
CallingConvention cc = null;
try {
cc = _conventionManager.getCallingConvention(request);
// Only an InvalidRequestException is expected. If a different kind of
// exception is received, then that is considered a programming error.
} catch (Throwable exception) {
int statusCode;
String reason;
if (exception instanceof InvalidRequestException) {
String method = request.getMethod();
String ccName = request.getParameter(CallingConventionManager.CALLING_CONVENTION_PARAMETER);
// Check if the method is known by at least one CC (otherwise 501)
if (!_conventionManager.getSupportedMethods().contains(method)) {
statusCode = HttpServletResponse.SC_NOT_IMPLEMENTED;
reason = "The HTTP method \"" + method + "\" is not known by any of the usable calling conventions.";
// Check if the method is known for the specified CC (otherwise 405)
} else if (ccName != null &&
_conventionManager.getCallingConvention2(ccName) != null &&
!Arrays.asList(_conventionManager.getCallingConvention2(ccName).getSupportedMethods(request)).contains(method)) {
statusCode = HttpServletResponse.SC_METHOD_NOT_ALLOWED;
reason = "The HTTP method \"" + method + "\" is not allowed for the calling convention \"" + ccName + "\".";
} else {
statusCode = HttpServletResponse.SC_BAD_REQUEST;
reason = "Unable to activate appropriate calling convention";
String exceptionMessage = exception.getMessage();
if (TextUtils.isEmpty(exceptionMessage)) {
reason += '.';
} else {
reason += ": " + exceptionMessage;
}
}
} else {
statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
reason = "Internal error while trying to determine "
+ "appropriate calling convention.";
}
// Log
handleUnprocessableRequest(request, response, statusCode, reason, exception);
response.sendError(statusCode);
}
return cc;
}
/**
* Invokes a function, using the specified calling convention to from an
* HTTP request and to an HTTP response.
*
* @param start
* timestamp indicating when the call was received by the framework, in
* milliseconds since the
* <a href="http://en.wikipedia.org/wiki/Unix_Epoch">UNIX Epoch</a>.
*
* @param cc
* the calling convention to use, cannot be <code>null</code>.
*
* @param request
* the HTTP request, cannot be <code>null</code>.
*
* @param response
* the HTTP response, cannot be <code>null</code>.
*
* @throws IOException
* in case of an I/O error.
*/
private void invokeFunction(long start,
CallingConvention cc,
HttpServletRequest request,
HttpServletResponse response)
throws IOException {
FunctionResult result = null;
// Call interceptors
_interceptorManager.beforeCallingConvention(request);
// Convert the HTTP request to a XINS request
FunctionRequest xinsRequest;
try {
xinsRequest = cc.convertRequest(request);
// Only an InvalidRequestException or a FunctionNotSpecifiedException is
// expected. If a different kind of exception is received, then that is
// considered a programming error.
} catch (Throwable exception) {
int statusCode;
String reason;
if (exception instanceof FunctionNotSpecifiedException) {
statusCode = HttpServletResponse.SC_NOT_FOUND;
reason = "Cannot determine which function to invoke.";
result = new FunctionResult(DefaultResultCodes._FUNCTION_NOT_FOUND.getName());
} else if (exception instanceof InvalidRequestException) {
if (exception instanceof InvalidRequestFormatException) {
result = new FunctionResult(DefaultResultCodes._INVALID_REQUEST_FORMAT.getName());
} else {
result = new FunctionResult(DefaultResultCodes._INVALID_REQUEST.getName());
}
statusCode = HttpServletResponse.SC_BAD_REQUEST;
reason = "Calling convention \""
+ cc.getClass().getName()
+ "\" cannot process the request";
String exceptionMessage = exception.getMessage();
if (! TextUtils.isEmpty(exceptionMessage)) {
reason += ": " + exceptionMessage;
} else {
reason += '.';
}
} else {
result = new FunctionResult(DefaultResultCodes._INTERNAL_ERROR.getName());
statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
reason = "Internal error (" + exception.getClass().getSimpleName()
+ "): " + exception.getMessage();
}
// Log
handleUnprocessableRequest(request, response, statusCode, reason, exception);
Map<String, Object> backpack = new HashMap<String, Object>();
backpack.put(BackpackConstants.SKIP_FUNCTION_CALL, true);
backpack.put(BackpackConstants.STATUS_CODE, statusCode);
xinsRequest = new FunctionRequest("_NoOp", new HashMap<String, String>(), null, backpack);
}
// Call the function
try {
xinsRequest.getBackpack().put(BackpackConstants.FUNCTION_NAME, xinsRequest.getFunctionName());
xinsRequest.getBackpack().put(BackpackConstants.IP, request.getRemoteAddr());
xinsRequest.getBackpack().put(BackpackConstants.START, start);
if (result == null) {
xinsRequest = _interceptorManager.beforeFunctionCall(request, xinsRequest);
// The call to the function
result = _api.handleCall(xinsRequest, cc);
result = _interceptorManager.afterFunctionCall(xinsRequest, result, response);
}
// The only expected exceptions are NoSuchFunctionException and
// AccessDeniedException. Other exceptions are considered to indicate
// a programming error.
} catch (Throwable exception) {
int statusCode;
String reason;
// Access denied
if (exception instanceof AccessDeniedException) {
statusCode = HttpServletResponse.SC_FORBIDDEN;
reason = "Access is denied.";
result = new FunctionResult(DefaultResultCodes._NOT_ALLOWED.getName());
// No such function
} else if (exception instanceof NoSuchFunctionException) {
statusCode = HttpServletResponse.SC_NOT_FOUND;
reason = "The specified function \""
+ xinsRequest.getFunctionName()
+ "\" is unknown.";
result = new FunctionResult(DefaultResultCodes._FUNCTION_NOT_FOUND.getName());
// Internal error
} else {
statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
reason = "Internal error while processing function call.";
result = new FunctionResult(DefaultResultCodes._INTERNAL_ERROR.getName());
}
// Log
handleUnprocessableRequest(request, response, statusCode, reason, exception);
xinsRequest.getBackpack().put(BackpackConstants.STATUS_CODE, statusCode);
}
// Shortcut for the _WSDL meta function
if (xinsRequest.getFunctionName().equals("_WSDL")) {
handleWsdlRequest(response);
return;
}
// Shortcut for the _SMD meta function
if (xinsRequest.getFunctionName().equals("_SMD")) {
handleSmdRequest(request, response);
return;
}
// Convert the XINS result to an HTTP response
try {
cc.convertResult(result, response, xinsRequest.getBackpack());
// NOTE: If the convertResult method throws an exception, then it
// will have been logged within the CallingConvention class already.
} catch (Throwable exception) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
_interceptorManager.afterCallingConvention(xinsRequest, result, response);
}
/**
* Handles an <em>OPTIONS</em> request for a specific calling convention
* or for the resource <code>*</code> if no calling convention is given.
*
* @param cc
* the calling convention, can be <code>null</code>. if no calling
* convention is specified all possible method names are returned.
*
* @param request
* the request, never <code>null</code>.
*
* @param response
* the response to fill, never <code>null</code>.
*
* @throws IOException
* in case of an I/O error.
*/
private void handleOptions(CallingConvention cc,
HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Create the string with the supported HTTP methods
String[] methods;
if (cc != null) {
methods = cc.getSupportedMethods(request);
} else {
Set supportedMethods = _conventionManager.getSupportedMethods();
methods = (String[]) supportedMethods.toArray(new String[supportedMethods.size()]);
}
String methodsList = "OPTIONS";
for (int i = 0; i < methods.length; i++) {
methodsList += ", " + methods[i];
}
// Return the full response
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader("Accept", methodsList);
response.setContentLength(0);
}
/**
* Destroys this servlet. A best attempt will be made to release all
* resources.
*
* <p>After this method has finished, it will set the state to
* <em>disposed</em>. In that state no more requests will be handled.
*/
void destroy() {
// Log: Shutting down XINS/Java Server Framework
Log.log_3600();
// Set the state temporarily to DISPOSING
_stateMachine.setState(EngineState.DISPOSING);
// Destroy the configuration manager
if (_configManager != null) {
try {
_configManager.destroy();
} catch (Throwable exception) {
Utils.logIgnoredException(exception);
}
}
// Destroy the API
if (_api != null) {
try {
_api.deinit();
} catch (Throwable exception) {
Utils.logIgnoredException(exception);
}
}
// Deinit the interceptors
if (_interceptorManager != null) {
try {
_interceptorManager.deinit();
} catch (Throwable exception) {
Utils.logIgnoredException(exception);
}
}
// Set the state to DISPOSED
_stateMachine.setState(EngineState.DISPOSED);
// Log: Shutdown completed
Log.log_3602();
}
/**
* Re-initializes the configuration file listener if there is no file
* watcher; otherwise interrupts the file watcher.
*/
void reloadPropertiesIfChanged() {
_configManager.reloadPropertiesIfChanged();
}
/**
* Returns the <code>ServletConfig</code> object which contains the
* build-time properties for this servlet. The returned
* {@link ServletConfig} object is the one that was passed to the
* constructor.
*
* @return
* the {@link ServletConfig} object that was used to initialize this
* servlet, never <code>null</code>.
*/
ServletConfig getServletConfig() {
return _servletConfig;
}
/**
* Returns the name of the API.
*
* @return
* the name of the API or <code>null</code> if not determined yet.
*/
String getApiName() {
return _apiName;
}
/**
* Returns the interceptor manager of the API.
*
* @return
* the interceptor of the API or <code>null</code> if not determined yet.
*/
InterceptorManager getInterceptorManager() {
return _interceptorManager;
}
/**
* Gets the location of a file or a directory included in the WAR file.
*
* @param path
* the relative path in the WAR to locate the file or the directory.
*
* @return
* the String representation of the URL of the given path or <code>null</code>
* if the path cannot be found.
*/
String getFileLocation(String path) {
String baseURL = null;
ServletConfig config = getServletConfig();
ServletContext context = config.getServletContext();
try {
String realPath = context.getRealPath(path);
if (realPath != null) {
baseURL = new File(realPath).toURI().toURL().toExternalForm();
} else {
URL pathURL = context.getResource(path);
if (pathURL == null) {
pathURL = getClass().getResource(path);
}
if (pathURL != null) {
baseURL = pathURL.toExternalForm();
} else {
Log.log_3517(path, null);
}
}
} catch (MalformedURLException muex) {
// Let the base URL be null
Log.log_3517(path, muex.getMessage());
}
return baseURL;
}
/**
* Gets the resource in the WAR file.
*
* @param path
* the path for the resource, cannot be <code>null</code> and should start with /.
*
* @return
* the InputStream to use to read this resource or <code>null</code> if
* the resource cannot be found.
*
* @throws IllegalArgumentException
* if <code>path == null</code> or if the path doesn't start with /.
*
* @since XINS 2.1.
*/
InputStream getResourceAsStream(String path) throws IllegalArgumentException {
MandatoryArgumentChecker.check("path", path);
if (!path.startsWith("/")) {
throw new IllegalArgumentException("The path '" + path + "' should start with /.");
}
String resource = getFileLocation(path);
if (resource != null) {
try {
return new URL(resource).openStream();
} catch (IOException ioe) {
// Fall through and return null
}
}
return null;
}
/**
* Handles the request for the _WSDL meta function.
*
* @param response
* the response to fill, never <code>null</code>.
*
* @throws IOException
* if the WSDL cannot be found in the WAR file.
*/
private void handleWsdlRequest(HttpServletResponse response) throws IOException {
String wsdlLocation = getFileLocation("/WEB-INF/" + _apiName + ".wsdl");
if (wsdlLocation == null) {
throw new FileNotFoundException("/WEB-INF/" + _apiName + ".wsdl not found.");
}
InputStream inputXSLT = new URL(wsdlLocation).openStream();
String wsdlText = IOReader.readFully(inputXSLT);
// Write the text to the output
response.setContentType("text/xml");
response.setStatus(HttpServletResponse.SC_OK);
Writer outputResponse = response.getWriter();
outputResponse.write(wsdlText);
outputResponse.close();
}
/**
* Handles the request for the _SMD meta function.
*
* @param request
* the request asking for the SMD, never <code>null</code>.
*
* @param response
* the response to fill, never <code>null</code>.
*
* @throws IOException
* if the SMD cannot be created or sent to the output stream.
*/
private void handleSmdRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (_smd == null) {
try {
_smd = createSMD(request);
} catch (Exception ex) {
throw new IOException(ex.getMessage());
}
}
// Write the text to the output
response.setContentType(JSONRPCCallingConvention.RESPONSE_CONTENT_TYPE);
response.setStatus(HttpServletResponse.SC_OK);
Writer outputResponse = response.getWriter();
outputResponse.write(_smd);
outputResponse.close();
}
/**
* Creates the SMD for this API.
* More info at http://dojo.jot.com/SMD and
* http://manual.dojotoolkit.org/WikiHome/DojoDotBook/Book9.
*
* @param request
* the request asking for the SMD, never <code>null</code>.
*
* @return
* the String representation of the SMD JSON Object, never <code>null</code>.
*
* @throws InvalidSpecificationException
* if the specification of the API cannot be found.
*
* @throws EntityNotFoundException
* if the specification of a function cannot be found.
*
* @throws JSONException
* if the JSON object cannot be created correctly.
*/
private String createSMD(HttpServletRequest request)
throws InvalidSpecificationException, EntityNotFoundException, JSONException {
APISpec apiSpec = _api.getAPISpecification();
JSONObject smdObject = new JSONObject();
smdObject.put("SMDVersion", ".1");
smdObject.put("objectName", _api.getName());
smdObject.put("serviceType", "JSON-RPC");
String requestURL = request.getRequestURI();
if (requestURL.indexOf('?') != -1) {
requestURL = requestURL.substring(0, requestURL.indexOf('?'));
}
smdObject.put("serviceURL", requestURL + "?_convention=_xins-jsonrpc");
JSONArray methods = new JSONArray();
for (Function nextFunction : _api.getFunctionList()) {
String functionName = nextFunction.getName();
JSONObject functionObject = new JSONObject();
functionObject.put("name", nextFunction);
JSONArray inputParameters = new JSONArray();
Map<String, ParameterSpec> inputParamSpecs = apiSpec.getFunction(functionName).getInputParameters();
for (String nextParam : inputParamSpecs.keySet()) {
JSONObject paramObject = new JSONObject();
paramObject.put("name", nextParam);
inputParameters.put(paramObject);
}
functionObject.put("parameters",inputParameters);
methods.put(functionObject);
}
smdObject.put("methods", methods);
return smdObject.toString();
}
}