blob: 4bbd3c4eb172294a6da33f0c421ec8e776ccf96d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package flex.messaging.services.remoting.adapters;
import flex.management.runtime.messaging.services.remoting.adapters.JavaAdapterControl;
import flex.messaging.FlexComponent;
import flex.messaging.Destination;
import flex.messaging.FactoryInstance;
import flex.messaging.FlexFactory;
import flex.messaging.MessageException;
import flex.messaging.config.ConfigMap;
import flex.messaging.config.ConfigurationConstants;
import flex.messaging.config.ConfigurationException;
import flex.messaging.config.SecurityConstraint;
import flex.messaging.messages.Message;
import flex.messaging.messages.RemotingMessage;
import flex.messaging.security.SecurityException;
import flex.messaging.services.ServiceAdapter;
import flex.messaging.services.remoting.RemotingDestination;
import flex.messaging.util.MethodMatcher;
import flex.messaging.util.MethodMatcher.Match;
import flex.messaging.util.ExceptionUtil;
import flex.messaging.util.StringUtils;
import flex.messaging.log.LogCategories;
import flex.messaging.log.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class JavaAdapter extends ServiceAdapter {
static final String LOG_CATEGORY = LogCategories.MESSAGE_REMOTING;
public static final String[] PROTECTED_PACKAGES = new String[]{"jrun", "jrunx", "macromedia",
"flex", "flex2", "coldfusion",
"allaire", "com.allaire", "com.macromedia"};
private static final int REMOTING_METHOD_NULL_NAME_ERRMSG = 10658;
private static final int REMOTING_METHOD_REFS_UNDEFINED_CONSTRAINT_ERRMSG = 10659;
private static final int REMOTING_METHOD_NOT_DEFINED_ERRMSG = 10660;
private static final String PROPERTY_INCLUDE_METHODS = "include-methods";
private static final String PROPERTY_EXCLUDE_METHODS = "exclude-methods";
private static final String METHOD_ELEMENT = "method";
private static final String NAME_ELEMENT = "name";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructs an unmanaged <code>JavaAdapter</code> instance.
*/
public JavaAdapter() {
this(false);
}
/**
* Constructs a <code>JavaAdapter</code> instance.
*
* @param enableManagement <code>true</code> if the <code>JavaAdapter</code> has a
* corresponding MBean control for management; otherwise <code>false</code>.
*/
public JavaAdapter(boolean enableManagement) {
super(enableManagement);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* The MBean control for this adapter.
*/
private JavaAdapterControl controller;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// destination
//----------------------------------
/**
* Casts the <code>Destination</code> into <code>RemotingDestination</code>
* and calls super.setDestination.
*
* @param destination remoting destination to associate with this adapter
*/
@Override
public void setDestination(Destination destination) {
Destination dest = (RemotingDestination) destination;
super.setDestination(dest);
}
//----------------------------------
// excludeMethods
//----------------------------------
private Map excludeMethods;
/**
* Returns an <tt>Iterator</tt> over the currently registered exclude methods.
*
* @return an <tt>Iterator</tt> over the currently registered exclude methods
*/
public Iterator getExcludeMethodIterator() {
return excludeMethods == null ? Collections.EMPTY_LIST.iterator() : excludeMethods.values().iterator();
}
/**
* Adds a method to the list of excluded methods for the adapter.
* Invocations of excluded methods are blocked.
*
* @param value method to exclude
*/
public void addExcludeMethod(RemotingMethod value) {
String name = value.getName();
if (name == null) {
ConfigurationException ce = new ConfigurationException();
ce.setMessage(REMOTING_METHOD_NULL_NAME_ERRMSG, new Object[]{getDestination().getId()});
throw ce;
}
// Validate that a method with this name is defined on the source class.
if (!isMethodDefinedBySource(name)) {
ConfigurationException ce = new ConfigurationException();
ce.setMessage(REMOTING_METHOD_NOT_DEFINED_ERRMSG, new Object[]{name, getDestination().getId()});
throw ce;
}
if (excludeMethods == null) {
excludeMethods = new HashMap();
excludeMethods.put(name, value);
} else if (!excludeMethods.containsKey(name)) {
excludeMethods.put(name, value);
}
}
/**
* Removes a method from the list of excluded methods for the adapter.
*
* @param value method to remove from exlcuded methods list
*/
public void removeExcludeMethod(RemotingMethod value) {
excludeMethods.remove(value.getName());
}
//----------------------------------
// includeMethods
//----------------------------------
private Map includeMethods;
/**
* Returns an <tt>Iterator</tt> over the currently registered include methods.
*
* @return an <tt>Iterator</tt> over the currently registered include methods
*/
public Iterator getIncludeMethodIterator() {
return includeMethods == null ? Collections.EMPTY_LIST.iterator() : includeMethods.values().iterator();
}
/**
* Adds a method to the list of included methods for the adapter.
* Invocations of included methods are allowed, and invocations of any non-included methods will be blocked.
*
* @param value method to include
*/
public void addIncludeMethod(RemotingMethod value) {
String name = value.getName();
if (name == null) {
ConfigurationException ce = new ConfigurationException();
ce.setMessage(REMOTING_METHOD_NULL_NAME_ERRMSG, new Object[]{getDestination().getId()});
throw ce;
}
// Validate that a method with this name is defined on the source class.
if (!isMethodDefinedBySource(name)) {
ConfigurationException ce = new ConfigurationException();
ce.setMessage(REMOTING_METHOD_NOT_DEFINED_ERRMSG, new Object[]{name, getDestination().getId()});
throw ce;
}
if (includeMethods == null) {
includeMethods = new HashMap();
includeMethods.put(name, value);
} else if (!includeMethods.containsKey(name)) {
includeMethods.put(name, value);
}
}
/**
* Removes a method from the list of included methods for the adapter.
*
* @param value method to remove from the included methods list
*/
public void removeIncludeMethod(RemotingMethod value) {
includeMethods.remove(value.getName());
}
//--------------------------------------------------------------------------
//
// Initialize, validate, start, and stop methods.
//
//--------------------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public void initialize(String id, ConfigMap properties) {
ConfigMap methodsToInclude = properties.getPropertyAsMap(PROPERTY_INCLUDE_METHODS, null);
if (methodsToInclude != null) {
List methods = methodsToInclude.getPropertyAsList(METHOD_ELEMENT, null);
if ((methods != null) && !methods.isEmpty()) {
int n = methods.size();
for (int i = 0; i < n; i++) {
ConfigMap methodSettings = (ConfigMap) methods.get(i);
String name = methodSettings.getPropertyAsString(NAME_ELEMENT, null);
RemotingMethod method = new RemotingMethod();
method.setName(name);
// Check for security constraint.
String constraintRef = methodSettings.getPropertyAsString(ConfigurationConstants.SECURITY_CONSTRAINT_ELEMENT, null);
if (constraintRef != null) {
try {
method.setSecurityConstraint(getDestination().getService().getMessageBroker().getSecurityConstraint(constraintRef));
} catch (SecurityException se) {
// Rethrow with a more descriptive message.
ConfigurationException ce = new ConfigurationException();
ce.setMessage(REMOTING_METHOD_REFS_UNDEFINED_CONSTRAINT_ERRMSG, new Object[]{name, getDestination().getId(), constraintRef});
throw ce;
}
}
addIncludeMethod(method);
}
}
}
ConfigMap methodsToExclude = properties.getPropertyAsMap(PROPERTY_EXCLUDE_METHODS, null);
if (methodsToExclude != null) {
// Warn that <exclude-properties> will be ignored.
if (includeMethods != null) {
RemotingDestination dest = (RemotingDestination) getDestination();
if (Log.isWarn())
Log.getLogger(LogCategories.CONFIGURATION).warn("The remoting destination '" + dest.getId() + "' contains both <include-methods/> and <exclude-methods/> configuration. The <exclude-methods/> block will be ignored.");
}
// Excludes must be processed regardless of whether we add them or not to avoid 'Unused tags in <properties>' exceptions.
List methods = methodsToExclude.getPropertyAsList(METHOD_ELEMENT, null);
if ((methods != null) && !methods.isEmpty()) {
int n = methods.size();
for (int i = 0; i < n; i++) {
ConfigMap methodSettings = (ConfigMap) methods.get(i);
String name = methodSettings.getPropertyAsString(NAME_ELEMENT, null);
RemotingMethod method = new RemotingMethod();
method.setName(name);
// Check for security constraint.
String constraintRef = methodSettings.getPropertyAsString(ConfigurationConstants.SECURITY_CONSTRAINT_ELEMENT, null);
// Conditionally add, only if include methods are not defined.
if (includeMethods == null) {
if (constraintRef != null) {
RemotingDestination dest = (RemotingDestination) getDestination();
if (Log.isWarn())
Log.getLogger(LogCategories.CONFIGURATION).warn("The method '" + name + "' for remoting destination '" + dest.getId() + "' is configured to use a security constraint, but security constraints are not applicable for excluded methods.");
}
addExcludeMethod(method);
}
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void start() {
if (isStarted()) {
return;
}
super.start();
validateInstanceSettings();
RemotingDestination remotingDestination = (RemotingDestination) getDestination();
if (FlexFactory.SCOPE_APPLICATION.equals(remotingDestination.getScope())) {
FactoryInstance factoryInstance = remotingDestination.getFactoryInstance();
createInstance(factoryInstance.getInstanceClass());
}
}
//--------------------------------------------------------------------------
//
// Other public APIs
//
//--------------------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public Object invoke(Message message) {
RemotingDestination remotingDestination = (RemotingDestination) getDestination();
RemotingMessage remotingMessage = (RemotingMessage) message;
FactoryInstance factoryInstance = remotingDestination.getFactoryInstance();
// We don't allow the client to specify the source for
// Java based services.
String className = factoryInstance.getSource();
remotingMessage.setSource(className);
String methodName = remotingMessage.getOperation();
List parameters = remotingMessage.getParameters();
Object result = null;
try {
// Test that the target method may be invoked based upon include/exclude method settings.
validateAgainstMethodFilters(methodName);
// Lookup and invoke.
Object instance = createInstance(factoryInstance.getInstanceClass());
if (instance == null) {
MessageException me = new MessageException("Null instance returned from: " + factoryInstance);
me.setCode("Server.Processing");
throw me;
}
Class c = instance.getClass();
MethodMatcher methodMatcher = remotingDestination.getMethodMatcher();
Method method = methodMatcher.getMethod(c, methodName, parameters);
result = method.invoke(instance, parameters.toArray());
saveInstance(instance);
} catch (InvocationTargetException ex) {
/*
* If the invocation exception wraps a message exception, unwrap it and
* rethrow the nested message exception. Otherwise, build and throw a new
* message exception.
*/
Throwable cause = ex.getCause();
if ((cause != null) && (cause instanceof MessageException)) {
throw (MessageException) cause;
} else if (cause != null) {
// Log a warning for this client's selector and continue
if (Log.isError()) {
Log.getLogger(LOG_CATEGORY).error("Error processing remote invocation: " +
cause.toString() + StringUtils.NEWLINE +
" incomingMessage: " + message + StringUtils.NEWLINE +
ExceptionUtil.toString(cause));
}
MessageException me = new MessageException(cause.getClass().getName() + " : " + cause.getMessage());
me.setCode("Server.Processing");
me.setRootCause(cause);
throw me;
} else {
MessageException me = new MessageException(ex.getMessage());
me.setCode("Server.Processing");
throw me;
}
} catch (IllegalAccessException ex) {
MessageException me = new MessageException(ex.getMessage());
me.setCode("Server.Processing");
throw me;
}
return result;
}
//--------------------------------------------------------------------------
//
// Protected/private APIs
//
//--------------------------------------------------------------------------
/**
* Checks if the method is allowed to be invoked, i.e., if it has been
* explicitly excluded, or if inclusions have been specified and it is not
* on the inclusion list.
*
* @throw MessageException if method is not allowed.
*/
protected void validateAgainstMethodFilters(String methodName) {
if (includeMethods != null) {
RemotingMethod method = (RemotingMethod) includeMethods.get(methodName);
if (method == null)
MethodMatcher.methodNotFound(methodName, null, new Match(null));
// Check method-level security constraint, if defined.
SecurityConstraint constraint = method.getSecurityConstraint();
if (constraint != null)
getDestination().getService().getMessageBroker().getLoginManager().checkConstraint(constraint);
} else if ((excludeMethods != null) && excludeMethods.containsKey(methodName))
MethodMatcher.methodNotFound(methodName, null, new Match(null));
}
/**
* This method returns the instance of the given class. You can override this in
* your subclass to control how the instance is constructed. Note that you can
* can more general control how components are created by implementing the
* flex.messaging.FlexFactory interface.
*
* @see flex.messaging.FlexFactory
*/
protected Object createInstance(Class cl) {
RemotingDestination remotingDestination = (RemotingDestination) getDestination();
// Note: this breaks the admin console right now as we use this to call
// mbean methods. Might have performance impact as well?
//assertAccess(cl.getName());
FactoryInstance factoryInstance = remotingDestination.getFactoryInstance();
Object instance = factoryInstance.lookup();
if (isStarted() && instance instanceof FlexComponent
&& !((FlexComponent) instance).isStarted()) {
((FlexComponent) instance).start();
}
return instance;
}
/**
* This method is called by the adapter after the remote method has been invoked.
* For session scoped components, by default FlexFactory provides an
* operationComplete method to implement this operation. For the JavaFactory,
* this sets the attribute in the FlexSession to trigger sesison replication
* for this attribute.
*/
protected void saveInstance(Object instance) {
RemotingDestination remotingDestination = (RemotingDestination) getDestination();
FactoryInstance factoryInstance = remotingDestination.getFactoryInstance();
factoryInstance.operationComplete(instance);
}
protected void assertAccess(String serviceClass) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// if there is a SecurityManager, check for specific access privileges on this class
if (serviceClass.indexOf('.') != -1) {
StringBuffer permissionData = new StringBuffer("accessClassInPackage.");
permissionData.append(serviceClass.substring(0, serviceClass.lastIndexOf('.')));
RuntimePermission perm = new RuntimePermission(permissionData.toString());
AccessController.checkPermission(perm);
}
} else {
// even without a SecurityManager, protect server packages
for (int i = 0; i < PROTECTED_PACKAGES.length; i++) {
if (serviceClass.startsWith(PROTECTED_PACKAGES[i])) {
StringBuffer permissionData = new StringBuffer("accessClassInPackage.");
permissionData.append(PROTECTED_PACKAGES[i].substring(0, PROTECTED_PACKAGES[i].length()));
RuntimePermission perm = new RuntimePermission(permissionData.toString());
AccessController.checkPermission(perm);
}
}
}
}
protected void validateInstanceSettings() {
RemotingDestination remotingDestination = (RemotingDestination) getDestination();
// This will validate that we have a valid factory instance and accesses
// any constructor properties needed for our factory so they do not give
// startup warnings.
remotingDestination.getFactoryInstance();
}
/**
* Returns the log category of the <code>JavaAdapter</code>.
*
* @return The log category.
*/
@Override
protected String getLogCategory() {
return LOG_CATEGORY;
}
/**
* Invoked automatically to allow the <code>JavaAdapter</code> to setup its corresponding
* MBean control.
*
* @param broker The <code>Destination</code> that manages this <code>JavaAdapter</code>.
*/
@Override
protected void setupAdapterControl(Destination destination) {
controller = new JavaAdapterControl(this, destination.getControl());
controller.register();
setControl(controller);
}
/**
* Tests whether the backing source class for this adapter defines a method with the specified name.
*
* @param methodName The method name.
* @return <code>true</code> if the method is defined; otherwise <code>false</code>.
*/
private boolean isMethodDefinedBySource(String methodName) {
RemotingDestination remotingDestination = (RemotingDestination) getDestination();
FactoryInstance factoryInstance = remotingDestination.getFactoryInstance();
Class c = factoryInstance.getInstanceClass();
if (c == null)
return true; // No source class; ignore validation and generate an error at runtime.
Method[] methods = c.getMethods();
int n = methods.length;
for (int i = 0; i < n; i++) {
if (methods[i].getName().equals(methodName))
return true;
}
return false;
}
}