1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.chemistry.opencmis.server.impl.atompub;
20
21 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_ACL;
22 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_ALLOWABLEACIONS;
23 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_BULK_UPDATE;
24 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_CHANGES;
25 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_CHECKEDOUT;
26 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_CHILDREN;
27 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_CONTENT;
28 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_DESCENDANTS;
29 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_ENTRY;
30 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_FOLDERTREE;
31 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_OBJECTBYID;
32 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_OBJECTBYPATH;
33 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_PARENT;
34 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_PARENTS;
35 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_POLICIES;
36 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_QUERY;
37 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_RELATIONSHIPS;
38 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_TYPE;
39 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_TYPES;
40 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_TYPESDESC;
41 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_UNFILED;
42 import static org.apache.chemistry.opencmis.server.impl.atompub.AbstractAtomPubServiceCall.RESOURCE_VERSIONS;
43 import static org.apache.chemistry.opencmis.server.shared.Dispatcher.METHOD_DELETE;
44 import static org.apache.chemistry.opencmis.server.shared.Dispatcher.METHOD_GET;
45 import static org.apache.chemistry.opencmis.server.shared.Dispatcher.METHOD_HEAD;
46 import static org.apache.chemistry.opencmis.server.shared.Dispatcher.METHOD_POST;
47 import static org.apache.chemistry.opencmis.server.shared.Dispatcher.METHOD_PUT;
48
49 import java.io.IOException;
50 import java.io.PrintWriter;
51 import java.util.Map;
52
53 import javax.servlet.ServletConfig;
54 import javax.servlet.ServletException;
55 import javax.servlet.http.HttpServletRequest;
56 import javax.servlet.http.HttpServletResponse;
57
58 import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
59 import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
60 import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
61 import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
62 import org.apache.chemistry.opencmis.commons.exceptions.CmisFilterNotValidException;
63 import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
64 import org.apache.chemistry.opencmis.commons.exceptions.CmisNameConstraintViolationException;
65 import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
66 import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
67 import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
68 import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
69 import org.apache.chemistry.opencmis.commons.exceptions.CmisServiceUnavailableException;
70 import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException;
71 import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException;
72 import org.apache.chemistry.opencmis.commons.exceptions.CmisTooManyRequestsException;
73 import org.apache.chemistry.opencmis.commons.exceptions.CmisUnauthorizedException;
74 import org.apache.chemistry.opencmis.commons.exceptions.CmisUpdateConflictException;
75 import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException;
76 import org.apache.chemistry.opencmis.commons.impl.IOUtils;
77 import org.apache.chemistry.opencmis.commons.server.CallContext;
78 import org.apache.chemistry.opencmis.commons.server.CmisService;
79 import org.apache.chemistry.opencmis.server.impl.ServerVersion;
80 import org.apache.chemistry.opencmis.server.shared.AbstractCmisHttpServlet;
81 import org.apache.chemistry.opencmis.server.shared.Dispatcher;
82 import org.apache.chemistry.opencmis.server.shared.ExceptionHelper;
83 import org.apache.chemistry.opencmis.server.shared.HEADHttpServletRequestWrapper;
84 import org.apache.chemistry.opencmis.server.shared.HttpUtils;
85 import org.apache.chemistry.opencmis.server.shared.NoBodyHttpServletResponseWrapper;
86 import org.apache.chemistry.opencmis.server.shared.QueryStringHttpServletRequestWrapper;
87 import org.apache.chemistry.opencmis.server.shared.ServiceCall;
88 import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
89 import org.apache.commons.lang3.StringEscapeUtils;
90 import org.slf4j.Logger;
91 import org.slf4j.LoggerFactory;
92
93
94
95
96 public class CmisAtomPubServlet extends AbstractCmisHttpServlet {
97
98 private static final Logger LOG = LoggerFactory.getLogger(CmisAtomPubServlet.class);
99
100 private static final String OPENCMIS_CSS_STYLE = "<style>"
101 + "<!--H1 {font-size:24px;line-height:normal;font-weight:bold;background-color:#f0f0f0;color:#003366;border-bottom:1px solid #3c78b5;padding:2px;} "
102 + "BODY {font-family:Verdana,arial,sans-serif;color:black;font-size:14px;} "
103 + "HR {color:#3c78b5;height:1px;}--></style>";
104
105 private static final long serialVersionUID = 1L;
106
107 private final Dispatcher dispatcher = new Dispatcher();
108
109 @Override
110 public void init(ServletConfig config) throws ServletException {
111 super.init(config);
112
113
114 setBinding(CallContext.BINDING_ATOMPUB);
115
116
117 String cmisVersionStr = config.getInitParameter(PARAM_CMIS_VERSION);
118 if (cmisVersionStr != null) {
119 try {
120 setCmisVersion(CmisVersion.fromValue(cmisVersionStr));
121 } catch (IllegalArgumentException e) {
122 LOG.warn("CMIS version is invalid! Setting it to CMIS 1.0.");
123 setCmisVersion(CmisVersion.CMIS_1_0);
124 }
125 } else {
126 LOG.warn("CMIS version is not defined! Setting it to CMIS 1.0.");
127 setCmisVersion(CmisVersion.CMIS_1_0);
128 }
129
130
131 addResource("", METHOD_GET, new RepositoryService.GetRepositories());
132 addResource(RESOURCE_TYPES, METHOD_GET, new RepositoryService.GetTypeChildren());
133 addResource(RESOURCE_TYPES, METHOD_POST, new RepositoryService.CreateType());
134 addResource(RESOURCE_TYPESDESC, METHOD_GET, new RepositoryService.GetTypeDescendants());
135 addResource(RESOURCE_TYPE, METHOD_GET, new RepositoryService.GetTypeDefinition());
136 addResource(RESOURCE_TYPE, METHOD_PUT, new RepositoryService.UpdateType());
137 addResource(RESOURCE_TYPE, METHOD_DELETE, new RepositoryService.DeleteType());
138 addResource(RESOURCE_CHILDREN, METHOD_GET, new NavigationService.GetChildren());
139 addResource(RESOURCE_DESCENDANTS, METHOD_GET, new NavigationService.GetDescendants());
140 addResource(RESOURCE_FOLDERTREE, METHOD_GET, new NavigationService.GetFolderTree());
141 addResource(RESOURCE_PARENT, METHOD_GET, new NavigationService.GetFolderParent());
142 addResource(RESOURCE_PARENTS, METHOD_GET, new NavigationService.GetObjectParents());
143 addResource(RESOURCE_CHECKEDOUT, METHOD_GET, new NavigationService.GetCheckedOutDocs());
144 addResource(RESOURCE_ENTRY, METHOD_GET, new ObjectService.GetObject());
145 addResource(RESOURCE_OBJECTBYID, METHOD_GET, new ObjectService.GetObject());
146 addResource(RESOURCE_OBJECTBYPATH, METHOD_GET, new ObjectService.GetObjectByPath());
147 addResource(RESOURCE_ALLOWABLEACIONS, METHOD_GET, new ObjectService.GetAllowableActions());
148 addResource(RESOURCE_CONTENT, METHOD_GET, new ObjectService.GetContentStream());
149 addResource(RESOURCE_CONTENT, METHOD_PUT, new ObjectService.SetOrAppendContentStream());
150 addResource(RESOURCE_CONTENT, METHOD_DELETE, new ObjectService.DeleteContentStream());
151 addResource(RESOURCE_CHILDREN, METHOD_POST, new ObjectService.Create());
152 addResource(RESOURCE_RELATIONSHIPS, METHOD_POST, new ObjectService.CreateRelationship());
153 addResource(RESOURCE_ENTRY, METHOD_PUT, new ObjectService.UpdateProperties());
154 addResource(RESOURCE_ENTRY, METHOD_DELETE, new ObjectService.DeleteObject());
155 addResource(RESOURCE_CHILDREN, METHOD_DELETE, new ObjectService.DeleteTree());
156 addResource(RESOURCE_DESCENDANTS, METHOD_DELETE, new ObjectService.DeleteTree());
157 addResource(RESOURCE_FOLDERTREE, METHOD_DELETE, new ObjectService.DeleteTree());
158 addResource(RESOURCE_BULK_UPDATE, METHOD_POST, new ObjectService.BulkUpdateProperties());
159 addResource(RESOURCE_CHECKEDOUT, METHOD_POST, new VersioningService.CheckOut());
160 addResource(RESOURCE_VERSIONS, METHOD_GET, new VersioningService.GetAllVersions());
161 addResource(RESOURCE_VERSIONS, METHOD_DELETE, new VersioningService.DeleteAllVersions());
162 addResource(RESOURCE_QUERY, METHOD_GET, new DiscoveryService.Query());
163 addResource(RESOURCE_QUERY, METHOD_POST, new DiscoveryService.Query());
164 addResource(RESOURCE_CHANGES, METHOD_GET, new DiscoveryService.GetContentChanges());
165 addResource(RESOURCE_RELATIONSHIPS, METHOD_GET, new RelationshipService.GetObjectRelationships());
166 addResource(RESOURCE_UNFILED, METHOD_POST, new MultiFilingService.RemoveObjectFromFolder());
167 addResource(RESOURCE_ACL, METHOD_GET, new AclService.GetAcl());
168 addResource(RESOURCE_ACL, METHOD_PUT, new AclService.ApplyAcl());
169 addResource(RESOURCE_POLICIES, METHOD_GET, new PolicyService.GetAppliedPolicies());
170 addResource(RESOURCE_POLICIES, METHOD_POST, new PolicyService.ApplyPolicy());
171 addResource(RESOURCE_POLICIES, METHOD_DELETE, new PolicyService.RemovePolicy());
172 }
173
174 @Override
175 protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
176 IOException {
177 CallContext context = null;
178
179 boolean flush = true;
180 try {
181
182 if (!METHOD_GET.equals(request.getMethod()) && !METHOD_HEAD.equals(request.getMethod())) {
183 checkCsrfToken(request, response, false, false);
184 }
185
186
187 String[] pathFragments = HttpUtils.splitPath(request);
188
189
190 TempStoreOutputStreamFactory streamFactoy = TempStoreOutputStreamFactory.newInstance(getServiceFactory(),
191 pathFragments.length > 0 ? pathFragments[0] : null, request);
192
193
194 if (METHOD_HEAD.equals(request.getMethod())) {
195 request = new HEADHttpServletRequestWrapper(request);
196 response = new NoBodyHttpServletResponseWrapper(response);
197 } else {
198 request = new QueryStringHttpServletRequestWrapper(request);
199 }
200
201
202 response.addHeader("Cache-Control", "private, max-age=0");
203 response.addHeader("Server", ServerVersion.OPENCMIS_SERVER);
204
205 context = createContext(getServletContext(), request, response, streamFactoy);
206 dispatch(context, request, response, pathFragments);
207 } catch (Exception e) {
208 if (e instanceof CmisUnauthorizedException) {
209 response.setHeader("WWW-Authenticate", "Basic realm=\"CMIS\", charset=\"UTF-8\"");
210 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Required");
211 } else if (e instanceof CmisPermissionDeniedException) {
212 if (context == null || (context.getUsername() == null)) {
213 response.setHeader("WWW-Authenticate", "Basic realm=\"CMIS\", charset=\"UTF-8\"");
214 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Required");
215 } else {
216 printError(e, request, response);
217 }
218 } else {
219
220
221
222
223 flush = !(e instanceof IOException);
224
225 printError(e, request, response);
226 }
227
228 } catch (Throwable t) {
229 LOG.error(createLogMessage(t, request), t);
230
231 try {
232 response.resetBuffer();
233 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
234 response.setContentType("text/html");
235 response.setCharacterEncoding(IOUtils.UTF8);
236
237 PrintWriter pw = response.getWriter();
238 writeHtmlErrorPage(pw, 500, "runtime", "An error occurred!", t);
239 pw.flush();
240 } catch (Exception te) {
241
242
243 flush = false;
244 }
245
246 throw t;
247 } finally {
248
249 if (flush) {
250 try {
251 response.flushBuffer();
252 } catch (IOException ioe) {
253 LOG.error("Could not flush resposne: {}", ioe.toString(), ioe);
254 }
255 }
256 }
257 }
258
259
260
261
262 protected void addResource(String resource, String httpMethod, ServiceCall serviceCall) {
263 dispatcher.addResource(resource, httpMethod, serviceCall);
264 }
265
266
267
268
269 private void dispatch(CallContext context, HttpServletRequest request, HttpServletResponse response,
270 String[] pathFragments) throws Exception {
271
272 CmisService service = null;
273 try {
274
275 service = getServiceFactory().getService(context);
276
277
278 if (pathFragments.length < 2) {
279
280 checkCsrfToken(request, response, true, false);
281
282
283 dispatcher.dispatch("", METHOD_GET, context, service, null, request, response);
284 return;
285 }
286
287 String method = request.getMethod();
288 String repositoryId = pathFragments[0];
289 String resource = pathFragments[1];
290
291
292 checkCsrfToken(request, response, false, RESOURCE_CONTENT.equals(resource) && METHOD_GET.equals(method));
293
294
295 boolean callServiceFound = dispatcher.dispatch(resource, method, context, service, repositoryId, request,
296 response);
297
298
299
300 if (!callServiceFound) {
301 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Unknown operation");
302 }
303 } finally {
304 if (service != null) {
305 service.close();
306 }
307 }
308 }
309
310
311
312
313 protected int getErrorCode(CmisBaseException ex) {
314 if (ex instanceof CmisConstraintException) {
315 return 409;
316 } else if (ex instanceof CmisContentAlreadyExistsException) {
317 return 409;
318 } else if (ex instanceof CmisFilterNotValidException) {
319 return 400;
320 } else if (ex instanceof CmisInvalidArgumentException) {
321 return 400;
322 } else if (ex instanceof CmisNameConstraintViolationException) {
323 return 409;
324 } else if (ex instanceof CmisNotSupportedException) {
325 return 405;
326 } else if (ex instanceof CmisObjectNotFoundException) {
327 return 404;
328 } else if (ex instanceof CmisPermissionDeniedException) {
329 return 403;
330 } else if (ex instanceof CmisStorageException) {
331 return 500;
332 } else if (ex instanceof CmisStreamNotSupportedException) {
333 return 403;
334 } else if (ex instanceof CmisUpdateConflictException) {
335 return 409;
336 } else if (ex instanceof CmisVersioningException) {
337 return 409;
338 } else if (ex instanceof CmisTooManyRequestsException) {
339 return 429;
340 } else if (ex instanceof CmisServiceUnavailableException) {
341 return 503;
342 }
343
344 return 500;
345 }
346
347
348
349
350 protected void printError(Exception ex, HttpServletRequest request, HttpServletResponse response) {
351 int statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
352 String exceptionName = "runtime";
353
354 if (ex instanceof CmisRuntimeException) {
355 LOG.error(createLogMessage(ex, request), ex);
356 statusCode = getErrorCode((CmisRuntimeException) ex);
357 } else if (ex instanceof CmisStorageException) {
358 LOG.error(createLogMessage(ex, request), ex);
359 statusCode = getErrorCode((CmisStorageException) ex);
360 exceptionName = ((CmisStorageException) ex).getExceptionName();
361 } else if (ex instanceof CmisBaseException) {
362 statusCode = getErrorCode((CmisBaseException) ex);
363 exceptionName = ((CmisBaseException) ex).getExceptionName();
364
365 if (statusCode == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) {
366 LOG.error(createLogMessage(ex, request), ex);
367 }
368 } else if (ex instanceof IOException) {
369 LOG.warn(createLogMessage(ex, request), ex);
370 } else {
371 LOG.error(createLogMessage(ex, request), ex);
372 }
373
374 if (response.isCommitted()) {
375 LOG.warn("Failed to send error message to client. Response is already committed.", ex);
376 return;
377 }
378
379 String message = ex.getMessage();
380 if (!(ex instanceof CmisBaseException)) {
381 message = "An error occurred!";
382 }
383
384 try {
385 response.resetBuffer();
386 response.setStatus(statusCode);
387 response.setContentType("text/html");
388 response.setCharacterEncoding(IOUtils.UTF8);
389
390 PrintWriter pw = response.getWriter();
391 writeHtmlErrorPage(pw, statusCode, exceptionName, message, ex);
392 pw.flush();
393 } catch (Exception e) {
394 LOG.error(createLogMessage(ex, request), e);
395 try {
396 response.sendError(statusCode, message);
397 } catch (Exception en) {
398
399 }
400 }
401 }
402
403 protected void writeHtmlErrorPage(PrintWriter pw, int statusCode, String exceptionName, String message, Throwable t)
404 throws IOException {
405 pw.print("<html><head><title>Apache Chemistry OpenCMIS - " + exceptionName + " error</title>"
406 + OPENCMIS_CSS_STYLE + "</head><body>");
407 pw.print("<h1>HTTP Status " + statusCode + " - <!--exception-->" + exceptionName + "<!--/exception--></h1>");
408 pw.print("<p><!--message-->");
409 StringEscapeUtils.ESCAPE_HTML4.translate(message, pw);
410 pw.print("<!--/message--></p>");
411
412 String st = ExceptionHelper.getStacktraceAsString(t);
413 if (st != null) {
414 pw.print("<hr noshade='noshade'/><!--stacktrace--><pre>\n<!--key-->stacktrace<!--/key><!--value-->" + st
415 + "<!--/value-->\n</pre><!--/stacktrace--><hr noshade='noshade'/>");
416 }
417
418 if (t instanceof CmisBaseException) {
419 Map<String, String> additionalData = ((CmisBaseException) t).getAdditionalData();
420 if (additionalData != null && !additionalData.isEmpty()) {
421 pw.print("<hr noshade='noshade'/>Additional data:<br><br>");
422 for (Map.Entry<String, String> e : additionalData.entrySet()) {
423 pw.print("<!--key-->");
424 StringEscapeUtils.ESCAPE_HTML4.translate(e.getKey(), pw);
425 pw.print("<!--/key--> = <!--value-->");
426 StringEscapeUtils.ESCAPE_HTML4.translate(e.getValue(), pw);
427 pw.print("<!--/value--><br>");
428 }
429 }
430 }
431
432 pw.print("</body></html>");
433 }
434 }