Project

General

Profile

1
/**
2
 * Copyright 2006 OCLC Online Computer Library Center Licensed under the Apache
3
 * License, Version 2.0 (the "License"); you may not use this file except in
4
 * compliance with the License. You may obtain a copy of the License at
5
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or
6
 * agreed to in writing, software distributed under the License is distributed on
7
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
8
 * or implied. See the License for the specific language governing permissions and
9
 * limitations under the License.
10
 */
11
package edu.ucsb.nceas.metacat.oaipmh.provider.server;
12

    
13
import java.io.FileInputStream;
14
import java.io.FileNotFoundException;
15
import java.io.IOException;
16
import java.io.InputStream;
17
import java.io.OutputStreamWriter;
18
import java.io.Writer;
19
import java.net.SocketException;
20
import java.util.Date;
21
import java.util.Enumeration;
22
import java.util.HashMap;
23
import java.util.Properties;
24
import java.util.zip.DeflaterOutputStream;
25
import java.util.zip.GZIPOutputStream;
26

    
27
import javax.servlet.ServletConfig;
28
import javax.servlet.ServletContext;
29
import javax.servlet.ServletException;
30
import javax.servlet.http.HttpServlet;
31
import javax.servlet.http.HttpServletRequest;
32
import javax.servlet.http.HttpServletResponse;
33

    
34
import javax.xml.transform.Transformer;
35
import javax.xml.transform.TransformerException;
36
import javax.xml.transform.TransformerFactory;
37
import javax.xml.transform.stream.StreamSource;
38

    
39
import org.apache.log4j.Logger;
40
import org.apache.log4j.PropertyConfigurator;
41

    
42
import edu.ucsb.nceas.metacat.oaipmh.provider.server.crosswalk.Eml;
43
import edu.ucsb.nceas.metacat.oaipmh.provider.server.crosswalk.Eml2oai_dc;
44
import edu.ucsb.nceas.metacat.service.PropertyService;
45
import edu.ucsb.nceas.metacat.shared.ServiceException;
46

    
47
import ORG.oclc.oai.server.catalog.AbstractCatalog;
48
import ORG.oclc.oai.server.verb.BadVerb;
49
import ORG.oclc.oai.server.verb.GetRecord;
50
import ORG.oclc.oai.server.verb.Identify;
51
import ORG.oclc.oai.server.verb.ListIdentifiers;
52
import ORG.oclc.oai.server.verb.ListMetadataFormats;
53
import ORG.oclc.oai.server.verb.ListRecords;
54
import ORG.oclc.oai.server.verb.ListSets;
55
import ORG.oclc.oai.server.verb.OAIInternalServerError;
56
import ORG.oclc.oai.server.verb.ServerVerb;
57

    
58

    
59
/**
60
 * OAIHandler is the primary Servlet for OAICat.
61
 * 
62
 * @author Jeffrey A. Young, OCLC Online Computer Library Center
63
 */
