KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > sync4j > server > engine > SyncAdapter


1 /**
2  * Copyright (C) 2003-2005 Funambol
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17  */

18 package sync4j.server.engine;
19
20 import java.io.*;
21 import java.util.Map JavaDoc;
22 import java.util.logging.Logger JavaDoc;
23 import java.util.logging.Level JavaDoc;
24 import javax.naming.*;
25
26 import sync4j.framework.server.SyncResponse;
27 import sync4j.framework.logging.Sync4jLogger;
28 import sync4j.framework.config.ConfigurationConstants;
29 import sync4j.framework.config.ConfigClassLoader;
30 import sync4j.framework.config.ConfigurationException;
31 import sync4j.framework.core.Constants;
32 import sync4j.framework.core.StatusCode;
33 import sync4j.framework.core.SyncML;
34 import sync4j.framework.core.Sync4jException;
35 import sync4j.framework.core.Util;
36 import sync4j.framework.engine.MessageSizeCalculator;
37 import sync4j.framework.protocol.ProtocolException;
38 import sync4j.framework.protocol.ProtocolUtil;
39 import sync4j.framework.server.session.*;
40 import sync4j.framework.server.error.*;
41 import sync4j.framework.tools.WBXMLTools;
42 import sync4j.framework.tools.WBXMLSizeCalculator;
43 import sync4j.framework.tools.XMLSizeCalculator;
44 import sync4j.framework.tools.beans.*;
45
46 import sync4j.framework.engine.pipeline.PipelineManager;
47 import sync4j.framework.engine.pipeline.MessageProcessingContext;
48
49 import sync4j.server.SyncMLCanonizer;
50 import sync4j.server.config.Configuration;
51 import sync4j.server.session.SyncSessionHandler;
52
53 import org.jibx.runtime.*;
54 import org.jibx.runtime.impl.*;
55
56 /**
57  * This class handles a synchronization request.
58  * <p>
59  * This server accepts synchronization requests addressed to the hostname
60  * indicated by the configuration property pointed by {CFG_SERVER_URI} (see
61  * Sync4j.xml).
62  * <p>
63  *
64  * LOG NAME: sync4j.server
65  *
66  * @author Luigia Fassina @ Funambol
67  *
68  * @version $Id: SyncAdapter.java,v 1.32 2005/06/01 11:19:04 harrie Exp $
69  *
70  */

