package org.xins.server;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.http.HttpServletResponse;
import org.xins.common.FormattedParameters;
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.manageable.BootstrapException;
import org.xins.common.manageable.DeinitializationException;
import org.xins.common.manageable.InitializationException;
import org.xins.common.manageable.Manageable;
import org.xins.common.net.IPAddressUtils;
import org.xins.common.spec.APISpec;
import org.xins.common.spec.InvalidSpecificationException;
import org.xins.common.text.ParseException;
import org.w3c.dom.Element;
public abstract class API extends Manageable {
static final FunctionResult SUCCESSFUL_RESULT = new FunctionResult();
private static final String ACL_PROPERTY = "org.xins.server.acl";
static final String API_VERSION_PROPERTY = "org.xins.api.version";
private static final String BUILD_HOST_PROPERTY = "org.xins.api.build.host";
private static final String BUILD_TIME_PROPERTY = "org.xins.api.build.time";
private static final String BUILD_XINS_VERSION_PROPERTY = "org.xins.api.build.version";
private Engine _engine;
private String _name;
private List<Manageable> _manageableObjects;
private Map<String, Function> _functionsByName;
private List<Function> _functionList;
private Map<String, String> _buildSettings;
private RuntimeProperties _emptyProperties;
private Map<String, String> _runtimeSettings;
private long _startupTimestamp;
private String _buildHost;
private String _buildTime;
private String _buildVersion;
private TimeZone _timeZone;
private String _apiVersion;
private AccessRuleList _apiAccessRuleList;
private AccessRuleList _accessRuleList;
private APISpec _apiSpecification;
private String _localIPAddress;
private HashMap<String, AtomicInteger> _metaFunctionCallIDs;
private boolean _apiDisabled;
protected API(String name)
throws IllegalArgumentException {
MandatoryArgumentChecker.check("name", name);
if (name.length() < 1) {
String message = "name.length() == "
+ name.length();
throw new IllegalArgumentException(message);
}
_name = name;
_startupTimestamp = System.currentTimeMillis();
_manageableObjects = new ArrayList<Manageable>(20);
_functionsByName = new HashMap<String, Function>(89);
_functionList = new ArrayList<Function>(80);
_emptyProperties = new RuntimeProperties();
_timeZone = TimeZone.getDefault();
_localIPAddress = IPAddressUtils.getLocalHostIPAddress();
_apiDisabled = false;
_metaFunctionCallIDs = new HashMap<String, AtomicInteger>(89);
_metaFunctionCallIDs.put("_NoOp", new AtomicInteger());
_metaFunctionCallIDs.put("_GetFunctionList", new AtomicInteger());
_metaFunctionCallIDs.put("_GetStatistics", new AtomicInteger());
_metaFunctionCallIDs.put("_GetVersion", new AtomicInteger());
_metaFunctionCallIDs.put("_CheckLinks", new AtomicInteger());
_metaFunctionCallIDs.put("_GetSettings", new AtomicInteger());
_metaFunctionCallIDs.put("_DisableFunction", new AtomicInteger());
_metaFunctionCallIDs.put("_EnableFunction", new AtomicInteger());
_metaFunctionCallIDs.put("_ResetStatistics", new AtomicInteger());
_metaFunctionCallIDs.put("_ReloadProperties", new AtomicInteger());
_metaFunctionCallIDs.put("_WSDL", new AtomicInteger());
_metaFunctionCallIDs.put("_SMD", new AtomicInteger());
_metaFunctionCallIDs.put("_DisableAPI", new AtomicInteger());
_metaFunctionCallIDs.put("_EnableAPI", new AtomicInteger());
}
public final String getName() {
return _name;
}
public final List<Function> getFunctionList() {
return _functionList;
}
public Map<String, String> getBootstrapProperties() {
return _buildSettings;
}
Map<String, String> getRuntimeProperties() {
return _runtimeSettings;
}
public RuntimeProperties getProperties() {
return _emptyProperties;
}
public final long getStartupTimestamp() {
return _startupTimestamp;
}
public final TimeZone getTimeZone() {
return _timeZone;
}
public final InputStream getResourceAsStream(String path) throws IllegalArgumentException {
return _engine.getResourceAsStream(path);
}
@Override
protected final void bootstrapImpl(Map<String, String> buildSettings)
throws IllegalStateException,
MissingRequiredPropertyException,
InvalidPropertyValueException,
BootstrapException {
Manageable.State state = getState();
if (state != BOOTSTRAPPING) {
String message = "State is " + state.getName() + " instead of BOOTSTRAPPING.";
Utils.logProgrammingError(message);
throw new IllegalStateException(message);
}
String tzShortName = _timeZone.getDisplayName(false, TimeZone.SHORT);
String tzLongName = _timeZone.getDisplayName(false, TimeZone.LONG);
Log.log_3404(tzShortName, tzLongName);
_buildSettings = buildSettings;
_apiVersion = buildSettings.get(API_VERSION_PROPERTY );
_buildHost = buildSettings.get(BUILD_HOST_PROPERTY );
_buildTime = buildSettings.get(BUILD_TIME_PROPERTY );
_buildVersion = buildSettings.get(BUILD_XINS_VERSION_PROPERTY);
Log.log_3212(_buildHost, _buildTime, _buildVersion, _name, _apiVersion);
if (_buildVersion == null) {
} else if (! Library.isProductionRelease(_buildVersion)) {
Log.log_3228(_buildVersion);
}
bootstrapImpl2(buildSettings);
int count = _manageableObjects.size();
for (int i = 0; i < count; i++) {
Manageable m = _manageableObjects.get(i);
String className = m.getClass().getName();
Log.log_3213(_name, className);
try {
m.bootstrap(buildSettings);
} catch (MissingRequiredPropertyException exception) {
Log.log_3215(_name, className, exception.getPropertyName(),
exception.getDetail());
throw exception;
} catch (InvalidPropertyValueException exception) {
Log.log_3216(_name,
className,
exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
throw exception;
} catch (Throwable exception) {
Log.log_3217(exception, _name, className);
if (exception instanceof BootstrapException) {
throw (BootstrapException) exception;
} else {
throw new BootstrapException(exception);
}
}
}
count = _functionList.size();
for (int i = 0; i < count; i++) {
Function f = _functionList.get(i);
String functionName = f.getName();
Log.log_3220(_name, functionName);
try {
f.bootstrap(buildSettings);
} catch (MissingRequiredPropertyException exception) {
Log.log_3222(_name, functionName, exception.getPropertyName(),
exception.getDetail());
throw exception;
} catch (InvalidPropertyValueException exception) {
Log.log_3223(_name,
functionName,
exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
throw exception;
} catch (Throwable exception) {
Log.log_3224(exception, _name, functionName);
if (exception instanceof BootstrapException) {
throw (BootstrapException) exception;
} else {
throw new BootstrapException(exception);
}
}
}
}
protected void bootstrapImpl2(Map<String, String> buildSettings)
throws MissingRequiredPropertyException,
InvalidPropertyValueException,
BootstrapException {
}
void setEngine(Engine engine) {
_engine = engine;
}
protected final void reinitializeImpl() {
_engine.initAPI();
}
protected final void initImpl(Map<String, String> runtimeSettings)
throws MissingRequiredPropertyException,
InvalidPropertyValueException,
InitializationException,
IllegalStateException {
Log.log_3405(_name);
_runtimeSettings = runtimeSettings;
String propName = ConfigManager.CONFIG_RELOAD_INTERVAL_PROPERTY;
String propValue = runtimeSettings.get(propName);
int interval = ConfigManager.DEFAULT_CONFIG_RELOAD_INTERVAL;
if (propValue != null && propValue.trim().length() > 0) {
try {
interval = Integer.parseInt(propValue);
} catch (NumberFormatException e) {
String detail = "Invalid interval. Must be a non-negative integer"
+ " number (32-bit signed).";
throw new InvalidPropertyValueException(propName, propValue,
detail);
}
if (interval < 0) {
throw new InvalidPropertyValueException(propName, propValue,
"Negative interval not allowed. Use 0 to disable reloading.");
}
}
if (_apiAccessRuleList != null) {
_apiAccessRuleList.dispose();
}
_apiAccessRuleList = createAccessRuleList(runtimeSettings, ACL_PROPERTY + '.' + _name, interval);
if (_accessRuleList != null) {
_accessRuleList.dispose();
}
_accessRuleList = createAccessRuleList(runtimeSettings, ACL_PROPERTY, interval);
getProperties().init(runtimeSettings);
int count = _manageableObjects.size();
for (int i = 0; i < count; i++) {
Manageable m = _manageableObjects.get(i);
String className = m.getClass().getName();
Log.log_3416(_name, className);
try {
m.init(runtimeSettings);
} catch (MissingRequiredPropertyException exception) {
Log.log_3418(_name, className, exception.getPropertyName(),
exception.getDetail());
throw exception;
} catch (InvalidPropertyValueException exception) {
Log.log_3419(_name,
className,
exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
throw exception;
} catch (Throwable exception) {
Log.log_3420(exception, _name, className);
if (exception instanceof InitializationException) {
throw (InitializationException) exception;
} else {
throw new InitializationException(exception);
}
}
}
count = _functionList.size();
for (int i = 0; i < count; i++) {
Function f = _functionList.get(i);
String functionName = f.getName();
Log.log_3421(_name, functionName);
try {
f.init(runtimeSettings);
} catch (MissingRequiredPropertyException exception) {
Log.log_3423(_name, functionName, exception.getPropertyName(),
exception.getDetail());
throw exception;
} catch (InvalidPropertyValueException exception) {
Log.log_3424(_name,
functionName,
exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
throw exception;
} catch (Throwable exception) {
Log.log_3425(exception, _name, functionName);
if (exception instanceof InitializationException) {
throw (InitializationException) exception;
} else {
throw new InitializationException(exception);
}
}
}
Log.log_3406(_name);
}
private AccessRuleList createAccessRuleList(Map<String, String> runtimeSettings,
String aclProperty, int interval)
throws InvalidPropertyValueException {
String acl = runtimeSettings.get(aclProperty);
if (acl == null || acl.trim().length() < 1) {
if (aclProperty.equals(ACL_PROPERTY)) {
Log.log_3426(aclProperty);
}
return AccessRuleList.EMPTY;
} else {
try {
AccessRuleList accessRuleList =
AccessRuleList.parseAccessRuleList(acl, interval);
int ruleCount = accessRuleList.getRuleCount();
Log.log_3427(ruleCount);
return accessRuleList;
} catch (ParseException exception) {
String exceptionMessage = exception.getMessage();
Log.log_3428(aclProperty, acl, exceptionMessage);
throw new InvalidPropertyValueException(aclProperty,
acl,
exceptionMessage);
}
}
}
protected final void add(Manageable m)
throws IllegalStateException,
IllegalArgumentException {
Manageable.State state = getState();
if (state != BOOTSTRAPPING) {
String message = "State is "
+ state
+ " instead of "
+ BOOTSTRAPPING
+ '.';
Utils.logProgrammingError(message);
throw new IllegalStateException(message);
}
MandatoryArgumentChecker.check("m", m);
String className = m.getClass().getName();
Log.log_3218(_name, className);
_manageableObjects.add(m);
}
protected final void deinitImpl() {
int count = _manageableObjects.size();
for (int i = 0; i < count; i++) {
Manageable m = _manageableObjects.get(i);
String className = m.getClass().getName();
Log.log_3603(_name, className);
try {
m.deinit();
} catch (DeinitializationException exception) {
Log.log_3605(_name, className, exception.getMessage());
} catch (Throwable exception) {
Log.log_3606(exception, _name, className);
}
}
_manageableObjects.clear();
count = _functionList.size();
for (int i = 0; i < count; i++) {
Function f = _functionList.get(i);
String functionName = f.getName();
Log.log_3607(_name, functionName);
try {
f.deinit();
} catch (DeinitializationException exception) {
Log.log_3609(_name, functionName, exception.getMessage());
} catch (Throwable exception) {
Log.log_3610(exception, _name, functionName);
}
}
}
final void functionAdded(Function function)
throws NullPointerException, IllegalStateException {
Manageable.State state = getState();
if (state != UNUSABLE) {
String message = "State is "
+ state
+ " instead of "
+ UNUSABLE
+ '.';
Utils.logProgrammingError(message);
throw new IllegalStateException(message);
}
_functionsByName.put(function.getName(), function);
_functionList.add(function);
}
final Function getFunction(String name) {
return _functionsByName.get(name);
}
public final APISpec getAPISpecification()
throws InvalidSpecificationException {
if (_apiSpecification == null) {
String baseURL = _engine.getFileLocation("/WEB-INF/specs/");
_apiSpecification = new APISpec(getClass(), baseURL);
}
return _apiSpecification;
}
public boolean allow(String ip, String functionName, String conventionName)
throws IllegalArgumentException {
if (_apiAccessRuleList == AccessRuleList.EMPTY &&
_accessRuleList == AccessRuleList.EMPTY &&
(ip.equals("127.0.0.1") || ip.equals("::1") ||
ip.startsWith("0:0:0:0:0:0:0:1%") || ip.equals(_localIPAddress))) {
return true;
}
Boolean allowed;
try {
allowed = _apiAccessRuleList.isAllowed(ip, functionName, conventionName);
if (allowed == null) {
allowed = _accessRuleList.isAllowed(ip, functionName, conventionName);
}
} catch (ParseException exception) {
String detail = "Malformed IP address: \"" + ip + "\".";
throw Utils.logProgrammingError(detail, exception);
}
if (allowed != null) {
return allowed.booleanValue();
}
Log.log_3553(ip, functionName, conventionName);
return false;
}
final FunctionResult handleCall(FunctionRequest functionRequest,
CallingConvention cc)
throws IllegalStateException,
NullPointerException,
NoSuchFunctionException,
AccessDeniedException {
assertUsable();
String functionName = functionRequest.getFunctionName();
if (_apiDisabled && !"_EnableAPI".equals(functionName)) {
functionRequest.getBackpack().put(BackpackConstants.STATUS_CODE, HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return new FunctionResult(DefaultResultCodes._DISABLED_API.getName());
}
String ip = (String) functionRequest.getBackpack().get(BackpackConstants.IP);
boolean allow = allow(ip, functionName, cc.getConventionName());
if (! allow) {
throw new AccessDeniedException(ip, functionName, cc.getConventionName());
}
FunctionResult result;
if (functionName.length() > 0 && functionName.charAt(0) == '_') {
int callID;
AtomicInteger counter = _metaFunctionCallIDs.get(functionName);
if (counter == null) {
throw new NoSuchFunctionException(functionName);
} else {
callID = counter.incrementAndGet();
}
try {
result = callMetaFunction(functionName, functionRequest);
} catch (Throwable exception) {
result = handleFunctionException(functionRequest, callID, exception);
}
} else {
Function function = getFunction(functionName);
if (function == null && !functionRequest.shouldSkipFunctionCall()) {
throw new NoSuchFunctionException(functionName);
}
if (function == null) {
Object inParams = new FormattedParameters(functionRequest.getParameters(), functionRequest.getDataElement());
Log.log_3516(functionRequest.getFunctionName(), inParams);
result = SUCCESSFUL_RESULT;
} else {
result = function.handleCall(functionRequest);
}
}
return result;
}
private FunctionResult callMetaFunction(String functionName,
FunctionRequest functionRequest)
throws NoSuchFunctionException {
FunctionResult result;
if ("_NoOp".equals(functionName)) {
result = SUCCESSFUL_RESULT;
} else if ("_GetFunctionList".equals(functionName)) {
result = doGetFunctionList();
} else if ("_GetStatistics".equals(functionName)) {
String detailedArg = functionRequest.getParameters().get("detailed");
boolean detailed = !"false".equals(detailedArg);
String targetFunction = functionRequest.getParameters().get("targetFunction");
result = doGetStatistics(detailed, targetFunction);
String resetArg = functionRequest.getParameters().get("reset");
boolean reset = "true".equals(resetArg);
if (reset) {
doResetStatistics();
}
} else if ("_GetVersion".equals(functionName)) {
result = doGetVersion();
} else if ("_CheckLinks".equals(functionName)) {
result = doCheckLinks();
} else if ("_GetSettings".equals(functionName)) {
result = doGetSettings();
} else if ("_DisableFunction".equals(functionName)) {
String disabledFunction = functionRequest.getParameters().get("functionName");
result = doDisableFunction(disabledFunction);
} else if ("_EnableFunction".equals(functionName)) {
String enabledFunction = functionRequest.getParameters().get("functionName");
result = doEnableFunction(enabledFunction);
} else if ("_ResetStatistics".equals(functionName)) {
result = doResetStatistics();
} else if ("_ReloadProperties".equals(functionName)) {
_engine.reloadPropertiesIfChanged();
result = SUCCESSFUL_RESULT;
} else if ("_IWantTheEasterEggs".equals(functionName)) {
result = SUCCESSFUL_RESULT;
} else if ("_WSDL".equals(functionName)) {
result = SUCCESSFUL_RESULT;
} else if ("_SMD".equals(functionName)) {
result = SUCCESSFUL_RESULT;
} else if ("_DisableAPI".equals(functionName)) {
_apiDisabled = true;
result = SUCCESSFUL_RESULT;
} else if ("_EnableAPI".equals(functionName)) {
_apiDisabled = false;
result = SUCCESSFUL_RESULT;
} else {
throw new NoSuchFunctionException(functionName);
}
return result;
}
FunctionResult handleFunctionException(FunctionRequest functionRequest,
int callID,
Throwable exception) {
Log.log_3500(exception, _name, callID);
Map<String, String> resultParams = new HashMap<String, String>();
String exceptionClass = exception.getClass().getName();
resultParams.put("_exception.class", exceptionClass);
String exceptionMessage = exception.getMessage();
if (exceptionMessage != null) {
exceptionMessage = exceptionMessage.trim();
if (exceptionMessage.length() > 0) {
resultParams.put("_exception.message", exceptionMessage);
}
}
StringWriter stWriter = new StringWriter(360);
PrintWriter printWriter = new PrintWriter(stWriter);
exception.printStackTrace(printWriter);
String stackTrace = stWriter.toString();
stackTrace = stackTrace.trim();
if (stackTrace.length() > 0) {
resultParams.put("_exception.stacktrace", stackTrace);
}
return new FunctionResult("_InternalError", resultParams);
}
private FunctionResult doGetFunctionList() {
FunctionResult builder = new FunctionResult();
int count = _functionList.size();
for (int i = 0; i < count; i++) {
Function function = _functionList.get(i);
String name = function.getName();
String version = function.getVersion();
String enabled = function.isEnabled()
? "true"
: "false";
Element functionElem = builder.getDataElementBuilder().createElement("function");
functionElem.setAttribute("name", name );
functionElem.setAttribute("version", version);
functionElem.setAttribute("enabled", enabled);
builder.getDataElement().appendChild(functionElem);
}
return builder;
}
private FunctionResult doGetStatistics(boolean detailed, String functionName) {
StatisticsInterceptor statInterceptor = getStatisticInterceptor();
FunctionResult result = statInterceptor.getStatistics(detailed, functionName);
return result;
}
private FunctionResult doGetVersion() {
FunctionResult builder = new FunctionResult();
builder.param("java.version", System.getProperty("java.version"));
builder.param("xmlenc.version", org.znerd.xmlenc.Library.getVersion());
builder.param("xins.version", Library.getVersion());
builder.param("api.version", _apiVersion);
return builder;
}
private FunctionResult doCheckLinks() {
return CheckLinks.checkLinks(getProperties().descriptors());
}
private FunctionResult doGetSettings() {
FunctionResult builder = new FunctionResult();
Element build = builder.getDataElementBuilder().createElement("build");
for (Map.Entry<String, String> names : _buildSettings.entrySet()) {
String key = names.getKey();
String value = names.getValue();
Element property = builder.getDataElementBuilder().createElement("property");
property.setAttribute("name", key);
property.setTextContent(value);
build.appendChild(property);
}
builder.getDataElement().appendChild(build);
Element runtime = builder.getDataElementBuilder().createElement("runtime");
for (Map.Entry<String, String> names : _runtimeSettings.entrySet()) {
String key = names.getKey();
String value = names.getValue();
Element property = builder.getDataElementBuilder().createElement("property");
property.setAttribute("name", key);
property.setTextContent(value);
runtime.appendChild(property);
}
builder.getDataElement().appendChild(runtime);
Properties sysProps;
try {
sysProps = System.getProperties();
} catch (SecurityException ex) {
Utils.logProgrammingError(ex);
sysProps = new Properties();
}
Enumeration e = sysProps.propertyNames();
Element system = builder.getDataElementBuilder().createElement("system");
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
String value = sysProps.getProperty(key);
if ( key != null && key.trim().length() > 0
&& value != null && value.trim().length() > 0) {
Element property = builder.getDataElementBuilder().createElement("property");
property.setAttribute("name", key);
property.setTextContent(value);
system.appendChild(property);
}
}
builder.getDataElement().appendChild(system);
return builder;
}
private FunctionResult doEnableFunction(String functionName) {
if (functionName == null || functionName.length() < 1) {
InvalidRequestResult invalidRequest = new InvalidRequestResult();
invalidRequest.addMissingParameter("functionName");
return invalidRequest;
}
Function function = getFunction(functionName);
if (function == null) {
return new InvalidRequestResult();
}
function.setEnabled(true);
return SUCCESSFUL_RESULT;
}
private FunctionResult doDisableFunction(String functionName) {
if (functionName == null || functionName.length() < 1) {
InvalidRequestResult invalidRequest = new InvalidRequestResult();
invalidRequest.addMissingParameter("functionName");
return invalidRequest;
}
Function function = getFunction(functionName);
if (function == null) {
return new InvalidRequestResult();
}
function.setEnabled(false);
return SUCCESSFUL_RESULT;
}
private FunctionResult doResetStatistics() {
StatisticsInterceptor statInterceptor = getStatisticInterceptor();
FunctionResult result = statInterceptor.resetStatistics();
return result;
}
StatisticsInterceptor getStatisticInterceptor() {
List<Interceptor> interceptors = _engine.getInterceptorManager().getInterceptors();
for (Interceptor interceptor : interceptors) {
if (interceptor instanceof StatisticsInterceptor) {
return (StatisticsInterceptor) interceptor;
}
}
return null;
}
}