KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > joseki > server > http > JosekiWebAPI


1 /*
2  * (c) Copyright 2003, 2004 Hewlett-Packard Development Company, LP
3  * [See end of file]
4  */

5
6 package org.joseki.server.http;
7
8 import java.io.*;
9 import java.util.*;
10 import org.apache.commons.logging.*;
11
12 import javax.servlet.*;
13 import javax.servlet.http.*;
14
15 import com.hp.hpl.jena.rdf.model.*;
16 import com.hp.hpl.jena.shared.JenaException;
17
18 import org.joseki.util.Convert;
19 import org.joseki.server.*;
20 import org.joseki.Joseki ;
21
22
23 /** The servlet class.
24  * @author Andy Seaborne
25  * @version $Id: JosekiWebAPI.java,v 1.25 2004/05/05 16:43:04 andy_seaborne Exp $
26  */

27
28 public class JosekiWebAPI extends HttpServlet implements Connector
29 {
30     // Use one logger.
31
protected static Log log = null ; //LogFactory.getLog(HTTP_Execution.class.getName()) ;
32

33     // This happens very early - check it.
34
static {
35         try { log = LogFactory.getLog(JosekiWebAPI.class) ; }
36         catch (Exception ex)
37         {
38             System.err.println("Exception creating the logger") ;
39             System.err.println("Commons logging jar files in WEB-INF/lib/?") ;
40             System.err.println(ex.getMessage());
41             //ex.printStackTrace(System.err) ;
42
}
43     }
44     
45     // The long URL limit
46
static int urlLimit = 4*1024 ;
47     static boolean initAttempted = false ;
48     
49     protected Dispatcher dispatcher = null ;
50     protected HttpOperationCodec httpCodec = new HttpOperationCodec() ;
51     
52     String printName = "HTTP RDF WebAPI";
53     // Servlet info
54
ServletConfig servletConfig = null;
55     // Web app info
56
ServletContext servletContext = null;
57     
58     // The servlet is either running as a webapp (web.xml coinfig file and all)
59
// or running as a plain servlet embedded in something else.
60
// The former means the configuration is in the webapp/servlet environment
61
// The latter means the main application will programmatically do it.
62

63     /** Creates new JosekiWebAPI */
64     public JosekiWebAPI()
65     {
66         log.debug("Created") ;
67     }
68
69     public void init() throws ServletException
70     {
71         super.init() ;
72         return ;
73     }
74
75     /** Initializes the servlet.
76     */

77     public void init(ServletConfig config) throws ServletException
78     {
79         super.init(config);
80
81         // It seems that if the servlet fails to initialize the first time,
82
// init can be called again (it has been observed in Tomcat log files
83
// but not explained).
84

85         if ( initAttempted )
86         {
87             log.info("Re-initialization of servlet attempted") ;
88             if ( dispatcher == null )
89                 dispatcher = new Dispatcher() ;
90             return ;
91         }
92         initAttempted = true ;
93         
94         servletConfig = config;
95         
96         if (config != null)
97         {
98             servletContext = config.getServletContext();
99             FileManager.getInstance().setServletContext(servletContext) ;
100         }
101
102         printName = config.getServletName();
103         
104         servletEnv() ;
105         DispatcherRegistry dispatcherRegistry = DispatcherRegistry.getInstance() ;
106         
107         if ( ! dispatcherRegistry.contains(RDFServer.DispatcherName) )
108         {
109             log.info("Creating dispatcher") ;
110             Dispatcher tmp = new Dispatcher() ;
111             // Initialize it.
112
try {
113                 initDispatcher(tmp);
114             } catch (ConfigurationErrorException confEx)
115             {
116                 throw new ServletException("Joseki configuration error", confEx) ;
117             }
118             dispatcherRegistry.add(RDFServer.DispatcherName, tmp) ;
119             dispatcher = dispatcherRegistry.find(RDFServer.DispatcherName) ;
120         }
121         else
122         {
123             log.info("Using existing dispatcher") ;
124             dispatcher = dispatcherRegistry.find(RDFServer.DispatcherName) ;
125         }
126     }
127     
128     /** Destroys the servlet.
129     */

130     public void destroy()
131     {
132         log.debug("destroy");
133     }
134
135     // Intercept incoming requests.
136
/*
137     protected void service(HttpServletRequest req, HttpServletResponse resp)
138         throws ServletException, IOException
139     {
140         super.service(req, resp);
141     }
142     */

143     public void doGet(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
144     {
145         try {
146             if ( log.isDebugEnabled() )
147                 log.debug(fmtRequest(httpRequest)) ;
148             
149             // getRequestURL is the exact string used by the caller in the request.
150
// Internally, its the "request URI" that names the model
151

152             String requestURL = httpRequest.getRequestURL().toString() ;
153             String uri = httpRequest.getRequestURI() ;
154
155             if ( uri.length() > urlLimit )
156             {
157                 httpResponse.setStatus(HttpServletResponse.SC_REQUEST_URI_TOO_LONG) ;
158                 return ;
159             }
160
161             uri = formDispatchURI(uri, httpRequest) ;
162             
163             //msg(Level.FINE, "Get: URL= "+uri) ;
164
//msg(Level.FINE, " QueryString = "+request.getQueryString()) ;
165

166             String queryLang = httpRequest.getParameter("lang");
167
168             if ( httpRequest.getParameterMap().size() == 0 )
169                 // It's a plain GET
170
queryLang = "GET" ;
171             
172             try {
173                 Request opRequest = dispatcher.createQueryRequest(uri, requestURL, queryLang) ;
174
175                 Map m = httpRequest.getParameterMap() ;
176                 for ( Iterator iter = m.keySet().iterator() ; iter.hasNext() ; )
177                 {
178                     String k = (String)iter.next() ;
179                     String[] v = (String[])m.get(k) ;
180                     if ( k.equals("q") )
181                         k = "query" ;
182                     for ( int vi = 0 ; vi < v.length ; vi++ )
183                         opRequest.setParam(k,v[vi]) ;
184                 }
185                 
186                 execute(opRequest, httpRequest, httpResponse) ;
187             } catch (ExecutionException execEx)
188             {
189                 doResponse(execEx, uri, httpResponse) ;
190                 return ;
191             }
192         } catch (Exception ex)
193         {
194             try {
195                 log.warn("Internal server error", ex) ;
196                 httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR) ;
197                 httpResponse.flushBuffer() ;
198                 httpResponse.getWriter().close() ;
199             } catch (Exception e) {}
200         }
201     }
202
203
204     public void doPost(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
205     {
206         try {
207             if ( log.isDebugEnabled() )
208                 log.debug(fmtRequest(httpRequest)) ;
209             
210             String requestURL = httpRequest.getRequestURL().toString() ;
211             String uri = httpRequest.getRequestURI() ;
212             String httpQueryString = httpRequest.getQueryString() ;
213             
214             uri = formDispatchURI(uri, httpRequest) ;
215             
216             if ( httpQueryString == null )
217             {
218                 httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "The HTTP query string specified in POST is empty: it must be ?op=<opName>") ;
219                 return ;
220             }
221
222             // TODO Make this position insensitive
223
// Parse the whole line using Joseki argument parsing code here.
224

225             // ---- Compatibility : old systems had ...&opName&....
226
String reqName = httpQueryString ;
227             // Split off the first word - up to the first '&'
228
int i = reqName.indexOf('&') ;
229             if ( i != -1 )
230             {
231                 reqName = reqName.substring(0,i) ;
232                 //httpQueryString = httpQueryString.substring(i) ;
233
}
234
235             // Old style: has just ?opName[&k=v&k=v]
236
// Documentation has ?op=opName
237
// Handle both
238

239             if ( reqName.startsWith("op=") )
240                 reqName = reqName.substring("op=".length()) ;
241             
242             try {
243                 Request opRequest =
244                     dispatcher.createOperation(uri, requestURL, reqName) ;
245                 httpCodec.setParameters(opRequest, httpRequest) ;
246                 httpCodec.setArgs(opRequest, httpRequest) ;
247                 execute(opRequest, httpRequest, httpResponse) ;
248             } catch (ExecutionException execEx)
249             {
250                 doResponse(execEx, uri, httpResponse) ;
251                 return ;
252             }
253         }
254         catch (Exception ex)
255         {
256             ex.printStackTrace(System.err) ;
257             // Problems.
258
doResponse(httpResponse, HttpServletResponse.SC_INTERNAL_SERVER_ERROR) ;
259             return ;
260         }
261     }
262
263
264     public void doOptions(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
265     throws ServletException, java.io.IOException
266     {
267         try {
268             log.debug(fmtRequest(httpRequest)) ;
269
270             String requestURL = httpRequest.getRequestURL().toString() ;
271             String uri = httpRequest.getRequestURI() ;
272             
273             uri = formDispatchURI(uri, httpRequest) ;
274
275             
276             //msg(Level.FINE, "Context path="+request.getContextPath()+" :: PathInfo="+request.getPathInfo()+" :: PathTranslated="+request.getPathTranslated()) ;
277
log.debug("Context path="+httpRequest.getContextPath()+" :: PathInfo="+httpRequest.getPathInfo()+" :: PathTranslated="+httpRequest.getPathTranslated()) ;
278             
279             if ( uri.length() > urlLimit )
280             {
281                 httpResponse.setStatus(HttpServletResponse.SC_REQUEST_URI_TOO_LONG) ;
282                 return ;
283             }
284             
285             // Base for this request - this servelet may be on many access points.
286
// Note - code else where assumes this does NOT end in a slash
287
String baseURL = "http://"+httpRequest.getServerName() ;
288             int port = httpRequest.getServerPort() ;
289             if ( port != 80 )
290                 baseURL= baseURL+":"+port ;
291
292             try {
293                 //The URLConnection class seems to have a problem with a uri of * (server)
294
if (uri.equals("*") || uri.equals("/") || uri.equals("/*"))
295                 {
296                     // There isn't an ModelSource for the server itself - do the work here.
297
try {
298                         Request opHostRequest = dispatcher.createRequest(uri, requestURL, "options", false) ;
299                         opHostRequest.setParam("baseURL", baseURL) ;
300                         //msg("Options: URI="+ req.getModelURI() + " Request="+req.getName()) ;
301
log.info("Options: URI="+ opHostRequest.getModelURI() + " Request="+opHostRequest.getName()) ;
302                         Model resultModel = dispatcher.getOptionsModel(baseURL);
303                         doResponse(resultModel, opHostRequest, httpRequest, httpResponse) ;
304                     }
305                     catch (ExecutionException execEx) { doResponse(execEx, uri, httpResponse); }
306                     return ;
307                 }
308                 
309                 Request opRequest = dispatcher.createOperation(uri, requestURL, "options") ;
310                 opRequest.setParam("baseURL", baseURL) ;
311                 execute(opRequest, httpRequest, httpResponse);
312             } catch (ExecutionException execEx)
313             {
314                 doResponse(execEx, uri, httpResponse) ;
315                 return ;
316             }
317         }
318         catch (Exception ex)
319         {
320             try
321             {
322                 httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
323                 httpResponse.flushBuffer();
324                 httpResponse.getWriter().close();
325             } catch (Exception e) { }
326         }
327     }
328
329     // ------------------------------------------
330
// This should return a list of possibilities to allow for
331
// future changes.
332
// e.g. Under Tomcat: http://server/joseki/books
333
// where as http://server/books us probably better.
334
// Don't want to break current deployed configurations.
335

336
337     private String formDispatchURI(String uri, HttpServletRequest httpRequest)
338     {
339         String dispatchURI = uri ;
340         String contextPath = httpRequest.getContextPath() ;
341         
342         if ( contextPath != null && contextPath.length() > 0 )
343             dispatchURI = dispatchURI.substring(contextPath.length()) ;
344         
345         // Suggested by Frank Hartman:
346
String servletPath = httpRequest.getServletPath() ;
347         if ( servletPath != null && servletPath.length() > 0 )
348             dispatchURI = dispatchURI.substring(servletPath.length()) ;
349
350         if ( log.isDebugEnabled() )
351         {
352             if ( servletPath == null )
353                 servletPath = "" ;
354             if ( contextPath == null )
355                 contextPath = "" ;
356             log.debug("DispatchURI: "+uri+" => "+dispatchURI+" (ContextPath = "+contextPath+", ServletPath = "+servletPath+")") ;
357         }
358         return dispatchURI ;
359     }
360     
361     
362     protected void execute(Request req, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ExecutionException
363     {
364         Model resultModel = null ;
365         
366         if ( req.getName().equals("query") )
367             log.debug("URI="+ req.getModelURI() + " Query="+req.getParam("lang")) ;
368         else
369             log.debug("URI="+ req.getModelURI() + " Request="+req.getName()) ;
370
371         resultModel = dispatcher.exec(req) ;
372         doResponse(resultModel, req, httpRequest, httpResponse);
373         
374         //TODO Replace with a real result object
375
// At the moment, relies on the fact that its always a memory model and
376
// nothing much happens on a close().
377
//resultModel.close() ;
378
}
379
380
381     // Safe version (does not read body)
382
// Don't use ServletRequest.getParameter as that reads form data.
383

384     private Map getParameters(String queryString)
385     {
386         Map map = new HashMap() ;
387         
388         String[] params = queryString.split("&") ;
389         for ( int i = 0 ; i < params.length ; i++ )
390         {
391             String p = params[i] ;
392             String[] x = p.split("=",2) ;
393             
394             if ( x.length == 0 )
395                 continue ;
396
397             String pName = x[0] ;
398             String pValue = "" ;
399             if ( x.length == 2 )
400                 pValue = x[1] ;
401             map.put(pName, "") ;
402         }
403         // Not found
404
return null ;
405     }
406
407     // Normal reply result is a model
408

409     protected void doResponse(Model resultModel, Request opRequest,
410             HttpServletRequest httpRequest,
411             HttpServletResponse httpResponse)
412     {
413         try
414         {
415             log.debug("Successful operation: URI = " + opRequest.getModelURI()+" : Request = "+opRequest.getName() ) ;
416             try
417             {
418                 if ( httpCodec.sendResponse(resultModel, opRequest,
419                         httpRequest, httpResponse) )
420                     log.info("OK - "+fmtRequest(httpRequest)) ;
421             }
422             catch (RDFException rdfEx)
423             {
424                 //msg(Level.WARNING, "RDFException", rdfEx);
425
log.warn("RDFException", rdfEx);
426                 //printStackTrace(printName + "(execute)", rdfEx);
427
httpResponse.sendError(
428                         HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
429                         "RDFException: " + rdfEx);
430                 return;
431             }
432             catch (JenaException jEx)
433             {
434                 //msg(Level.WARNING, "Exception", ex);
435
log.warn("JenaException: "+jEx.getMessage());
436                 if ( jEx.getCause() != null )
437                     jEx.getCause().printStackTrace(httpResponse.getWriter());
438                 else
439                     jEx.printStackTrace(httpResponse.getWriter());
440                 httpResponse.sendError(
441                         HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
442                         "JenaException: " + jEx);
443                 return;
444             }
445             catch (Exception ex)
446             {
447                 //msg(Level.WARNING, "Exception", ex);
448
log.warn("Exception", ex);
449
450                 httpResponse.sendError(
451                         HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
452                         "Exception: " + ex);
453                 return;
454             }
455         }
456         catch (IOException ioEx)
457         {
458             //msg(Level.WARNING,"IOException in normal response") ;
459
log.warn("IOException in normal response") ;
460             try {
461                 httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
462                 httpResponse.flushBuffer();
463                 httpResponse.getWriter().close();
464             } catch (Exception e) { }
465         }
466     }
467     
468     // Reply when an exception was generated
469

470     protected void doResponse(ExecutionException execEx, String uri, HttpServletResponse response)
471     {
472         
473         try {
474             String httpMsg = ExecutionError.errorString(execEx.returnCode);
475             //msg("Error in operation: URI = " + uri + " : " + httpMsg);
476
log.info("Error: URI = " + uri + " : " + httpMsg);
477             httpCodec.sendError(execEx, response) ;
478             return;
479         } catch (IOException ioEx)
480         {
481             log.warn("IOException in exception response") ;
482             try {
483                 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
484                 response.flushBuffer();
485                 response.getWriter().close();
486             } catch (Exception e) { }
487         }
488     }
489
490
491     // Desparate way to reply
492

493     protected void doResponse(HttpServletResponse response, int reason)
494     {
495         // Panic.
496
try {
497             response.setHeader(Joseki.httpHeaderField, Joseki.httpHeaderValue) ;
498             response.setStatus(reason) ;
499             response.flushBuffer() ;
500             response.getWriter().close() ;
501         } catch (Exception e) {}
502     }
503
504
505     /** Returns a short description of the servlet.
506     */

507     public String getServletInfo()
508     {
509         //return this.getClass().getName() ;
510
return printName;
511     }
512
513     static String fmtRequest(HttpServletRequest request)
514     {
515         StringBuffer sbuff = new StringBuffer() ;
516         sbuff.append(request.getMethod()) ;
517         sbuff.append(" ") ;
518         sbuff.append(Convert.decWWWForm(request.getRequestURL()));
519         
520         String qs = request.getQueryString();
521         if (qs != null)
522         {
523             String tmp = request.getQueryString() ;
524             tmp = Convert.decWWWForm(tmp) ;
525             tmp = tmp.replace('\n', ' ') ;
526             tmp = tmp.replace('\r', ' ') ;
527             sbuff.append("?").append(tmp);
528         }
529         return sbuff.toString() ;
530     }
531
532     // Need a way to pair requests and responses.
533
// Via a Request object?
534

535     //private void logResponse(HttpServletRequest request, HttpServletResponse response)
536
//{
537
//}
538

539
540     
541
542     // This method contains the pragmatic algorithm to determine the server model map.
543
//
544
// In this order (i.e. specific to general) to find the filename:
545
// System property: jena.rdfserver.modelmap
546
// Webapp init parameter: jena.rdfserver.modelmap
547
// Servlet init parameter: jena.rdfserver.modelmap
548
// and then the file is loaded.
549
//
550
// If the system property is explicit set a well known value, this is skipped.
551

552
553     private boolean initDispatcher(Dispatcher disp) throws ConfigurationErrorException
554     {
555         String tmp = System.getProperty(RDFServer.propertyModelSet) ;
556         if ( tmp != null && tmp.equals(RDFServer.noMapValue))
557             return false ;
558         
559         if ( tmp != null )
560             if (attemptLoad(tmp, "System property: " + RDFServer.propertyModelSet, disp))
561                 return true;
562
563         if (servletConfig != null
564             && attemptLoad(
565                 servletConfig.getInitParameter(RDFServer.propertyModelSet),
566                 "Servlet configuration parameter: " + RDFServer.propertyModelSet,
567                 disp) )
568             return true;
569
570         // Try default name and location
571
if (attemptLoad(RDFServer.defaultConfigFile, "Default filename",disp) )
572             return true;
573
574         return false;
575     }
576
577     private boolean attemptLoad(String filename, String reason, Dispatcher disp)
578         throws ConfigurationErrorException
579     {
580         if (filename == null)
581             return false;
582
583         try
584         {
585             disp.getModelSet().load(filename) ;
586             log.info("Loaded data source configuration: " + filename);
587             //msg("Loaded data source configuration: " + filename);
588
return true;
589         } catch (RDFException rdfEx)
590         {
591             // Trouble processing a configuration
592
throw new ConfigurationErrorException("RDF Exception: "+rdfEx.getMessage(), rdfEx) ;
593             //return false ;
594
} catch (IOException ioEx)
595         {
596             throw new ConfigurationErrorException("IO Exception: "+ioEx.getMessage(), ioEx) ;
597             //return false;
598
}
599     }
600
601     public void setDispatcher(Dispatcher d)
602     {
603         dispatcher = d ;
604     }
605
606     public Dispatcher getDispatcher()
607     {
608         return dispatcher ;
609     }
610
611
612     private void servletEnv()
613     {
614         if ( ! log.isDebugEnabled() )
615             return ;
616         
617         try {
618             java.net.URL url = servletContext.getResource("/") ;
619             log.trace("Joseki base directory: "+url) ;
620         } catch (Exception ex) {}
621         
622         if (servletConfig != null)
623         {
624             String tmp = servletConfig.getServletName() ;
625             log.trace("Servlet = " + (tmp != null ? tmp : "<null>"));
626             Enumeration enum = servletConfig.getInitParameterNames();
627             
628             for (; enum.hasMoreElements();)
629             {
630                 String s = (String) enum.nextElement();
631                 log.trace("Servlet parameter: " + s + " = " + servletConfig.getInitParameter(s));
632             }
633         }
634         if (servletContext != null)
635         {
636             // Name of webapp
637
String tmp = servletContext.getServletContextName();
638             //msg(Level.FINE, "Webapp = " + (tmp != null ? tmp : "<null>"));
639
log.debug("Webapp = " + (tmp != null ? tmp : "<null>"));
640
641             // NB This servlet may not have been loaded as part of a web app
642

643             Enumeration enum = servletContext.getInitParameterNames();
644             for (;enum.hasMoreElements();)
645             {
646                 String s = (String) enum.nextElement();
647                 log.debug("Webapp parameter: " + s + " = " + servletContext.getInitParameter(s));
648             }
649         }
650         /*
651         for ( Enumeration enum = servletContext.getAttributeNames() ; enum.hasMoreElements() ; )
652         {
653             String s = (String)enum.nextElement() ;
654             logger.log(LEVEL, "Webapp attribute: "+s+" = "+context.getAttribute(s)) ;
655         }
656          */

657     }
658 }
659
660 /*
661  * (c) Copyright 2003, 2004 Hewlett-Packard Development Company, LP
662  * All rights reserved.
663  *
664  * Redistribution and use in source and binary forms, with or without
665  * modification, are permitted provided that the following conditions
666  * are met:
667  * 1. Redistributions of source code must retain the above copyright
668  * notice, this list of conditions and the following disclaimer.
669  * 2. Redistributions in binary form must reproduce the above copyright
670  * notice, this list of conditions and the following disclaimer in the
671  * documentation and/or other materials provided with the distribution.
672  * 3. The name of the author may not be used to endorse or promote products
673  * derived from this software without specific prior written permission.
674  *
675  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
676  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
677  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
678  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
679  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
680  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
681  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
682  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
683  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
684  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
685  */

686  
Popular Tags