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;
final class Engine {
private static final String JMX_PROPERTY = "org.xins.server.jmx";
private final EngineStateMachine _stateMachine = new EngineStateMachine();
private final EngineStarter _starter;
private final ServletConfig _servletConfig;
private final API _api;
private String _apiName;
private final ConfigManager _configManager;
private CallingConventionManager _conventionManager;
private InterceptorManager _interceptorManager;
private String _smd;
Engine(ServletConfig config)
throws IllegalArgumentException, ServletException {
MandatoryArgumentChecker.check("config", config);
_starter = new EngineStarter(config);
_apiName = _starter.determineAPIName();
_configManager = new ConfigManager(this, config);
_servletConfig = config;
_stateMachine.setState(EngineState.BOOTSTRAPPING_FRAMEWORK);
_configManager.determineConfigFile();
_configManager.readRuntimeProperties();
if (!_configManager.propertiesRead()) {
_stateMachine.setState(EngineState.FRAMEWORK_BOOTSTRAP_FAILED);
throw new ServletException();
}
_starter.logBootMessages();
_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();
}
Log.log_3225(Library.getVersion());
_configManager.init();
if (_api == null) {
throw Utils.logProgrammingError("_api == null");
} else if (_apiName == null) {
throw Utils.logProgrammingError("_apiName == null");
}
long startTime = System.currentTimeMillis() - _starter.getStartedTime();
Log.log_3446(_apiName, (int) startTime);
}
private boolean bootstrapAPI() {
_stateMachine.setState(EngineState.BOOTSTRAPPING_API);
_api.setEngine(this);
Map<String, String> bootProps;
try {
_starter.loadLogdoc();
bootProps = _starter.bootstrap(_api);
} catch (ServletException se) {
_stateMachine.setState(EngineState.API_BOOTSTRAP_FAILED);
return false;
}
_conventionManager = new CallingConventionManager(_api);
try {
_conventionManager.bootstrap(bootProps);
} catch (MissingRequiredPropertyException exception) {
Log.log_3209(exception.getPropertyName(), exception.getDetail());
return false;
} catch (InvalidPropertyValueException exception) {
Log.log_3210(exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
return false;
} catch (Throwable exception) {
Log.log_3211(exception);
return false;
}
_interceptorManager = new InterceptorManager();
_interceptorManager.setApi(_api);
try {
_interceptorManager.bootstrap(bootProps);
} catch (Exception ex) {
return false;
}
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;
}
return true;
}
boolean initAPI() {
_stateMachine.setState(EngineState.INITIALIZING_API);
boolean localeInitialized = _configManager.determineLogLocale();
if (!localeInitialized) {
_stateMachine.setState(EngineState.API_INITIALIZATION_FAILED);
return false;
}
if (!_configManager.propertiesRead()) {
_stateMachine.setState(EngineState.API_INITIALIZATION_FAILED);
return false;
}
Map<String, String> properties = _configManager.getRuntimeProperties();
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) {
_stateMachine.setState(EngineState.API_INITIALIZATION_FAILED);
return false;
}
boolean succeeded = false;
try {
_api.init(properties);
_conventionManager.init(properties);
_interceptorManager.init(properties);
succeeded = true;
} catch (MissingRequiredPropertyException exception) {
Log.log_3411(exception.getPropertyName(), exception.getDetail());
} catch (InvalidPropertyValueException exception) {
Log.log_3412(exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
} catch (InitializationException exception) {
Log.log_3413(exception);
} catch (Throwable exception) {
Log.log_3414(exception);
} finally {
if (succeeded) {
_stateMachine.setState(EngineState.READY);
} else {
_stateMachine.setState(EngineState.API_INITIALIZATION_FAILED);
}
}
return succeeded;
}
void service(HttpServletRequest request, HttpServletResponse response)
throws IOException {
if (request.getCharacterEncoding() == null) {
request.setCharacterEncoding("UTF-8");
}
try {
doService(request, response);
} catch (Throwable exception) {
Log.log_3003(exception);
}
}
private void doService(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
long start = System.currentTimeMillis();
String method = request.getMethod();
String path = request.getRequestURI();
_interceptorManager.beginRequest(request);
EngineState state = _stateMachine.getState();
if (! state.allowsInvocations()) {
handleUnusableState(state, request, response);
} else if ("OPTIONS".equals(method)) {
if ("*".equals(path)) {
handleOptions(null, request, response);
} else {
delegateToCC(start, request, response);
}
} else {
delegateToCC(start, request, response);
}
_interceptorManager.endRequest(request, response);
}
private void handleUnprocessableRequest(HttpServletRequest request,
HttpServletResponse response,
int statusCode,
String reason,
Throwable exception)
throws IOException {
Log.log_3523(exception,
request.getRemoteAddr(),
request.getMethod(),
request.getRequestURI(),
request.getQueryString(),
statusCode,
reason);
}
private void handleUnusableState(EngineState state,
HttpServletRequest request,
HttpServletResponse response)
throws IOException {
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);
}
private void delegateToCC(long start,
HttpServletRequest request,
HttpServletResponse response)
throws IOException {
CallingConvention cc = determineCC(request, response);
if (cc != null) {
String method = request.getMethod();
if ("OPTIONS".equals(method)) {
handleOptions(cc, request, response);
} else {
invokeFunction(start, cc, request, response);
}
}
}
private final CallingConvention determineCC(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
CallingConvention cc = null;
try {
cc = _conventionManager.getCallingConvention(request);
} catch (Throwable exception) {
int statusCode;
String reason;
if (exception instanceof InvalidRequestException) {
String method = request.getMethod();
String ccName = request.getParameter(CallingConventionManager.CALLING_CONVENTION_PARAMETER);
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.";
} 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.";
}
handleUnprocessableRequest(request, response, statusCode, reason, exception);
response.sendError(statusCode);
}
return cc;
}
private void invokeFunction(long start,
CallingConvention cc,
HttpServletRequest request,
HttpServletResponse response)
throws IOException {
FunctionResult result = null;
_interceptorManager.beforeCallingConvention(request);
FunctionRequest xinsRequest;
try {
xinsRequest = cc.convertRequest(request);
} 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();
}
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);
}
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);
result = _api.handleCall(xinsRequest, cc);
result = _interceptorManager.afterFunctionCall(xinsRequest, result, response);
}
} catch (Throwable exception) {
int statusCode;
String reason;
if (exception instanceof AccessDeniedException) {
statusCode = HttpServletResponse.SC_FORBIDDEN;
reason = "Access is denied.";
result = new FunctionResult(DefaultResultCodes._NOT_ALLOWED.getName());
} 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());
} else {
statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
reason = "Internal error while processing function call.";
result = new FunctionResult(DefaultResultCodes._INTERNAL_ERROR.getName());
}
handleUnprocessableRequest(request, response, statusCode, reason, exception);
xinsRequest.getBackpack().put(BackpackConstants.STATUS_CODE, statusCode);
}
if (xinsRequest.getFunctionName().equals("_WSDL")) {
handleWsdlRequest(response);
return;
}
if (xinsRequest.getFunctionName().equals("_SMD")) {
handleSmdRequest(request, response);
return;
}
try {
cc.convertResult(result, response, xinsRequest.getBackpack());
} catch (Throwable exception) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
_interceptorManager.afterCallingConvention(xinsRequest, result, response);
}
private void handleOptions(CallingConvention cc,
HttpServletRequest request,
HttpServletResponse response)
throws IOException {
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];
}
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader("Accept", methodsList);
response.setContentLength(0);
}
void destroy() {
Log.log_3600();
_stateMachine.setState(EngineState.DISPOSING);
if (_configManager != null) {
try {
_configManager.destroy();
} catch (Throwable exception) {
Utils.logIgnoredException(exception);
}
}
if (_api != null) {
try {
_api.deinit();
} catch (Throwable exception) {
Utils.logIgnoredException(exception);
}
}
if (_interceptorManager != null) {
try {
_interceptorManager.deinit();
} catch (Throwable exception) {
Utils.logIgnoredException(exception);
}
}
_stateMachine.setState(EngineState.DISPOSED);
Log.log_3602();
}
void reloadPropertiesIfChanged() {
_configManager.reloadPropertiesIfChanged();
}
ServletConfig getServletConfig() {
return _servletConfig;
}
String getApiName() {
return _apiName;
}
InterceptorManager getInterceptorManager() {
return _interceptorManager;
}
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) {
Log.log_3517(path, muex.getMessage());
}
return baseURL;
}
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) {
}
}
return null;
}
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);
response.setContentType("text/xml");
response.setStatus(HttpServletResponse.SC_OK);
Writer outputResponse = response.getWriter();
outputResponse.write(wsdlText);
outputResponse.close();
}
private void handleSmdRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (_smd == null) {
try {
_smd = createSMD(request);
} catch (Exception ex) {
throw new IOException(ex.getMessage());
}
}
response.setContentType(JSONRPCCallingConvention.RESPONSE_CONTENT_TYPE);
response.setStatus(HttpServletResponse.SC_OK);
Writer outputResponse = response.getWriter();
outputResponse.write(_smd);
outputResponse.close();
}
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();
}
}