64
public class OAIHandler extends HttpServlet {
65

    
66
  /*
67
   * Class fields
68
   */
69
  
70
  private static final Logger log = Logger.getLogger(OAIHandler.class);
71
  private static final long serialVersionUID = 1L;
72
  private static final String VERSION = "1.5.57";
73
  private static boolean debug = false;
74
  
75
  // Set to false when developing and testing code outside of Metacat
76
  private static boolean integratedWithMetacat = true;
77

    
78
  
79
  /*
80
   * Class methods
81
   */
82
  
83
  /**
84
   * Get the VERSION number
85
   */
86
  public static String getVERSION() {
87
    return VERSION;
88
  }
89
  
90
  /**
91
   * Boolean to determine whether the Data Provider code is executing in a
92
   * Metacat application. This is normally true, but the 
93
   * 'integratedWithMetacat' value can be set to
94
   * false by a developer when testing the code outside of Metacat. This
95
   * eliminates dependencies on Metacat property settings.
96
   * 
97
   * @return
98
   */
99
  public static boolean isIntegratedWithMetacat() {
100
    return integratedWithMetacat;
101
  }
102

    
103

    
104
  /*
105
   * Instance fields
106
   */
107
  
108
  protected HashMap attributesMap = new HashMap();
109
  private final String CONFIG_DIR = "WEB-INF";
110
  private final String CONFIG_NAME = "metacat.properties";
111
  private final String LOG4J_NAME = "log4j.properties";
112
  private final String XSLT_DIR = "oaipmh";
113

    
114
  
115
  /*
116
   * Instance methods
117
   */
118
  
119
  /**
120
   * Peform the http GET action. Note that POST is shunted to here as well. The
121
   * verb widget is taken from the request and used to invoke an OAIVerb object
122
   * of the corresponding kind to do the actual work of the verb.
123
   * 
124
   * @param request
125
   *          the servlet's request information
126
   * @param response
127
   *          the servlet's response information
128
   * @exception IOException
129
   *              an I/O error occurred
130
   */
131
  public void doGet(HttpServletRequest request, HttpServletResponse response)
132
      throws IOException, ServletException {
133
    ServletConfig servletConfig = this.getServletConfig();
134
    //init_debug(servletConfig); // used when debugging the init() method
135
    String pathInfo = request.getPathInfo();
136
    HashMap attributes = getAttributes(pathInfo);
137
    Properties properties;
138
    
139
    if (!filterRequest(request, response)) { return; }
140
    
141
    log.debug("attributes=" + attributes);
142
    properties = (Properties) attributes.get("OAIHandler.properties");
143
    boolean monitor = false;
144
    
145
    if (properties.getProperty("OAIHandler.monitor") != null) {
146
      monitor = true;
147
    }
148
    
149
    boolean serviceUnavailable = isServiceUnavailable(properties);
150

    
151
    HashMap serverVerbs = ServerVerb.getVerbs(properties);
152
    Transformer transformer = 
153
                         (Transformer) attributes.get("OAIHandler.transformer");
154

    
155
    boolean forceRender = false;
156
    
157
    if ("true".equals(properties.getProperty("OAIHandler.forceRender"))) {
158
      forceRender = true;
159
    }
160

    
161
    // try {
162
    request.setCharacterEncoding("UTF-8");
163
    // } catch (UnsupportedEncodingException e) {
164
    // e.printStackTrace();
165
    // throw new IOException(e.getMessage());
166
    // }
167
    
168
    Date then = null;
169
    if (monitor) then = new Date();
170
    
171
    if (debug) {
172
      Enumeration headerNames = request.getHeaderNames();
173
      System.out.println("OAIHandler.doGet: ");
174
      
175
      while (headerNames.hasMoreElements()) {
176
        String headerName = (String) headerNames.nextElement();
177
        System.out.print(headerName);
178
        System.out.print(": ");
179
        System.out.println(request.getHeader(headerName));
180
      }
181
    }
182
    
183
    if (serviceUnavailable) {
184
      response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
185
          "Sorry. This server is down for maintenance");
186
    } 
187
    else {
188
      try {
189
        String userAgent = request.getHeader("User-Agent");
190
        
191
        if (userAgent == null) {
192
          userAgent = "";
193
        } else {
194
          userAgent = userAgent.toLowerCase();
195
        }
196
        
197
        Transformer serverTransformer = null;
198
        
199
        if (transformer != null) {
200
          // return HTML if the client is an old browser
201
          if (forceRender
202
              || userAgent.indexOf("opera") != -1
203
              || (userAgent.startsWith("mozilla") && userAgent
204
                  .indexOf("msie 6") == -1
205
              /* && userAgent.indexOf("netscape/7") == -1 */)) {
206
            serverTransformer = transformer;
207
          }
208
        }
209
        
210
        String result = getResult(attributes, request, response,
211
                                  serverTransformer, serverVerbs);
212
        
213
        // log.debug("result=" + result);
214
        // if (serverTransformer) { // render on the server
215
        // response.setContentType("text/html; charset=UTF-8");
216
        // StringReader stringReader = new StringReader(getResult(request));
217
        // StreamSource streamSource = new StreamSource(stringReader);
218
        // StringWriter stringWriter = new StringWriter();
219
        // transformer.transform(streamSource, new StreamResult(stringWriter));
220
        // result = stringWriter.toString();
221
        // } else { // render on the client
222
        // response.setContentType("text/xml; charset=UTF-8");
223
        // result = getResult(request);
224
        // }
225

    
226
        Writer out = getWriter(request, response);
227
        out.write(result);
228
        out.close();
229
      } 
230
      catch (FileNotFoundException e) {
231
        if (debug) {
232
          e.printStackTrace();
233
          System.out.println("SC_NOT_FOUND: " + e.getMessage());
234
        }
235
        response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
236
      } 
237
      catch (TransformerException e) {
238
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
239
                           e.getMessage()
240
                          );
241
      } 
242
      catch (OAIInternalServerError e) {
243
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
244
                           e.getMessage()
245
                          );
246
      } 
