org.xins.server.Engine Maven / Gradle / Ivy
/*
* $Id: Engine.java,v 1.128 2012/03/10 14:12:17 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.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.128 $ $Date: 2012/03/10 14:12:17 $
* @author Ernst de Haan
* @author Anthony Goubard
* @author Mees Witteman
*/
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 null
.
*/
private final EngineStateMachine _stateMachine = new EngineStateMachine();
/**
* The starter of this engine. Never null
.
*/
private final EngineStarter _starter;
/**
* The stored servlet configuration object. Never null
.
*/
private final ServletConfig _servletConfig;
/**
* The API that this engine forwards requests to. Never null
.
*/
private final API _api;
/**
* The name of the API. Never null
.
*/
private String _apiName;
/**
* The manager for the runtime configuration file. Never null
.
*/
private final ConfigManager _configManager;
/**
* The manager for the calling conventions. This field can be and initially
* is null
. This field is initialized by
* {@link #bootstrapAPI()}.
*/
private CallingConventionManager _conventionManager;
/**
* The manager for the interceptors. This field can be and initially
* is null
. This field is initialized by
* {@link #bootstrapAPI()}.
*/
private InterceptorManager _interceptorManager;
/**
* The SMD (Simple Method Description) of this API. This value is null
* until the meta function _SMD is called.
*/
private String _smd;
/**
* Constructs a new Engine
object.
*
* @param config
* the {@link ServletConfig} object which contains build-time properties
* for this servlet, cannot be null
.
*
* @throws IllegalArgumentException
* if config == null
.
*
* @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:
*
*
* - load the Logdoc, if available;
*
- bootstrap the API;
*
- construct and bootstrap the calling conventions;
*
- link the engine to the API;
*
- construct and bootstrap a context ID generator;
*
- perform JMX initialization.
*
*
* @return
* true
if the bootstrapping of the API succeeded,
* false
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 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
* true
if the initialization succeeded, otherwise
* false
.
*/
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 properties = _configManager.getRuntimeProperties();
// Determine at what level should the stack traces be displayed
String stackTraceAtMessageLevel = "true"; //properties.get(LogCentral.LOG_STACK_TRACE_AT_MESSAGE_LEVEL);
if ("true".equals(stackTraceAtMessageLevel)) {
//LogCentral.setStackTraceAtMessageLevel(true);
} else if ("false".equals(stackTraceAtMessageLevel)) {
//LogCentral.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 null
, then the behaviour of this method is
* undefined.
*
* @param request
* the servlet request, should not be null
.
*
* @param response
* the servlet response, should not be null
.
*
* @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 null
, then the behaviour of this method is
* undefined.
*
* This method is called from the corresponding wrapper method,
* {@link #service(HttpServletRequest,HttpServletResponse)}.
*
* @param request
* the servlet request, should not be null
.
*
* @param response
* the servlet response, should not be null
.
*
* @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 null
.
*
* @param response
* the HTTP request, cannot be null
.
*
* @param statusCode
* the HTTP status code to return.
*
* @param reason
* explanation, can be null
.
*
* @param exception
* the exception thrown, can be null
.
*
* @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);
// Send the HTTP status code to the client
response.sendError(statusCode);
}
/**
* Handles a request that comes in while function invocations are currently
* not allowed.
*
* @param state
* the current state, cannot be null
.
*
* @param request
* the HTTP request, cannot be null
.
*
* @param response
* the HTTP response to fill, cannot be null
.
*
* @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);
}
/**
* Delegates the specified incoming request to the appropriate
* CallingConvention
. The request may either be a function
* invocation or an OPTIONS request.
*
* @param start
* timestamp indicating when the call was received by the framework, in
* milliseconds since the
* UNIX Epoch.
*
* @param request
* the servlet request, should not be null
.
*
* @param response
* the servlet response, should not be null
.
*
* @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 null
.
*
* @param response
* the HTTP response, cannot be null
.
*
* @return
* the {@link CallingConvention} to use, or null
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 and respond
handleUnprocessableRequest(request, response, statusCode, reason, exception);
}
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
* UNIX Epoch.
*
* @param cc
* the calling convention to use, cannot be null
.
*
* @param request
* the HTTP request, cannot be null
.
*
* @param response
* the HTTP response, cannot be null
.
*
* @throws IOException
* in case of an I/O error.
*/
private void invokeFunction(long start,
CallingConvention cc,
HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// 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 InvalidRequestException) {
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 if (exception instanceof FunctionNotSpecifiedException) {
statusCode = HttpServletResponse.SC_NOT_FOUND;
reason = "Cannot determine which function to invoke.";
} else {
statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
reason = "Internal error.";
}
// Log and respond
handleUnprocessableRequest(request, response, statusCode, reason, exception);
return;
}
// Do not handle the call if the API is disabled
if (_api.isDisabled() && !"_EnableAPI".equals(xinsRequest.getFunctionName())) {
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return;
}
// Call the function
FunctionResult result;
try {
xinsRequest.getBackpack().put(BackpackConstants.IP, request.getRemoteAddr());
xinsRequest.getBackpack().put(BackpackConstants.START, start);
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.";
// No such function
} else if (exception instanceof NoSuchFunctionException) {
statusCode = HttpServletResponse.SC_NOT_FOUND;
reason = "The specified function \""
+ xinsRequest.getFunctionName()
+ "\" is unknown.";
// Internal error
} else {
statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
reason = "Internal error while processing function call.";
}
// Log and respond
handleUnprocessableRequest(request, response, statusCode, reason, exception);
return;
}
// 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 OPTIONS request for a specific calling convention
* or for the resource *
if no calling convention is given.
*
* @param cc
* the calling convention, can be null
. if no calling
* convention is specified all possible method names are returned.
*
* @param request
* the request, never null
.
*
* @param response
* the response to fill, never null
.
*
* @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.
*
*
After this method has finished, it will set the state to
* disposed. 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 ServletConfig
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 null
.
*/
ServletConfig getServletConfig() {
return _servletConfig;
}
/**
* Returns the name of the API.
*
* @return
* the name of the API or null
if not determined yet.
*/
String getApiName() {
return _apiName;
}
/**
* Returns the interceptor manager of the API.
*
* @return
* the interceptor of the API or null
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 null
* 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 null
and should start with /.
*
* @return
* the InputStream to use to read this resource or null
if
* the resource cannot be found.
*
* @throws IllegalArgumentException
* if path == null
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 null
.
*
* @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 null
.
*
* @param response
* the response to fill, never null
.
*
* @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 null
.
*
* @return
* the String representation of the SMD JSON Object, never null
.
*
* @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 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();
}
}