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