This project has retired. For details please refer to its Attic page.
CmisAtomPubServlet xref

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.server.impl.atompub;
20  
21  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_ACL;
22  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_ALLOWABLEACIONS;
23  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_CHANGES;
24  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_CHECKEDOUT;
25  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_CHILDREN;
26  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_CONTENT;
27  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_DESCENDANTS;
28  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_ENTRY;
29  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_FOLDERTREE;
30  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_OBJECTBYID;
31  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_OBJECTBYPATH;
32  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_PARENTS;
33  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_POLICIES;
34  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_QUERY;
35  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_RELATIONSHIPS;
36  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_TYPE;
37  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_TYPES;
38  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_TYPESDESC;
39  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_UNFILED;
40  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_VERSIONS;
41  import static org.apache.chemistry.opencmis.server.shared.Dispatcher.METHOD_DELETE;
42  import static org.apache.chemistry.opencmis.server.shared.Dispatcher.METHOD_GET;
43  import static org.apache.chemistry.opencmis.server.shared.Dispatcher.METHOD_POST;
44  import static org.apache.chemistry.opencmis.server.shared.Dispatcher.METHOD_PUT;
45  
46  import java.io.File;
47  import java.io.IOException;
48  import java.io.PrintWriter;
49  
50  import javax.servlet.ServletConfig;
51  import javax.servlet.ServletException;
52  import javax.servlet.http.HttpServlet;
53  import javax.servlet.http.HttpServletRequest;
54  import javax.servlet.http.HttpServletResponse;
55  
56  import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
57  import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
58  import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
59  import org.apache.chemistry.opencmis.commons.exceptions.CmisFilterNotValidException;
60  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
61  import org.apache.chemistry.opencmis.commons.exceptions.CmisNameConstraintViolationException;
62  import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
63  import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
64  import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
65  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
66  import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException;
67  import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException;
68  import org.apache.chemistry.opencmis.commons.exceptions.CmisUpdateConflictException;
69  import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException;
70  import org.apache.chemistry.opencmis.commons.server.CallContext;
71  import org.apache.chemistry.opencmis.commons.server.CmisService;
72  import org.apache.chemistry.opencmis.commons.server.CmisServiceFactory;
73  import org.apache.chemistry.opencmis.server.impl.CmisRepositoryContextListener;
74  import org.apache.chemistry.opencmis.server.impl.ServerVersion;
75  import org.apache.chemistry.opencmis.server.shared.CallContextHandler;
76  import org.apache.chemistry.opencmis.server.shared.Dispatcher;
77  import org.apache.chemistry.opencmis.server.shared.ExceptionHelper;
78  import org.apache.chemistry.opencmis.server.shared.HttpUtils;
79  import org.apache.commons.lang.StringEscapeUtils;
80  import org.apache.commons.logging.Log;
81  import org.apache.commons.logging.LogFactory;
82  
83  /**
84   * CMIS AtomPub servlet.
85   */
86  public class CmisAtomPubServlet extends HttpServlet {
87  
88      public static final String PARAM_CALL_CONTEXT_HANDLER = "callContextHandler";
89      public static final String PARAM_TRUSTED_PROXIES = "trustedProxies";
90  
91      private static final Log LOG = LogFactory.getLog(CmisAtomPubServlet.class.getName());
92  
93      private static final long serialVersionUID = 1L;
94  
95      private File tempDir;
96      private int memoryThreshold;
97  
98      private Dispatcher dispatcher;
99      private CallContextHandler callContextHandler;
100 
101     @Override
102     public void init(ServletConfig config) throws ServletException {
103         super.init(config);
104 
105         // initialize the call context handler
106         callContextHandler = null;
107         String callContextHandlerClass = config.getInitParameter(PARAM_CALL_CONTEXT_HANDLER);
108         if (callContextHandlerClass != null) {
109             try {
110                 callContextHandler = (CallContextHandler) Class.forName(callContextHandlerClass).newInstance();
111             } catch (Exception e) {
112                 throw new ServletException("Could not load call context handler: " + e, e);
113             }
114         }
115 
116         // get memory threshold and temp directory
117         CmisServiceFactory factory = (CmisServiceFactory) config.getServletContext().getAttribute(
118                 CmisRepositoryContextListener.SERVICES_FACTORY);
119 
120         tempDir = factory.getTempDirectory();
121         memoryThreshold = factory.getMemoryThreshold();
122 
123         // initialize the dispatcher
124         dispatcher = new Dispatcher();
125 
126         try {
127             dispatcher.addResource(RESOURCE_TYPES, METHOD_GET, RepositoryService.class, "getTypeChildren");
128             dispatcher.addResource(RESOURCE_TYPESDESC, METHOD_GET, RepositoryService.class, "getTypeDescendants");
129             dispatcher.addResource(RESOURCE_TYPE, METHOD_GET, RepositoryService.class, "getTypeDefinition");
130             dispatcher.addResource(RESOURCE_CHILDREN, METHOD_GET, NavigationService.class, "getChildren");
131             dispatcher.addResource(RESOURCE_DESCENDANTS, METHOD_GET, NavigationService.class, "getDescendants");
132             dispatcher.addResource(RESOURCE_FOLDERTREE, METHOD_GET, NavigationService.class, "getFolderTree");
133             dispatcher.addResource(RESOURCE_PARENTS, METHOD_GET, NavigationService.class, "getObjectParents");
134             dispatcher.addResource(RESOURCE_CHECKEDOUT, METHOD_GET, NavigationService.class, "getCheckedOutDocs");
135             dispatcher.addResource(RESOURCE_ENTRY, METHOD_GET, ObjectService.class, "getObject");
136             dispatcher.addResource(RESOURCE_OBJECTBYID, METHOD_GET, ObjectService.class, "getObject");
137             dispatcher.addResource(RESOURCE_OBJECTBYPATH, METHOD_GET, ObjectService.class, "getObjectByPath");
138             dispatcher.addResource(RESOURCE_ALLOWABLEACIONS, METHOD_GET, ObjectService.class, "getAllowableActions");
139             dispatcher.addResource(RESOURCE_CONTENT, METHOD_GET, ObjectService.class, "getContentStream");
140             dispatcher.addResource(RESOURCE_CONTENT, METHOD_PUT, ObjectService.class, "setContentStream");
141             dispatcher.addResource(RESOURCE_CONTENT, METHOD_DELETE, ObjectService.class, "deleteContentStream");
142             dispatcher.addResource(RESOURCE_CHILDREN, METHOD_POST, ObjectService.class, "create");
143             dispatcher.addResource(RESOURCE_RELATIONSHIPS, METHOD_POST, ObjectService.class, "createRelationship");
144             dispatcher.addResource(RESOURCE_ENTRY, METHOD_PUT, ObjectService.class, "updateProperties");
145             dispatcher.addResource(RESOURCE_ENTRY, METHOD_DELETE, ObjectService.class, "deleteObject");
146             dispatcher.addResource(RESOURCE_DESCENDANTS, METHOD_DELETE, ObjectService.class, "deleteTree");
147             dispatcher.addResource(RESOURCE_CHECKEDOUT, METHOD_POST, VersioningService.class, "checkOut");
148             dispatcher.addResource(RESOURCE_VERSIONS, METHOD_GET, VersioningService.class, "getAllVersions");
149             dispatcher.addResource(RESOURCE_VERSIONS, METHOD_DELETE, VersioningService.class, "deleteAllVersions");
150             dispatcher.addResource(RESOURCE_QUERY, METHOD_GET, DiscoveryService.class, "query");
151             dispatcher.addResource(RESOURCE_QUERY, METHOD_POST, DiscoveryService.class, "query");
152             dispatcher.addResource(RESOURCE_CHANGES, METHOD_GET, DiscoveryService.class, "getContentChanges");
153             dispatcher.addResource(RESOURCE_RELATIONSHIPS, METHOD_GET, RelationshipService.class,
154                     "getObjectRelationships");
155             dispatcher.addResource(RESOURCE_UNFILED, METHOD_POST, MultiFilingService.class, "removeObjectFromFolder");
156             dispatcher.addResource(RESOURCE_ACL, METHOD_GET, AclService.class, "getAcl");
157             dispatcher.addResource(RESOURCE_ACL, METHOD_PUT, AclService.class, "applyAcl");
158             dispatcher.addResource(RESOURCE_POLICIES, METHOD_GET, PolicyService.class, "getAppliedPolicies");
159             dispatcher.addResource(RESOURCE_POLICIES, METHOD_POST, PolicyService.class, "applyPolicy");
160             dispatcher.addResource(RESOURCE_POLICIES, METHOD_DELETE, PolicyService.class, "removePolicy");
161         } catch (NoSuchMethodException e) {
162             LOG.error("Cannot initialize dispatcher!", e);
163         }
164     }
165 
166     @Override
167     protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
168             IOException {
169         // set default headers
170         response.addHeader("Cache-Control", "private, max-age=0");
171         response.addHeader("Server", ServerVersion.OPENCMIS_SERVER);
172 
173         // create a context object, dispatch and handle exceptions
174         CallContext context = null;
175         try {
176             context = HttpUtils.createContext(request, response, getServletContext(), CallContext.BINDING_ATOMPUB,
177                     callContextHandler, tempDir, memoryThreshold);
178             dispatch(context, request, response);
179         } catch (Exception e) {
180             if (e instanceof CmisPermissionDeniedException) {
181                 if ((context == null) || (context.getUsername() == null)) {
182                     response.setHeader("WWW-Authenticate", "Basic realm=\"CMIS\"");
183                     response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Required");
184                 } else {
185                     response.sendError(getErrorCode((CmisPermissionDeniedException) e), e.getMessage());
186                 }
187             } else {
188                 printError(e, response);
189             }
190         }
191 
192         // we are done.
193         response.flushBuffer();
194     }
195 
196     /**
197      * Dispatches to feed, entry or whatever.
198      */
199     private void dispatch(CallContext context, HttpServletRequest request, HttpServletResponse response)
200             throws Exception {
201 
202         CmisService service = null;
203         try {
204             // get services factory
205             CmisServiceFactory factory = (CmisServiceFactory) getServletContext().getAttribute(
206                     CmisRepositoryContextListener.SERVICES_FACTORY);
207 
208             if (factory == null) {
209                 throw new CmisRuntimeException("Service factory not available! Configuration problem?");
210             }
211 
212             // get the service
213             service = factory.getService(context);
214 
215             // analyze the path
216             String[] pathFragments = HttpUtils.splitPath(request);
217 
218             if (pathFragments.length < 2) {
219                 // root -> service document
220                 RepositoryService.getRepositories(context, service, request, response);
221                 return;
222             }
223 
224             String method = request.getMethod();
225             String repositoryId = pathFragments[0];
226             String resource = pathFragments[1];
227 
228             // dispatch
229             boolean methodFound = dispatcher.dispatch(resource, method, context, service, repositoryId, request,
230                     response);
231 
232             // if the dispatcher couldn't find a matching method, return an
233             // error message
234             if (!methodFound) {
235                 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Unknown operation");
236             }
237         } finally {
238             if (service != null) {
239                 service.close();
240             }
241         }
242     }
243 
244     /**
245      * Translates an exception in an appropriate HTTP error code.
246      */
247     private static int getErrorCode(CmisBaseException ex) {
248         if (ex instanceof CmisConstraintException) {
249             return 409;
250         } else if (ex instanceof CmisContentAlreadyExistsException) {
251             return 409;
252         } else if (ex instanceof CmisFilterNotValidException) {
253             return 400;
254         } else if (ex instanceof CmisInvalidArgumentException) {
255             return 400;
256         } else if (ex instanceof CmisNameConstraintViolationException) {
257             return 409;
258         } else if (ex instanceof CmisNotSupportedException) {
259             return 405;
260         } else if (ex instanceof CmisObjectNotFoundException) {
261             return 404;
262         } else if (ex instanceof CmisPermissionDeniedException) {
263             return 403;
264         } else if (ex instanceof CmisStorageException) {
265             return 500;
266         } else if (ex instanceof CmisStreamNotSupportedException) {
267             return 403;
268         } else if (ex instanceof CmisUpdateConflictException) {
269             return 409;
270         } else if (ex instanceof CmisVersioningException) {
271             return 409;
272         }
273 
274         return 500;
275     }
276 
277     /**
278      * Prints the error HTML page.
279      */
280     private static void printError(Exception ex, HttpServletResponse response) {
281         int statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
282         String exceptionName = "runtime";
283 
284         if (ex instanceof CmisRuntimeException) {
285             LOG.error(ex.getMessage(), ex);
286         } else if (ex instanceof CmisBaseException) {
287             statusCode = getErrorCode((CmisBaseException) ex);
288             exceptionName = ((CmisBaseException) ex).getExceptionName();
289         } else {
290             LOG.error(ex.getMessage(), ex);
291         }
292 
293         try {
294             PrintWriter pw = response.getWriter();
295             response.setStatus(statusCode);
296             response.setContentType("text/html");
297 
298             pw.print("<html><head><title>Apache Chemistry OpenCMIS - "
299                     + exceptionName
300                     + " error</title>"
301                     + "<style><!--H1 {font-size:24px;line-height:normal;font-weight:bold;background-color:#f0f0f0;color:#003366;border-bottom:1px solid #3c78b5;padding:2px;} "
302                     + "BODY {font-family:Verdana,arial,sans-serif;color:black;font-size:14px;} "
303                     + "HR {color:#3c78b5;height:1px;}--></style></head><body>");
304             pw.print("<h1>HTTP Status " + statusCode + " - <!--exception-->" + exceptionName + "<!--/exception--></h1>");
305             pw.print("<p><!--message-->" + StringEscapeUtils.escapeHtml(ex.getMessage()) + "<!--/message--></p>");
306 
307             String st = ExceptionHelper.getStacktraceAsString(ex);
308             if (st != null) {
309                 pw.print("<hr noshade='noshade'/><!--stacktrace--><pre>\n" + st
310                         + "\n</pre><!--/stacktrace--><hr noshade='noshade'/>");
311             }
312 
313             pw.print("</body></html>");
314         } catch (Exception e) {
315             LOG.error(e.getMessage(), e);
316             try {
317                 response.sendError(statusCode, ex.getMessage());
318             } catch (Exception en) {
319             }
320         }
321     }
322 }