247
      catch (SocketException e) {
248
        System.out.println(e.getMessage());
249
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
250
                           e.getMessage()
251
                          );
252
      } 
253
      catch (Throwable e) {
254
        e.printStackTrace();
255
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
256
                           e.getMessage()
257
                          );
258
      }
259
    }
260
    
261
    if (monitor) {
262
      StringBuffer reqUri = new StringBuffer(request.getRequestURI().toString());
263
      String queryString = request.getQueryString(); // d=789
264
      
265
      if (queryString != null) {
266
        reqUri.append("?").append(queryString);
267
      }
268
      
269
      Runtime rt = Runtime.getRuntime();
270
      System.out.println(rt.freeMemory() + "/" + rt.totalMemory() + " "
271
          + ((new Date()).getTime() - then.getTime()) + "ms: "
272
          + reqUri.toString());
273
    }
274
  }
275

    
276

    
277
  /**
278
   * Perform a POST action. Actually this gets shunted to GET.
279
   * 
280
   * @param request
281
   *          the servlet's request information
282
   * @param response
283
   *          the servlet's response information
284
   * @exception IOException
285
   *              an I/O error occurred
286
   */
287
  public void doPost(HttpServletRequest request, HttpServletResponse response)
288
      throws IOException, ServletException {
289
    doGet(request, response);
290
  }
291

    
292
  
293
  /**
294
   * Override to do any pre-qualification; return false if the response should be
295
   * returned immediately, without further action.
296
   * 
297
   * @param request
298
   * @param response
299
   * @return false=return immediately, true=continue
300
   */
301
  protected boolean filterRequest(HttpServletRequest request,
302
      HttpServletResponse response) {
303
    return true;
304
  }
305

    
306

    
307
  public HashMap getAttributes(Properties properties) 
308
          throws Throwable {
309
    HashMap attributes = new HashMap();
310
    Enumeration attrNames = getServletContext().getAttributeNames();
311
    String serviceUnavailable;
312
    
313
    while (attrNames.hasMoreElements()) {
314
      String attrName = (String) attrNames.nextElement();
315
      Object attrValue = getServletContext().getAttribute(attrName);
316
      attributes.put(attrName, attrValue);
317
    }
318

    
319
    attributes.put("OAIHandler.properties", properties);
320
    String missingVerbClassName = properties.getProperty(
321
        "OAIHandler.missingVerbClassName", "ORG.oclc.oai.server.verb.BadVerb");
322
    Class missingVerbClass = Class.forName(missingVerbClassName);
323
    attributes.put("OAIHandler.missingVerbClass", missingVerbClass);
324

    
325
    serviceUnavailable = properties.getProperty("OAIHandler.serviceUnavailable");
326
    if (!"true".equals(serviceUnavailable)) {
327
      attributes.put("OAIHandler.version", VERSION);
328
      AbstractCatalog metacatCatalog = 
329
                       AbstractCatalog.factory(properties, getServletContext());
330
      attributes.put("OAIHandler.catalog", metacatCatalog);
331
    }
332
    
333
    boolean forceRender = false;
334
    if ("true".equals(properties.getProperty("OAIHandler.forceRender"))) {
335
      forceRender = true;
336
    }
337

    
338
    String styleSheet = properties.getProperty("OAIHandler.styleSheet");
339
    String appBase = properties.getProperty("OAIHandler.appBase");
340

    
341
    if (appBase == null) appBase = "webapps";
342

    
343
    String render = properties.getProperty("OAIHandler.renderForOldBrowsers");
344
    if ((styleSheet != null) && 
345
        ("true".equalsIgnoreCase(render) || forceRender)
346
       ) {
347
      InputStream is;
348
      
349
      try {
350
        is = new FileInputStream(appBase + "/" + styleSheet);
351
      } 
352
      catch (FileNotFoundException e) {
353
        // This is a silly way to skip the context name in the styleSheet name
354
        is = new FileInputStream(getServletContext().getRealPath(
355
            styleSheet.substring(styleSheet.indexOf("/", 1) + 1)));
356
      }
357
      
358
      StreamSource xslSource = new StreamSource(is);
359
      TransformerFactory tFactory = TransformerFactory.newInstance();
360
      Transformer transformer = tFactory.newTransformer(xslSource);
361
      attributes.put("OAIHandler.transformer", transformer);
362
    }
363

    
364
    return attributes;
365
  }