71 public class SyncAdapter
72 implements ConfigurationConstants {
73
74     // ------------------------------------------------------- Private constants
75
protected static final String JavaDoc CONFIG_SYNCML_CANONIZER
76     = "sync4j/server/SyncMLCanonizer.xml";
77
78     protected static final String JavaDoc PARAM_SESSION_ID = "sid";
79
80     protected static final String JavaDoc HEADER_CONTENT_TYPE = "content-type";
81
82     // ------------------------------------------------------------ Private data
83

84     protected transient Logger JavaDoc log = null;
85
86     protected SessionHandler sessionHandler = null;
87
88     protected Configuration config = null;
89
90     protected PipelineManager pipelineManager = null;
91
92     protected MessageProcessingContext mpc = null;
93
94     protected SyncMLCanonizer syncMLCanonizer = null;
95
96
97     // ------------------------------------------------------------ Constructors
98

99     /**
100      * Creates a new SyncAdapter.
101      *
102      * @param config the configuration object
103      */

104     public SyncAdapter(Configuration config) {
105         super();
106
107         log = Sync4jLogger.getLogger("server");
108
109         this.config = config;
110
111         sessionHandler = config.getSessionHandler();
112         pipelineManager = config.getPipelineManager();
113
114         try {
115             syncMLCanonizer = (SyncMLCanonizer)config.getBeanInstanceByName(CONFIG_SYNCML_CANONIZER);
116         } catch (Exception JavaDoc e) {
117             log.throwing("SyncAdapter", "constructor", e);
118             new Sync4jException("Error "
119                                 + e.getClass().getName()
120                                 + " creating the syncMLCanonizer: "
121                                 + e.getMessage()
122                                 ).printStackTrace();
123         }
124
125         mpc = new MessageProcessingContext();
126     }
127
128     // ---------------------------------------------------------- Public methods
129

130     /**
131      * Tells the SyncAdapter that a new Synchronizatin session is starting
132      *
133      * @param sessionId the session id
134      */

135     public void beginSync(String JavaDoc sessionId) {
136         mpc.setSessionProperty(mpc.PROPERTY_SESSION_ID, sessionId);
137     }
138
139     /**
140      * Finalizes the sync session
141      */

142     public void endSync() {
143         int currentState = sessionHandler.getCurrentState();
144
145         switch (currentState) {
146             case SessionHandler.STATE_END:
147                 sessionHandler.commit();
148                 break;
149
150             case SessionHandler.STATE_ERROR:
151                 sessionHandler.abort(StatusCode.PROCESSING_ERROR);
152                 break;
153
154             default:
155                 sessionHandler.abort(StatusCode.SESSION_EXPIRED);
156                 break;
157         }
158         log = null;
159     }
160
161     /**
162      * process the incoming XML sync message
163      *
164      * @param msg must be non-null
165      * @param parameters request parameters (at transport level)
166      * @param headers request headers (at transport level)
167      *
168      */

169     public SyncResponse processXMLMessage( final byte[] msg ,
170                                            final Map JavaDoc parameters,
171                                            final Map JavaDoc headers )
172     throws ServerException {
173         SyncResponse response = null;
174         String JavaDoc inMessage = null;
175         SyncML syncMLIn = null, syncMLOut = null;
176         String JavaDoc charset = null;
177
178         // We check first the prolog for the encoding.
179
charset = getCharSetOfProlog(msg);
180         if (charset == null) { // If not in the prolog, use mime-type.
181
charset = getCharSet(headers);
182             if (charset == null) { // If still not defined default to UTF-8.
183
charset = "UTF-8";
184                 if (log.isLoggable(Level.FINEST)) {
185                     log.finest("Charset (default): " + charset);
186                 }
187             } else { // charset was defined in mime-type.
188
if (log.isLoggable(Level.FINEST)) {
189                     log.finest("Charset from mime-type: " + charset);
190                 }
191             }
192         } else { // Charset was defined in the prolog.
193
if (log.isLoggable(Level.FINEST)) {
194                 log.finest("Charset of prolog: " + charset);
195             }
196         }
197
198         try {
199             inMessage = new String JavaDoc(msg, charset);
200         } catch (UnsupportedEncodingException e) {
201             // if we have a not known encoding try the default.
202
// FIXME, maybe better to bail out.
203
inMessage = new String JavaDoc(msg);
204         }
205         if (log.isLoggable(Level.FINEST)) {
206             log.finest("Message to translate into the SyncML object:\n" + inMessage);
207         }
208
209         inMessage = syncMLCanonizer.canonizeInput(inMessage);
210
211         syncMLIn = convert(inMessage);
212
213         mpc.setRequestProperty(mpc.PROPERTY_REQUEST_PARAMETERS, parameters);
214         mpc.setRequestProperty(mpc.PROPERTY_REQUEST_HEADERS , headers );
215
216         if (log.isLoggable(Level.FINE)) {
217             log.fine("Calling input pipeline");
218         }
219
220         pipelineManager.preProcessMessage(mpc, syncMLIn);
221
222         if (log.isLoggable(Level.FINEST)) {
223             log.finest("About processing message: " + Util.toXML(syncMLIn));
224         }
225
226         syncMLOut = processInputMessage(syncMLIn);
227
228         setRespURI(syncMLOut, (String JavaDoc)mpc.getSessionProperty(mpc.PROPERTY_SESSION_ID));
229         checkRespURISize(syncMLOut.getSyncHdr().getRespURI());
230
231         if (log.isLoggable(Level.FINEST)) {
232             log.finest("Calling output pipeline");
233         }
234
235         pipelineManager.postProcessMessage(mpc, syncMLOut);
236
237         byte[] out = convert(syncMLOut, charset);
238
239         response = new Sync4jResponse(out, syncMLOut);
240
241         return response;
242     }
243
244     /**
245      * process the incoming WBXML sync message
246      *
247      * @param msg must be non-null
248      * @param parameters request parameters (at transport level)
249      * @param headers request headers (at transport level)
250      *
251      */

252     public SyncResponse processWBXMLMessage( final byte[] msg ,
253                                              final Map JavaDoc parameters,
254                                              final Map JavaDoc headers )
255     throws ServerException {
256         SyncResponse response = null;
257         String JavaDoc inMessage = null;
258         SyncML syncMLIn = null, syncMLOut = null;
259         String JavaDoc charset = getCharSet(headers);
260
261         //
262
// Convert WBXML to XML, and then pass to processXMLMessage
263
//
264
if (log.isLoggable(Level.FINEST)) {
265             log.finest("Convertint message from wbxml to xml");
266             log.finest("Char-set: " + charset + " (null, means wbxml defined)");
267         }
268
269         try {
270             inMessage = WBXMLTools.wbxmlToXml(msg, charset);
271         } catch (Sync4jException e) {
272             throw new ServerException(e);
273         }
274
275
276         if (log.isLoggable(Level.FINEST)) {
277             log.finest("Message to translate into the SyncML object:\n" + inMessage);
278         }
279
280         inMessage = syncMLCanonizer.canonizeInput(inMessage);
281
282         syncMLIn = convert(inMessage);
283
284         mpc.setRequestProperty(mpc.PROPERTY_REQUEST_PARAMETERS, parameters);
285         mpc.setRequestProperty(mpc.PROPERTY_REQUEST_HEADERS , headers );
286
287         if (log.isLoggable(Level.FINE)) {
288             log.fine("Calling input pipeline");
289         }
290         pipelineManager.preProcessMessage(mpc, syncMLIn);
291
292         if (log.isLoggable(Level.FINEST)) {
293             log.finest("About processing message: " + Util.toXML(syncMLIn));
294         }
295
296         syncMLOut = processInputMessage(syncMLIn);
297
298         setRespURI(syncMLOut, (String JavaDoc)mpc.getSessionProperty(mpc.PROPERTY_SESSION_ID));
299         checkRespURISize(syncMLOut.getSyncHdr().getRespURI());
300
301         if (log.isLoggable(Level.FINEST)) {
302             log.finest("Calling output pipeline");
303         }
304
305         pipelineManager.postProcessMessage(mpc, syncMLOut);
306
307         byte[] out = null;
308         try {
309             //
310
// The marshalling is doing directly into WBXMLTools
311
// because that method is calling in the other code too
312
//
313
out = WBXMLTools.toWBXML(syncMLOut);
314
315         } catch(Exception JavaDoc e) {
316             if (log.isLoggable(Level.SEVERE)) {
317                 log.severe("Error processing the WBXML message: " + e.getMessage());
318             }
319             log.throwing("SyncAdapter", "processWBXMLMessage", e);
320
321             throw new ServerException(e);
322         }
323
324         response = new Sync4jResponse(out, syncMLOut);
325
326
327         return response;
328     }
329
330     /**
331      * Returns the charset encoding if it was found in the prolog.
332      * @param msg the message in which the prolog is searched.
333      * @returns the charset if specified otherwise null.
334      */

335     private String JavaDoc getCharSetOfProlog(byte[] msg) {
336         // Check if we have a start prolog, "<?xml".
337
if ((msg[0] == '<') && (msg[1] == '?') && (msg[2] == 'x') && (msg[2] == 'm') && (msg[4] == 'l')) {
338             // Now start looking for the end of the prolog.
339
int length = msg.length - 1; // substract one for the double byte check.
340
for (int i = 5; i < length; i++) {
341                  if ((msg[i] == '?') && (msg[i+1] == '>')) {
342                      // We found the end of the prolog.
343
String JavaDoc prolog = new String JavaDoc(msg, 0, i+1);
344                      if (log.isLoggable(Level.FINEST)) {
345                          log.finest("Message prolog: " + prolog);
346                      }
347                      // Search for encoding keyword.
348
int p = prolog.indexOf("encoding");
349                      // First double quote after the encoding.
350
int q1 = prolog.indexOf('"', p);
351                      // Second double quote after the encoding.
352
int q2 = prolog.indexOf('"', q1);
353                      return prolog.substring(q1, q2);
354                  }
355             }
356         }
357         return null; // Was not defined
358
}
359
360     /**
361      * Returns the charset encoding based on the header information.
362      * @param headers the collection of headers of the request.
363      * @returns the charset if specified otherwise null.
364      */

365     private String JavaDoc getCharSet(Map JavaDoc headers) {
366         String JavaDoc contentType = (String JavaDoc)headers.get(HEADER_CONTENT_TYPE);
367         if (log.isLoggable(Level.FINEST)) {
368             log.finest("Message has content type: " + contentType);
369         }
370         if (contentType != null) {
371             // Check if we have a charset at all.
372
int pointer = contentType.indexOf(';');
373             if (pointer > 0) {
374                 // Now strip of the 'charset=' by searching of the first '='.
375
// Only one is allowed anyway.
376
pointer = contentType.indexOf('=', pointer);
377                 if (pointer > 0) {
378                     String JavaDoc result = contentType.substring(pointer+1);
379                     // Just trim whitespace incase the client is not polite.
380
return result.trim();
381                 }
382             }
383         }
384         return null; // Was not defined
385
}
386
387     /**
388      * Used to process a status information as needed by the client object (i.e.
389      * errors or success).
390      *
391      * @param statusCode the status code
392      * @param statusMessage additional descriptive message
393      *
394      * @see sync4j.framework.core.StatusCode for valid status codes.
395      */

396     public SyncResponse processStatusCode(int statusCode, String JavaDoc info){
397         if (statusCode != StatusCode.OK) {
398             sessionHandler.abort(statusCode);
399         }
400         return null;
401     }
402
403     // --------------------------------------------------------- private methods
404

405     /**
406      * Processes the input SyncML message after convertion to objects. See class
407      * description for more information.
408      *
409      * @return the response message
410      *
411      * @throws ServerException in case of errors
412      */

413     private SyncML processInputMessage(final SyncML syncMLIn) throws ServerException {
414         try {
415             try {
416                 sessionHandler.setSizeCalculator(
417                     getSizeCalculator()
418                 );
419                 return sessionHandler.processMessage(syncMLIn, mpc);
420             } catch (InvalidCredentialsException e) {
421                 return sessionHandler.processError(syncMLIn, e);
422             } catch (ServerException e) {
423                 return sessionHandler.processError(syncMLIn, e);
424             } catch (ProtocolException e) {
425                 return sessionHandler.processError(syncMLIn,
426                                        new BadRequestException(e.getMessage()));
427             }
428
429         } catch (Sync4jException e1) {
430             //
431
// This can be due only to processError
432
//
433
throw new ServerException(e1);
434         }
435     }
436
437     /**
438      * Converts the given SyncML message into a <i>SyncML</i> object.
439      *
440      * @param msg the SyncML message
441      *
442      * @return the corresponding SyncML message
443      *
444      * @throws ServerException in case of translating errors
445      */

446     private SyncML convert(final String JavaDoc msg) throws ServerException {
447         try {
448             IBindingFactory f = BindingDirectory.getFactory(SyncML.class);
449             IUnmarshallingContext c = f.createUnmarshallingContext();
450
451             Object JavaDoc syncML = c.unmarshalDocument(new StringReader(msg));
452
453             return (SyncML)syncML;
454         } catch(JiBXException e) {
455             if (log.isLoggable(Level.SEVERE)) {
456                 log.severe("Error unmarshalling message:" + e.getMessage());
457             }
458             log.throwing(getClass().getName(), "convert", e);
459             throw new ServerException(e);
460         }
461     }
462
463     /**
464      * Converts a <i>SyncML</i> object into the corresponding SyncML message (as
465      * String).
466      *
467      * @param msg the SyncML object
468      *
469      * @return the corresponding SyncML message
470      *
471      * @throws ServerException in case of translation errors
472      */

473     private byte[] convert(final SyncML msg, String JavaDoc charset)
474     throws ServerException {
475         try {
476             if (log.isLoggable(Level.FINEST)) {
477                 log.finest("Creating response with charset: " + charset);
478             }
479
480             ByteArrayOutputStream bout = new ByteArrayOutputStream();
481             IBindingFactory f = BindingDirectory.getFactory(SyncML.class);
482             IMarshallingContext c = f.createMarshallingContext();
483             c.setIndent(0);
484             c.marshalDocument(msg, charset, null, bout);
485
486             //
487
// Turn the output message in the canonical form and return it
488
//
489
return syncMLCanonizer.canonizeOutput(bout.toString(charset)).getBytes(charset);
490
491         } catch(Exception JavaDoc e) {
492             if (log.isLoggable(Level.SEVERE)) {
493                 log.severe("Error in converting the message:" + e.getMessage());
494             }
495             log.throwing(getClass().getName(), "convert", e);
496
497             throw new ServerException(e);
498         }
499     }
500
501     /**
502      * Set the RespURI in the given message
503      *
504      * @param msg the message into wich set the RespURI
505      * @param sessionId the session id
506      */

507     private void setRespURI(SyncML msg, final String JavaDoc sessionId) {
508         msg.getSyncHdr().setRespURI(
509             config.getServerConfig().getEngineConfiguration().getServerURI() +
510             '?' +
511             PARAM_SESSION_ID +
512             '=' +
513             sessionId
514         );
515     }
516
517     /**
518      * Checks if the RespURI element is going to be bigger than the space we
519      * reserved for it. In this case we trace a worning in the log. Note that
520      * not necessarily a RespURI bigger than the save space will result in a
521      * message bigger then the maxmsgsize, therefore a warning looks the more
522      * appropriate level at which logging it.
523      *
524      * @param uri the resp URI
525      */

526     private void checkRespURISize(String JavaDoc uri) {
527         //
528
// The worse case is using XML: <RespURI></RespURI> -> 20 chars
529
//
530
final long s = uri.length() + 20;
531         final long h = getSizeCalculator().getRespURIOverhead();
532
533         if ((log.isLoggable(Level.WARNING)) && (h < s)) {
534             log.warning( "The RespURI element size ("
535                        + s
536                        + ") exeeds the reserved hoverhead ("
537                        + h
538                        + ')'
539                        );
540         }
541     }
542
543     /**
544      * Returns the MessageSizeCalculator to be used accordingly to the message
545      * content type (retrieved by the processing context)
546      *
547      * @return
548      */

549     private MessageSizeCalculator getSizeCalculator() {
550         Map JavaDoc headers = (Map JavaDoc)mpc.getRequestProperty(mpc.PROPERTY_REQUEST_HEADERS);
551
552         String JavaDoc contentType = (String JavaDoc)headers.get(HEADER_CONTENT_TYPE);
553
554         MessageSizeCalculator calculator = null;
555
556         if ((contentType != null) && Constants.MIMETYPE_SYNCMLDS_WBXML.equals(contentType)) {
557             calculator = new WBXMLSizeCalculator();
558         } else {
559             calculator = new XMLSizeCalculator();
560         }
561
562         return calculator;
563     }
564
565     // ------------------------------------------------------------ SyncResponse
566

567     private static class Sync4jResponse implements SyncResponse {
568         private byte[] msg;
569         private boolean completed;
570
571         /**
572          * Create a new Sync4jResponse.
573          *
574          * @param msg the marshalled response
575          * @param syncML the SyncML object
576          * @param resultMimeType the mime type
577          *
578          */

579         private Sync4jResponse(final byte[] msg,
580                                final SyncML syncML) {
581             this.msg = msg;
582             this.completed = (syncML.isLastMessage() &&
583                              ProtocolUtil.noMoreResponse(syncML) &&
584                              (ProtocolUtil.getStatusChal(syncML) == null));
585         }
586
587         public byte[] getMessage() {
588             return this.msg;
589         }
590
591         /**
592          * Is this message the last message allowed for the current session?
593          *
594          * @return true if yes, false otherwise
595          */

596         public boolean isCompleted() {
597             return completed;
598         }
599     }
600 }
601
Popular Tags