KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > sync4j > transport > http > server > Sync4jServlet


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
19
20 package sync4j.transport.http.server;
21
22 import java.io.*;
23 import java.net.InetAddress JavaDoc;
24 import java.net.UnknownHostException JavaDoc;
25 import java.util.Enumeration JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.Map JavaDoc;
28 import java.util.logging.Logger JavaDoc;
29 import java.util.logging.Level JavaDoc;
30
31 import javax.naming.*;
32 import javax.servlet.*;
33 import javax.servlet.http.*;
34
35 import sync4j.framework.core.*;
36 import sync4j.framework.logging.Sync4jLogger;
37 import sync4j.framework.server.*;
38 import sync4j.framework.tools.Base64;
39 import sync4j.framework.transport.http.SyncHolder;
40 import sync4j.framework.transport.http.SyncHolderCache;
41 import sync4j.framework.protocol.ProtocolException;
42
43 import sync4j.server.config.Configuration;
44
45 import org.apache.commons.lang.StringUtils;
46
47 import org.jibx.runtime.*;
48 import org.jibx.runtime.impl.*;
49
50 /**
51  * Receives the HTTP request and does session management.
52  *
53  * <b>Session Management</b>
54  * <p>
55  * If the url contains a parameter {PARAM_SESSION_ID}={SESSION_ID}, the session
56  * id is used to lookup in the <i>handlerCache</i> if there is already a
57  * <i>SyncHolder</i> associated with the given session id it is used to process
58  * the incoming request. Otherwise (either if the session parameter is not
59  * specified or not cached handler is found), a new session id is created and a
60  * new <i>SyncHolder</i> object is instantiated and stored in the cache.<br>
61  * The session id is created as followed:
62  * <p>
63  * <ol>
64  * <li> the first four bytes contains the IP address of the remote client
65  * <li> a dash ('-') is appended
66  * <li> the creation timestamp is appended
67  * <li> the resulting string is encoded base 64
68  * </ol>
69  * Note that no session expiration is handled by this class. That is delegated
70  * to the <i>SyncHolderCache</i> object.
71  *
72  * @author Stefano Fornari @ Funambol
73  *
74  * @version $Id: Sync4jServlet.java,v 1.20 2005/05/27 12:04:53 nichele Exp $
75  *
76  */

