This project has retired. For details please refer to its Attic page.
BrowserBindingUtils 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.browser;
20  
21  import static org.apache.chemistry.opencmis.server.shared.HttpUtils.getBooleanParameter;
22  import static org.apache.chemistry.opencmis.server.shared.HttpUtils.getStringParameter;
23  
24  import java.io.IOException;
25  import java.math.BigDecimal;
26  import java.math.BigInteger;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.GregorianCalendar;
30  import java.util.List;
31  import java.util.Map;
32  
33  import javax.servlet.http.Cookie;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  
37  import org.apache.chemistry.opencmis.commons.PropertyIds;
38  import org.apache.chemistry.opencmis.commons.data.Ace;
39  import org.apache.chemistry.opencmis.commons.data.Acl;
40  import org.apache.chemistry.opencmis.commons.data.ContentStream;
41  import org.apache.chemistry.opencmis.commons.data.ObjectData;
42  import org.apache.chemistry.opencmis.commons.data.Properties;
43  import org.apache.chemistry.opencmis.commons.data.PropertyData;
44  import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
45  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
46  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
47  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
48  import org.apache.chemistry.opencmis.commons.impl.Base64;
49  import org.apache.chemistry.opencmis.commons.impl.Constants;
50  import org.apache.chemistry.opencmis.commons.impl.TypeCache;
51  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
52  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl;
53  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
54  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl;
55  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
56  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
57  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl;
58  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl;
59  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalImpl;
60  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlImpl;
61  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
62  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl;
63  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
64  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl;
65  import org.apache.chemistry.opencmis.commons.impl.json.JSONObject;
66  import org.apache.chemistry.opencmis.commons.impl.json.JSONStreamAware;
67  import org.apache.chemistry.opencmis.commons.server.CallContext;
68  import org.apache.chemistry.opencmis.commons.server.CmisService;
69  import org.apache.chemistry.opencmis.server.impl.CallContextImpl;
70  import org.apache.chemistry.opencmis.server.shared.HttpUtils;
71  
72  public class BrowserBindingUtils {
73  
74      public static final String JSON_MIME_TYPE = "application/json";
75      public static final String HTML_MIME_TYPE = "text/html";
76  
77      public static final String ROOT_PATH_FRAGMENT = "root";
78  
79      public static final String CONTEXT_OBJECT_ID = "org.apache.chemistry.opencmis.browserbinding.objectId";
80      public static final String CONTEXT_OBJECT_TYPE_ID = "org.apache.chemistry.opencmis.browserbinding.objectTypeId";
81      public static final String CONTEXT_BASETYPE_ID = "org.apache.chemistry.opencmis.browserbinding.basetypeId";
82      public static final String CONTEXT_TRANSACTION = "org.apache.chemistry.opencmis.browserbinding.transaction";
83  
84      public enum CallUrl {
85          SERVICE, REPOSITORY, ROOT
86      }
87  
88      // Utility class.
89      private BrowserBindingUtils() {
90      }
91  
92      /**
93       * Compiles the base URL for links, collections and templates.
94       */
95      public static UrlBuilder compileBaseUrl(HttpServletRequest request) {
96          UrlBuilder url = new UrlBuilder(request.getScheme(), request.getServerName(), request.getServerPort(), null);
97  
98          url.addPath(request.getContextPath());
99          url.addPath(request.getServletPath());
100 
101         return url;
102     }
103 
104     public static UrlBuilder compileRepositoryUrl(HttpServletRequest request, String repositoryId) {
105         return compileBaseUrl(request).addPathSegment(repositoryId);
106     }
107 
108     public static UrlBuilder compileRootUrl(HttpServletRequest request, String repositoryId) {
109         return compileRepositoryUrl(request, repositoryId).addPathSegment(ROOT_PATH_FRAGMENT);
110     }
111 
112     /**
113      * Returns the current CMIS path.
114      */
115     public static String getPath(HttpServletRequest request) {
116         String[] pathFragments = HttpUtils.splitPath(request);
117         if (pathFragments.length < 2) {
118             return null;
119         }
120         if (pathFragments.length == 2) {
121             return "/";
122         }
123 
124         StringBuilder sb = new StringBuilder();
125         for (int i = 2; i < pathFragments.length; i++) {
126             if (pathFragments[i].length() == 0) {
127                 continue;
128             }
129 
130             sb.append("/");
131             sb.append(pathFragments[i]);
132         }
133 
134         return sb.toString();
135     }
136 
137     /**
138      * Returns the object id of the current request.
139      */
140     public static void prepareContext(CallContext context, CallUrl callUrl, CmisService service, String repositoryId,
141             String objectId, String transaction, HttpServletRequest request) {
142         CallContextImpl contextImpl = null;
143         if (context instanceof CallContextImpl) {
144             contextImpl = (CallContextImpl) context;
145             contextImpl.put(CONTEXT_TRANSACTION, transaction);
146         }
147 
148         if (callUrl != CallUrl.ROOT) {
149             return;
150         }
151 
152         ObjectData object = null;
153 
154         if (objectId != null) {
155             object = service.getObject(repositoryId, objectId, "cmis:objectId,cmis:objectTypeId,cmis:baseTypeId",
156                     false, IncludeRelationships.NONE, "cmis:none", false, false, null);
157         } else {
158             object = service.getObjectByPath(repositoryId, getPath(request),
159                     "cmis:objectId,cmis:objectTypeId,cmis:baseTypeId", false, IncludeRelationships.NONE, "cmis:none",
160                     false, false, null);
161         }
162 
163         if (contextImpl != null) {
164             contextImpl.put(CONTEXT_OBJECT_ID, object.getId());
165             contextImpl.put(CONTEXT_OBJECT_TYPE_ID, getProperty(object, PropertyIds.OBJECT_TYPE_ID, String.class));
166             contextImpl.put(CONTEXT_BASETYPE_ID, getProperty(object, PropertyIds.BASE_TYPE_ID, String.class));
167         }
168     }
169 
170     /**
171      * Extracts a property from an object.
172      */
173     @SuppressWarnings("unchecked")
174     public static <T> T getProperty(ObjectData object, String name, Class<T> clazz) {
175         if (object == null) {
176             return null;
177         }
178 
179         Properties propData = object.getProperties();
180         if (propData == null) {
181             return null;
182         }
183 
184         Map<String, PropertyData<?>> properties = propData.getProperties();
185         if (properties == null) {
186             return null;
187         }
188 
189         PropertyData<?> property = properties.get(name);
190         if (property == null) {
191             return null;
192         }
193 
194         Object value = property.getFirstValue();
195         if (!clazz.isInstance(value)) {
196             return null;
197         }
198 
199         return (T) value;
200     }
201 
202     public static Properties createProperties(ControlParser controlParser, String typeId, TypeCache typeCache) {
203         List<String> propertyIds = controlParser.getValues(Constants.CONTROL_PROP_ID);
204         if (propertyIds == null) {
205             return null;
206         }
207 
208         Map<Integer, String> singleValuePropertyMap = controlParser.getOneDimMap(Constants.CONTROL_PROP_VALUE);
209         Map<Integer, Map<Integer, String>> multiValuePropertyMap = controlParser
210                 .getTwoDimMap(Constants.CONTROL_PROP_VALUE);
211 
212         if (typeId == null) {
213             // it's a create call -> find type id in properties
214             int i = 0;
215             for (String propertId : propertyIds) {
216                 if (PropertyIds.OBJECT_TYPE_ID.equals(propertId)) {
217                     typeId = singleValuePropertyMap.get(i);
218                     break;
219                 }
220 
221                 i++;
222             }
223 
224             if (typeId == null) {
225                 throw new CmisInvalidArgumentException(PropertyIds.OBJECT_TYPE_ID + " not set!");
226             }
227         }
228 
229         TypeDefinition typeDef = typeCache.getTypeDefinition(typeId);
230         if (typeDef == null) {
231             throw new CmisInvalidArgumentException("Invalid type: " + typeId);
232         }
233 
234         PropertiesImpl result = new PropertiesImpl();
235 
236         int i = 0;
237         for (String propertyId : propertyIds) {
238             PropertyDefinition<?> propDef = typeDef.getPropertyDefinitions().get(propertyId);
239             if (propDef == null) {
240                 throw new CmisInvalidArgumentException(propertyId + " is unknown!");
241             }
242 
243             PropertyData<?> propertyData = null;
244 
245             if (singleValuePropertyMap != null && singleValuePropertyMap.containsKey(i)) {
246                 propertyData = createPropertyData(propDef, singleValuePropertyMap.get(i));
247             } else if (multiValuePropertyMap != null && multiValuePropertyMap.containsKey(i)) {
248                 propertyData = createPropertyData(propDef, controlParser.getValues(Constants.CONTROL_PROP_VALUE, i));
249             } else {
250                 propertyData = createPropertyData(propDef, null);
251             }
252 
253             result.addProperty(propertyData);
254 
255             i++;
256         }
257 
258         return result;
259     }
260 
261     @SuppressWarnings("unchecked")
262     private static PropertyData<?> createPropertyData(PropertyDefinition<?> propDef, Object value) {
263 
264         List<String> strValues;
265         if (value == null) {
266             strValues = Collections.emptyList();
267         } else if (value instanceof String) {
268             strValues = new ArrayList<String>();
269             strValues.add((String) value);
270         } else {
271             strValues = (List<String>) value;
272         }
273 
274         PropertyData<?> propertyData = null;
275         switch (propDef.getPropertyType()) {
276         case STRING:
277             propertyData = new PropertyStringImpl(propDef.getId(), strValues);
278             break;
279         case ID:
280             propertyData = new PropertyIdImpl(propDef.getId(), strValues);
281             break;
282         case BOOLEAN:
283             List<Boolean> boolValues = new ArrayList<Boolean>(strValues.size());
284             try {
285                 for (String s : strValues) {
286                     boolValues.add(Boolean.valueOf(s));
287                 }
288             } catch (NumberFormatException e) {
289                 throw new CmisInvalidArgumentException(propDef.getId() + " value is not a boolean value!");
290             }
291             propertyData = new PropertyBooleanImpl(propDef.getId(), boolValues);
292             break;
293         case INTEGER:
294             List<BigInteger> intValues = new ArrayList<BigInteger>(strValues.size());
295             try {
296                 for (String s : strValues) {
297                     intValues.add(new BigInteger(s));
298                 }
299             } catch (NumberFormatException e) {
300                 throw new CmisInvalidArgumentException(propDef.getId() + " value is not an integer value!");
301             }
302             propertyData = new PropertyIntegerImpl(propDef.getId(), intValues);
303             break;
304         case DECIMAL:
305             List<BigDecimal> decValues = new ArrayList<BigDecimal>(strValues.size());
306             try {
307                 for (String s : strValues) {
308                     decValues.add(new BigDecimal(s));
309                 }
310             } catch (NumberFormatException e) {
311                 throw new CmisInvalidArgumentException(propDef.getId() + " value is not an integer value!");
312             }
313             propertyData = new PropertyDecimalImpl(propDef.getId(), decValues);
314             break;
315         case DATETIME:
316             List<GregorianCalendar> calValues = new ArrayList<GregorianCalendar>(strValues.size());
317             try {
318                 for (String s : strValues) {
319                     GregorianCalendar cal = new GregorianCalendar();
320                     cal.setTimeInMillis(Long.parseLong(s));
321                     calValues.add(cal);
322                 }
323             } catch (NumberFormatException e) {
324                 throw new CmisInvalidArgumentException(propDef.getId() + " value is not an datetime value!");
325             }
326             propertyData = new PropertyDateTimeImpl(propDef.getId(), calValues);
327             break;
328         case HTML:
329             propertyData = new PropertyHtmlImpl(propDef.getId(), strValues);
330             break;
331         case URI:
332             propertyData = new PropertyUriImpl(propDef.getId(), strValues);
333             break;
334         }
335 
336         return propertyData;
337     }
338 
339     public static List<String> createPolicies(ControlParser controlParser) {
340         return controlParser.getValues(Constants.CONTROL_POLICY);
341     }
342 
343     public static Acl createAddAcl(ControlParser controlParser) {
344         List<String> principals = controlParser.getValues(Constants.CONTROL_ADD_ACE_PRINCIPAL);
345         if (principals == null) {
346             return null;
347         }
348 
349         List<Ace> aces = new ArrayList<Ace>();
350 
351         int i = 0;
352         for (String principalId : principals) {
353             aces.add(new AccessControlEntryImpl(new AccessControlPrincipalDataImpl(principalId), controlParser
354                     .getValues(Constants.CONTROL_ADD_ACE_PERMISSION, i)));
355             i++;
356         }
357 
358         return new AccessControlListImpl(aces);
359     }
360 
361     public static Acl createRemoveAcl(ControlParser controlParser) {
362         List<String> principals = controlParser.getValues(Constants.CONTROL_REMOVE_ACE_PRINCIPAL);
363         if (principals == null) {
364             return null;
365         }
366 
367         List<Ace> aces = new ArrayList<Ace>();
368 
369         int i = 0;
370         for (String principalId : principals) {
371             aces.add(new AccessControlEntryImpl(new AccessControlPrincipalDataImpl(principalId), controlParser
372                     .getValues(Constants.CONTROL_REMOVE_ACE_PERMISSION, i)));
373             i++;
374         }
375 
376         return new AccessControlListImpl(aces);
377     }
378 
379     public static ContentStream createContentStream(HttpServletRequest request) {
380         ContentStreamImpl result = null;
381 
382         if (request instanceof POSTHttpServletRequestWrapper) {
383             POSTHttpServletRequestWrapper post = (POSTHttpServletRequestWrapper) request;
384             if (post.getStream() != null) {
385                 result = new ContentStreamImpl(post.getFilename(), post.getSize(), post.getContentType(),
386                         post.getStream());
387             }
388         }
389 
390         return result;
391     }
392 
393     protected static ObjectData getSimpleObject(CmisService service, String repositoryId, String objectId) {
394         return service.getObject(repositoryId, objectId, null, false, IncludeRelationships.NONE, "cmis:none", false,
395                 false, null);
396     }
397 
398     /**
399      * Sets the given HTTP status code if the surpessResponseCodes parameter is
400      * not set to true; otherwise sets HTTP status code 200 (OK).
401      */
402     public static void setStatus(HttpServletRequest request, HttpServletResponse response, int statusCode) {
403         if (getBooleanParameter(request, Constants.PARAM_SUPPRESS_RESPONSE_CODES, false)) {
404             statusCode = HttpServletResponse.SC_OK;
405         }
406 
407         response.setStatus(statusCode);
408     }
409 
410     /**
411      * Transforms the transaction into a cookie name.
412      */
413     public static String getCookieName(String transaction) {
414         if (transaction == null || transaction.length() == 0) {
415             return "cmis%";
416         }
417 
418         return "cmis_" + Base64.encodeBytes(transaction.getBytes()).replace('=', '%');
419     }
420 
421     /**
422      * Sets a transaction cookie.
423      */
424     public static void setCookie(HttpServletRequest request, HttpServletResponse response, String repositoryId,
425             String transaction, String value) {
426         setCookie(request, response, repositoryId, transaction, value, 3600);
427     }
428 
429     /**
430      * Deletes a transaction cookie.
431      */
432     public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String repositoryId,
433             String transaction) {
434         setCookie(request, response, repositoryId, transaction, "", 0);
435     }
436 
437     /**
438      * Sets a transaction cookie.
439      */
440     public static void setCookie(HttpServletRequest request, HttpServletResponse response, String repositoryId,
441             String transaction, String value, int expiry) {
442         if (transaction != null && transaction.length() > 0) {
443             Cookie transactionCookie = new Cookie(getCookieName(transaction), value);
444             transactionCookie.setMaxAge(expiry);
445             transactionCookie.setPath(request.getContextPath() + request.getServletPath() + "/" + repositoryId);
446             response.addCookie(transactionCookie);
447         }
448     }
449 
450     public static String createCookieValue(int code, String objectId, String ex, String message) {
451         JSONObject result = new JSONObject();
452 
453         result.put("code", code);
454         result.put("objectId", objectId == null ? "" : objectId);
455         result.put("exception", ex == null ? "" : ex);
456         result.put("message", message == null ? "" : message);
457 
458         return result.toJSONString();
459     }
460 
461     /**
462      * Writes JSON to the servlet response and adds a callback wrapper if
463      * requested.
464      */
465     public static void writeJSON(JSONStreamAware json, HttpServletRequest request, HttpServletResponse response)
466             throws IOException {
467         String transaction = getStringParameter(request, Constants.PARAM_TRANSACTION);
468 
469         if (transaction == null) {
470             response.setContentType(JSON_MIME_TYPE);
471             response.setCharacterEncoding("UTF-8");
472 
473             String callback = getStringParameter(request, Constants.PARAM_CALLBACK);
474             if (callback != null) {
475                 if (!callback.matches("[A-Za-z0-9._\\[\\]]*")) {
476                     throw new CmisInvalidArgumentException("Invalid callback name!");
477                 }
478                 response.getWriter().print(callback + "(");
479             }
480 
481             json.writeJSONString(response.getWriter());
482 
483             if (callback != null) {
484                 response.getWriter().print(");");
485             }
486         } else {
487             response.setContentType(HTML_MIME_TYPE);
488             response.setContentLength(0);
489         }
490 
491         response.getWriter().flush();
492     }
493 
494     public static void writeEmpty(HttpServletRequest request, HttpServletResponse response) throws IOException {
495         response.setContentLength(0);
496         response.setContentType("text/plain");
497         response.getWriter().flush();
498     }
499 }