/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed 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 org.springframework.flex.remoting;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.flex.core.AbstractDestinationFactory;
import org.springframework.flex.core.MessageBrokerFactoryBean;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import flex.messaging.Destination;
import flex.messaging.FactoryInstance;
import flex.messaging.FlexFactory;
import flex.messaging.MessageBroker;
import flex.messaging.config.ConfigMap;
import flex.messaging.services.RemotingService;
import flex.messaging.services.Service;
import flex.messaging.services.ServiceAdapter;
import flex.messaging.services.remoting.RemotingDestination;
import flex.messaging.services.remoting.adapters.JavaAdapter;
import flex.messaging.services.remoting.adapters.RemotingMethod;
/**
* An factory for exposing a Spring-managed bean to a Flex client for direct remoting calls.
*
* <p>
* The exported service will be exposed to the Flex client as a BlazeDS remoting service destination. By default, the
* destination id will be the same as the bean name of this factory. This may be overridden using the serviceId
* property. <i>Note that this convention is slightly different from that employed by the <code>remote-service</code>
* xml config tag. See the xsd docs for details.</i>
*
* <p>
* The methods on the exported service that are exposed to the Flex client can be controlled using the includeMethods
* and excludeMethods properties.
* </p>
*
* @see MessageBrokerFactoryBean
*
* @author Jeremy Grelle
* @author Mark Fisher
*/
public class RemotingDestinationExporter extends AbstractDestinationFactory implements FlexFactory {
private static final Log log = LogFactory.getLog(RemotingDestinationExporter.class);
private Object service;
private String[] includeMethods;
private String[] excludeMethods;
private Class<?> sourceClass = null;
/**
*
* {@inheritDoc}
*/
public FactoryInstance createFactoryInstance(String id, ConfigMap properties) {
return new ServiceFactoryInstance(this, id, properties);
}
public void initialize(String id, ConfigMap configMap) {
// No-op
}
/**
* Lookup will be handled directly by the created FactoryInstance
*
*/
public Object lookup(FactoryInstance instanceInfo) {
throw new UnsupportedOperationException("FlexFactory.lookup");
}
/**
* Sets the methods to be excluded from the bean being exported
*
* @param excludeMethods the methods to exclude
*/
public void setExcludeMethods(String[] excludeMethods) {
this.excludeMethods = StringUtils.trimArrayElements(excludeMethods);
}
/**
* Sets the methods to included from the bean being exported
*
* @param includeMethods the methods to include
*/
public void setIncludeMethods(String[] includeMethods) {
this.includeMethods = StringUtils.trimArrayElements(includeMethods);
}
/**
* Sets the bean being exported.
*
* Supports setting either a direct bean instance reference, or the String identifier of the bean, to be looked up
* from the BeanFactory. The latter is preferred, as it allows more accurate calculation of the "source" property of
* the destination, required for tooling introspection of the destination.
*
* @param service the bean being exported
*/
public void setService(Object service) {
this.service = service;
}
/**
*
* {@inheritDoc}
*/
@Override
protected Destination createDestination(String destinationId, MessageBroker broker) {
Assert.notNull(this.service, "The 'service' property is required.");
String source = null;
if (this.service instanceof String) {
String beanId = (String) service;
this.service = getBeanFactory().getBean(beanId);
this.sourceClass = AopUtils.getTargetClass(this.service);
//As of Spring 3.0.2, AopUtils is guaranteed not to return null
if (this.sourceClass == null || Proxy.isProxyClass(this.sourceClass)) {
this.sourceClass = getBeanFactory().getType(beanId);
}
} else {
this.sourceClass = AopUtils.getTargetClass(this.service);
}
if (this.sourceClass != null) {
source = this.sourceClass.getName();
} else {
if (log.isWarnEnabled()) {
log.warn("The source class being exported as RemotingDestination with id '"+destinationId+"' cannot be calculated.");
}
}
// Look up the remoting service
RemotingService remotingService = (RemotingService) broker.getServiceByType(RemotingService.class.getName());
Assert.notNull(remotingService, "Could not find a proper RemotingService in the Flex MessageBroker.");
// Register and start the destination
RemotingDestination destination = (RemotingDestination) remotingService.createDestination(destinationId);
destination.setFactory(this);
destination.setSource(source);
if (log.isInfoEnabled()) {
log.info("Created remoting destination with id '" + destinationId + "'");
}
return destination;
}
/**
*
* {@inheritDoc}
*/
@Override
protected void destroyDestination(String destinationId, MessageBroker broker) {
RemotingService remotingService = (RemotingService) broker.getServiceByType(RemotingService.class.getName());
if (remotingService == null) {
return;
}
if (log.isInfoEnabled()) {
log.info("Removing remoting destination '" + destinationId + "'");
}
remotingService.removeDestination(destinationId);
}
/**
*
* {@inheritDoc}
*/
@Override
protected Service getTargetService(MessageBroker broker) {
return broker.getServiceByType(RemotingService.class.getName());
}
/**
*
* {@inheritDoc}
*/
@Override
protected void initializeDestination(Destination destination) {
destination.start();
Assert.isInstanceOf(ServiceAdapter.class, destination.getAdapter(),
"Spring beans exported as a RemotingDestination require a ServiceAdapter.");
configureIncludes(destination);
configureExcludes(destination);
if (log.isInfoEnabled()) {
log.info("Remoting destination '" + destination.getId() + "' has been started started successfully.");
}
}
private void configureExcludes(Destination destination) {
if (this.excludeMethods == null) {
return;
}
JavaAdapter adapter = (JavaAdapter) destination.getAdapter();
for (RemotingMethod method : getRemotingMethods(this.excludeMethods)) {
adapter.addExcludeMethod(method);
}
}
private void configureIncludes(Destination destination) {
if (this.includeMethods == null) {
return;
}
JavaAdapter adapter = (JavaAdapter) destination.getAdapter();
for (RemotingMethod method : getRemotingMethods(this.includeMethods)) {
adapter.addIncludeMethod(method);
}
}
private List<RemotingMethod> getRemotingMethods(String[] methodNames) {
List<RemotingMethod> remotingMethods = new ArrayList<RemotingMethod>();
for (String name : methodNames) {
Class<?> classToCheck = this.sourceClass != null ? this.sourceClass : this.service.getClass();
Assert.isTrue(ClassUtils.hasAtLeastOneMethodWithName(classToCheck, name), "Could not find method with name '" + name
+ "' on the exported service of type " + classToCheck);
RemotingMethod method = new RemotingMethod();
method.setName(name);
remotingMethods.add(method);
}
return remotingMethods;
}
private final class ServiceFactoryInstance extends FactoryInstance {
public ServiceFactoryInstance(FlexFactory factory, String id, ConfigMap properties) {
super(factory, id, properties);
}
@Override
public Object lookup() {
return RemotingDestinationExporter.this.service;
}
}
}