1 /*------------------------------------------------------------------------------
2 Name: XmlScriptClient.java
3 Project: xmlBlaster.org
4 Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
5 ------------------------------------------------------------------------------*/
6 package org.xmlBlaster.client.script;
7
8 import java.util.logging.Logger;
9 import java.util.logging.Level;
10 import org.xmlBlaster.util.Global;
11 import org.xmlBlaster.util.MsgUnit;
12 import org.xmlBlaster.util.XmlBlasterException;
13 import org.xmlBlaster.client.I_Callback;
14 import org.xmlBlaster.client.I_XmlBlasterAccess;
15 import org.xmlBlaster.client.key.UpdateKey;
16 import org.xmlBlaster.client.qos.ConnectQos;
17 import org.xmlBlaster.client.qos.ConnectReturnQos;
18 import org.xmlBlaster.client.qos.DisconnectQos;
19 import org.xmlBlaster.client.qos.EraseReturnQos;
20 import org.xmlBlaster.client.qos.PublishReturnQos;
21 import org.xmlBlaster.client.qos.SubscribeReturnQos;
22 import org.xmlBlaster.client.qos.UnSubscribeReturnQos;
23 import org.xmlBlaster.client.qos.UpdateQos;
24 import org.xmlBlaster.util.def.ErrorCode;
25 import org.xmlBlaster.util.def.MethodName;
26 import org.xmlBlaster.util.qos.ConnectQosData;
27 import org.xmlBlaster.util.qos.ConnectQosSaxFactory;
28 import org.xmlBlaster.util.qos.DisconnectQosData;
29 import org.xmlBlaster.util.qos.DisconnectQosSaxFactory;
30 import org.xmlBlaster.util.xbformat.MsgInfo;
31
32 import java.io.FileOutputStream;
33 import java.io.FileReader;
34 import java.io.OutputStream;
35 import java.util.HashMap;
36
37 /**
38 * XmlScriptClient
39 * <p>
40 * Example for usage:
41 * </p>
42 * <p>
43 * <tt>
44 * java javaclients.XmlScript -requestFile inFile.xml -responseFile outFile.xml -updateFile updFile.xml
45 * </tt>
46 * </p>
47 * @author <a href="mailto:michele@laghi.eu">Michele Laghi</a>
48 * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/client.script.html">The client.script requirement</a>
49 */
50 public class XmlScriptClient extends XmlScriptInterpreter implements I_Callback {
51
52 private final String ME = "XmlScriptClient";
53 private static Logger log = Logger.getLogger(XmlScriptClient.class.getName());
54 private I_XmlBlasterAccess access;
55
56 private boolean isConnected;
57
58 private I_MsgUnitCb msgUnitCb;
59
60 private final Global glob;
61 private I_Callback callback;
62 private ConnectQosSaxFactory connectQosFactory;
63 private DisconnectQosSaxFactory disconnectQosFactory;
64
65 // Teh default is to re-throw only connect() exception
66 private boolean throwAllExceptions;
67
68 private boolean allowImplicitConnect = true;
69
70 /**
71 * This constructor is the most generic one (more degrees of freedom)
72 * @param glob the global to use
73 * @param access the I_XmlBlasterAccess to use (can be different from the default
74 * given by the global.
75 * @param callback The I_Callback implementation to be used (you can provide your own desidered behaviour)
76 * @param attachments the attachments where to search when a content is stored in the attachment (with the 'link' attribute)
77 * @param out the OutputStream where to send the responses of the invocations done to xmlBlaster
78 */
79 public XmlScriptClient(Global glob, I_XmlBlasterAccess access, I_Callback callback, HashMap attachments, OutputStream out) {
80 super(glob, attachments, out);
81 this.glob = glob;
82
83 this.access = access;
84 this.callback = callback;
85 this.connectQosFactory = new ConnectQosSaxFactory(this.glob);
86 this.disconnectQosFactory = new DisconnectQosSaxFactory(this.glob);
87
88 try {
89 this.allowImplicitConnect = this.glob.get("xmlBlaster/XmlScript/allowImplicitConnect", this.allowImplicitConnect, null, null);
90 } catch (XmlBlasterException e) {
91 e.printStackTrace();
92 }
93
94 if (this.access != null) {
95 this.isConnected = this.access.isConnected();
96 }
97 }
98
99 /**
100 * This is a convenience constructor which takes the default I_Callback implementation provided
101 * (StreamCallback).
102 *
103 * @param glob the global to use
104 * @param access the I_XmlBlasterAccess to use (can be different from the default
105 * given by the global.
106 * @param cbStream the OutputStream where to send the information coming in
107 * asynchroneously via the update method (could be different from the
108 * synchroneous output stream).
109 * @param responseStream the synchroneous OutputStream
110 * @param attachments the attachments where to find attached contents
111 *
112 * @see StreamCallback
113 */
114 public XmlScriptClient(Global glob, I_XmlBlasterAccess access, OutputStream cbStream, OutputStream responseStream, HashMap attachments) {
115 this(glob, access, new StreamCallback(glob, cbStream, " "), attachments, responseStream);
116 }
117
118 /**
119 * Convenience constructor which takes a minimal amount of parameters. The
120 * accessor taken is the one provided by the given global. The I_Callback
121 * implementation used is the StreamCallback. The asynchroneous output is sent
122 * to the same stream as the synchroneous one.
123 * @param glob the global to use. The I_XmlBlasterAccess will be taken from
124 * it.
125 * @param out. The OutputStream used for all outputs (sync and async).
126 */
127 public XmlScriptClient(Global glob, OutputStream out) {
128 this(glob, glob.getXmlBlasterAccess(), out, out, null);
129 }
130
131 /**
132 * You can register a callback which can manipulate the MsgUnit just
133 * before it is sent.
134 */
135 public void registerMsgUnitCb(I_MsgUnitCb msgUnitCb) {
136 this.msgUnitCb = msgUnitCb;
137 }
138
139 public void setProperty(String key, String value) throws XmlBlasterException {
140 this.glob.getProperty().set(key, value);
141 }
142
143 public boolean fireMethod(MethodName methodName,
144 String sessionId, String requestId, byte type)
145 throws XmlBlasterException {
146 if (log.isLoggable(Level.FINE)) XmlScriptClient.log.fine("fireMethod "
147 + MsgInfo.getTypeStr(type)
148 + ": " + methodName.toString()
149 + ": " + this.key.toString()
150 + " " + this.qos.toString());
151 if (type != MsgInfo.INVOKE_BYTE)
152 log.warning("Unexpected message of type '" + MsgInfo.getTypeStr(type) + "'");
153 try {
154 if (MethodName.CONNECT.equals(methodName) || !this.isConnected) {
155 boolean implicitConnect = !MethodName.CONNECT.equals(methodName);
156 if (implicitConnect && !this.allowImplicitConnect)
157 throw new XmlBlasterException(glob, ErrorCode.USER_CLIENTCODE, ME, "Please provide an initial connect string or set -xmlBlaster/XmlScript/allowImplicitConnect true", null);
158 // if (this.qos.length() < 1) this.qos.append("<qos />");
159 String ret = null;
160 I_Callback cb = null;
161 if (this.callback != null) cb = this; // we intercept callbacks
162 if (implicitConnect || this.qos.length() < 1) {
163 log.warning("Doing implicit xmlBlaster.connect() as no valid <connect/> markup is in the script");
164 ConnectQos connectQos = new ConnectQos(this.glob);
165 ret = this.access.connect(connectQos, cb).toXml();
166 }
167 else {
168 ConnectQosData data = this.connectQosFactory.readObject(this.qos.toString());
169 // nectQosData data = new ConnectQosServer(this.glob, this.qos.toString()).getData();
170 ConnectReturnQos tmp = this.access.connect(new ConnectQos(this.glob, data), cb);
171 if (tmp != null) ret = tmp.toXml(" ");
172 else ret = "";
173 }
174 writeResponse(methodName, ret);
175 this.isConnected = true;
176 if (!implicitConnect) {
177 return true;
178 }
179 }
180 if (MethodName.DISCONNECT.equals(methodName)) {
181 if (this.qos.length() < 1) this.qos.append("<qos />");
182 DisconnectQosData disconnectQosData = this.disconnectQosFactory.readObject(this.qos.toString());
183 boolean ret = this.access.disconnect(new DisconnectQos(this.glob, disconnectQosData));
184 writeResponse(methodName, "\n"+ret);
185 return true;
186 }
187
188 if (this.qos.length() < 1) this.qos.append("<qos />");
189 if (this.key.length() < 1) this.key.append("<key />");
190
191 if (MethodName.PUBLISH.equals(methodName)) {
192 MsgUnit msgUnit = buildMsgUnit();
193 if (this.msgUnitCb != null) {
194 this.msgUnitCb.intercept(msgUnit);
195 }
196 if (log.isLoggable(Level.FINE)) XmlScriptClient.log.fine("appendEndOfElement publish: " + msgUnit.toXml());
197 PublishReturnQos ret = this.access.publish(msgUnit);
198 writeResponse(methodName, (ret != null)?ret.toXml(" "):null);
199 return true;
200 }
201 if (MethodName.PUBLISH_ARR.equals(methodName)) {
202 int size = this.messageList.size();
203 MsgUnit[] msgs = new MsgUnit[size];
204 for (int i=0; i < size; i++) {
205 if (log.isLoggable(Level.FINE)) XmlScriptClient.log.fine("appendEndOfElement publishArr: " + msgs[i].toXml());
206 msgs[i] = (MsgUnit)this.messageList.get(i);
207 }
208 PublishReturnQos[] ret = this.access.publishArr(msgs);
209 String[] retStr = new String[ret.length];
210 for (int i=0; i < ret.length; i++) retStr[i] = ret[i].toXml(" ");
211 writeResponse(methodName, retStr);
212 return true;
213 }
214 if (MethodName.PUBLISH_ONEWAY.equals(methodName)) {
215 int size = this.messageList.size();
216 MsgUnit[] msgs = new MsgUnit[size];
217 for (int i=0; i < size; i++) {
218 if (log.isLoggable(Level.FINE)) XmlScriptClient.log.fine("appendEndOfElement publishArr: " + msgs[i].toXml());
219 msgs[i] = (MsgUnit)this.messageList.get(i);
220 }
221 this.access.publishOneway(msgs);
222 return true;
223 }
224 if (MethodName.SUBSCRIBE.equals(methodName)) {
225 SubscribeReturnQos ret = this.access.subscribe(this.key.toString(), this.qos.toString());
226 writeResponse(methodName, ret.toXml(" "));
227 return true;
228 }
229 if (MethodName.UNSUBSCRIBE.equals(methodName)) {
230 UnSubscribeReturnQos[] ret = this.access.unSubscribe(this.key.toString(), this.qos.toString());
231 String[] retStr = new String[ret.length];
232 for (int i=0; i < ret.length; i++) retStr[i] = ret[i].toXml(" ");
233 writeResponse(methodName, retStr);
234 return true;
235 }
236 if (MethodName.ERASE.equals(methodName)) {
237 EraseReturnQos[] ret = this.access.erase(this.key.toString(), this.qos.toString());
238 String[] retStr = new String[ret.length];
239 for (int i=0; i < ret.length; i++) retStr[i] = ret[i].toXml(" ");
240 writeResponse(methodName, retStr);
241 return true;
242 }
243 if (MethodName.GET.equals(methodName)) {
244 MsgUnit[] ret = this.access.get(this.key.toString(), this.qos.toString());
245 String[] retStr = new String[ret.length];
246 for (int i=0; i < ret.length; i++) retStr[i] = ret[i].toXml(" ");
247 writeResponse(methodName, retStr);
248 return true;
249 }
250 }
251 catch (XmlBlasterException e) {
252 log.warning(e.getMessage());
253
254 // The exception has already a <exception> root tag
255 writeResponse(null/*MethodName.EXCEPTION*/, e.toXml(" "));
256
257 // For connect exceptions we stop parsing
258 if (MethodName.CONNECT.equals(methodName))
259 throw new XmlBlasterException(glob, ErrorCode.INTERNAL_STOP, ME, e.toString(), e);
260
261 if (this.throwAllExceptions)
262 throw new XmlBlasterException(glob, ErrorCode.INTERNAL_STOP, ME, e.toString(), e);
263
264 return true;
265 }
266 catch (Throwable e) {
267 log.severe(e.toString());
268 throw new XmlBlasterException(this.glob, ErrorCode.INTERNAL_UNKNOWN, ME, "", e);
269 }
270
271 log.warning("fireMethod with methodName=" + methodName.toString() + " is not implemented: " + this.key.toString() + " " + this.qos.toString());
272 return false;
273 }
274
275 /*
276 <xmlBlasterResponse>
277 <publish>
278 <qos>
279 <key oid='1'/>
280 <rcvTimestamp nanos='1131654994574000000'/>
281 <isPublish/>
282 </qos>
283 </publish>
284 <publishArr>
285 <message>
286 <qos>
287 <key oid='test'/>
288 <rcvTimestamp nanos='1075409585342000001'/>
289 <isPublish/>
290 </qos>
291 </message>
292 <message>
293 <qos>
294 <key oid='test'/>
295 <rcvTimestamp nanos='1075409585348000001'/>
296 <isPublish/>
297 </qos>
298 </message>
299 </publishArr>
300 </xmlBlasterResponse>
301 */
302 /**
303 * Write respone message to OutputStream.
304 * @param methodName Can be null
305 * @param message A well formed XML message or null
306 */
307 private void writeResponse(MethodName methodName, String message) throws XmlBlasterException {
308 String[] messages = new String[(message==null) ? 0 : 1];
309 if (messages.length == 1) messages[0] = message;
310 writeResponse(methodName, messages);
311 }
312
313 private void writeResponse(MethodName methodName, String[] messages) throws XmlBlasterException {
314 //super.response.append("\n<!-- __________________________________ ").append((methodName==null)?"":methodName.toString()).append(" response _____________________ -->");
315 if (methodName != null) super.response.append("\n<").append(methodName.toString()).append(">");
316 if (messages != null && messages.length > 0) {
317 if (messages.length == 1)
318 super.response.append(messages[0]);
319 else {
320 for (int i=0; i < messages.length; i++) {
321 super.response.append("\n <message>");
322 super.response.append(messages[i]);
323 super.response.append("\n </message>\n");
324 }
325 }
326 }
327 if (methodName != null) super.response.append("\n</").append(methodName.toString()).append(">\n");
328 flushResponse();
329 }
330
331 public static void main(String[] args) {
332 /*
333 String request = "<xmlBlaster>\n" +
334 " <connect>" +
335 " <securityPlugin type='aaa' version='bbb'>\n" +
336 " <user>michele</user>\n" +
337 " <passwd><![CDATA[secret ]]></passwd>\n" +
338 " </securityPlugin>\n" +
339 " </connect>\n" +
340 " <publish>\n" +
341 " <key>xxxx</key>\n" +
342 " <content xlink='sss'/>\n" +
343 " <qos></qos>\n" +
344 " </publish>\n" +
345 " <subscribe/>\n" +
346 " <disconnect/>\n" +
347 "</xmlBlaster>";
348 */
349 try {
350 Global glob = new Global();
351 String[] tmp = new String[args.length-2];
352 for (int i=0; i < tmp.length; i++) tmp[i] = args[i+2];
353
354 glob.init(tmp);
355 FileOutputStream out = new FileOutputStream(args[1]);
356 XmlScriptClient client = new XmlScriptClient(glob, out);
357 FileReader in = new FileReader(args[0]);
358 client.parse(in);
359 }
360 catch (Exception ex) {
361 ex.printStackTrace();
362 }
363 }
364
365 public String update(String cbSessionId, UpdateKey updateKey, byte[] content, UpdateQos updateQos) throws XmlBlasterException {
366 super.update(cbSessionId, updateKey, content, updateQos);
367 if (this.callback != null) {
368 return this.callback.update(cbSessionId, updateKey, content, updateQos);
369 }
370 return null;
371 }
372
373 public boolean isThrowAllExceptions() {
374 return throwAllExceptions;
375 }
376
377 public void setThrowAllExceptions(boolean throwAllExceptions) {
378 this.throwAllExceptions = throwAllExceptions;
379 }
380 }
syntax highlighted by Code2HTML, v. 0.9.1