001 package com.mockrunner.connector; 002 003 import java.util.HashMap; 004 import java.util.Iterator; 005 import java.util.Map; 006 007 import javax.resource.ResourceException; 008 import javax.resource.cci.InteractionSpec; 009 import javax.resource.cci.MappedRecord; 010 import javax.resource.cci.Record; 011 012 import com.mockrunner.base.NestedApplicationException; 013 import com.mockrunner.mock.connector.cci.MockMappedRecord; 014 015 /** 016 * This interaction implementor works with mapped records. It takes a <code>Map</code> for 017 * the request and a <code>Map</code> or a <code>Record</code> instance for the response. 018 * If the request <code>Map</code> is <code>null</code>, which is the default, 019 * the implementor accepts any request and returns the specified result. If a request 020 * <code>Map</code> is specified, this implementor accepts only requests that contain the same 021 * data as the specified expected request <code>Map</code>. The underlying maps are compared 022 * as described in the <code>Map.equals</code> method. 023 * If a request is accepted, this implementor replies with the specified 024 * response. You can use the various constructors and <code>set</code> methods 025 * to configure the expected request and the response.<br> 026 * Please check out the documentation of the various methods for details. 027 */ 028 public class MappedRecordInteraction implements InteractionImplementor 029 { 030 private boolean enabled; 031 private Map expectedRequest; 032 private Map responseData; 033 private Class responseClass; 034 private Record responseRecord; 035 036 /** 037 * Sets the expected request and the response to <code>null</code>, 038 * i.e. an empty response is returned for every request. 039 */ 040 public MappedRecordInteraction() 041 { 042 this(null, null, MockMappedRecord.class); 043 } 044 045 /** 046 * Sets the expected request to <code>null</code> and prepares 047 * the specified response <code>Map</code>. The response class for the 048 * {@link #execute(InteractionSpec,Record)} method is set 049 * to the default {@link com.mockrunner.mock.connector.cci.MockMappedRecord}. 050 * It is allowed to pass <code>null</code> for the response <code>Map</code> 051 * which is equivalent to an empty response. 052 * The specified response is returned for every request. 053 * @param responseMap the response <code>Map</code> 054 */ 055 public MappedRecordInteraction(Map responseMap) 056 { 057 this(null, responseMap, MockMappedRecord.class); 058 } 059 060 /** 061 * Sets the specified expected request <code>Map</code> and prepares 062 * the specified response <code>Map</code>. The response class for the 063 * {@link #execute(InteractionSpec,Record)} method is set 064 * to the default {@link com.mockrunner.mock.connector.cci.MockMappedRecord}. 065 * It is allowed to pass <code>null</code> for the request and response <code>Map</code> 066 * which is equivalent to an empty expected request (i.e. every request is accepted) 067 * or to an empty response respectively. 068 * The specified response is returned, if the actual request matches the specified expected 069 * request. 070 * @param expectedRequest the expected request <code>Map</code> 071 * @param responseMap the response <code>Map</code> 072 */ 073 public MappedRecordInteraction(Map expectedRequest, Map responseMap) 074 { 075 this(expectedRequest, responseMap, MockMappedRecord.class); 076 } 077 078 /** 079 * Sets the expected request to <code>null</code> and prepares 080 * the specified response <code>Map</code>. The response class for the 081 * {@link #execute(InteractionSpec,Record)} method is set 082 * to the specified <code>responseClass</code>. The specified 083 * <code>responseClass</code> must implement <code>MappedRecord</code>, 084 * otherwise an <code>IllegalArgumentException</code> will be thrown. 085 * It is allowed to pass <code>null</code> for the response <code>Map</code> 086 * which is equivalent to an empty response. 087 * The specified response is returned for every request. 088 * @param responseMap the response <code>Map</code 089 * @param responseClass the response <code>Record</code> class 090 * @throws IllegalArgumentException if the <code>responseClass</code> 091 * is not valid 092 */ 093 public MappedRecordInteraction(Map responseMap, Class responseClass) 094 { 095 this(null, responseMap, responseClass); 096 } 097 098 /** 099 * Sets the specified expected request <code>Map</code> and prepares 100 * the specified response <code>Map</code>. The response class for the 101 * {@link #execute(InteractionSpec,Record)} method is set 102 * to the specified <code>responseClass</code>. The specified 103 * <code>responseClass</code> must implement <code>MappedRecord</code>, 104 * otherwise an <code>IllegalArgumentException</code> will be thrown. 105 * It is allowed to pass <code>null</code> for the request and response <code>Map</code> 106 * which is equivalent to an empty expected request (i.e. every request is accepted) 107 * or to an empty response respectively. 108 * The specified response is returned, if the actual request matches the specified expected 109 * request. 110 * @param expectedRequest the expected request <code>Map</code> 111 * @param responseMap the response <code>Map</code 112 * @param responseClass the response <code>Record</code> class 113 * @throws IllegalArgumentException if the <code>responseClass</code> 114 * is not valid 115 */ 116 public MappedRecordInteraction(Map expectedRequest, Map responseMap, Class responseClass) 117 { 118 setExpectedRequest(expectedRequest); 119 setResponse(responseMap, responseClass); 120 this.enabled = true; 121 } 122 123 /** 124 * Sets the specified expected request <code>Map</code> and the response 125 * <code>Record</code> for the {@link #execute(InteractionSpec, Record)} 126 * method. The response <code>Record</code> is ignored for 127 * {@link #execute(InteractionSpec,Record,Record)} but takes precedence 128 * over the specified response <code>Map</code> for {@link #execute(InteractionSpec, Record)}. 129 * It is allowed to pass <code>null</code> for the request and response <code>Record</code> 130 * which is equivalent to an empty expected request (i.e. every request is accepted) 131 * or to no specified response <code>Record</code>, i.e. the specified response 132 * <code>Map</code> is taken. 133 * The specified response is returned, if the actual request matches the specified expected 134 * request. 135 * @param expectedRequest the expected request <code>Map</code> 136 * @param responseRecord the response <code>Record</code> 137 */ 138 public MappedRecordInteraction(Map expectedRequest, Record responseRecord) 139 { 140 setExpectedRequest(expectedRequest); 141 setResponse(responseRecord); 142 this.enabled = true; 143 } 144 145 /** 146 * Sets the expected request to <code>null</code> and prepares the response 147 * <code>Record</code> for the {@link #execute(InteractionSpec, Record)} 148 * method. The response <code>Record</code> is ignored for 149 * {@link #execute(InteractionSpec,Record,Record)} but takes precedence 150 * over the specified response <code>Map</code> for {@link #execute(InteractionSpec, Record)}. 151 * It is allowed to pass <code>null</code> for the response <code>Record</code> 152 * which is equivalent to no specified response <code>Record</code>, i.e. the specified response 153 * <code>Map</code> is taken. 154 * The specified response is returned for every request. 155 * @param responseRecord the response <code>Record</code> 156 */ 157 public MappedRecordInteraction(Record responseRecord) 158 { 159 this(null, responseRecord); 160 } 161 162 /** 163 * Enables this implementor. 164 */ 165 public void enable() 166 { 167 this.enabled = true; 168 } 169 170 /** 171 * Disables this implementor. {@link #canHandle(InteractionSpec, Record, Record)} 172 * always returns <code>false</code>, if this implementor is disabled. 173 */ 174 public void disable() 175 { 176 this.enabled = false; 177 } 178 179 /** 180 * Sets the specified expected request <code>Map</code>. The response is returned, 181 * if the actual request matches the specified expected request <code>Map</code> 182 * according to <code>Map.equals</code>. 183 * It is allowed to pass <code>null</code> for the request <code>Map</code> 184 * which is equivalent to an empty expected request (i.e. every request 185 * is accepted). 186 * @param expectedRequest the expected request <code>Map</code> 187 */ 188 public void setExpectedRequest(Map expectedRequest) 189 { 190 if(null == expectedRequest) 191 { 192 this.expectedRequest = null; 193 } 194 else 195 { 196 this.expectedRequest = new HashMap(expectedRequest); 197 } 198 } 199 200 /** 201 * Prepares the specified response <code>Map</code>. The response class for the 202 * {@link #execute(InteractionSpec,Record)} method is set 203 * to the default {@link com.mockrunner.mock.connector.cci.MockMappedRecord}. 204 * It is allowed to pass <code>null</code> for the response <code>Map</code> 205 * which is equivalent to an empty response. 206 * @param responseMap the response <code>Map</code 207 */ 208 public void setResponse(Map responseMap) 209 { 210 setResponse(responseMap, MockMappedRecord.class); 211 } 212 213 /** 214 * Prepares the specified response <code>Map</code>. The response class for the 215 * {@link #execute(InteractionSpec,Record)} method is set 216 * to the specified <code>responseClass</code>. The specified 217 * <code>responseClass</code> must implement <code>MappedRecord</code>, 218 * otherwise an <code>IllegalArgumentException</code> will be thrown. 219 * It is allowed to pass <code>null</code> for the response <code>Map</code> 220 * which is equivalent to an empty response. 221 * @param responseMap the response <code>Map</code> 222 * @param responseClass the response <code>Record</code> class 223 * @throws IllegalArgumentException if the <code>responseClass</code> 224 * is not valid 225 */ 226 public void setResponse(Map responseMap, Class responseClass) 227 { 228 if(!isResponseClassAcceptable(responseClass)) 229 { 230 throw new IllegalArgumentException("responseClass must implement " + MappedRecord.class.getName()); 231 } 232 if(null == responseMap) 233 { 234 this.responseData = null; 235 } 236 else 237 { 238 this.responseData = new HashMap(responseMap); 239 } 240 this.responseClass = responseClass; 241 } 242 243 /** 244 * Prepares the response <code>Record</code> for the 245 * {@link #execute(InteractionSpec, Record)} method. The response 246 * <code>Record</code> is ignored for {@link #execute(InteractionSpec,Record,Record)} 247 * but takes precedence over the specified response <code>Map</code> for 248 * {@link #execute(InteractionSpec, Record)}. 249 * It is allowed to pass <code>null</code> for the response <code>Record</code> 250 * which is equivalent to no specified response <code>Record</code>, i.e. the specified response 251 * <code>Map</code> is taken. 252 * @param responseRecord the response <code>Record</code> 253 */ 254 public void setResponse(Record responseRecord) 255 { 256 this.responseRecord = responseRecord; 257 } 258 259 /** 260 * Returns <code>true</code> if this implementor is enabled and will handle the request. 261 * This method returns <code>true</code> if the following prerequisites are fulfilled:<br><br> 262 * It is enabled.<br><br> 263 * The response <code>Record</code> must implement <code>MappedRecord</code> 264 * or it must be <code>null</code> (which is the case, if the actual request 265 * targets the {@link #execute(InteractionSpec,Record)} method instead of 266 * {@link #execute(InteractionSpec,Record,Record)}).<br><br> 267 * The expected request must be <code>null</code> (use the various 268 * <code>setExpectedRequest</code> methods) or the actual request <code>Record</code> 269 * must implement <code>MappedRecord</code> and must contain the same data as 270 * the specified expected request <code>Map</code> according to <code>Map.equals</code>.<br><br> 271 * Otherwise, <code>false</code> is returned. 272 * @param interactionSpec the <code>InteractionSpec</code> for the actual call 273 * @param actualRequest the request for the actual call 274 * @param actualResponse the response for the actual call, may be <code>null</code> 275 * @return <code>true</code> if this implementor will handle the request and 276 * will return the specified response, <code>false</code> otherwise 277 */ 278 public boolean canHandle(InteractionSpec interactionSpec, Record actualRequest, Record actualResponse) 279 { 280 if(!enabled) return false; 281 if(!isResponseAcceptable(actualResponse)) return false; 282 return doesRequestMatch(actualRequest); 283 } 284 285 private boolean doesRequestMatch(Record request) 286 { 287 if(null == expectedRequest) return true; 288 if(null == request) return false; 289 if(request instanceof MappedRecord) 290 { 291 try 292 { 293 MappedRecord mappedRequest = (MappedRecord)request; 294 if(mappedRequest.size() != expectedRequest.size()) return false; 295 Iterator keys = mappedRequest.keySet().iterator(); 296 while(keys.hasNext()) 297 { 298 Object nextKey = keys.next(); 299 Object actualValue = mappedRequest.get(nextKey); 300 Object expectedValue = expectedRequest.get(nextKey); 301 if(!areObjectsEquals(actualValue, expectedValue)) 302 { 303 return false; 304 } 305 } 306 return true; 307 } 308 catch(Exception exc) 309 { 310 throw new NestedApplicationException(exc); 311 } 312 } 313 return false; 314 } 315 316 private boolean areObjectsEquals(Object object1, Object object2) 317 { 318 if(null == object1 && null == object2) return true; 319 if(null == object1) return false; 320 return object1.equals(object2); 321 } 322 323 private boolean isResponseAcceptable(Record response) 324 { 325 return (null == response) || (response instanceof MappedRecord); 326 } 327 328 private boolean isResponseClassAcceptable(Class responseClass) 329 { 330 return (null == responseClass) || (MappedRecord.class.isAssignableFrom(responseClass)); 331 } 332 333 /** 334 * First version of the <code>execute</code> methods.<br><br> 335 * This method returns <code>null</code>, if the request does not match 336 * according to the contract of {@link #canHandle}. This never happens under 337 * normal conditions since the {@link InteractionHandler} does not call 338 * <code>execute</code>, if {@link #canHandle} returns <code>false</code>. 339 * <br><br> 340 * Otherwise, this method returns the specified response. If a response 341 * <code>Record</code> object is specified (use {@link #setResponse(Record)}), 342 * it always takes precedence, i.e. the response <code>Map</code> will be ignored. 343 * If no <code>Record</code> object is specified, a <code>Record</code> object 344 * is created and filled with the specified response <code>Map</code> data. Use the 345 * <code>setResponse</code> methods that take a <code>Map</code> 346 * to prepare the response <code>Map</code>. The created <code>Record</code> is of the the 347 * specified type (the <code>setResponse</code> method that takes a second 348 * <code>Class</code> parameter allows for specifying a type). If no type 349 * is specified, a {@link com.mockrunner.mock.connector.cci.MockMappedRecord} 350 * is created. If no response <code>Map</code> is specified at all, an empty 351 * {@link com.mockrunner.mock.connector.cci.MockMappedRecord} 352 * will be returned. 353 * @param interactionSpec the interaction spec 354 * @param actualRequest the actual request 355 * @return the response according to the current request 356 */ 357 public Record execute(InteractionSpec interactionSpec, Record actualRequest) throws ResourceException 358 { 359 if(!canHandle(interactionSpec, actualRequest, null)) return null; 360 if(null != responseRecord) return responseRecord; 361 MappedRecord response = null; 362 try 363 { 364 if(null == responseClass) 365 { 366 response = new MockMappedRecord(); 367 } 368 else 369 { 370 response = (MappedRecord)responseClass.newInstance(); 371 } 372 if(null != responseData) 373 { 374 response.putAll(responseData); 375 } 376 } 377 catch(Exception exc) 378 { 379 ResourceException resExc = new ResourceException("execute() failed"); 380 resExc.setLinkedException(exc); 381 throw resExc; 382 } 383 return (Record)response; 384 } 385 386 /** 387 * Second version of the <code>execute</code> methods.<br><br> 388 * This method returns <code>false</code>, if the request does not match 389 * according to the contract of {@link #canHandle}. This never happens under 390 * normal conditions since the {@link InteractionHandler} does not call 391 * <code>execute</code>, if {@link #canHandle} returns <code>false</code>. 392 * <br><br> 393 * Otherwise, this method fills the response <code>Record</code> with the 394 * specified response <code>Map</code> data. Use the <code>setResponse</code> methods that 395 * take a <code>Map</code> to prepare the response <code>Map</code>. 396 * The response <code>Record</code> must implement <code>MappedRecord</code> 397 * (it does, otherwise the request would have been rejected by 398 * {@link #canHandle}). If no response <code>Map</code> is specified at all, 399 * the response <code>Record</code> is not touched but <code>true</code> 400 * is returned anyway 401 * @param interactionSpec the interaction spec 402 * @param actualRequest the actual request 403 * @param actualResponse the actual response 404 * @return <code>true</code> under normal conditions 405 */ 406 public boolean execute(InteractionSpec interactionSpec, Record actualRequest, Record actualResponse) throws ResourceException 407 { 408 if(!canHandle(interactionSpec, actualRequest, actualResponse)) return false; 409 try 410 { 411 if(null != responseData && null != actualResponse) 412 { 413 ((MappedRecord)actualResponse).clear(); 414 ((MappedRecord)actualResponse).putAll(responseData); 415 } 416 } 417 catch(Exception exc) 418 { 419 ResourceException resExc = new ResourceException("execute() failed"); 420 resExc.setLinkedException(exc); 421 throw resExc; 422 } 423 return true; 424 } 425 }