1 /* 2 * Copyright the original author or authors. 3 * 4 * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.mozilla.org/MPL/MPL-1.1.html 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import org.as2lib.env.except.IllegalArgumentException; 18 import org.as2lib.io.conn.core.client.ClientServiceProxy; 19 import org.as2lib.io.conn.core.client.AbstractClientServiceProxy; 20 import org.as2lib.io.conn.core.client.UnknownServiceException; 21 import org.as2lib.io.conn.core.server.ReservedServiceException; 22 import org.as2lib.io.conn.core.event.MethodInvocationCallback; 23 import org.as2lib.io.conn.core.event.MethodInvocationReturnInfo; 24 import org.as2lib.io.conn.core.event.MethodInvocationErrorInfo; 25 import org.as2lib.io.conn.core.event.MethodInvocationErrorListener; 26 import org.as2lib.io.conn.local.core.EnhancedLocalConnection; 27 28 /** 29 * {@code LocalClientServiceProxy} handles client requests to a certain service 30 * and its responses. 31 * 32 * <p>Example: 33 * <code> 34 * var client:LocalClientServiceProxy = new LocalClientServiceProxy("local.as2lib.org/myService"); 35 * var callback:MethodInvocationCallback = client.invoke("myMethod", ["firstArgument", "secondArgument"]); 36 * callback.onReturn = function(returnInfo:MethodInvocationReturnInfo):Void { 37 * trace("myMethod - return value: " + returnInfo.getReturnValue()); 38 * } 39 * callback.onError = function(errorInfo:MethodInvocationErrorInfo):Void { 40 * trace("myMethod - error: " + errorInfo.getException()); 41 * } 42 * </code> 43 * 44 * <p>It is also possible to call the method directly on the proxy. But you can't 45 * type the proxy then. 46 * <code> 47 * var client = new LocalClientServiceProxy("local.as2lib.org/myService"); 48 * var callback:MethodInvocationCallback = client.myMethod("firstArgument", "secondArgument"); 49 * </code> 50 * 51 * <p>The neatest way is to use {@code LocalClientServiceProxyFactory} to get a proxy 52 * for a service interface or class, which enables compiler checks. For more 53 * information on this refer to the {@link LocalClientServiceProxyFactory} class. 54 * 55 * <p>If the return value is not of type {@code Number}, {@code Boolean}, {@code String} 56 * or {@code Array} that are converted directly into the appropriate type you must 57 * do the following to receive a value of correct type. Otherwise the return value 58 * will be an instance of type Object that is populated with the instance variables 59 * of the sent object. Note that this must be done on the client as well as on the 60 * server and the 'symbolId' in this case {@code "MyClass"} must be the same. 61 * <code> 62 * Object.registerClass("MyClass", MyClass); 63 * </code> 64 * 65 * <p>The received object will now be of correct type. But you still have to be aware 66 * of some facts:<br> 67 * Flash creates a new object in the background and sets the instance variables of 68 * the sent instance to the new object. It then registers this object with the 69 * appropriate class (if registered previously) and applies the constructor of that 70 * class to the new object passing no arguments. This means if the constructor sets 71 * instance variables it overwrites the ones set previously by {@code undefined}. 72 * 73 * @author Simon Wacker 74 * @author Christoph Atteneder 75 * @see org.as2lib.io.conn.local.client.LocalClientServiceProxyFactory 76 */ 77 class org.as2lib.io.conn.local.client.LocalClientServiceProxy extends AbstractClientServiceProxy implements ClientServiceProxy { 78 79 /** 80 * Generates the response url for a service. 81 * 82 * <p>The response url is composed as follows: 83 * <pre>theServiceUrl.theMethodName_Return_theIndex</pre> 84 * 85 * <p>If the passed-in {@code methodName} is {@code null}, {@code undefined} or an 86 * empty string the response url will be composed as follows: 87 * <pre>theServiceUrl_Return_theIndex</pre> 88 * 89 * <p>{@code index} is a number from 0 to infinite depending on how many responses 90 * are pending. 91 * 92 * @param serviceUrl the url to the service 93 * @param methodName the name of the responsing method 94 * @return the generated response url 95 * @throws IllegalArgumentException if the passed-in {@code serviceUrl} is {@code null}, 96 * {@code undefined} or an empty stirng 97 */ 98 public static function generateResponseServiceUrl(serviceUrl:String, methodName:String):String { 99 if (!serviceUrl) throw new IllegalArgumentException("Service url must not be null, undefined or an empty string.", eval("th" + "is"), arguments); 100 if (!methodName) { 101 var result:String = serviceUrl + "_Return"; 102 var i:Number = 0; 103 while (EnhancedLocalConnection.connectionExists(result + "_" + i)) { 104 i++; 105 } 106 return (result + "_" + i); 107 } else { 108 var result:String = serviceUrl + "_" + methodName + "_Return"; 109 var i:Number = 0; 110 while (EnhancedLocalConnection.connectionExists(result + "_" + i)) { 111 i++; 112 } 113 return (result + "_" + i); 114 } 115 } 116 117 /** The url of the service. */ 118 private var url:String; 119 120 /** Used EnhancedLocalConnection. */ 121 private var connection:EnhancedLocalConnection; 122 123 /** Stores all currently used response services. */ 124 private var responseServices:Array; 125 126 /** 127 * Constructs a new {@code LocalClientServiceProxy} instance. 128 * 129 * @param url the url of the service 130 * @throws IllegalArgumentException if {@code url} is {@code null}, {@code undefined} 131 * or an empty string 132 */ 133 public function LocalClientServiceProxy(url:String) { 134 if (!url) throw new IllegalArgumentException("Argument 'url' must not be null, undefined or an empty string.", this, arguments); 135 this.url = url; 136 connection = new EnhancedLocalConnection(); 137 responseServices = new Array(); 138 } 139 140 /** 141 * Returns the url of the service this proxy invokes methods on. 142 * 143 * <p>The returned url is never {@code null}, {@code undefined} or an empty string. 144 * 145 * @return the url of the service this proxy invokes methods on 146 */ 147 public function getUrl(Void):String { 148 return url; 149 } 150 151 /** 152 * Invokes the method with passed-in {@code methodName} on the 'remote' service, 153 * passing the elements of the passed-in {@code args} as parameters and invokes 154 * the appropriate method on the passed-in {@code callback} on response. 155 * 156 * <p>The response of the method invocation is delegated to the appropriate method 157 * on the passed-in {@code callback}. This is either the {@code onReturn} when no 158 * error occured, or the {@code onError} method in case something went wrong. 159 * 160 * <p>If the passed-in {@code callback} is {@code null} a new {@code MethodInvocationCallback} 161 * instance will be created and returned. It is possible to still set the callback 162 * methods there, after invoking this method. 163 * 164 * @param methodName the name of the method to invoke on the 'remote' service 165 * @param args the arguments that are passed to the method as parameters 166 * @param callback the callback that handles the response 167 * @return either the passed-in callback or a new callback if {@code null} 168 * @throws IllegalArgumentException if the passed-in {@code methodName} is {@code null}, 169 * {@code undefined} or an empty string 170 */ 171 public function invokeByNameAndArgumentsAndCallback(methodName:String, args:Array, callback:MethodInvocationCallback):MethodInvocationCallback { 172 if (!methodName) throw new IllegalArgumentException("Argument 'methodName' must not be null, undefined or an empty string.", this, arguments); 173 if (!args) args = new Array(); 174 if (!callback) callback = getBlankMethodInvocationCallback(); 175 176 var responseUrl:String = generateResponseServiceUrl(url, methodName); 177 178 var responseService:EnhancedLocalConnection = new EnhancedLocalConnection(); 179 var index:Number = responseServices.push(responseService) - 1; 180 var owner:LocalClientServiceProxy = this; 181 responseService["onReturn"] = function(returnValue):Void { 182 // "owner.responseServices" is not MTASC compatible because "responseServices" is private 183 owner["responseServices"].splice(index, 1); 184 // "owner.url" is not MTASC compatible because "url" is private 185 callback.onReturn(new MethodInvocationReturnInfo(owner["url"], methodName, args, returnValue)); 186 this.close(); 187 }; 188 responseService["onError"] = function(errorCode:Number, exception):Void { 189 // "owner.responseServices" is not MTASC compatible because "responseServices" is private 190 owner["responseServices"].splice(index, 1); 191 // "owner.url" is not MTASC compatible because "url" is private 192 callback.onError(new MethodInvocationErrorInfo(owner["url"], methodName, args, errorCode, exception)); 193 this.close(); 194 }; 195 try { 196 responseService.connect(responseUrl); 197 } catch (exception:org.as2lib.io.conn.local.core.ReservedConnectionException) { 198 // "new ReservedServiceException" without braces is not MTASC compatible 199 throw (new ReservedServiceException("Response service with url [" + responseUrl + "] does already exist.", this, arguments)).initCause(exception); 200 } 201 202 var errorListener:MethodInvocationErrorListener = getBlankMethodInvocationErrorListener(); 203 errorListener.onError = function(info:MethodInvocationErrorInfo) { 204 callback.onError(info); 205 }; 206 207 try { 208 connection.send(url, "invokeMethod", [methodName, args, responseUrl], errorListener); 209 } catch (exception:org.as2lib.io.conn.local.core.UnknownConnectionException) { 210 // "new UnknownServiceException" without braces is not MTASC compatible 211 throw (new UnknownServiceException("Service with url [" + url + "] does not exist.", this, arguments)).initCause(exception); 212 } 213 214 return callback; 215 } 216 217 /** 218 * Returns a blank method invocation error listener. This is an error listern with 219 * no implemented methods. 220 * 221 * @return a blank method invocation error listener 222 */ 223 private function getBlankMethodInvocationErrorListener(Void):MethodInvocationErrorListener { 224 var result = new Object(); 225 result.__proto__ = MethodInvocationErrorListener["prototype"]; 226 result.__constructor__ = MethodInvocationErrorListener; 227 return result; 228 } 229 230 /** 231 * Returns a blank method invocation callback. This is a callback with no implemented 232 * methods. 233 * 234 * @return a blank method invocation callback 235 */ 236 private function getBlankMethodInvocationCallback(Void):MethodInvocationCallback { 237 var result = new Object(); 238 result.__proto__ = MethodInvocationCallback["prototype"]; 239 result.__constructor__ = MethodInvocationCallback; 240 return result; 241 } 242 243 /** 244 * Enables you to invoke the method to be invoked on the 'remote' service directly 245 * on this proxy. 246 * 247 * <p>The usage is mostly the same. 248 * <code>myProxy.myMethod("myArg1");</code> 249 * <code>myProxy.myMethod("myArg1", myCallback);</code> 250 * <code>var callback:MethodInvocationCallback = myProxy.myMethod("myArg1");</code> 251 * 252 * @param methodName the name of the method to invoke on the 'remote' service 253 * @return the function to execute as the actual method passing the actual arguments 254 */ 255 private function __resolve(methodName:String):Function { 256 var owner:ClientServiceProxy = this; 257 return (function():MethodInvocationCallback { 258 if (arguments[arguments.length] instanceof MethodInvocationCallback) { 259 return owner.invokeByNameAndArgumentsAndCallback(methodName, arguments, MethodInvocationCallback(arguments.pop())); 260 } else { 261 return owner.invokeByNameAndArgumentsAndCallback(methodName, arguments, null); 262 } 263 }); 264 } 265 266 }