KickJava   Java API By Example, From Geeks To Geeks.

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


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

73 public final class ClusterableSync4jServlet
74 extends HttpServlet
75 implements Constants {
76     // ------------------------------------------------------------ Private data
77
private String JavaDoc jndiAddress = DEFAULT_JNDI_ADDRESS;
78     private RemoteHolderCache holderCache = null;
79
80     private Logger JavaDoc log = Logger.getLogger(LOG_NAME);
81
82     //
83
// To check if the engine was initialized
84
//
85
private boolean initialized = false;
86     private static final String JavaDoc PATH_STATUS = "/sync4j/sync/status";
87
88     private static final int SIZE_INPUT_BUFFER = 4096;
89     // ---------------------------------------------------------- Public methods
90

91     public void init() throws ServletException {
92         initialized = false;
93         String JavaDoc value;
94         
95         if (log.isLoggable(Level.INFO)) {
96             System.getProperties().list(System.out);
97         }
98
99         ServletConfig config = getServletConfig();
100
101         value = config.getInitParameter(PARAM_CHANNEL_PROPERTIES);
102         if (StringUtils.isEmpty(value)) {
103             String JavaDoc msg = "The servlet configuration parameter "
104                        + PARAM_CHANNEL_PROPERTIES
105                        + " cannot be empty ("
106                        + value
107                        + ")"
108                        ;
109             log.severe(msg);
110             throw new ServletException(msg);
111         }
112
113         value = config.getInitParameter(PARAM_SESSION_TTL);
114         if (!StringUtils.isEmpty(value)) {
115             try {
116                 Long.parseLong(value);
117             } catch (NumberFormatException JavaDoc e) {
118                 String JavaDoc msg = "The servlet configuration parameter "
119                            + PARAM_SESSION_TTL
120                            + " is not an integer number ("
121                            + value
122                            + ")"
123                            ;
124                 log.severe(msg);
125                 throw new ServletException(msg);
126             }
127         }
128
129         value = config.getInitParameter(PARAM_JNDI_ADDRESS);
130         if (StringUtils.isEmpty(value)) {
131             String JavaDoc msg = "Missing optional parameter "
132                        + PARAM_JNDI_ADDRESS
133                        + ", default value ("
134                        + jndiAddress
135                        + ") used."
136                        ;
137             log.warning(msg);
138         } else {
139             jndiAddress = value;
140         }
141
142         holderCache = new RemoteHolderCache(config);
143
144         //
145
// Now we are ready to bootstrap the Sync4j components
146
//
147
bootstrap();
148
149         //
150
// Engine initialized!
151
//
152
initialized = true;
153     }
154
155     public void doPost(final HttpServletRequest request ,
156                        final HttpServletResponse response)
157     throws ServletException, IOException {
158         if (log.isLoggable(Level.INFO)) {
159             StringBuffer JavaDoc sb = new StringBuffer JavaDoc("Handling incoming request ");
160
161             sb.append(request.getContextPath())
162               .append(request.getServletPath());
163             if (request.getPathInfo() != null) {
164                 sb.append(request.getPathInfo());
165             }
166
167             if (request.getQueryString() != null) {
168                 sb.append('?').append(request.getQueryString());
169             }
170             sb.append('.');
171
172             log.info(sb.toString());
173         }
174
175         final String JavaDoc contentType = request.getContentType().split(";")[0];
176         final int contentLength = request.getContentLength();
177
178         if (log.isLoggable(Level.FINEST)) {
179             log.finest("contentType: " + contentType);
180             log.finest("contentLength: " + contentLength);
181         }
182
183         if (contentLength < 1) {
184             throw new Error JavaDoc("Content length < 1 (" + contentLength + ")");
185         }
186
187         Map JavaDoc params = getRequestParameters(request);
188         Map JavaDoc headers = getRequestHeaders (request);
189         
190         byte[] requestData = null;
191         InputStream in = request.getInputStream();
192
193         try{
194             requestData = readContent(in);
195         } catch (Exception JavaDoc e) {
196             handleError(request, response, "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(request, response, 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
RemoteEJBSyncHolder holder = null;
217         try {
218             holder = createHolder(request);
219         } catch (Exception JavaDoc e) {
220             handleError(request, response, "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(request, response, "Protocol error", cause);
244                 return;
245             } else {
246                 throw new ServletException(e);
247             }
248         } finally {
249             //
250
// Now we can store the handler into the distributed cache. Note
251
// that when the holder is put in the cache a serialized copy is
252
// actually stored, not the simple local reference.
253
//
254
if (holder.isNew()) {
255                 holderCache.put(holder);
256             }
257         }
258
259         OutputStream out = null;
260         try {
261             out = response.getOutputStream();
262             response.setContentType(contentType);
263             
264             byte[] buf = resp.getMessage();
265             response.setContentLength(buf.length);
266             out.write(buf);
267             out.flush();
268             
269         } finally {
270             if (log.isLoggable(Level.FINEST)) {
271                 log.finest("Finally");
272             }
273             if (out != null) {
274                 out.close();
275             }
276         }
277
278         //
279
// If the message completed the SyncML communication, the holder
280
// must be closed and discarded.
281
//
282
if (resp.isCompleted()) {
283             releaseHolder(holder);
284         }
285     }
286
287     public void doGet(final HttpServletRequest request,
288                       final HttpServletResponse response)
289     throws ServletException, IOException {
290         if (log.isLoggable(Level.INFO)) {
291             log.info("holderCache: " + holderCache);
292         }
293         super.doGet(request, response);
294
295         String JavaDoc requestURI = request.getRequestURI();
296
297         if (log.isLoggable(Level.FINEST)) {
298             log.finest("requestURI: " + requestURI);
299         }
300         if (requestURI.equals(PATH_STATUS)) {
301             if (initialized) {
302                 //
303
//The request sent by the client was successful
304
//
305
response.setStatus(response.SC_OK);
306                 if (log.isLoggable(Level.FINEST)) {
307                     log.finest("Request succeeded normally");
308                 }
309             } else {
310                 //
311
//The request was unsuccessful to the server being down or overloaded.
312
//
313
response.setStatus(response.SC_SERVICE_UNAVAILABLE);
314                 if (log.isLoggable(Level.FINEST)) {
315                     log.finest("The HTTP server is unable to handle the request");
316                 }
317             }
318         }
319     }
320
321     // ------------------------------------------------------- Protected methods
322
/**
323      * Reads the request body without to known the length of the data.
324      *
325      * @param in the request InputStream
326      *
327      * @throws IOException in case of errors
328      */

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

361     private RemoteEJBSyncHolder createHolder(HttpServletRequest request)
362     throws Exception JavaDoc {
363         String JavaDoc sessionId = request.getParameter(PARAM_SESSION_ID);
364
365         RemoteEJBSyncHolder holder = null;
366
367         if (log.isLoggable(Level.FINEST)) {
368             log.finest("holderCache: " + holderCache);
369         }
370
371         if (!StringUtils.isEmpty(sessionId)) {
372             holder = (RemoteEJBSyncHolder)holderCache.get(sessionId);
373         }
374
375         if (holder == null) {
376             holder = new RemoteEJBSyncHolder(true);
377
378             holder.setSessionId(createSessionId(request));
379             holder.setJndiAddress(jndiAddress);
380         }
381
382         return holder;
383     }
384
385     /**
386      * Closes the given <i>SyncHolder</i> and removes it from the cache.
387      *
388      * @param holder the holder to releases
389      */

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

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

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

470     private Map JavaDoc getRequestParameters(HttpServletRequest request) {
471         Map JavaDoc params = new HashMap JavaDoc();
472         
473         String JavaDoc paramName = null;
474         for (Enumeration JavaDoc e=request.getParameterNames(); e.hasMoreElements(); ) {
475             paramName = (String JavaDoc)e.nextElement();
476             
477             params.put(paramName, request.getParameter(paramName));
478         }
479         
480         return params;
481     }
482     
483     /**
484      * Extracts the request headers and returns them in a <i>Map</i>
485      *
486      * @param request the request
487      */

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

510     private void handleError(final HttpServletRequest request,
511                              final HttpServletResponse response,
512                              final String JavaDoc msg) {
513         this.handleError(request, response, msg, null);
514     }
515
516     /**
517      * Bootstraps the Sync4j server components and logs the server startup
518      * event on both the console and the logging system.
519      */

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