org.xins.server.API Maven / Gradle / Ivy
/*
* $Id: API.java,v 1.386 2012/03/03 21:23:44 agoubard Exp $
*
* See the COPYRIGHT file for redistribution and use restrictions.
*/
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 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;
/**
* Base class for API implementation classes.
*
* @version $Revision: 1.386 $ $Date: 2012/03/03 21:23:44 $
* @author Ernst de Haan
* @author Anthony Goubard
* @author Tauseef Rehman
*
* @since XINS 1.0.0
*/
public abstract class API extends Manageable {
/**
* Successful empty call result.
*/
static final FunctionResult SUCCESSFUL_RESULT = new FunctionResult();
/**
* The runtime (initialization) property that defines the ACL (access
* control list) rules.
*/
private static final String ACL_PROPERTY = "org.xins.server.acl";
/**
* The name of the bootstrap property that specifies the version of the API.
*/
static final String API_VERSION_PROPERTY = "org.xins.api.version";
/**
* The name of the bootstrap property that specifies the hostname of the
* machine the package was built on.
*/
private static final String BUILD_HOST_PROPERTY = "org.xins.api.build.host";
/**
* The name of the bootstrap property that specifies the time the package was
* built.
*/
private static final String BUILD_TIME_PROPERTY = "org.xins.api.build.time";
/**
* The name of the bootstrap property that specifies which version of XINS was
* used to build the package.
*/
private static final String BUILD_XINS_VERSION_PROPERTY = "org.xins.api.build.version";
/**
* The engine that owns this API
object.
*/
private Engine _engine;
/**
* The name of this API. Cannot be null
and cannot be an empty
* string.
*/
private String _name;
/**
* List of registered manageable objects. See {@link #add(Manageable)}.
*
* This field is initialized to a non-null
value by the
* constructor.
*/
private List _manageableObjects;
/**
* Map that maps function names to Function
instances.
* Contains all functions associated with this API.
*
* This field is initialized to a non-null
value by the
* constructor.
*/
private Map _functionsByName;
/**
* List of all functions. This field cannot be null
.
*/
private List _functionList;
/**
* The build-time settings. This field is initialized exactly once by
* {@link #bootstrap(Map)}. It can be null
before
* that.
*/
private Map _buildSettings;
/**
* The {@link RuntimeProperties} containing the method to verify and access
* the defined runtime properties.
*/
private RuntimeProperties _emptyProperties;
/**
* The runtime-time settings. This field is initialized by
* {@link #init(Map)}. It can be null
before that.
*/
private Map _runtimeSettings;
/**
* Timestamp indicating when this API instance was created.
*/
private long _startupTimestamp;
/**
* Host name for the machine that was used for this build.
*/
private String _buildHost;
/**
* Time stamp that indicates when this build was done.
*/
private String _buildTime;
/**
* XINS version used to build the web application package.
*/
private String _buildVersion;
/**
* The time zone used when generating dates for output.
*/
private TimeZone _timeZone;
/**
* Version of the API.
*/
private String _apiVersion;
/**
* The API specific access rule list.
*/
private AccessRuleList _apiAccessRuleList;
/**
* The general access rule list.
*/
private AccessRuleList _accessRuleList;
/**
* The API specification.
*/
private APISpec _apiSpecification;
/**
* The local IP address.
*/
private String _localIPAddress;
/**
* Mapping from function name to the call ID for all meta-functions. This
* field is never null
.
*/
private HashMap _metaFunctionCallIDs;
/**
* Flag indicating that the API is down for maintenance.
*/
private boolean _apiDisabled;
/**
* Constructs a new API
object.
*
* @param name
* the name of the API, cannot be null
nor can it be an
* empty string.
*
* @throws IllegalArgumentException
* if name == null
* || name.{@link String#length() length()} < 1
.
*/
protected API(String name)
throws IllegalArgumentException {
// Check preconditions
MandatoryArgumentChecker.check("name", name);
if (name.length() < 1) {
String message = "name.length() == "
+ name.length();
throw new IllegalArgumentException(message);
}
// Initialize fields
_name = name;
_startupTimestamp = System.currentTimeMillis();
_manageableObjects = new ArrayList(20);
_functionsByName = new HashMap(89);
_functionList = new ArrayList(80);
_emptyProperties = new RuntimeProperties();
_timeZone = TimeZone.getDefault();
_localIPAddress = IPAddressUtils.getLocalHostIPAddress();
_apiDisabled = false;
// Initialize mapping from meta-function to call ID
_metaFunctionCallIDs = new HashMap(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());
}
/**
* Gets the name of this API.
*
* @return
* the name of this API, never null
and never an empty
* string.
*/
public final String getName() {
return _name;
}
/**
* Gets the list of the functions of this API.
*
* @return
* the functions of this API as a {@link List} of {@link Function} objects, never null
.
*
* @since XINS 1.5.0.
*/
public final List getFunctionList() {
return _functionList;
}
/**
* Gets the bootstrap properties specified for the API.
*
* @return
* the bootstrap properties, cannot be null
.
*
* @since XINS 1.5.0.
*/
public Map getBootstrapProperties() {
return _buildSettings;
}
/**
* Gets the API runtime properties.
*
* @return
* the runtime properties, cannot be null
.
*/
Map getRuntimeProperties() {
return _runtimeSettings;
}
/**
* Gets the runtime properties specified in the implementation.
*
* @return
* the runtime properties for the API, cannot be null
.
*/
public RuntimeProperties getProperties() {
// This method is overridden by the APIImpl to return the generated
// RuntimeProperties class which contains the runtime properties.
return _emptyProperties;
}
/**
* Gets the timestamp that indicates when this API
instance
* was created.
*
* @return
* the time this instance was constructed, as a number of milliseconds
* since the
* UNIX Epoch.
*/
public final long getStartupTimestamp() {
return _startupTimestamp;
}
/**
* Returns the applicable time zone.
*
* @return
* the time zone, never null
.
*/
public final TimeZone getTimeZone() {
return _timeZone;
}
/**
* 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.0.
*/
public final InputStream getResourceAsStream(String path) throws IllegalArgumentException {
return _engine.getResourceAsStream(path);
}
/**
* Bootstraps this API (wrapper method). This method calls
* {@link #bootstrapImpl2(Map)}.
*
* @param buildSettings
* the build-time configuration properties, not null
.
*
* @throws IllegalStateException
* if this API is currently not bootstraping.
*
* @throws MissingRequiredPropertyException
* if a required property is not given.
*
* @throws InvalidPropertyValueException
* if a property has an invalid value.
*
* @throws BootstrapException
* if the bootstrap fails.
*/
@Override
protected final void bootstrapImpl(Map buildSettings)
throws IllegalStateException,
MissingRequiredPropertyException,
InvalidPropertyValueException,
BootstrapException {
// Check state
Manageable.State state = getState();
if (state != BOOTSTRAPPING) {
String message = "State is " + state.getName() + " instead of BOOTSTRAPPING.";
Utils.logProgrammingError(message);
throw new IllegalStateException(message);
}
// Log the time zone
String tzShortName = _timeZone.getDisplayName(false, TimeZone.SHORT);
String tzLongName = _timeZone.getDisplayName(false, TimeZone.LONG);
Log.log_3404(tzShortName, tzLongName);
// Store the build-time settings
_buildSettings = buildSettings;
// Get build-time properties
_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);
// Skip check if build version is not set
if (_buildVersion == null) {
// fall through
// Check if build version identifies a production release of XINS
} else if (! Library.isProductionRelease(_buildVersion)) {
Log.log_3228(_buildVersion);
}
// Let the subclass perform initialization
// TODO: What if bootstrapImpl2 throws an unexpected exception?
bootstrapImpl2(buildSettings);
// Bootstrap all instances
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);
// Missing property
} catch (MissingRequiredPropertyException exception) {
Log.log_3215(_name, className, exception.getPropertyName(),
exception.getDetail());
throw exception;
// Invalid property
} catch (InvalidPropertyValueException exception) {
Log.log_3216(_name,
className,
exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
throw exception;
// Catch BootstrapException and any other exceptions not caught
// by previous catch statements
} catch (Throwable exception) {
// Log event
Log.log_3217(exception, _name, className);
// Throw a BootstrapException. If necessary, wrap around the
// caught exception
if (exception instanceof BootstrapException) {
throw (BootstrapException) exception;
} else {
throw new BootstrapException(exception);
}
}
}
// Bootstrap all functions
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);
// Missing required property
} catch (MissingRequiredPropertyException exception) {
Log.log_3222(_name, functionName, exception.getPropertyName(),
exception.getDetail());
throw exception;
// Invalid property value
} catch (InvalidPropertyValueException exception) {
Log.log_3223(_name,
functionName,
exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
throw exception;
// Catch BootstrapException and any other exceptions not caught
// by previous catch statements
} catch (Throwable exception) {
// Log this event
Log.log_3224(exception, _name, functionName);
// Throw a BootstrapException. If necessary, wrap around the
// caught exception
if (exception instanceof BootstrapException) {
throw (BootstrapException) exception;
} else {
throw new BootstrapException(exception);
}
}
}
}
/**
* Bootstraps this API (implementation method).
*
* The implementation of this method in class {@link API} is empty.
* Custom subclasses can perform any necessary bootstrapping in this
* class.
*
* Note that bootstrapping and initialization are different. Bootstrap
* includes only the one-time configuration of the API based on the
* build-time settings, while the initialization
*
* The {@link #add(Manageable)} may be called from this method,
* and from this method only.
*
* @param buildSettings
* the build-time properties, guaranteed not to be null
.
*
* @throws MissingRequiredPropertyException
* if a required property is not given.
*
* @throws InvalidPropertyValueException
* if a property has an invalid value.
*
* @throws BootstrapException
* if the bootstrap fails.
*/
protected void bootstrapImpl2(Map buildSettings)
throws MissingRequiredPropertyException,
InvalidPropertyValueException,
BootstrapException {
// empty
}
/**
* Stores a reference to the Engine
that owns this
* API
object.
*
* @param engine
* the {@link Engine} instance, should not be null
.
*/
void setEngine(Engine engine) {
_engine = engine;
}
/**
* Triggers re-initialization of this API. This method is meant to be
* called by API function implementations when it is anticipated that the
* API should be re-initialized.
*/
protected final void reinitializeImpl() {
_engine.initAPI();
}
/**
* Initializes this API.
*
* @param runtimeSettings
* the runtime configuration settings, cannot be null
.
*
* @throws MissingRequiredPropertyException
* if a required property is missing.
*
* @throws InvalidPropertyValueException
* if a property has an invalid value.
*
* @throws InitializationException
* if the initialization failed for some other reason.
*
* @throws IllegalStateException
* if this API is currently not initializing.
*/
protected final void initImpl(Map runtimeSettings)
throws MissingRequiredPropertyException,
InvalidPropertyValueException,
InitializationException,
IllegalStateException {
Log.log_3405(_name);
// Store runtime settings
_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.");
}
}
// Initialize ACL subsystem
// First with the API specific access rule list
if (_apiAccessRuleList != null) {
_apiAccessRuleList.dispose();
}
_apiAccessRuleList = createAccessRuleList(runtimeSettings, ACL_PROPERTY + '.' + _name, interval);
// Then read the generic access rule list
if (_accessRuleList != null) {
_accessRuleList.dispose();
}
_accessRuleList = createAccessRuleList(runtimeSettings, ACL_PROPERTY, interval);
// Initialize the RuntimeProperties object.
getProperties().init(runtimeSettings);
// Initialize all instances
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);
// Missing required property
} catch (MissingRequiredPropertyException exception) {
Log.log_3418(_name, className, exception.getPropertyName(),
exception.getDetail());
throw exception;
// Invalid property value
} catch (InvalidPropertyValueException exception) {
Log.log_3419(_name,
className,
exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
throw exception;
// Catch InitializationException and any other exceptions not caught
// by previous catch statements
} catch (Throwable exception) {
// Log this event
Log.log_3420(exception, _name, className);
if (exception instanceof InitializationException) {
throw (InitializationException) exception;
} else {
throw new InitializationException(exception);
}
}
}
// Initialize all functions
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);
// Missing required property
} catch (MissingRequiredPropertyException exception) {
Log.log_3423(_name, functionName, exception.getPropertyName(),
exception.getDetail());
throw exception;
// Invalid property value
} catch (InvalidPropertyValueException exception) {
Log.log_3424(_name,
functionName,
exception.getPropertyName(),
exception.getPropertyValue(),
exception.getReason());
throw exception;
// Catch InitializationException and any other exceptions not caught
// by previous catch statements
} catch (Throwable exception) {
// Log this event
Log.log_3425(exception, _name, functionName);
// Throw an InitializationException. If necessary, wrap around the
// caught exception
if (exception instanceof InitializationException) {
throw (InitializationException) exception;
} else {
throw new InitializationException(exception);
}
}
}
Log.log_3406(_name);
}
/**
* Creates the access rule list for the given property.
*
* @param runtimeSettings
* the runtime properties, never null
.
*
* @param aclProperty
* the ACL property, never null
*
* @param interval
* the interval in seconds to chack if the ACL file has changed and
* should be reloaded.
*
* @return
* the access rule list created from the property value, never null
.
*
* @throws InvalidPropertyValueException
* if the value for the property is invalid.
*/
private AccessRuleList createAccessRuleList(Map runtimeSettings,
String aclProperty, int interval)
throws InvalidPropertyValueException {
String acl = runtimeSettings.get(aclProperty);
// New access control list is empty
if (acl == null || acl.trim().length() < 1) {
if (aclProperty.equals(ACL_PROPERTY)) {
Log.log_3426(aclProperty);
}
return AccessRuleList.EMPTY;
// New access control list is non-empty
} else {
// Parse the new ACL
try {
AccessRuleList accessRuleList =
AccessRuleList.parseAccessRuleList(acl, interval);
int ruleCount = accessRuleList.getRuleCount();
Log.log_3427(ruleCount);
return accessRuleList;
// Parsing failed
} catch (ParseException exception) {
String exceptionMessage = exception.getMessage();
Log.log_3428(aclProperty, acl, exceptionMessage);
throw new InvalidPropertyValueException(aclProperty,
acl,
exceptionMessage);
}
}
}
/**
* Adds the specified manageable object. It will not immediately be
* bootstrapped and initialized.
*
* @param m
* the manageable object to add, not null
.
*
* @throws IllegalStateException
* if this API is currently not bootstrapping.
*
* @throws IllegalArgumentException
* if instance == null
.
*/
protected final void add(Manageable m)
throws IllegalStateException,
IllegalArgumentException {
// Check state
Manageable.State state = getState();
if (state != BOOTSTRAPPING) {
String message = "State is "
+ state
+ " instead of "
+ BOOTSTRAPPING
+ '.';
Utils.logProgrammingError(message);
throw new IllegalStateException(message);
}
// Check preconditions
MandatoryArgumentChecker.check("m", m);
String className = m.getClass().getName();
Log.log_3218(_name, className);
// Store the manageable object in the list
_manageableObjects.add(m);
}
/**
* Performs shutdown of this XINS API. This method will never throw any
* exception.
*/
protected final void deinitImpl() {
// Deinitialize instances
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();
// Deinitialize functions
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);
}
}
}
/**
* Callback method invoked when a function is constructed.
*
* @param function
* the function that is added, not null
.
*
* @throws NullPointerException
* if function == null
.
*
* @throws IllegalStateException
* if this API state is incorrect.
*/
final void functionAdded(Function function)
throws NullPointerException, IllegalStateException {
// Check state
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);
}
/**
* Returns the function with the specified name.
*
* @param name
* the name of the function, will not be checked if it is
* null
.
*
* @return
* the function with the specified name, or null
if there
* is no match.
*/
final Function getFunction(String name) {
return _functionsByName.get(name);
}
/**
* Get the specification of the API.
*
* @return
* the {@link APISpec} specification object, never null
.
*
* @throws InvalidSpecificationException
* if the specification cannot be found or is invalid.
*
* @since XINS 1.3.0
*/
public final APISpec getAPISpecification()
throws InvalidSpecificationException {
if (_apiSpecification == null) {
String baseURL = _engine.getFileLocation("/WEB-INF/specs/");
_apiSpecification = new APISpec(getClass(), baseURL);
}
return _apiSpecification;
}
/**
* Determines if the specified IP address is allowed to access the
* specified function, returning a boolean
value.
*
* This method finds the first matching rule and then returns the
* allow property of that rule (see
* {@link AccessRule#isAllowRule()}). If there is no matching rule, then
* false
is returned.
*
* @param ip
* the IP address, cannot be null
.
*
* @param functionName
* the name of the function, cannot be null
.
*
* @param conventionName
* the name of the calling convention, can be null
.
*
* @return
* true
if the request is allowed, false
if
* the request is denied.
*
* @throws IllegalArgumentException
* if ip == null || functionName == null
.
*
* @since XINS 2.1.
*/
public boolean allow(String ip, String functionName, String conventionName)
throws IllegalArgumentException {
// If no property is defined only localhost is allowed
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;
}
// Match an access rule
Boolean allowed;
try {
// First check with the API specific one, then use the generic one.
allowed = _apiAccessRuleList.isAllowed(ip, functionName, conventionName);
if (allowed == null) {
allowed = _accessRuleList.isAllowed(ip, functionName, conventionName);
}
// If the IP address cannot be parsed there is a programming error
// somewhere
} catch (ParseException exception) {
String detail = "Malformed IP address: \"" + ip + "\".";
throw Utils.logProgrammingError(detail, exception);
}
// If there is a match, return the allow-indication
if (allowed != null) {
return allowed.booleanValue();
}
// No matching access rule match, do not allow
Log.log_3553(ip, functionName, conventionName);
return false;
}
/**
* Forwards a call to a function. The call will actually be handled by
* {@link Function#handleCall(FunctionRequest)}.
*
* @param functionRequest
* the function request, never null
.
*
* @param cc
* the calling convention to use to handle the call, never null
.
*
* @return
* the result of the call, never null
.
*
* @throws IllegalStateException
* if this object is currently not initialized.
*
* @throws NullPointerException
* if functionRequest == null
or cc == null
.
*
* @throws NoSuchFunctionException
* if there is no matching function for the specified request.
*
* @throws AccessDeniedException
* if access is denied for the specified combination of IP address and
* function name.
*/
final FunctionResult handleCall(FunctionRequest functionRequest,
CallingConvention cc)
throws IllegalStateException,
NullPointerException,
NoSuchFunctionException,
AccessDeniedException {
// Check state first
assertUsable();
// Determine the function name
String functionName = functionRequest.getFunctionName();
// Check the access rule list
String ip = (String) functionRequest.getBackpack().get(BackpackConstants.IP);
boolean allow = allow(ip, functionName, cc.getConventionName());
if (! allow) {
throw new AccessDeniedException(ip, functionName, cc.getConventionName());
}
// Handle meta-functions
FunctionResult result;
if (functionName.length() > 0 && functionName.charAt(0) == '_') {
// Determine the call ID
int callID;
AtomicInteger counter = _metaFunctionCallIDs.get(functionName);
if (counter == null) {
throw new NoSuchFunctionException(functionName);
} else {
callID = counter.incrementAndGet();
}
// Call the meta-function
try {
result = callMetaFunction(functionName, functionRequest);
} catch (Throwable exception) {
result = handleFunctionException(functionRequest, callID, exception);
}
// Handle normal functions
} 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;
}
/**
* Handles a call to a meta-function.
*
* @param functionName
* the name of the meta-function, cannot be null
and must
* start with the underscore character '_'
.
*
* @param functionRequest
* the function request, never null
.
*
* @return
* the result of the function call, never null
.
*
* @throws NoSuchFunctionException
* if there is no meta-function by the specified name.
*/
private FunctionResult callMetaFunction(String functionName,
FunctionRequest functionRequest)
throws NoSuchFunctionException {
FunctionResult result;
// No Operation
if ("_NoOp".equals(functionName)) {
result = SUCCESSFUL_RESULT;
// Retrieve function list
} else if ("_GetFunctionList".equals(functionName)) {
result = doGetFunctionList();
// Get function call quantity and performance statistics
} else if ("_GetStatistics".equals(functionName)) {
// Determine value of 'detailed' argument
String detailedArg = functionRequest.getParameters().get("detailed");
boolean detailed = !"false".equals(detailedArg);
// Determine the name of the specific function, if any
String targetFunction = functionRequest.getParameters().get("targetFunction");
// Get the statistics
result = doGetStatistics(detailed, targetFunction);
// Determine value of 'reset' argument
String resetArg = functionRequest.getParameters().get("reset");
boolean reset = "true".equals(resetArg);
if (reset) {
doResetStatistics();
}
// Get version information
} else if ("_GetVersion".equals(functionName)) {
result = doGetVersion();
// Check links to underlying systems
} else if ("_CheckLinks".equals(functionName)) {
result = doCheckLinks();
// Retrieve configuration settings
} else if ("_GetSettings".equals(functionName)) {
result = doGetSettings();
// Disable a function
} else if ("_DisableFunction".equals(functionName)) {
String disabledFunction = functionRequest.getParameters().get("functionName");
result = doDisableFunction(disabledFunction);
// Enable a function
} else if ("_EnableFunction".equals(functionName)) {
String enabledFunction = functionRequest.getParameters().get("functionName");
result = doEnableFunction(enabledFunction);
// Reset the statistics
} else if ("_ResetStatistics".equals(functionName)) {
result = doResetStatistics();
// Reload the runtime properties
} else if ("_ReloadProperties".equals(functionName)) {
_engine.reloadPropertiesIfChanged();
result = SUCCESSFUL_RESULT;
// Retrieve eggs
} else if ("_IWantTheEasterEggs".equals(functionName)) {
result = SUCCESSFUL_RESULT;
// Return the WSDL description of the API
} else if ("_WSDL".equals(functionName)) {
result = SUCCESSFUL_RESULT;
// Return the SMD (Simple Method Description) description of the API
} else if ("_SMD".equals(functionName)) {
result = SUCCESSFUL_RESULT;
// Disable the API
} else if ("_DisableAPI".equals(functionName)) {
_apiDisabled = true;
result = SUCCESSFUL_RESULT;
// Enable the API
} else if ("_EnableAPI".equals(functionName)) {
_apiDisabled = false;
result = SUCCESSFUL_RESULT;
// Meta-function does not exist
} else {
throw new NoSuchFunctionException(functionName);
}
return result;
}
/**
* Handles an exception caught while a function was executed.
*
* @param functionRequest
* the request, never null
.
*
* @param callID
* the call identifier, never null
.
*
* @param exception
* the exception caught, never null
.
*
* @return
* the call result, never null
.
*/
FunctionResult handleFunctionException(FunctionRequest functionRequest,
int callID,
Throwable exception) {
Log.log_3500(exception, _name, callID);
// Create a set of parameters for the result
Map resultParams = new HashMap();
// Add the exception class
String exceptionClass = exception.getClass().getName();
resultParams.put("_exception.class", exceptionClass);
// Add the exception message, if any
String exceptionMessage = exception.getMessage();
if (exceptionMessage != null) {
exceptionMessage = exceptionMessage.trim();
if (exceptionMessage.length() > 0) {
resultParams.put("_exception.message", exceptionMessage);
}
}
// Add the stack trace, if any
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);
}
/**
* Returns a list of all functions in this API. Per function the name and
* the version are returned.
*
* @return
* the call result, never null
.
*/
private FunctionResult doGetFunctionList() {
FunctionResult builder = new FunctionResult();
// Loop over all functions
int count = _functionList.size();
for (int i = 0; i < count; i++) {
// Get some details about the function
Function function = _functionList.get(i);
String name = function.getName();
String version = function.getVersion();
String enabled = function.isEnabled()
? "true"
: "false";
// Add an element describing the function
Element functionElem = builder.getDataElementBuilder().createElement("function");
functionElem.setAttribute("name", name );
functionElem.setAttribute("version", version);
functionElem.setAttribute("enabled", enabled);
builder.getDataElement().appendChild(functionElem);
}
return builder;
}
/**
* Returns the call statistics for all functions in this API.
*
* @param detailed
* If true
, the unsuccessful result will be returned sorted
* per error code. Otherwise the unsuccessful result won't be displayed
* by error code.
*
* @param functionName
* the name of the specific function to return the statistics for,
* if null
, then the stats for all functions are returned.
*
* @return
* the call result, never null
.
*/
private FunctionResult doGetStatistics(boolean detailed, String functionName) {
StatisticsInterceptor statInterceptor = getStatisticInterceptor();
FunctionResult result = statInterceptor.getStatistics(detailed, functionName);
return result;
}
/**
* Returns the XINS version.
*
* @return
* the call result, never null
.
*/
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;
}
/**
* Returns the links in linked system components. It uses the
* {@link CheckLinks} to connect to each link and builds a
* {@link FunctionResult} which will have the total link count and total
* link failures.
*
* @return
* the call result, never null
.
*/
private FunctionResult doCheckLinks() {
return CheckLinks.checkLinks(getProperties().descriptors());
}
/**
* Returns the settings.
*
* @return
* the call result, never null
.
*/
private FunctionResult doGetSettings() {
FunctionResult builder = new FunctionResult();
// Build settings
Element build = builder.getDataElementBuilder().createElement("build");
for (Map.Entry 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);
// Runtime settings
Element runtime = builder.getDataElementBuilder().createElement("runtime");
for (Map.Entry 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);
// System properties
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;
}
/**
* Enables a function.
*
* @param functionName
* the name of the function to disable, can be null
.
*
* @return
* the call result, never null
.
*/
private FunctionResult doEnableFunction(String functionName) {
// Get the name of the function to enable
if (functionName == null || functionName.length() < 1) {
InvalidRequestResult invalidRequest = new InvalidRequestResult();
invalidRequest.addMissingParameter("functionName");
return invalidRequest;
}
// Get the Function object
Function function = getFunction(functionName);
if (function == null) {
return new InvalidRequestResult();
}
// Enable or disable the function
function.setEnabled(true);
return SUCCESSFUL_RESULT;
}
/**
* Disables a function.
*
* @param functionName
* the name of the function to disable, can be null
.
*
* @return
* the call result, never null
.
*/
private FunctionResult doDisableFunction(String functionName) {
// Get the name of the function to disable
if (functionName == null || functionName.length() < 1) {
InvalidRequestResult invalidRequest = new InvalidRequestResult();
invalidRequest.addMissingParameter("functionName");
return invalidRequest;
}
// Get the Function object
Function function = getFunction(functionName);
if (function == null) {
return new InvalidRequestResult();
}
// Enable or disable the function
function.setEnabled(false);
return SUCCESSFUL_RESULT;
}
/**
* Resets the statistics.
*
* @return
* the call result, never null
.
*/
private FunctionResult doResetStatistics() {
StatisticsInterceptor statInterceptor = getStatisticInterceptor();
FunctionResult result = statInterceptor.resetStatistics();
return result;
}
StatisticsInterceptor getStatisticInterceptor() {
List interceptors = _engine.getInterceptorManager().getInterceptors();
for (Interceptor interceptor : interceptors) {
if (interceptor instanceof StatisticsInterceptor) {
return (StatisticsInterceptor) interceptor;
}
}
return null;
}
/**
* Indicates whether the API is down for maintenance or not.
*
* @return
* true
if the API is disable, false
otherwise.
*/
boolean isDisabled() {
return _apiDisabled;
}
}