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.atompub;
20  
21  import org.apache.chemistry.opencmis.commons.PropertyIds;
22  import org.apache.chemistry.opencmis.commons.data.Acl;
23  import org.apache.chemistry.opencmis.commons.data.ContentStream;
24  import org.apache.chemistry.opencmis.commons.data.ObjectData;
25  import org.apache.chemistry.opencmis.commons.data.Properties;
26  import org.apache.chemistry.opencmis.commons.data.PropertyData;
27  import org.apache.chemistry.opencmis.commons.data.PropertyId;
28  import org.apache.chemistry.opencmis.commons.data.PropertyString;
29  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
30  import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
31  import org.apache.chemistry.opencmis.commons.impl.Base64;
32  import org.apache.chemistry.opencmis.commons.impl.Constants;
33  import org.apache.chemistry.opencmis.commons.impl.Converter;
34  import org.apache.chemistry.opencmis.commons.impl.JaxBHelper;
35  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
36  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
37  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
38  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisObjectType;
39  import org.apache.chemistry.opencmis.server.shared.ThresholdOutputStream;
40  
41  import java.io.ByteArrayInputStream;
42  import java.io.ByteArrayOutputStream;
43  import java.io.File;
44  import java.io.InputStream;
45  
46  import java.math.BigInteger;
47  
48  import java.util.Iterator;
49  import java.util.List;
50  import java.util.Map;
51  
52  import javax.xml.bind.JAXBElement;
53  import javax.xml.bind.Unmarshaller;
54  import javax.xml.namespace.QName;
55  import javax.xml.stream.XMLInputFactory;
56  import javax.xml.stream.XMLOutputFactory;
57  import javax.xml.stream.XMLStreamException;
58  import javax.xml.stream.XMLStreamReader;
59  import javax.xml.stream.XMLStreamWriter;
60  
61  
62  /**
63   * Parser for Atom Entries.
64   */
65  public class AtomEntryParser
66  {
67      private static final String TAG_ENTRY = "entry";
68      private static final String TAG_TITLE = "title";
69      private static final String TAG_OBJECT = "object";
70      private static final String TAG_CONTENT = "content";
71      private static final String TAG_BASE64 = "base64";
72      private static final String TAG_MEDIATYPE = "mediatype";
73      private static final String ATTR_SRC = "src";
74      private static final String ATTR_TYPE = "type";
75      protected boolean ignoreAtomContentSrc;
76      private File tempDir;
77      private int memoryThreshold;
78      private ObjectData object;
79      private ContentStreamImpl atomContentStream;
80      private ContentStreamImpl cmisContentStream;
81  
82      /**
83       * Constructor.
84       */
85      public AtomEntryParser( File tempDir, int memoryThreshold )
86      {
87          this.tempDir = tempDir;
88          this.memoryThreshold = memoryThreshold;
89      }
90  
91      /**
92       * Constructor that immediately parses the given stream.
93       */
94      public AtomEntryParser( InputStream stream, File tempDir, int memoryThreshold )
95          throws Exception
96      {
97          this( tempDir, memoryThreshold );
98          parse( stream );
99      }
100 
101     /**
102      * Sets the flag controlling whether atom content src (external content) is
103      * ignored. This flag is false by default (not ignored).
104      */
105     public void setIgnoreAtomContentSrc( boolean ignoreAtomContentSrc )
106     {
107         this.ignoreAtomContentSrc = ignoreAtomContentSrc;
108     }
109 
110     /**
111      * Returns the object.
112      */
113     public ObjectData getObject(  )
114     {
115         return object;
116     }
117 
118     /**
119      * Returns the properties of the object.
120      */
121     public Properties getProperties(  )
122     {
123         return ( ( object == null ) ? null : object.getProperties(  ) );
124     }
125 
126     /**
127      * Returns the Id of the object.
128      */
129     public String getId(  )
130     {
131         Properties properties = getProperties(  );
132 
133         if ( properties == null )
134         {
135             return null;
136         }
137 
138         Map<String, PropertyData<?>> propertiesMap = properties.getProperties(  );
139 
140         if ( propertiesMap == null )
141         {
142             return null;
143         }
144 
145         PropertyData<?> property = propertiesMap.get( PropertyIds.OBJECT_ID );
146 
147         if ( property instanceof PropertyId )
148         {
149             return ( (PropertyId) property ).getFirstValue(  );
150         }
151 
152         return null;
153     }
154 
155     /**
156      * Returns the ACL of the object.
157      */
158     public Acl getAcl(  )
159     {
160         return ( ( object == null ) ? null : object.getAcl(  ) );
161     }
162 
163     /**
164      * Returns the policy id list of the object.
165      */
166     public List<String> getPolicyIds(  )
167     {
168         if ( ( object == null ) || ( object.getPolicyIds(  ) == null ) )
169         {
170             return null;
171         }
172 
173         return object.getPolicyIds(  ).getPolicyIds(  );
174     }
175 
176     /**
177      * Returns the content stream.
178      */
179     public ContentStream getContentStream(  )
180     {
181         return ( ( cmisContentStream == null ) ? atomContentStream : cmisContentStream );
182     }
183 
184     /**
185      * Parses the stream.
186      */
187     public void parse( InputStream stream ) throws Exception
188     {
189         object = null;
190         atomContentStream = null;
191         cmisContentStream = null;
192 
193         if ( stream == null )
194         {
195             return;
196         }
197 
198         XMLInputFactory factory = XMLInputFactory.newInstance(  );
199         factory.setProperty( XMLInputFactory.IS_COALESCING, Boolean.FALSE );
200 
201         XMLStreamReader parser = factory.createXMLStreamReader( stream );
202 
203         while ( true )
204         {
205             int event = parser.getEventType(  );
206 
207             if ( event == XMLStreamReader.START_ELEMENT )
208             {
209                 QName name = parser.getName(  );
210 
211                 if ( Constants.NAMESPACE_ATOM.equals( name.getNamespaceURI(  ) ) &&
212                         ( TAG_ENTRY.equals( name.getLocalPart(  ) ) ) )
213                 {
214                     parseEntry( parser );
215 
216                     break;
217                 }
218                 else
219                 {
220                     throw new CmisInvalidArgumentException( "XML is not an Atom entry!" );
221                 }
222             }
223 
224             if ( !next( parser ) )
225             {
226                 break;
227             }
228         }
229 
230         parser.close(  );
231     }
232 
233     /**
234      * Parses an Atom entry.
235      */
236     private void parseEntry( XMLStreamReader parser ) throws Exception
237     {
238         String atomTitle = null;
239 
240         next( parser );
241 
242         // walk through all tags in entry
243         while ( true )
244         {
245             int event = parser.getEventType(  );
246 
247             if ( event == XMLStreamReader.START_ELEMENT )
248             {
249                 QName name = parser.getName(  );
250 
251                 if ( Constants.NAMESPACE_RESTATOM.equals( name.getNamespaceURI(  ) ) )
252                 {
253                     if ( TAG_OBJECT.equals( name.getLocalPart(  ) ) )
254                     {
255                         parseObject( parser );
256                     }
257                     else if ( TAG_CONTENT.equals( name.getLocalPart(  ) ) )
258                     {
259                         parseCmisContent( parser );
260                     }
261                     else
262                     {
263                         skip( parser );
264                     }
265                 }
266                 else if ( Constants.NAMESPACE_ATOM.equals( name.getNamespaceURI(  ) ) )
267                 {
268                     if ( TAG_CONTENT.equals( name.getLocalPart(  ) ) )
269                     {
270                         parseAtomContent( parser );
271                     }
272                     else if ( TAG_TITLE.equals( name.getLocalPart(  ) ) )
273                     {
274                         atomTitle = readText( parser );
275                     }
276                     else
277                     {
278                         skip( parser );
279                     }
280                 }
281                 else
282                 {
283                     skip( parser );
284                 }
285             }
286             else if ( event == XMLStreamReader.END_ELEMENT )
287             {
288                 break;
289             }
290             else
291             {
292                 if ( !next( parser ) )
293                 {
294                     break;
295                 }
296             }
297         }
298 
299         // overwrite cmis:name with Atom title
300         if ( ( object != null ) && ( object.getProperties(  ) != null ) && ( atomTitle != null ) &&
301                 ( atomTitle.length(  ) > 0 ) )
302         {
303             PropertyString nameProperty = new PropertyStringImpl( PropertyIds.NAME, atomTitle );
304             ( (PropertiesImpl) object.getProperties(  ) ).replaceProperty( nameProperty );
305         }
306     }
307 
308     /**
309      * Parses a CMIS object.
310      */
311     private void parseObject( XMLStreamReader parser )
312         throws Exception
313     {
314         Unmarshaller u = JaxBHelper.createUnmarshaller(  );
315         JAXBElement<CmisObjectType> jaxbObject = u.unmarshal( parser, CmisObjectType.class );
316 
317         if ( jaxbObject != null )
318         {
319             object = Converter.convert( jaxbObject.getValue(  ) );
320         }
321     }
322 
323     /**
324      * Extract the content stream.
325      */
326     private void parseAtomContent( XMLStreamReader parser )
327         throws Exception
328     {
329         atomContentStream = new ContentStreamImpl(  );
330 
331         // read attributes
332         String type = "text";
333 
334         for ( int i = 0; i < parser.getAttributeCount(  ); i++ )
335         {
336             QName attrName = parser.getAttributeName( i );
337 
338             if ( ATTR_TYPE.equals( attrName.getLocalPart(  ) ) )
339             {
340                 atomContentStream.setMimeType( parser.getAttributeValue( i ) );
341 
342                 if ( parser.getAttributeValue( i ) != null )
343                 {
344                     type = parser.getAttributeValue( i ).trim(  ).toLowerCase(  );
345                 }
346             }
347             else if ( ATTR_SRC.equals( attrName.getLocalPart(  ) ) )
348             {
349                 if ( ignoreAtomContentSrc )
350                 {
351                     atomContentStream = null;
352                     skip( parser );
353 
354                     return;
355                 }
356 
357                 throw new CmisNotSupportedException( "External content not supported!" );
358             }
359         }
360 
361         byte[] bytes = null;
362 
363         if ( type.equals( "text" ) || type.equals( "html" ) )
364         {
365             bytes = readText( parser ).getBytes( "UTF-8" );
366         }
367         else if ( type.equals( "xhtml" ) )
368         {
369             bytes = copy( parser );
370         }
371         else if ( type.endsWith( "/xml" ) || type.endsWith( "+xml" ) )
372         {
373             bytes = copy( parser );
374         }
375         else if ( type.startsWith( "text/" ) )
376         {
377             bytes = readText( parser ).getBytes( "UTF-8" );
378         }
379         else
380         {
381             ThresholdOutputStream ths = readBase64( parser );
382             atomContentStream.setStream( ths.getInputStream(  ) );
383             atomContentStream.setLength( BigInteger.valueOf( ths.getSize(  ) ) );
384         }
385 
386         if ( bytes != null )
387         {
388             atomContentStream.setStream( new ByteArrayInputStream( bytes ) );
389             atomContentStream.setLength( BigInteger.valueOf( bytes.length ) );
390         }
391     }
392 
393     /**
394      * Extract the content stream.
395      */
396     private void parseCmisContent( XMLStreamReader parser )
397         throws Exception
398     {
399         cmisContentStream = new ContentStreamImpl(  );
400 
401         next( parser );
402 
403         // walk through all tags in content
404         while ( true )
405         {
406             int event = parser.getEventType(  );
407 
408             if ( event == XMLStreamReader.START_ELEMENT )
409             {
410                 QName name = parser.getName(  );
411 
412                 if ( Constants.NAMESPACE_RESTATOM.equals( name.getNamespaceURI(  ) ) )
413                 {
414                     if ( TAG_MEDIATYPE.equals( name.getLocalPart(  ) ) )
415                     {
416                         cmisContentStream.setMimeType( readText( parser ) );
417                     }
418                     else if ( TAG_BASE64.equals( name.getLocalPart(  ) ) )
419                     {
420                         ThresholdOutputStream ths = readBase64( parser );
421                         cmisContentStream.setStream( ths.getInputStream(  ) );
422                         cmisContentStream.setLength( BigInteger.valueOf( ths.getSize(  ) ) );
423                     }
424                     else
425                     {
426                         skip( parser );
427                     }
428                 }
429                 else
430                 {
431                     skip( parser );
432                 }
433             }
434             else if ( event == XMLStreamReader.END_ELEMENT )
435             {
436                 break;
437             }
438             else
439             {
440                 if ( !next( parser ) )
441                 {
442                     break;
443                 }
444             }
445         }
446 
447         next( parser );
448     }
449 
450     /**
451      * Parses a tag that contains text.
452      */
453     private static String readText( XMLStreamReader parser )
454         throws Exception
455     {
456         StringBuilder sb = new StringBuilder(  );
457 
458         next( parser );
459 
460         while ( true )
461         {
462             int event = parser.getEventType(  );
463 
464             if ( event == XMLStreamReader.END_ELEMENT )
465             {
466                 break;
467             }
468             else if ( event == XMLStreamReader.CHARACTERS )
469             {
470                 String s = parser.getText(  );
471 
472                 if ( s != null )
473                 {
474                     sb.append( s );
475                 }
476             }
477             else if ( event == XMLStreamReader.START_ELEMENT )
478             {
479                 throw new RuntimeException( "Unexpected tag: " + parser.getName(  ) );
480             }
481 
482             if ( !next( parser ) )
483             {
484                 break;
485             }
486         }
487 
488         next( parser );
489 
490         return sb.toString(  );
491     }
492 
493     /**
494      * Parses a tag that contains base64 encoded content.
495      */
496     private ThresholdOutputStream readBase64( XMLStreamReader parser )
497         throws Exception
498     {
499         ThresholdOutputStream bufferStream = new ThresholdOutputStream( tempDir, memoryThreshold );
500         Base64.OutputStream b64stream = new Base64.OutputStream( bufferStream, Base64.DECODE );
501 
502         next( parser );
503 
504         try
505         {
506             while ( true )
507             {
508                 int event = parser.getEventType(  );
509 
510                 if ( event == XMLStreamReader.END_ELEMENT )
511                 {
512                     break;
513                 }
514                 else if ( event == XMLStreamReader.CHARACTERS )
515                 {
516                     String s = parser.getText(  );
517 
518                     if ( s != null )
519                     {
520                         b64stream.write( s.getBytes( "US-ASCII" ) );
521                     }
522                 }
523                 else if ( event == XMLStreamReader.START_ELEMENT )
524                 {
525                     throw new RuntimeException( "Unexpected tag: " + parser.getName(  ) );
526                 }
527 
528                 if ( !next( parser ) )
529                 {
530                     break;
531                 }
532             }
533 
534             b64stream.close(  );
535         }
536         catch ( Exception e )
537         {
538             bufferStream.destroy(  ); // remove temp file
539             throw e;
540         }
541 
542         next( parser );
543 
544         return bufferStream;
545     }
546 
547     /**
548      * Copies a subtree into a stream.
549      */
550     private static byte[] copy( XMLStreamReader parser )
551         throws Exception
552     {
553         // create a writer
554         ByteArrayOutputStream out = new ByteArrayOutputStream(  );
555         XMLStreamWriter writer = XMLOutputFactory.newInstance(  ).createXMLStreamWriter( out );
556 
557         writer.writeStartDocument(  );
558 
559         // copy subtree
560         int level = 1;
561 
562         while ( next( parser ) )
563         {
564             int event = parser.getEventType(  );
565 
566             if ( event == XMLStreamReader.START_ELEMENT )
567             {
568                 copyStartElement( parser, writer );
569                 level++;
570             }
571             else if ( event == XMLStreamReader.CHARACTERS )
572             {
573                 writer.writeCharacters( parser.getText(  ) );
574             }
575             else if ( event == XMLStreamReader.COMMENT )
576             {
577                 writer.writeComment( parser.getText(  ) );
578             }
579             else if ( event == XMLStreamReader.CDATA )
580             {
581                 writer.writeCData( parser.getText(  ) );
582             }
583             else if ( event == XMLStreamReader.END_ELEMENT )
584             {
585                 level--;
586 
587                 if ( level == 0 )
588                 {
589                     break;
590                 }
591 
592                 writer.writeEndElement(  );
593             }
594             else
595             {
596                 break;
597             }
598         }
599 
600         writer.writeEndDocument(  );
601 
602         next( parser );
603 
604         return out.toByteArray(  );
605     }
606 
607     /**
608      * Copies a XML start element.
609      */
610     private static void copyStartElement( XMLStreamReader parser, XMLStreamWriter writer )
611         throws Exception
612     {
613         String namespaceUri = parser.getNamespaceURI(  );
614         String prefix = parser.getPrefix(  );
615         String localName = parser.getLocalName(  );
616 
617         // write start element
618         if ( namespaceUri != null )
619         {
620             if ( ( prefix == null ) || ( prefix.length(  ) == 0 ) )
621             {
622                 writer.writeStartElement( localName );
623             }
624             else
625             {
626                 writer.writeStartElement( prefix, localName, namespaceUri );
627             }
628         }
629         else
630         {
631             writer.writeStartElement( localName );
632         }
633 
634         // set namespaces
635         for ( int i = 0; i < parser.getNamespaceCount(  ); i++ )
636         {
637             addNamespace( writer, parser.getNamespacePrefix( i ), parser.getNamespaceURI( i ) );
638         }
639 
640         addNamespaceIfMissing( writer, prefix, namespaceUri );
641 
642         // write attributes
643         for ( int i = 0; i < parser.getAttributeCount(  ); i++ )
644         {
645             String attrNamespaceUri = parser.getAttributeNamespace( i );
646             String attrPrefix = parser.getAttributePrefix( i );
647             String attrName = parser.getAttributeLocalName( i );
648             String attrValue = parser.getAttributeValue( i );
649 
650             if ( ( attrNamespaceUri == null ) || ( attrNamespaceUri.trim(  ).length(  ) == 0 ) )
651             {
652                 writer.writeAttribute( attrName, attrValue );
653             }
654             else if ( ( attrPrefix == null ) || ( attrPrefix.trim(  ).length(  ) == 0 ) )
655             {
656                 writer.writeAttribute( attrNamespaceUri, attrName, attrValue );
657             }
658             else
659             {
660                 addNamespaceIfMissing( writer, attrPrefix, attrNamespaceUri );
661                 writer.writeAttribute( attrPrefix, attrNamespaceUri, attrName, attrValue );
662             }
663         }
664     }
665 
666     /**
667      * Checks if the given prefix is assigned to the given namespace.
668      */
669     @SuppressWarnings( "unchecked" )
670     private static void addNamespaceIfMissing( XMLStreamWriter writer, String prefix, String namespaceUri )
671         throws Exception
672     {
673         if ( ( namespaceUri == null ) || ( namespaceUri.trim(  ).length(  ) == 0 ) )
674         {
675             return;
676         }
677 
678         if ( prefix == null )
679         {
680             prefix = "";
681         }
682 
683         Iterator<String> iter = (Iterator<String>) writer.getNamespaceContext(  ).getPrefixes( namespaceUri );
684 
685         if ( iter == null )
686         {
687             return;
688         }
689 
690         while ( iter.hasNext(  ) )
691         {
692             String p = iter.next(  );
693 
694             if ( ( p != null ) && ( p.equals( prefix ) ) )
695             {
696                 return;
697             }
698         }
699 
700         addNamespace( writer, prefix, namespaceUri );
701     }
702 
703     /**
704      * Adds a namespace to a XML element.
705      */
706     private static void addNamespace( XMLStreamWriter writer, String prefix, String namespaceUri )
707         throws Exception
708     {
709         if ( ( prefix == null ) || ( prefix.trim(  ).length(  ) == 0 ) )
710         {
711             writer.setDefaultNamespace( namespaceUri );
712             writer.writeDefaultNamespace( namespaceUri );
713         }
714         else
715         {
716             writer.setPrefix( prefix, namespaceUri );
717             writer.writeNamespace( prefix, namespaceUri );
718         }
719     }
720 
721     /**
722      * Skips a tag or subtree.
723      */
724     private static void skip( XMLStreamReader parser )
725         throws Exception
726     {
727         int level = 1;
728 
729         while ( next( parser ) )
730         {
731             int event = parser.getEventType(  );
732 
733             if ( event == XMLStreamReader.START_ELEMENT )
734             {
735                 level++;
736             }
737             else if ( event == XMLStreamReader.END_ELEMENT )
738             {
739                 level--;
740 
741                 if ( level == 0 )
742                 {
743                     break;
744                 }
745             }
746         }
747 
748         next( parser );
749     }
750 
751     private static boolean next( XMLStreamReader parser )
752         throws Exception
753     {
754         if ( parser.hasNext(  ) )
755         {
756             try
757             {
758                 parser.next(  );
759             }
760             catch ( XMLStreamException e )
761             {
762                 return false;
763             }
764 
765             return true;
766         }
767 
768         return false;
769     }
770 }