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.ClassUtils;
023
024/**
025 * Abstract base class for classes that export a remote service.
026 * Provides "service" and "serviceInterface" bean properties.
027 *
028 * <p>Note that the service interface being used will show some signs of
029 * remotability, like the granularity of method calls that it offers.
030 * Furthermore, it has to have serializable arguments etc.
031 *
032 * @author Juergen Hoeller
033 * @since 26.12.2003
034 */
035public abstract class RemoteExporter extends RemotingSupport {
036
037        private Object service;
038
039        private Class<?> serviceInterface;
040
041        private Boolean registerTraceInterceptor;
042
043        private Object[] interceptors;
044
045
046        /**
047         * Set the service to export.
048         * Typically populated via a bean reference.
049         */
050        public void setService(Object service) {
051                this.service = service;
052        }
053
054        /**
055         * Return the service to export.
056         */
057        public Object getService() {
058                return this.service;
059        }
060
061        /**
062         * Set the interface of the service to export.
063         * The interface must be suitable for the particular service and remoting strategy.
064         */
065        public void setServiceInterface(Class<?> serviceInterface) {
066                if (serviceInterface != null && !serviceInterface.isInterface()) {
067                        throw new IllegalArgumentException("'serviceInterface' must be an interface");
068                }
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                if (getService() == null) {
114                        throw new IllegalArgumentException("Property 'service' is required");
115                }
116        }
117
118        /**
119         * Check whether a service reference has been set,
120         * and whether it matches the specified service.
121         * @see #setServiceInterface
122         * @see #setService
123         */
124        protected void checkServiceInterface() throws IllegalArgumentException {
125                Class<?> serviceInterface = getServiceInterface();
126                Object service = getService();
127                if (serviceInterface == null) {
128                        throw new IllegalArgumentException("Property 'serviceInterface' is required");
129                }
130                if (service instanceof String) {
131                        throw new IllegalArgumentException("Service [" + service + "] is a String " +
132                                        "rather than an actual service reference: Have you accidentally specified " +
133                                        "the service bean name as value instead of as reference?");
134                }
135                if (!serviceInterface.isInstance(service)) {
136                        throw new IllegalArgumentException("Service interface [" + serviceInterface.getName() +
137                                        "] needs to be implemented by service [" + service + "] of class [" +
138                                        service.getClass().getName() + "]");
139                }
140        }
141
142        /**
143         * Get a proxy for the given service object, implementing the specified
144         * service interface.
145         * <p>Used to export a proxy that does not expose any internals but just
146         * a specific interface intended for remote access. Furthermore, a
147         * {@link RemoteInvocationTraceInterceptor} will be registered (by default).
148         * @return the proxy
149         * @see #setServiceInterface
150         * @see #setRegisterTraceInterceptor
151         * @see RemoteInvocationTraceInterceptor
152         */
153        protected Object getProxyForService() {
154                checkService();
155                checkServiceInterface();
156
157                ProxyFactory proxyFactory = new ProxyFactory();
158                proxyFactory.addInterface(getServiceInterface());
159
160                if (this.registerTraceInterceptor != null ? this.registerTraceInterceptor : this.interceptors == null) {
161                        proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
162                }
163                if (this.interceptors != null) {
164                        AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
165                        for (Object interceptor : this.interceptors) {
166                                proxyFactory.addAdvisor(adapterRegistry.wrap(interceptor));
167                        }
168                }
169
170                proxyFactory.setTarget(getService());
171                proxyFactory.setOpaque(true);
172
173                return proxyFactory.getProxy(getBeanClassLoader());
174        }
175
176        /**
177         * Return a short name for this exporter.
178         * Used for tracing of remote invocations.
179         * <p>Default is the unqualified class name (without package).
180         * Can be overridden in subclasses.
181         * @see #getProxyForService
182         * @see RemoteInvocationTraceInterceptor
183         * @see org.springframework.util.ClassUtils#getShortName
184         */
185        protected String getExporterName() {
186                return ClassUtils.getShortName(getClass());
187        }
188
189}