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 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
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
84
85 public AtomEntryParser( File tempDir, int memoryThreshold )
86 {
87 this.tempDir = tempDir;
88 this.memoryThreshold = memoryThreshold;
89 }
90
91
92
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
103
104
105 public void setIgnoreAtomContentSrc( boolean ignoreAtomContentSrc )
106 {
107 this.ignoreAtomContentSrc = ignoreAtomContentSrc;
108 }
109
110
111
112
113 public ObjectData getObject( )
114 {
115 return object;
116 }
117
118
119
120
121 public Properties getProperties( )
122 {
123 return ( ( object == null ) ? null : object.getProperties( ) );
124 }
125
126
127
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
157
158 public Acl getAcl( )
159 {
160 return ( ( object == null ) ? null : object.getAcl( ) );
161 }
162
163
164
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
178
179 public ContentStream getContentStream( )
180 {
181 return ( ( cmisContentStream == null ) ? atomContentStream : cmisContentStream );
182 }
183
184
185
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
235
236 private void parseEntry( XMLStreamReader parser ) throws Exception
237 {
238 String atomTitle = null;
239
240 next( parser );
241
242
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
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
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
325
326 private void parseAtomContent( XMLStreamReader parser )
327 throws Exception
328 {
329 atomContentStream = new ContentStreamImpl( );
330
331
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
395
396 private void parseCmisContent( XMLStreamReader parser )
397 throws Exception
398 {
399 cmisContentStream = new ContentStreamImpl( );
400
401 next( parser );
402
403
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
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
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( );
539 throw e;
540 }
541
542 next( parser );
543
544 return bufferStream;
545 }
546
547
548
549
550 private static byte[] copy( XMLStreamReader parser )
551 throws Exception
552 {
553
554 ByteArrayOutputStream out = new ByteArrayOutputStream( );
555 XMLStreamWriter writer = XMLOutputFactory.newInstance( ).createXMLStreamWriter( out );
556
557 writer.writeStartDocument( );
558
559
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
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
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
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
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
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
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
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 }