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.messaging.serviceproxies;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.log4j.Logger;
020import org.kuali.rice.core.api.exception.RiceRuntimeException;
021import org.kuali.rice.core.api.util.ClassLoaderUtils;
022import org.kuali.rice.core.api.util.ContextClassLoaderProxy;
023import org.kuali.rice.core.api.util.reflect.BaseInvocationHandler;
024import org.kuali.rice.core.api.util.reflect.TargetedInvocationHandler;
025import org.kuali.rice.ksb.api.bus.Endpoint;
026import org.kuali.rice.ksb.api.bus.ServiceConfiguration;
027import org.kuali.rice.ksb.api.messaging.AsynchronousCall;
028import org.kuali.rice.ksb.api.messaging.AsynchronousCallback;
029import org.kuali.rice.ksb.messaging.PersistedMessageBO;
030import org.kuali.rice.ksb.service.KSBServiceLocator;
031import org.kuali.rice.ksb.util.KSBConstants;
032
033import java.io.Serializable;
034import java.lang.reflect.Method;
035import java.lang.reflect.Proxy;
036import java.util.List;
037
038/**
039 * Standard default proxy used to call services asynchronously. Persists the method call to the db so call is never
040 * lost and only sent when transaction is committed.
041 *
042 * @author Kuali Rice Team (rice.collab@kuali.org)
043 */
044public class AsynchronousServiceCallProxy extends BaseInvocationHandler implements TargetedInvocationHandler {
045
046    private static final Logger LOG = Logger.getLogger(AsynchronousServiceCallProxy.class);
047
048    private AsynchronousCallback callback;
049
050    private List<Endpoint> endpoints;
051
052    private Serializable context;
053
054    private String value1;
055
056    private String value2;
057
058    protected AsynchronousServiceCallProxy(List<Endpoint> endpoints, AsynchronousCallback callback,
059            Serializable context, String value1, String value2) {
060        this.endpoints = endpoints;
061        this.callback = callback;
062        this.context = context;
063        this.value1 = value1;
064        this.value2 = value2;
065    }
066
067    public static Object createInstance(List<Endpoint> endpoints, AsynchronousCallback callback, Serializable context,
068            String value1, String value2) {
069        if (endpoints == null || endpoints.isEmpty()) {
070            throw new RuntimeException("Cannot create service proxy, no service(s) passed in.");
071        }
072        try {
073            return Proxy.newProxyInstance(ClassLoaderUtils.getDefaultClassLoader(),
074                    ContextClassLoaderProxy.getInterfacesToProxy(endpoints.get(0).getService()),
075                    new AsynchronousServiceCallProxy(endpoints, callback, context, value1, value2));
076        } catch (Exception e) {
077            throw new RiceRuntimeException(e);
078        }
079    }
080
081    @Override
082    protected Object invokeInternal(Object proxy, Method method, Object[] arguments) throws Throwable {
083
084        if (LOG.isDebugEnabled()) {
085            LOG.debug("creating messages for method invocation: " + method.getName());
086        }
087        // there are multiple service calls to make in the case of topics.
088        AsynchronousCall methodCall = null;
089        PersistedMessageBO message = null;
090        synchronized (this) {
091            // consider moving all this topic invocation stuff to the service
092            // invoker for speed reasons
093            for (Endpoint endpoint : this.endpoints) {
094                ServiceConfiguration serviceConfiguration = endpoint.getServiceConfiguration();
095                methodCall = new AsynchronousCall(method.getParameterTypes(), arguments, serviceConfiguration,
096                        method.getName(), this.callback, this.context);
097                message = PersistedMessageBO.buildMessage(serviceConfiguration, methodCall);
098                message.setValue1(this.value1);
099                message.setValue2(this.value2);
100                saveMessage(message);
101                executeMessage(message);
102                // only do one iteration if this is a queue. The load balancing
103                // will be handled when the service is
104                // fetched by the MessageServiceInvoker through the GRL (and
105                // then through the RemoteResourceServiceLocatorImpl)
106                if (serviceConfiguration.isQueue()) {
107                    break;
108                }
109            }
110        }
111        if (LOG.isDebugEnabled()) {
112            LOG.debug("finished creating messages for method invocation: " + method.getName());
113        }
114        return null;
115    }
116
117    @Override
118    protected String proxyToString(Object proxy) {
119        StringBuilder builder = new StringBuilder();
120        builder.append("Service call proxy (" + getClass().getName() + ") - for endpoints with service name: ");
121        if (CollectionUtils.isNotEmpty(this.endpoints)) {
122            builder.append(this.endpoints.get(0).getServiceConfiguration().getServiceName().toString());
123        } else {
124            builder.append("<< no endpoints on this proxy!!! >>");
125        }
126        return builder.toString();
127    }
128
129    protected void saveMessage(PersistedMessageBO message) {
130        message.setQueueStatus(KSBConstants.ROUTE_QUEUE_ROUTING);
131        KSBServiceLocator.getMessageQueueService().save(message);
132    }
133
134    protected void executeMessage(PersistedMessageBO message) throws Exception {
135        MessageSender.sendMessage(message);
136    }
137
138    /**
139     * Returns the List<RemotedServiceHolder> of asynchronous services which will be invoked by calls to this proxy.
140     * This is a List because, in the case of Topics, there can be more than one service invoked.
141     */
142    public Object getTarget() {
143        return this.endpoints;
144    }
145
146    public AsynchronousCallback getCallback() {
147        return this.callback;
148    }
149
150    public void setCallback(AsynchronousCallback callback) {
151        this.callback = callback;
152    }
153
154}