View Javadoc

1   /* The contents of this file are subject to the license and copyright terms
2    * detailed in the license directory at the root of the source tree (also 
3    * available online at http://www.fedora.info/license/).
4    */
5   package org.fedoracommons.funapi;
6   
7   import java.io.IOException;
8   import java.io.InputStream;
9   import java.io.OutputStream;
10  
11  import java.util.ArrayList;
12  import java.util.List;
13  
14  import javax.servlet.ServletException;
15  import javax.servlet.http.HttpServlet;
16  import javax.servlet.http.HttpServletRequest;
17  import javax.servlet.http.HttpServletResponse;
18  
19  
20  /**
21   * An implementation of the unAPI version 1 HTTP interface functions.
22   * 
23   * @author Edwin Shin
24   * @since 0.1
25   * @version $Id: UnapiServlet.java 33 2008-10-25 19:31:36Z pangloss $
26   * @see <a href="http://unapi.info/specs/">unAPI Specs</a>
27   */
28  public class UnapiServlet
29          extends HttpServlet {
30  
31      private static final long serialVersionUID = 1L;
32      
33      /** Content type for xml. */
34      private static final String CONTENT_TYPE_XML = "application/xml; charset=UTF-8";
35      
36      private List<UnapiFormat> defaultFormats = new ArrayList<UnapiFormat>();
37      
38      private FormatsSerializer serializer;
39      
40      private ObjectResolver resolver;
41      
42      @Override
43      public void init() throws ServletException {
44          String className = getServletConfig().getInitParameter("resolver");
45          
46          try {
47              serializer = new FormatsSerializer();
48              Class<?> klass  = Class.forName(className);
49              resolver = (ObjectResolver)klass.newInstance();
50          } catch (ClassNotFoundException e) {
51              throw new ServletException(e.getMessage(), e);
52          } catch (InstantiationException e) {
53              throw new ServletException(e.getMessage(), e);
54          } catch (IllegalAccessException e) {
55              throw new ServletException(e.getMessage(), e);
56          } catch (UnapiException e) {
57              throw new ServletException(e.getMessage(), e);
58          }
59      }
60      
61      @Override
62      public void doGet(HttpServletRequest request, HttpServletResponse response)
63              throws ServletException, IOException {
64          String id = request.getParameter("id");
65          String format = request.getParameter("format");
66  
67          if (id == null && format == null) {
68              listDefaultFormats(response);
69          } else if (format == null) {
70              listObjectFormats(response, id);
71          } else {
72              getObject(response, id, format);
73          }
74      }
75      
76      /**
77       * Provide a list of object formats which should be supported for all 
78       * objects available through the unAPI service. 
79       * Content-type must be "application/xml".
80       * 
81       * @throws IOException 
82       */
83      private void listDefaultFormats(HttpServletResponse response) throws IOException {
84          UnapiFormats formats = null;
85          try {
86              if (defaultFormats.size() > 0) {
87                  formats = new UnapiFormats(null, defaultFormats);
88              } else {
89                  formats = resolver.getFormats();
90              }
91              response.setContentType(CONTENT_TYPE_XML);
92              serializer.serialize(formats, response.getOutputStream());
93          } catch (UnapiException e) {
94              response.sendError(e.getErrorCode(), e.getMessage());
95          }
96      }
97      
98      /**
99       * Provide a list of object formats available from the unAPI service for the 
100      * object identified by <code>id</code>. Content-type must be 
101      * "application/xml". 
102      * It is similar to the {@link #listDefaultFormats(HttpServletResponse) 
103      * listDefaultFormats} response, adding only an "id" attribute on the 
104      * root "formats" element; this echoes the requested <code>id</code>.
105      * 
106      * @param id
107      * @throws IOException 
108      */
109     private void listObjectFormats(HttpServletResponse response, String id) throws IOException {
110         try {
111             UnapiFormats formats = resolver.getFormats(id);
112             response.setContentType(CONTENT_TYPE_XML);
113             response.setStatus(HttpServletResponse.SC_MULTIPLE_CHOICES);
114             //FIXME is SC_MULTIPLE_CHOICES only sent when there are more than 
115             //one format, or always?
116             //if (formats.getFormats().size() > 1) {
117             //    response.setStatus(HttpServletResponse.SC_MULTIPLE_CHOICES);
118             //}
119             serializer.serialize(formats, response.getOutputStream());
120         } catch(UnapiException e) {
121             response.sendError(e.getErrorCode(), e.getMessage());
122         }
123     }
124     
125     /**
126      * Provide the bare object specified by <code>id</code> in the format 
127      * specified by <code>format</code>. <code>format</code> should be a format 
128      * name as specified by the value of the "name" attribute in the 
129      * UNAPI?id=<code>id</code> response, and the response content-type must be 
130      * the content-type specified by the value of the "type" attribute in the 
131      * UNAPI?id=<code>id</code> response for this format.
132      * 
133      * @param id
134      * @param format
135      */
136     private void getObject(HttpServletResponse response, String id, String format) throws IOException {
137         try {
138             UnapiObject obj = resolver.getObject(id, format);
139             if (obj.getRedirectUrl() != null) {
140                 response.sendRedirect(obj.getRedirectUrl());
141             } else {
142                 InputStream in = obj.getInputStream();
143                 if (in == null) {
144                     throw new UnapiException("Error getting " + id + " as " + 
145                                              format + ". Neither redirectUrl " +
146                                              "nor InputStream was provided.");
147                 }
148                 response.setContentType(obj.getContentType());
149                 copy(in, response.getOutputStream());
150                 in.close();
151             }
152         } catch(UnapiException e) {
153             response.sendError(500, e.getMessage());
154         }
155     }
156     
157     private static final int BUFF_SIZE = 100000;
158     
159     /**
160      * A static 100K buffer used by the copy operation.
161      */
162     private static final byte[] buffer = new byte[BUFF_SIZE];
163 
164     /**
165      * Copy an InputStream to an OutputStream. 
166      * While this method will automatically close the destination OutputStream,
167      * the caller is responsible for closing the source InputStream.
168      * 
169      * @param source
170      * @param destination
171      * @return <code>true</code> if the operation was successful;
172      *         <code>false</code> otherwise (which includes a null input).
173      * @see <a href="http://java.sun.com/docs/books/performance/1st_edition/html/JPIOPerformance.fm.html#22980">
174      * http://java.sun.com/docs/books/performance/1st_edition/html/JPIOPerformance.fm.html#22980</a>
175      */
176     public static boolean copy(InputStream source, OutputStream destination) {
177         try {
178             while (true) {
179                 synchronized (buffer) {
180                     int amountRead = source.read(buffer);
181                     if (amountRead == -1) {
182                         break;
183                     }
184                     destination.write(buffer, 0, amountRead);
185                 }
186             }
187             destination.flush();
188             destination.close();
189             return true;
190         } catch (IOException e) {
191             return false;
192         }
193     }
194 }