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
20 package org.apache.axiom.om.impl.llom;
21
22
23 import org.apache.axiom.om.OMAttribute;
24 import org.apache.axiom.om.OMConstants;
25 import org.apache.axiom.om.OMContainer;
26 import org.apache.axiom.om.OMElement;
27 import org.apache.axiom.om.OMException;
28 import org.apache.axiom.om.OMFactory;
29 import org.apache.axiom.om.OMNamespace;
30 import org.apache.axiom.om.OMText;
31 import org.apache.axiom.om.OMXMLParserWrapper;
32 import org.apache.axiom.om.impl.MTOMXMLStreamWriter;
33 import org.apache.axiom.om.impl.OMNamespaceImpl;
34 import org.apache.axiom.om.impl.builder.XOPBuilder;
35 import org.apache.axiom.om.impl.util.OMSerializerUtil;
36 import org.apache.axiom.om.util.TextHelper;
37 import org.apache.axiom.om.util.UUIDGenerator;
38
39 import javax.xml.namespace.QName;
40 import javax.xml.stream.XMLStreamException;
41 import javax.xml.stream.XMLStreamWriter;
42 import java.io.IOException;
43 import java.io.InputStream;
44
45 public class OMTextImpl extends OMNodeImpl implements OMText, OMConstants {
46 /** Field nameSpace used when serializing Binary stuff as MTOM optimized. */
47 public static final OMNamespace XOP_NS = new OMNamespaceImpl(
48 "http://www.w3.org/2004/08/xop/include", "xop");
49
50 protected String value = null;
51 protected char[] charArray;
52
53 private boolean calcNS; // Set to true after textNS is calculated
54 protected OMNamespace textNS;
55
56 protected String mimeType;
57
58 protected boolean optimize = false;
59
60 protected boolean isBinary = false;
61
62 /** Field contentID for the mime part used when serializing Binary stuff as MTOM optimized. */
63 private String contentID = null;
64
65 /**
66 * Field dataHandler contains the DataHandler Declaring as Object to remove the dependency on
67 * Javax.activation.DataHandler
68 */
69 private Object dataHandlerObject = null;
70
71 /** Field localName used when serializing Binary stuff as MTOM optimized. */
72 protected String localName = "Include";
73
74 /** Field attributes used when serializing Binary stuff as MTOM optimized. */
75 protected OMAttribute attribute;
76 private static final String EMTPY_STRING = "";
77
78 /**
79 * Constructor OMTextImpl.
80 *
81 * @param s
82 */
83 public OMTextImpl(String s, OMFactory factory) {
84 this(s, TEXT_NODE, factory);
85 }
86
87 /**
88 * @param s
89 * @param nodeType - OMText can handle CHARACTERS, SPACES, CDATA and ENTITY REFERENCES.
90 * Constants for this can be found in OMNode.
91 */
92 public OMTextImpl(String s, int nodeType, OMFactory factory) {
93 this(null, s, nodeType, factory);
94 }
95
96 /**
97 * Constructor OMTextImpl.
98 *
99 * @param parent
100 * @param text
101 */
102 public OMTextImpl(OMContainer parent, String text, OMFactory factory) {
103 this(parent, text, TEXT_NODE, factory);
104 }
105
106 /**
107 * Construct OMTextImpl that is a copy of the source OMTextImpl
108 * @param parent
109 * @param source OMTextImpl
110 * @param factory
111 */
112 public OMTextImpl(OMContainer parent, OMTextImpl source, OMFactory factory) {
113 super(parent, factory, true);
114 // Copy the value of the text
115 this.value = source.value;
116 this.nodeType = source.nodeType;
117
118 // Clone the charArray (if it exists)
119 if (source.charArray != null) {
120 this.charArray = new char[source.charArray.length];
121 System.arraycopy(source.charArray, 0, this.charArray, 0, source.charArray.length);
122 }
123
124 // Turn off calcNS...the namespace will need to be recalculated
125 // in the new tree's context.
126 this.calcNS = false;
127 this.textNS = null;
128
129 // Copy the optimized related settings.
130 this.optimize = source.optimize;
131 this.mimeType = source.mimeType;
132 this.isBinary = source.isBinary;
133
134 // TODO
135 // Do we need a deep copy of the data-handler
136 this.contentID = source.contentID;
137 this.dataHandlerObject = source.dataHandlerObject;
138
139 this.localName = source.localName;
140 if (source.attribute != null) {
141 this.attribute = factory.createOMAttribute(source.attribute.getLocalName(),
142 source.attribute.getNamespace(),
143 source.attribute.getAttributeValue());
144 }
145 }
146
147 public OMTextImpl(OMContainer parent, String text, int nodeType,
148 OMFactory factory) {
149 super(parent, factory, true);
150 this.value = text == null ? EMTPY_STRING : text;
151 this.nodeType = nodeType;
152 }
153
154 public OMTextImpl(OMContainer parent, char[] charArray, int nodeType,
155 OMFactory factory) {
156 super(parent, factory, true);
157 this.charArray = charArray;
158 this.nodeType = nodeType;
159 }
160
161
162 public OMTextImpl(OMContainer parent, QName text, OMFactory factory) {
163 this(parent, text, TEXT_NODE, factory);
164 }
165
166 public OMTextImpl(OMContainer parent, QName text, int nodeType,
167 OMFactory factory) {
168 super(parent, factory, true);
169 if (text == null) throw new IllegalArgumentException("QName text arg cannot be null!");
170 this.calcNS = true;
171 this.textNS =
172 ((OMElementImpl) parent).handleNamespace(text.getNamespaceURI(), text.getPrefix());
173 this.value = textNS.getPrefix() + ":" + text.getLocalPart();
174 this.nodeType = nodeType;
175 }
176
177 /**
178 * @param s - base64 encoded String representation of Binary
179 * @param mimeType of the Binary
180 */
181 public OMTextImpl(String s, String mimeType, boolean optimize,
182 OMFactory factory) {
183 this(null, s, mimeType, optimize, factory);
184 }
185
186 /**
187 * @param parent
188 * @param s - base64 encoded String representation of Binary
189 * @param mimeType of the Binary
190 */
191 public OMTextImpl(OMContainer parent, String s, String mimeType,
192 boolean optimize, OMFactory factory) {
193 this(parent, s, factory);
194 this.mimeType = mimeType;
195 this.optimize = optimize;
196 this.isBinary = true;
197 done = true;
198 this.nodeType = TEXT_NODE;
199 }
200
201 /** @param dataHandler To send binary optimised content Created programatically. */
202 public OMTextImpl(Object dataHandler, OMFactory factory) {
203 this(dataHandler, true, factory);
204 }
205
206 /**
207 * @param dataHandler
208 * @param optimize To send binary content. Created progrmatically.
209 */
210 public OMTextImpl(Object dataHandler, boolean optimize, OMFactory factory) {
211 super(factory);
212 this.dataHandlerObject = dataHandler;
213 this.isBinary = true;
214 this.optimize = optimize;
215 done = true;
216 this.nodeType = TEXT_NODE;
217 }
218
219 /**
220 * @param contentID
221 * @param parent
222 * @param builder Used when the builder is encountered with a XOP:Include tag Stores a
223 * reference to the builder and the content-id. Supports deferred parsing of
224 * MIME messages.
225 */
226 public OMTextImpl(String contentID, OMContainer parent,
227 OMXMLParserWrapper builder, OMFactory factory) {
228 super(parent, factory, false);
229 this.contentID = contentID;
230 this.optimize = true;
231 this.isBinary = true;
232 this.builder = builder;
233 this.nodeType = TEXT_NODE;
234 }
235
236 /**
237 * @param writer
238 * @throws XMLStreamException
239 */
240 public void internalSerialize(XMLStreamWriter writer) throws XMLStreamException {
241 internalSerializeLocal(writer);
242 }
243
244 /**
245 * Writes the relevant output.
246 *
247 * @param writer
248 * @throws XMLStreamException
249 */
250 private void writeOutput(XMLStreamWriter writer) throws XMLStreamException {
251 int type = getType();
252 if (type == TEXT_NODE || type == SPACE_NODE) {
253 writer.writeCharacters(this.getText());
254 } else if (type == CDATA_SECTION_NODE) {
255 writer.writeCData(this.getText());
256 } else if (type == ENTITY_REFERENCE_NODE) {
257 writer.writeEntityRef(this.getText());
258 }
259 }
260
261 /** Returns the value. */
262 public String getText() throws OMException {
263 if (charArray != null || this.value != null) {
264 return getTextFromProperPlace();
265 } else {
266 try {
267 return TextHelper.toString(getInputStream());
268 } catch (Exception e) {
269 throw new OMException(e);
270 }
271 }
272 }
273
274 public char[] getTextCharacters() {
275 return charArray != null ? charArray : value.toCharArray();
276 }
277
278 public boolean isCharacters() {
279 return charArray != null;
280 }
281
282 /**
283 * This OMText contains two data source:value and charArray. This method will return text from
284 * correct place.
285 */
286 private String getTextFromProperPlace() {
287 return charArray != null ? new String(charArray) : value;
288 }
289
290
291 /** Returns the value. */
292 public QName getTextAsQName() throws OMException {
293 return ((OMElement)parent).resolveQName(getTextFromProperPlace());
294 }
295
296 /* (non-Javadoc)
297 * @see org.apache.axiom.om.OMText#getNamespace()
298 */
299 public OMNamespace getNamespace() {
300 // If the namespace has already been determined, return it
301 // Otherwise calculate the namespace if the text contains a colon and is not detached.
302 if (calcNS) {
303 return textNS;
304 } else {
305 calcNS = true;
306 if (getParent() != null) {
307 String text = getTextFromProperPlace();
308 if (text != null) {
309 int colon = text.indexOf(':');
310 if (colon > 0) {
311 textNS = ((OMElementImpl) getParent()).
312 findNamespaceURI(text.substring(0, colon));
313 if (textNS != null) {
314 charArray = null;
315 value = text.substring(colon + 1);
316 }
317 }
318 }
319 }
320 }
321 return textNS;
322 }
323
324 public boolean isOptimized() {
325 return optimize;
326 }
327
328 public void setOptimize(boolean value) {
329 this.optimize = value;
330 if (value) {
331 isBinary = true;
332 }
333 }
334
335 /**
336 * Receiving binary can happen as either MTOM attachments or as Base64 Text In the case of
337 * Base64 user has to explicitly specify that the content is binary, before calling
338 * getDataHandler(), getInputStream()....
339 */
340 public void setBinary(boolean value) {
341 isBinary = value;
342 }
343
344 public boolean isBinary() {
345 return isBinary;
346 }
347
348
349 /**
350 * Gets the datahandler.
351 *
352 * @return Returns javax.activation.DataHandler
353 */
354 public Object getDataHandler() {
355 if ((value != null || charArray != null) && isBinary) {
356 String text = getTextFromProperPlace();
357 return org.apache.axiom.attachments.utils.DataHandlerUtils
358 .getDataHandlerFromText(text, mimeType);
359 } else {
360
361 if (dataHandlerObject == null) {
362 if (contentID == null) {
363 throw new RuntimeException("ContentID is null");
364 }
365 dataHandlerObject = ((XOPBuilder) builder)
366 .getDataHandler(contentID);
367 }
368 return dataHandlerObject;
369 }
370 }
371
372 public String getLocalName() {
373 return localName;
374 }
375
376 public java.io.InputStream getInputStream() throws OMException {
377 if (isBinary) {
378 if (dataHandlerObject == null) {
379 getDataHandler();
380 }
381 InputStream inStream;
382 javax.activation.DataHandler dataHandler =
383 (javax.activation.DataHandler) dataHandlerObject;
384 try {
385 inStream = dataHandler.getDataSource().getInputStream();
386 } catch (IOException e) {
387 throw new OMException(
388 "Cannot get InputStream from DataHandler." + e);
389 }
390 return inStream;
391 } else {
392 throw new OMException("Unsupported Operation");
393 }
394 }
395
396 public String getContentID() {
397 if (contentID == null) {
398 contentID = UUIDGenerator.getUUID()
399 + "@apache.org";
400 }
401 return this.contentID;
402 }
403
404 public void internalSerializeAndConsume(XMLStreamWriter writer)
405 throws XMLStreamException {
406 internalSerializeLocal(writer);
407 }
408
409 private void internalSerializeLocal(XMLStreamWriter writer2) throws XMLStreamException {
410
411 if ((!this.isBinary) || (!this.isOptimized())) {
412 writeOutput(writer2);
413 } else {
414 //check whether we have a MTOMXMLStreamWriter. if so
415 //we can optimize the writing!
416 if (writer2 instanceof MTOMXMLStreamWriter) {
417 MTOMXMLStreamWriter writer = (MTOMXMLStreamWriter) writer2;
418 if (writer.isOptimized() && writer.isOptimizedThreshold(this)) {
419 if (contentID == null) {
420 contentID = writer.getNextContentId();
421 }
422 // send binary as MTOM optimised
423 this.attribute = new OMAttributeImpl("href",
424 new OMNamespaceImpl("", ""),
425 "cid:" + getContentID(), this.factory);
426 this.serializeStartpart(writer);
427 writer.writeOptimized(this);
428 writer.writeEndElement();
429 } else {
430 //do normal base64
431 writeOutput(writer);
432 }
433 } else {
434 //we do not have a optimized writer. Just do the normal
435 //base64 writing
436 writeOutput(writer2);
437 }
438
439 }
440 }
441
442 /*
443 * Methods to copy from OMSerialize utils
444 */
445 private void serializeStartpart(XMLStreamWriter writer)
446 throws XMLStreamException {
447 String nameSpaceName = XOP_NS.getNamespaceURI();
448 String writer_prefix = writer.getPrefix(nameSpaceName);
449 String prefix = XOP_NS.getPrefix();
450 if (writer_prefix != null) {
451 writer.writeStartElement(nameSpaceName, this
452 .getLocalName());
453 } else {
454 // According to StAX, setPrefix must occur before
455 // writeStartElement
456 if (OMSerializerUtil.isSetPrefixBeforeStartElement(writer)) {
457 writer.setPrefix(prefix, nameSpaceName);
458 writer.writeStartElement(prefix, this.getLocalName(),
459 nameSpaceName);
460 } else {
461 writer.writeStartElement(prefix, this.getLocalName(),
462 nameSpaceName);
463 writer.setPrefix(prefix, nameSpaceName);
464 }
465 }
466 // add the elements attribute "href"
467 serializeAttribute(this.attribute, writer);
468 // add the namespace
469 serializeNamespace(XOP_NS, writer);
470 }
471
472 /**
473 * Method serializeAttribute.
474 *
475 * @param attr
476 * @throws XMLStreamException
477 */
478 static void serializeAttribute(OMAttribute attr, XMLStreamWriter writer)
479 throws XMLStreamException {
480 // first check whether the attribute is associated with a namespace
481 OMNamespace ns = attr.getNamespace();
482 String prefix;
483 String namespaceName;
484 if (ns != null) {
485 // add the prefix if it's availble
486 prefix = ns.getPrefix();
487 namespaceName = ns.getNamespaceURI();
488 if (prefix != null) {
489 writer.writeAttribute(prefix, namespaceName, attr
490 .getLocalName(), attr.getAttributeValue());
491 } else {
492 writer.writeAttribute(namespaceName, attr.getLocalName(), attr
493 .getAttributeValue());
494 }
495 } else {
496 writer.writeAttribute(attr.getLocalName(), attr.getAttributeValue());
497 }
498 }
499
500 /**
501 * Method serializeNamespace.
502 *
503 * @param namespace
504 * @param writer
505 * @throws XMLStreamException
506 */
507 static void serializeNamespace(OMNamespace namespace, XMLStreamWriter writer)
508 throws XMLStreamException {
509 if (namespace != null) {
510 String uri = namespace.getNamespaceURI();
511 String ns_prefix = namespace.getPrefix();
512 writer.writeNamespace(ns_prefix, namespace.getNamespaceURI());
513 writer.setPrefix(ns_prefix, uri);
514 }
515 }
516
517 /**
518 * A slightly different implementation of the discard method.
519 *
520 * @throws OMException
521 */
522 public void discard() throws OMException {
523 if (done) {
524 this.detach();
525 }
526 }
527
528 /* (non-Javadoc)
529 * @see org.apache.axiom.om.OMNode#buildAll()
530 */
531 public void buildWithAttachments() {
532 if (!this.done) {
533 this.build();
534 }
535 if (isOptimized()) {
536 this.getDataHandler();
537 }
538 }
539
540 public void setContentID(String cid) {
541 this.contentID = cid;
542 }
543
544 }