/* * 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; } } }