001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.remoting.support;
018
019import org.springframework.aop.framework.ProxyFactory;
020import org.springframework.aop.framework.adapter.AdvisorAdapterRegistry;
021import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry;
022import org.springframework.util.Assert;
023import org.springframework.util.ClassUtils;
024
025/**
026 * Abstract base class for classes that export a remote service.
027 * Provides "service" and "serviceInterface" bean properties.
028 *
029 * <p>Note that the service interface being used will show some signs of
030 * remotability, like the granularity of method calls that it offers.
031 * Furthermore, it has to have serializable arguments etc.
032 *
033 * @author Juergen Hoeller
034 * @since 26.12.2003
035 */
036public abstract class RemoteExporter extends RemotingSupport {
037
038        private Object service;
039
040        private Class<?> serviceInterface;
041
042        private Boolean registerTraceInterceptor;
043
044        private Object[] interceptors;
045
046
047        /**
048         * Set the service to export.
049         * Typically populated via a bean reference.
050         */
051        public void setService(Object service) {
052                this.service = service;
053        }
054
055        /**
056         * Return the service to export.
057         */
058        public Object getService() {
059                return this.service;
060        }
061
062        /**
063         * Set the interface of the service to export.
064         * The interface must be suitable for the particular service and remoting strategy.
065         */
066        public void setServiceInterface(Class<?> serviceInterface) {
067                Assert.notNull(serviceInterface, "'serviceInterface' must not be null");
068                Assert.isTrue(serviceInterface.isInterface(), "'serviceInterface' must be an interface");
069                this.serviceInterface = serviceInterface;
070        }
071
072        /**
073         * Return the interface of the service to export.
074         */
075        public Class<?> getServiceInterface() {
076                return this.serviceInterface;
077        }
078
079        /**
080         * Set whether to register a RemoteInvocationTraceInterceptor for exported
081         * services. Only applied when a subclass uses {@code getProxyForService}
082         * for creating the proxy to expose.
083         * <p>Default is "true". RemoteInvocationTraceInterceptor's most important value
084         * is that it logs exception stacktraces on the server, before propagating an
085         * exception to the client. Note that RemoteInvocationTraceInterceptor will <i>not</i>
086         * be registered by default if the "interceptors" property has been specified.
087         * @see #setInterceptors
088         * @see #getProxyForService
089         * @see RemoteInvocationTraceInterceptor
090         */
091        public void setRegisterTraceInterceptor(boolean registerTraceInterceptor) {
092                this.registerTraceInterceptor = registerTraceInterceptor;
093        }
094
095        /**
096         * Set additional interceptors (or advisors) to be applied before the
097         * remote endpoint, e.g. a PerformanceMonitorInterceptor.
098         * <p>You may specify any AOP Alliance MethodInterceptors or other
099         * Spring AOP Advices, as well as Spring AOP Advisors.
100         * @see #getProxyForService
101         * @see org.springframework.aop.interceptor.PerformanceMonitorInterceptor
102         */
103        public void setInterceptors(Object[] interceptors) {
104                this.interceptors = interceptors;
105        }
106
107
108        /**
109         * Check whether the service reference has been set.
110         * @see #setService
111         */
112        protected void checkService() throws IllegalArgumentException {
113                Assert.notNull(getService(), "Property 'service' is required");
114        }
115
116        /**
117         * Check whether a service reference has been set,
118         * and whether it matches the specified service.
119         * @see #setServiceInterface
120         * @see #setService
121         */
122        protected void checkServiceInterface() throws IllegalArgumentException {
123                Class<?> serviceInterface = getServiceInterface();
124                Assert.notNull(serviceInterface, "Property 'serviceInterface' is required");
125
126                Object service = getService();
127                if (service instanceof String) {
128                        throw new IllegalArgumentException("Service [" + service + "] is a String " +
129                                        "rather than an actual service reference: Have you accidentally specified " +
130                                        "the service bean name as value instead of as reference?");
131                }
132                if (!serviceInterface.isInstance(service)) {
133                        throw new IllegalArgumentException("Service interface [" + serviceInterface.getName() +
134                                        "] needs to be implemented by service [" + service + "] of class [" +
135                                        service.getClass().getName() + "]");
136                }
137        }
138
139        /**
140         * Get a proxy for the given service object, implementing the specified
141         * service interface.
142         * <p>Used to export a proxy that does not expose any internals but just
143         * a specific interface intended for remote access. Furthermore, a
144         * {@link RemoteInvocationTraceInterceptor} will be registered (by default).
145         * @return the proxy
146         * @see #setServiceInterface
147         * @see #setRegisterTraceInterceptor
148         * @see RemoteInvocationTraceInterceptor
149         */
150        protected Object getProxyForService() {
151                checkService();
152                checkServiceInterface();
153
154                ProxyFactory proxyFactory = new ProxyFactory();
155                proxyFactory.addInterface(getServiceInterface());
156
157                if (this.registerTraceInterceptor != null ? this.registerTraceInterceptor : this.interceptors == null) {
158                        proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
159                }
160                if (this.interceptors != null) {
161                        AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
162                        for (Object interceptor : this.interceptors) {
163                                proxyFactory.addAdvisor(adapterRegistry.wrap(interceptor));
164                        }
165                }
166
167                proxyFactory.setTarget(getService());
168                proxyFactory.setOpaque(true);
169
170                return proxyFactory.getProxy(getBeanClassLoader());
171        }
172
173        /**
174         * Return a short name for this exporter.
175         * Used for tracing of remote invocations.
176         * <p>Default is the unqualified class name (without package).
177         * Can be overridden in subclasses.
178         * @see #getProxyForService
179         * @see RemoteInvocationTraceInterceptor
180         * @see org.springframework.util.ClassUtils#getShortName
181         */
182        protected String getExporterName() {
183                return ClassUtils.getShortName(getClass());
184        }
185
186}