001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community 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 * http://www.opensource.org/licenses/ecl2.php
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 */
016package org.kuali.rice.ksb.api.bus.support;
017
018import org.apache.commons.lang.ArrayUtils;
019import org.apache.commons.lang.StringUtils;
020import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
021import org.kuali.rice.core.api.resourceloader.ResourceLoaderException;
022import org.kuali.rice.ksb.api.KsbApiServiceLocator;
023import org.springframework.beans.factory.FactoryBean;
024import org.springframework.beans.factory.FactoryBeanNotInitializedException;
025import org.springframework.beans.factory.InitializingBean;
026
027import javax.xml.namespace.QName;
028import java.lang.reflect.InvocationHandler;
029import java.lang.reflect.InvocationTargetException;
030import java.lang.reflect.Method;
031import java.lang.reflect.Proxy;
032
033/**
034 * Loads a lazy proxy to a service from the {@link org.kuali.rice.ksb.api.bus.ServiceBus}.  This proxy is created based on either the
035 * proxy interfaces that are injected into this bean, or derived from the objectType which is injected into this bean.
036 * If neither of these are injected, then an exception will be through during bean initialization.
037 *
038 * The attempt to fetch the resource from the ServiceBus won't be attempted until a method on the resulting
039 * proxy is invoked.  If it fails to locate the resource, it will throw a ResourceLoaderException indicating the service
040 * could not be loaded.
041 *
042 * <p>This allows for referencing of a potentially remote service in the spring context during startup which won't get
043 * used until after startup.  If the remote service gets used *during* startup, then it must be available from the GRL
044 * during startup or else the ResourceLoaderException will be thrown.</p>
045 *
046 * @author Kuali Rice Team (rice.collab@kuali.org)
047 *
048 */
049public class LazyServiceFactoryBean implements FactoryBean<Object>, InitializingBean {
050
051    private String serviceNamespace;
052        private String serviceName;
053    private String applicationId;
054    private Class<?> objectType;
055    private Class<?>[] proxyInterfaces;
056    private Object proxyObject;
057
058        public LazyServiceFactoryBean() {
059        this.objectType = Object.class;
060        this.proxyInterfaces = null;
061        }
062
063    public void afterPropertiesSet() throws Exception {
064        if (ArrayUtils.isEmpty(getProxyInterfaces())) {
065            setProxyInterfaces(detectProxyInterfaces());
066            if (ArrayUtils.isEmpty(getProxyInterfaces())) {
067                throw new FactoryBeanNotInitializedException("Failed to initialize factory bean because " +
068                        "proxyInterfaces were not injected or could not be derived from object type.");
069            }
070        }
071        this.proxyObject = Proxy.newProxyInstance(getClass().getClassLoader(), getProxyInterfaces(),
072                new LazyInvocationHandler());
073        }
074
075    protected Class<?>[] detectProxyInterfaces() {
076        Class<?> type = getObjectType();
077        if (type != null && type.isInterface()) {
078            return new Class<?>[] {type};
079        } else if (type != null) {
080            return type.getInterfaces();
081        } else {
082            return null;
083        }
084    }
085
086    @Override
087    public Object getObject() throws Exception {
088        return this.proxyObject;
089    }
090
091    @Override
092    public Class<?> getObjectType() {
093        return this.objectType;
094    }
095
096    public void setObjectType(Class<?> objectType) {
097        this.objectType = objectType;
098    }
099
100    @Override
101    public boolean isSingleton() {
102        return true;
103    }
104
105    public String getServiceNamespace() {
106        return serviceNamespace;
107    }
108
109    public void setServiceNamespace(String serviceNamespace) {
110        this.serviceNamespace = serviceNamespace;
111    }
112
113    public String getServiceName() {
114        return serviceName;
115    }
116
117    public void setServiceName(String serviceName) {
118        this.serviceName = serviceName;
119    }
120
121    public String getApplicationId() {
122        return applicationId;
123    }
124
125    public void setApplicationId(String applicationId) {
126        this.applicationId = applicationId;
127    }
128
129    public Class<?>[] getProxyInterfaces() {
130        return proxyInterfaces;
131    }
132
133    public void setProxyInterfaces(Class<?>[] proxyInterfaces) {
134        this.proxyInterfaces = proxyInterfaces;
135    }
136
137    private class LazyInvocationHandler implements InvocationHandler {
138        private volatile Object service = null;
139        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
140            try {
141                if (service == null) {
142                    QName name = new QName(getServiceNamespace(), getServiceName());
143                    if (StringUtils.isNotBlank(getApplicationId())) {
144                        service = KsbApiServiceLocator.getServiceBus().getService(name, getApplicationId());
145                    } else {
146                        service = KsbApiServiceLocator.getServiceBus().getService(name);
147                    }
148                    if (service == null) {
149                        throw new ResourceLoaderException("Failed to locate resource with name: " + name);
150                    }
151                }
152                return method.invoke(service, args);
153            } catch (InvocationTargetException e) {
154                throw e.getTargetException();
155            }
156        }
157
158    }
159
160}