001 /** 002 * 003 * Copyright 2005 LogicBlaze, Inc. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 **/ 018 019 package org.logicblaze.lingo.jms; 020 021 import org.logicblaze.lingo.LingoInvocation; 022 import org.logicblaze.lingo.LingoRemoteInvocationFactory; 023 import org.logicblaze.lingo.MetadataStrategy; 024 import org.logicblaze.lingo.MethodMetadata; 025 import org.logicblaze.lingo.SimpleMetadataStrategy; 026 import org.logicblaze.lingo.jms.impl.AsyncReplyHandler; 027 import org.logicblaze.lingo.jms.impl.MultiplexingRequestor; 028 import org.logicblaze.lingo.jms.marshall.DefaultMarshaller; 029 import org.logicblaze.lingo.jms.marshall.Marshaller; 030 import org.aopalliance.intercept.MethodInterceptor; 031 import org.aopalliance.intercept.MethodInvocation; 032 import org.springframework.aop.support.AopUtils; 033 import org.springframework.beans.factory.DisposableBean; 034 import org.springframework.beans.factory.InitializingBean; 035 import org.springframework.remoting.RemoteAccessException; 036 import org.springframework.remoting.support.RemoteInvocationBasedAccessor; 037 import org.springframework.remoting.support.RemoteInvocationFactory; 038 import org.springframework.remoting.support.RemoteInvocationResult; 039 040 import javax.jms.ConnectionFactory; 041 import javax.jms.Destination; 042 import javax.jms.JMSException; 043 import javax.jms.Message; 044 import java.util.Map; 045 import java.util.WeakHashMap; 046 047 /** 048 * Interceptor for accessing a JMS based service which must be configured with a 049 * {@link org.logicblaze.lingo.LingoRemoteInvocationFactory} instance. 050 * 051 * @author James Strachan 052 * @see #setServiceInterface 053 * @see #setServiceUrl 054 * @see JmsServiceExporter 055 * @see JmsProxyFactoryBean 056 */ 057 public class JmsClientInterceptor extends RemoteInvocationBasedAccessor 058 implements MethodInterceptor, InitializingBean, DisposableBean { 059 060 private Map remoteObjects = new WeakHashMap(); 061 private Requestor requestor; 062 private Destination destination; 063 private Destination responseDestination; 064 private String correlationID; 065 private Marshaller marshaller; 066 private ConnectionFactory connectionFactory; 067 068 public JmsClientInterceptor() { 069 setRemoteInvocationFactory(createRemoteInvocationFactory()); 070 } 071 072 public JmsClientInterceptor(Requestor requestor) { 073 this.requestor = requestor; 074 setRemoteInvocationFactory(createRemoteInvocationFactory()); 075 } 076 077 public JmsClientInterceptor(Requestor requestor, LingoRemoteInvocationFactory factory) { 078 this.requestor = requestor; 079 setRemoteInvocationFactory(factory); 080 } 081 082 public Object invoke(MethodInvocation methodInvocation) throws Throwable { 083 if (AopUtils.isToStringMethod(methodInvocation.getMethod())) { 084 return "JMS invoker proxy for service URL [" + getServiceUrl() + "]"; 085 } 086 LingoInvocation invocation = (LingoInvocation) createRemoteInvocation(methodInvocation); 087 MethodMetadata metadata = invocation.getMetadata(); 088 replaceRemoteReferences(invocation, metadata); 089 try { 090 Message requestMessage = marshaller.createRequestMessage(requestor, invocation); 091 populateHeaders(requestMessage); 092 if (metadata.isOneWay()) { 093 requestor.oneWay(destination, requestMessage); 094 return null; 095 } 096 else { 097 Message response = requestor.request(destination, requestMessage); 098 RemoteInvocationResult result = marshaller.extractInvocationResult(response); 099 return recreateRemoteInvocationResult(result); 100 } 101 } 102 catch (JMSException ex) { 103 throw new RemoteAccessException("Cannot access JMS invoker remote service at [" + getServiceUrl() + "]", ex); 104 } 105 } 106 107 public void afterPropertiesSet() throws JMSException { 108 RemoteInvocationFactory factory = getRemoteInvocationFactory(); 109 if (!(factory instanceof LingoRemoteInvocationFactory)) { 110 throw new IllegalArgumentException("remoteInvocationFactory must be an instance of LingoRemoteInvocationFactory but was: " + factory); 111 112 } 113 if (requestor == null) { 114 if (connectionFactory == null) { 115 throw new IllegalArgumentException("requestor or connectionFactory is required"); 116 } 117 else { 118 requestor = MultiplexingRequestor.newInstance(connectionFactory, destination, responseDestination); 119 } 120 } 121 if (marshaller == null) { 122 // default to standard JMS marshalling 123 marshaller = new DefaultMarshaller(); 124 } 125 } 126 127 public void destroy() throws Exception { 128 requestor.close(); 129 } 130 131 // Properties 132 //------------------------------------------------------------------------- 133 public Requestor getRequestor() { 134 return requestor; 135 } 136 137 public void setRequestor(Requestor requestor) { 138 this.requestor = requestor; 139 } 140 141 public Destination getDestination() { 142 return destination; 143 } 144 145 /** 146 * Sets the destination used to make requests 147 * 148 * @param destination 149 */ 150 public void setDestination(Destination destination) { 151 this.destination = destination; 152 } 153 154 public Destination getResponseDestination() { 155 return responseDestination; 156 } 157 158 /** 159 * Sets the destination used to consume responses on - or null 160 * and a temporary queue will be created. 161 * 162 * @param responseDestination 163 */ 164 public void setResponseDestination(Destination responseDestination) { 165 this.responseDestination = responseDestination; 166 } 167 168 public void setCorrelationID(String correlationID) { 169 this.correlationID = correlationID; 170 } 171 172 public Marshaller getMarshaller() { 173 return marshaller; 174 } 175 176 public void setMarshaller(Marshaller marshaller) { 177 this.marshaller = marshaller; 178 } 179 180 public ConnectionFactory getConnectionFactory() { 181 return connectionFactory; 182 } 183 184 /** 185 * Used to create a default {@link Requestor} if no requestor is explicitly 186 * configured. 187 */ 188 public void setConnectionFactory(ConnectionFactory connectionFactory) { 189 this.connectionFactory = connectionFactory; 190 } 191 192 // Implementation methods 193 //------------------------------------------------------------------------- 194 195 protected void populateHeaders(Message requestMessage) throws JMSException { 196 if (correlationID != null) { 197 requestMessage.setJMSCorrelationID(correlationID); 198 } 199 } 200 201 202 /** 203 * Recreate the invocation result contained in the given RemoteInvocationResult 204 * object. The default implementation calls the default recreate method. 205 * <p>Can be overridden in subclass to provide custom recreation, potentially 206 * processing the returned result object. 207 * 208 * @param result the RemoteInvocationResult to recreate 209 * @return a return value if the invocation result is a successful return 210 * @throws Throwable if the invocation result is an exception 211 * @see org.springframework.remoting.support.RemoteInvocationResult#recreate 212 */ 213 protected Object recreateRemoteInvocationResult(RemoteInvocationResult result) throws Throwable { 214 return result.recreate(); 215 } 216 217 protected void replaceRemoteReferences(LingoInvocation invocation, MethodMetadata metadata) { 218 Object[] arguments = invocation.getArguments(); 219 Class[] parameterTypes = invocation.getParameterTypes(); 220 for (int i = 0; i < parameterTypes.length; i++) { 221 if (metadata.isRemoteParameter(i)) { 222 arguments[i] = remoteReference(parameterTypes[i], arguments[i]); 223 } 224 } 225 } 226 227 protected Object remoteReference(Class type, Object value) { 228 if (value == null) { 229 return null; 230 } 231 String correlationID = (String) remoteObjects.get(value); 232 if (correlationID == null) { 233 correlationID = requestor.createCorrelationID(); 234 remoteObjects.put(value, correlationID); 235 } 236 if (requestor instanceof MultiplexingRequestor) { 237 MultiplexingRequestor multiplexingRequestor = (MultiplexingRequestor) requestor; 238 multiplexingRequestor.registerHandler(correlationID, new AsyncReplyHandler(value, marshaller)); 239 } 240 else { 241 throw new IllegalArgumentException("You can only pass remote references with a MultiplexingRequestor"); 242 } 243 return correlationID; 244 } 245 246 /** 247 * Factory method to create a default lingo based invocation factory if none is configured 248 */ 249 protected LingoRemoteInvocationFactory createRemoteInvocationFactory() { 250 return new LingoRemoteInvocationFactory(createMetadataStrategy()); 251 } 252 253 /** 254 * Factory method to create a default metadata strategy if none is configured 255 * 256 * @return 257 */ 258 protected MetadataStrategy createMetadataStrategy() { 259 return new SimpleMetadataStrategy(); 260 } 261 262 }