366

    
367

    
368
  public HashMap getAttributes(String pathInfo) {
369
    HashMap attributes = null;
370
    log.debug("pathInfo=" + pathInfo);
371

    
372
    if (pathInfo != null && pathInfo.length() > 0) {
373
      if (attributesMap.containsKey(pathInfo)) {
374
        log.debug("attributesMap containsKey");
375
        attributes = (HashMap) attributesMap.get(pathInfo);
376
      } 
377
      else {
378
        log.debug("!attributesMap containsKey");
379
        
380
        try {
381
          String fileName = pathInfo.substring(1) + ".properties";
382
          log.debug("attempting load of " + fileName);
383
          Thread thread = Thread.currentThread();
384
          ClassLoader classLoader = thread.getContextClassLoader();
385
          InputStream in = classLoader.getResourceAsStream(fileName);
386
          
387
          if (in != null) {
388
            log.debug("file found");
389
            Properties properties = new Properties();
390
            properties.load(in);
391
            attributes = getAttributes(properties);
392
          } 
393
          else {
394
            log.debug("file not found");
395
          }
396
          
397
          attributesMap.put(pathInfo, attributes);
398
        } 
399
        catch (Throwable e) {
400
          log.debug("Couldn't load file", e);
401
          // do nothing
402
        }
403
      }
404
    }
405
    
406
    if (attributes == null) log.debug("use global attributes");
407
    attributes = (HashMap) attributesMap.get("global");
408

    
409
    return attributes;
410
  }
411

    
412

    
413
  public static String getResult(HashMap attributes,
414
                                 HttpServletRequest request, 
415
                                 HttpServletResponse response,
416
                                 Transformer serverTransformer, 
417
                                 HashMap serverVerbs
418
                                ) 
419
      throws Throwable {
420
    String verb = request.getParameter("verb");
421
    log.debug("verb: " + verb);
422
    String result;
423

    
424
    if (verb.equals("GetRecord")) {
425
      result = GetRecord.construct(attributes, request, response,
426
          serverTransformer);
427
    } 
428
    else if (verb.equals("Identify")) {
429
      result = Identify.construct(attributes, request, response,
430
          serverTransformer);
431
    } 
432
    else if (verb.equals("ListIdentifiers")) {
433
      result = ListIdentifiers.construct(attributes, request, response,
434
          serverTransformer);
435
    } 
436
    else if (verb.equals("ListMetadataFormats")) {
437
      result = ListMetadataFormats.construct(attributes, request, response,
438
          serverTransformer);
439
    } 
440
    else if (verb.equals("ListRecords")) {
441
      result = ListRecords.construct(attributes, request, response,
442
          serverTransformer);
443
    } 
444
    else if (verb.equals("ListSets")) {
445
      result = ListSets.construct(attributes, request, response,
446
          serverTransformer);
447
    } 
448
    else {
449
      result = BadVerb.construct(attributes, request, response,
450
          serverTransformer);
451
    }
452

    
453
    return result;
454
    
455
    /*
456
     * } catch (NoSuchMethodException e) { throw new
457
     * OAIInternalServerError(e.getMessage()); } catch (IllegalAccessException
458
     * e) { throw new OAIInternalServerError(e.getMessage()); }
459
     */
460
  }
461

    
462

    
463
  /**
464
   * Get a response Writer depending on acceptable encodings
465
   * 
466
   * @param request
467
   *          the servlet's request information
468
   * @param response
469
   *          the servlet's response information
470
   * @exception IOException
471
   *              an I/O error occurred
472
   */