77 public final class Sync4jServlet
78 extends HttpServlet
79 implements Constants {
80
81     // --------------------------------------------------------------- Constants
82

83     // ------------------------------------------------------------ Private data
84

85     private Logger JavaDoc log = Sync4jLogger.getLogger(LOG_NAME);
86
87     private static String JavaDoc syncHolderClass = null;
88     private static SyncHolderCache holderCache = null;
89
90     //
91
// To check if the engine was initialized
92
//
93
private boolean initialized = false;
94     private static final String JavaDoc PATH_STATUS = "/sync4j/sync/status";
95
96     private static final int SIZE_INPUT_BUFFER = 4096;
97     // ---------------------------------------------------------- Public methods
98

99     public void init() throws ServletException {
100         initialized = false;
101         String JavaDoc value;
102         long timeToLive = 0;
103
104         if (log.isLoggable(Level.INFO)) {
105             System.getProperties().list(System.out);
106         }
107
108         ServletConfig config = getServletConfig();
109
110         value = config.getInitParameter(PARAM_SYNCHOLDER_CLASS);
111
112         if (StringUtils.isEmpty(value)) {
113             String JavaDoc msg = "The servlet configuration parameter "
114             + PARAM_SYNCHOLDER_CLASS
115             + " cannot be empty ("
116             + value
117             + ")"
118             ;
119             log(msg);
120             throw new ServletException(msg);
121         }
122         syncHolderClass = value;
123
124         value = config.getInitParameter(PARAM_SESSION_TTL);
125         if (!StringUtils.isEmpty(value)) {
126             try {
127                 timeToLive = Long.parseLong(value);
128             } catch (NumberFormatException JavaDoc e) {
129                 String JavaDoc msg = "The servlet configuration parameter "
130                 + PARAM_SESSION_TTL
131                 + " is not an integer number ("
132                 + value
133                 + ")"
134                 ;
135                 log.severe(msg);
136                 throw new ServletException(msg);
137             }
138         }
139
140         holderCache = new SyncHolderCache(timeToLive);
141
142         //
143
// Now we are ready to bootstrap the Sync4j components
144
//
145
bootstrap();
146
147         //
148
// Engine initialized!
149
//
150
initialized = true;
151     }
152
153
154     public void doPost(final HttpServletRequest httpRequest ,
155                        final HttpServletResponse httpResponse)
156     throws ServletException, IOException {
157
158         if (log.isLoggable(Level.FINEST)) {
159             showHeaders(httpRequest);
160         }
161
162         if (log.isLoggable(Level.INFO)) {
163             StringBuffer JavaDoc sb = new StringBuffer JavaDoc("Handling incoming request ");
164             sb.append(httpRequest.getContextPath())
165               .append(httpRequest.getServletPath());
166             if (httpRequest.getPathInfo() != null) {
167                 sb.append(httpRequest.getPathInfo());
168             }
169
170             if (httpRequest.getQueryString() != null) {
171                 sb.append('?').append(httpRequest.getQueryString());
172             }
173             sb.append('.');
174
175             log.info(sb.toString());
176         }
177
178         final String JavaDoc contentType = httpRequest.getContentType().split(";")[0];
179         final int contentLength = httpRequest.getContentLength();
180
181         if (log.isLoggable(Level.FINEST)) {
182             log.finest("contentType: " + contentType);
183             log.finest("contentLength: " + contentLength);
184         }
185
186
187         Map JavaDoc params = getRequestParameters(httpRequest);
188         Map JavaDoc headers = getRequestHeaders (httpRequest);
189
190         byte[] requestData = null;
191         InputStream in = httpRequest.getInputStream();
192
193         try{
194             requestData = readContent(in);
195         } catch (Exception JavaDoc e) {
196             handleError(httpRequest, httpResponse, "Error reading the request", e);
197             return;
198         } finally {
199
200             try {
201                 if (in != null) {
202                     in.close();
203                 }
204             } catch (Exception JavaDoc ex) {
205                 handleError(httpRequest, httpResponse, ex.getClass().getName(), ex);
206                 return;
207             }
208             in = null;
209         }
210
211         //
212
// If the session id is not specified in the URL, a new remote object
213
// will be created. Otherwise the session id specifies which remote
214
// object shall handles the request.
215
//
216
SyncHolder holder = null;
217         try {
218             holder = createHolder(httpRequest);
219         } catch (Exception JavaDoc e) {
220             handleError(httpRequest, httpResponse, "Error creating SyncBean", e);
221             return;
222         }
223
224         SyncResponse resp = null;
225         try {
226             if (sync4j.framework.core.Constants.MIMETYPE_SYNCMLDS_WBXML.equals(contentType)) {
227                 resp = holder.processWBXMLMessage(requestData, params, headers);
228             } else if (sync4j.framework.core.Constants.MIMETYPE_SYNCMLDS_XML.equals(contentType)) {
229                 resp = holder.processXMLMessage(requestData, params, headers);
230             } else {
231                 throw new ProtocolException( "Mime type "
232                                            + contentType
233                                            + " nor supported or unknown" );
234             }
235         } catch (Exception JavaDoc e) {
236             Throwable JavaDoc cause = e.getCause();
237
238             if ( (cause != null)
239             && ( (cause instanceof ProtocolException)
240             || (cause instanceof Sync4jException )
241             )
242             ) {
243                 handleError(httpRequest, httpResponse, "Protocol error", cause);
244                 return;
245             } else {
246                 throw new ServletException(e);
247             }
248         }
249
250         OutputStream out = null;
251         try {
252             out = httpResponse.getOutputStream();
253             httpResponse.setContentType(contentType);
254
255             byte[] response = resp.getMessage();
256
257             if (log.isLoggable(Level.FINEST)) {
258                 log.finest("Outgoing message length: " + response.length);
259             }
260
261             httpResponse.setContentLength(response.length);
262             out.write(response);
263             out.flush();
264
265         } finally {
266             if (out != null) {
267                 out.close();
268             }
269             if (log.isLoggable(Level.FINEST)) {
270                 log.finest("Finally");
271             }
272         }
273
274         //
275
// If the message completed the SyncML communication, the holder
276
// must be closed and discarded.
277
//
278
if (resp.isCompleted()) {
279             releaseHolder(holder);
280         }
281     }
282
283     public void doGet(final HttpServletRequest httpRequest ,
284                       final HttpServletResponse httpResponse)
285     throws ServletException, IOException {
286
287         String JavaDoc requestURI = httpRequest.getRequestURI();
288
289         if (log.isLoggable(Level.FINEST)) {
290             log.finest("requestURI: " + requestURI);
291         }
292         if (requestURI.equals(PATH_STATUS)) {
293             if (initialized) {
294                 //
295
//The request sent by the client was successful
296
//
297
httpResponse.setStatus(httpResponse.SC_OK);
298                 if (log.isLoggable(Level.FINEST)) {
299                     log.finest("Request succeeded normally");
300                 }
301             } else {
302                 //
303
//The request was unsuccessful to the server being down or overloaded.
304
//
305
httpResponse.setStatus(httpResponse.SC_SERVICE_UNAVAILABLE);
306                 if (log.isLoggable(Level.FINEST)) {
307                     log.finest("The HTTP server is unable to handle the request");
308                 }
309             }
310         }
311     }
312
313     // ------------------------------------------------------- Protected methods
314
/**
315      * Reads the request body without to known the length of the data.
316      *
317      * @param in the request InputStream
318      *
319      * @throws IOException in case of errors
320      */

321     protected byte[] readContent(final InputStream in) throws IOException {
322         ByteArrayOutputStream bout = new ByteArrayOutputStream();
323         byte[] buf = new byte[SIZE_INPUT_BUFFER];
324
325         int c = 0;
326         int b = 0;
327         while ((c < buf.length) && (b = in.read(buf, c, buf.length-c)) >= 0) {
328             c+=b;
329             if (c == SIZE_INPUT_BUFFER) {
330                 bout.write(buf);
331                 buf = new byte[SIZE_INPUT_BUFFER];
332                 c = 0;
333             }
334         }
335         if (c != 0) {
336             bout.write(buf, 0, c);
337         }
338         return bout.toByteArray();
339     }
340
341     // --------------------------------------------------------- Private methods
342
/**
343      * Factory method for <i>SyncHolder</i> objects. If the session id is
344      * passed as a CGI parameter, it is transformed in a EJB handle. If the
345      * handle is not valid, it is considered expired. If the session id is not
346      * specified, a new EJB is created
347      *
348      * @param request the associated HTTP request object
349      *
350      * @return a new <i>SyncHolder</i>
351      *
352      */

353     private SyncHolder createHolder(HttpServletRequest request)
354     throws Exception JavaDoc {
355         String JavaDoc sessionId = request.getParameter(PARAM_SESSION_ID);
356
357         SyncHolder holder = null;
358
359         if (log.isLoggable(Level.FINEST)) {
360             log.finest("cache: " + holderCache);
361         }
362
363         if (!StringUtils.isEmpty(sessionId)) {
364             holder = (SyncHolder)holderCache.get(sessionId);
365         }
366
367         if (holder == null) {
368             holder = (SyncHolder)getClass().forName(syncHolderClass).newInstance();
369
370             sessionId = createSessionId(request);
371             holder.setSessionId(sessionId);
372             holderCache.put(holder);
373         }
374
375         return holder;
376     }
377
378     /**
379      * Closes the given <i>SyncHolder</i> and removes it from the cache.
380      *
381      * @param holder the holder to releases
382      */

383     private void releaseHolder(SyncHolder holder) {
384         try {
385             holder.close();
386         } catch (Exception JavaDoc e) {
387             log.severe(e.getMessage());
388             log.throwing(getClass().getName(), "releaseHolder", e.getCause());
389         }
390         holderCache.remove(holder.getSessionId());
391     }
392
393     /**
394      * Handles errors conditions returning an appropriate content to the client.
395      *
396      * @param request the request object
397      * @param response the response object
398      * @msg a desctiptive message
399      * @t a throwable object
400      *
401      */

402     private void handleError(final HttpServletRequest request ,
403                              final HttpServletResponse response,
404                              String JavaDoc msg ,
405                              final Throwable JavaDoc t ) {
406
407         if (msg == null) {
408             msg = "";
409         }
410
411         if (t == null) {
412             log.severe(msg);
413         } else {
414             log.severe(msg);
415             log.throwing(getClass().getName(), "unknown", t);
416         }
417         try {
418             response.sendError(response.SC_BAD_REQUEST, msg);
419         } catch (IOException e) {
420             log.severe(e.getMessage());
421             log.throwing(getClass().getName(), "unknown", t);
422         }
423     }
424
425     /**
426      * Creates the session id (see the class description for details).
427      *
428      * @param request the HTTP request object
429      *
430      * @return a newly created session id
431      */

432     private String JavaDoc createSessionId(HttpServletRequest request) {
433         String JavaDoc clientIP = request.getRemoteAddr() ;
434         long timestamp = System.currentTimeMillis();
435
436         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
437
438         byte[] ip = null;
439
440         try {
441             ip = InetAddress.getByName(clientIP).getAddress();
442         } catch (UnknownHostException JavaDoc e) {
443             ip = new byte[] {0, 0, 0, 0};
444         }
445
446         sb.append(ip).append('-').append(timestamp);
447
448         String JavaDoc stringSessionId = new String JavaDoc(Base64.encode(sb.toString().getBytes()));
449
450         //
451
// strips out tailing '='
452
//
453
int i = stringSessionId.lastIndexOf('=');
454
455         return ((i>0) ? stringSessionId.substring(0, i-1) : stringSessionId);
456     }
457
458     /**
459      * Extracts the request parameters and returns them in a <i>Map</i>
460      *
461      * @param request the request
462      */

463     private Map JavaDoc getRequestParameters(HttpServletRequest request) {
464         Map JavaDoc params = new HashMap JavaDoc();
465
466         String JavaDoc paramName = null;
467         for (Enumeration JavaDoc e=request.getParameterNames(); e.hasMoreElements(); ) {
468             paramName = (String JavaDoc)e.nextElement();
469
470             params.put(paramName, request.getParameter(paramName));
471         }
472
473         return params;
474     }
475
476     /**
477      * Extracts the request headers and returns them in a <i>Map</i>
478      *
479      * @param request the request
480      */

481     private Map JavaDoc getRequestHeaders(HttpServletRequest request) {
482         Map JavaDoc headers = new HashMap JavaDoc();
483
484         String JavaDoc headerName = null;
485         for (Enumeration JavaDoc e=request.getHeaderNames(); e.hasMoreElements(); ) {
486             headerName = (String JavaDoc)e.nextElement();
487
488             headers.put(headerName.toLowerCase(), request.getHeader(headerName));
489         }
490
491         return headers;
492     }
493
494     /**
495      * The same as <i>handleError(reqest, response, msg, t, null)</i>.
496      *
497      * @param request the request object
498      * @param response the response object
499      * @msg a desctiptive message
500      * @t a throwable object
501      *
502      */

503     private void handleError(final HttpServletRequest request,
504                              final HttpServletResponse response,
505                              final String JavaDoc msg) {
506         this.handleError(request, response, msg, null);
507     }
508
509     /**
510      * Bootstraps the Sync4j server components and logs the server startup
511      * event on both the console and the logging system.
512      */

513     private void bootstrap() {
514         Configuration c = null;
515
516         c = Configuration.getConfiguration();
517
518         String JavaDoc msg1 = "================================================================================";
519         String JavaDoc sp = "\n\n";
520         String JavaDoc msg2 = "SyncServer v. "
521                     + c.getServerConfig().getServerInfo().getSwV()
522                     + " engine started.\nConfiguration object found."
523                     ;
524
525         //
526
// NOTE: this code uses the stdout by choice, so that it will always
527
// be displayed, regardless how logging is configured.
528
//
529
System.out.println(msg1);
530         System.out.println(sp);
531         System.out.println(msg2);
532         System.out.println(sp);
533         System.getProperties().list(System.out);
534         System.out.println(sp);
535         System.out.println(msg1);
536
537         if (log.isLoggable(Level.INFO)) {
538             log.info(msg1+sp);
539             log.info(msg2+sp);
540             log.info(System.getProperties().toString()+sp);
541             log.info(msg1);
542         }
543
544         if (log.isLoggable(Level.FINEST)) {
545             log.finest("Engine configuration:");
546             log.finest(" - store: "
547                       + c.getServerConfig().getEngineConfiguration().getStore()
548                       + " (" + c.getStore() + ")"
549             );
550             log.finest(" - officer: "
551                       + c.getServerConfig().getEngineConfiguration().getOfficer()
552                       + " (" + c.getOfficer() + ")"
553             );
554             log.finest(" - strategy: "
555                       + c.getServerConfig().getEngineConfiguration().getStrategy()
556                       + " (" + c.getStrategy() + ")"
557             );
558
559             log.finest("Default encoding: "
560                        + (new OutputStreamWriter(new ByteArrayOutputStream())).
561                       getEncoding()
562             );
563
564         }
565     }
566
567     /**
568      * Log the headers of the request
569      * @param request HttpServletRequest
570      */

571     private void showHeaders(HttpServletRequest request) {
572         Enumeration JavaDoc enumHeaders = request.getHeaderNames();
573         String JavaDoc headerName = null;
574         log.finest("Http header: ");
575         while (enumHeaders.hasMoreElements()) {
576             headerName = (String JavaDoc)enumHeaders.nextElement();
577             log.finest(headerName + ": " + request.getHeader(headerName));
578         }
579     }
580 }
581
Popular Tags