473
  public static Writer getWriter(HttpServletRequest request,
474
      HttpServletResponse response) throws IOException {
475
    Writer out;
476
    String encodings = request.getHeader("Accept-Encoding");
477
    
478
    if (debug) { System.out.println("encodings=" + encodings); }
479
    
480
    if (encodings != null && encodings.indexOf("gzip") != -1) {
481
      // System.out.println("using gzip encoding");
482
      // log.debug("using gzip encoding");
483
      response.setHeader("Content-Encoding", "gzip");
484
      out = new OutputStreamWriter(new GZIPOutputStream(response
485
          .getOutputStream()), "UTF-8");
486
      // } else if (encodings != null && encodings.indexOf("compress") != -1) {
487
      // // System.out.println("using compress encoding");
488
      // response.setHeader("Content-Encoding", "compress");
489
      // ZipOutputStream zos = new ZipOutputStream(response.getOutputStream());
490
      // zos.putNextEntry(new ZipEntry("dummy name"));
491
      // out = new OutputStreamWriter(zos, "UTF-8");
492
    } 
493
    else if (encodings != null && encodings.indexOf("deflate") != -1) {
494
      // System.out.println("using deflate encoding");
495
      // log.debug("using deflate encoding");
496
      response.setHeader("Content-Encoding", "deflate");
497
      out = new OutputStreamWriter(new DeflaterOutputStream(response
498
          .getOutputStream()), "UTF-8");
499
    } 
500
    else {
501
      // log.debug("using no encoding");
502
      out = response.getWriter();
503
    }
504
    
505
    return out;
506
  }
507

    
508

    
509
  /**
510
   * init is called one time when the Servlet is loaded. This is the place where
511
   * one-time initialization is done. Specifically, we load the properties file
512
   * for this application, and create the AbstractCatalog object for subsequent
513
   * use.
514
   * 
515
   * @param config
516
   *          servlet configuration information
517
   * @exception ServletException
518
   *              there was a problem with initialization
519
   */
520
  //public void init_debug(ServletConfig config) throws ServletException {
521
  public void init(ServletConfig config) throws ServletException {
522
    super.init(config);
523

    
524
    if (isIntegratedWithMetacat()) {
525
      try {
526
        PropertyService.getInstance();
527
      } 
528
      catch (ServiceException se) {
529
        System.err.println("Error in loading properties: " + se.getMessage());
530
      }
531
    }
532
    
533
    ServletContext servletContext = config.getServletContext();
534
    String configDirPath = servletContext.getRealPath(CONFIG_DIR);
535
    String configPath = configDirPath + "/" + CONFIG_NAME;
536

    
537
    // Initialize the properties file for log4j
538
    String log4jPath = configDirPath + "/" + LOG4J_NAME;
539
    PropertyConfigurator.configureAndWatch(log4jPath);
540
    
541
    // Initialize the directory path to the crosswalk XSLT files
542
    String xsltDirPath = servletContext.getRealPath(XSLT_DIR);
543
    Eml2oai_dc.setDirPath(xsltDirPath);
544

    
545
    try {
546
      HashMap attributes = null;
547
      Properties properties = null;
548
      InputStream in;
549

    
550
      try {
551
        log.debug("configPath=" + configPath);
552
        in = new FileInputStream(configPath);
553
      } 
554
      catch (FileNotFoundException e) {
555
        log.debug("configPath not found. Try the classpath: " + configPath);
556
        Thread thread = Thread.currentThread();
557
        ClassLoader classLoader = thread.getContextClassLoader();
558
        in = classLoader.getResourceAsStream(configPath);
559
      }
560

    
561
      if (in != null) {
562
        log.debug("configPath '" + configPath + "' found. Loading properties");
563
        properties = new Properties();
564
        properties.load(in);
565
        attributes = getAttributes(properties);
566
      }
567

    
568
      attributesMap.put("global", attributes);
569
    }
570
    catch (FileNotFoundException e) {
571
      e.printStackTrace();
572
      throw new ServletException(e.getMessage());
573
    } 
574
    catch (ClassNotFoundException e) {
575
      e.printStackTrace();
576
      throw new ServletException(e.getMessage());
577
    } 
578
    catch (IllegalArgumentException e) {
579
      e.printStackTrace();
580
      throw new ServletException(e.getMessage());
581
    } 
582
    catch (IOException e) {
583
      e.printStackTrace();
584
      throw new ServletException(e.getMessage());
585
    } 
586
    catch (Throwable e) {
587
      e.printStackTrace();
588
      throw new ServletException(e.getMessage());
589
    }
590
  }
591

    
592

    
593
  /**
594
   * Should the server report itself down for maintenance? Override this method
595
   * if you want to do this check another way.
596
   * 
597
   * @param properties
598
   * @return true=service is unavailable, false=service is available
599
   */
600
  protected boolean isServiceUnavailable(Properties properties) {
601
    if (properties.getProperty("OAIHandler.serviceUnavailable") != null) {
602
      return true;
603
    }
604
    
605
    return false;
606
  }
607

    
608
}
    (1-1/1)