001 /*
002 * Copyright 2012 UnboundID Corp.
003 *
004 * This program is free software; you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License (GPLv2 only)
006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
007 * as published by the Free Software Foundation.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program; if not, see <http://www.gnu.org/licenses>.
016 */
017
018 package com.unboundid.scim.marshal.xml;
019
020 import com.unboundid.scim.data.BaseResource;
021 import com.unboundid.scim.marshal.StreamMarshaller;
022 import com.unboundid.scim.schema.AttributeDescriptor;
023 import com.unboundid.scim.sdk.BulkOperation;
024 import com.unboundid.scim.sdk.Debug;
025 import com.unboundid.scim.sdk.Resources;
026 import com.unboundid.scim.sdk.SCIMAttribute;
027 import com.unboundid.scim.sdk.SCIMAttributeValue;
028 import com.unboundid.scim.sdk.SCIMConstants;
029 import com.unboundid.scim.sdk.SCIMException;
030 import com.unboundid.scim.sdk.ServerErrorException;
031
032 import javax.xml.XMLConstants;
033 import javax.xml.stream.XMLOutputFactory;
034 import javax.xml.stream.XMLStreamException;
035 import javax.xml.stream.XMLStreamWriter;
036 import java.io.IOException;
037 import java.io.OutputStream;
038 import java.util.Collections;
039 import java.util.List;
040 import java.util.Set;
041
042
043
044 /**
045 * This class provides a stream marshaller implementation to write a stream of
046 * SCIM objects to their XML representation.
047 */
048 public class XmlStreamMarshaller implements StreamMarshaller
049 {
050 private static final String xsiURI =
051 XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI;
052
053 private final OutputStream outputStream;
054 private final XMLStreamWriter xmlStreamWriter;
055
056
057
058 /**
059 * Create a new XML marshaller that writes to the provided output stream.
060 * The resulting marshaller must be closed after use.
061 *
062 * @param outputStream The output stream to be written by this marshaller.
063 *
064 * @throws SCIMException If the marshaller could not be created.
065 */
066 public XmlStreamMarshaller(final OutputStream outputStream)
067 throws SCIMException
068 {
069 this.outputStream = outputStream;
070
071 try
072 {
073 final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
074 xmlStreamWriter =
075 outputFactory.createXMLStreamWriter(outputStream, "UTF-8");
076 }
077 catch (Exception e)
078 {
079 Debug.debugException(e);
080 throw new ServerErrorException(
081 "Cannot create XML marshaller: " + e.getMessage());
082 }
083 }
084
085
086
087 /**
088 * {@inheritDoc}
089 */
090 @Override
091 public void close() throws SCIMException
092 {
093 try
094 {
095 xmlStreamWriter.close();
096 }
097 catch (XMLStreamException e)
098 {
099 Debug.debugException(e);
100 }
101
102 try
103 {
104 outputStream.close();
105 }
106 catch (IOException e)
107 {
108 Debug.debugException(e);
109 }
110 }
111
112
113
114 /**
115 * {@inheritDoc}
116 */
117 public void marshal(final BaseResource resource)
118 throws SCIMException
119 {
120 try
121 {
122 xmlStreamWriter.writeStartDocument("UTF-8", "1.0");
123 xmlStreamWriter.setDefaultNamespace(SCIMConstants.SCHEMA_URI_CORE);
124
125 final String resourceSchemaURI =
126 resource.getResourceDescriptor().getSchema();
127
128 xmlStreamWriter.writeStartElement(
129 SCIMConstants.DEFAULT_SCHEMA_PREFIX,
130 resource.getResourceDescriptor().getName(), resourceSchemaURI);
131 marshal(resource, xmlStreamWriter, null);
132 xmlStreamWriter.writeEndElement();
133
134 xmlStreamWriter.writeEndDocument();
135 }
136 catch (XMLStreamException e)
137 {
138 Debug.debugException(e);
139 throw new ServerErrorException(
140 "Cannot write resource: " + e.getMessage());
141 }
142 }
143
144
145
146 /**
147 * {@inheritDoc}
148 */
149 public void marshal(final SCIMException response)
150 throws SCIMException
151 {
152 try
153 {
154 xmlStreamWriter.writeStartDocument("UTF-8", "1.0");
155
156 xmlStreamWriter.setPrefix(SCIMConstants.DEFAULT_SCHEMA_PREFIX,
157 SCIMConstants.SCHEMA_URI_CORE);
158 xmlStreamWriter.setPrefix("xsi", xsiURI);
159 xmlStreamWriter.writeStartElement(SCIMConstants.SCHEMA_URI_CORE,
160 "Response");
161 xmlStreamWriter.writeNamespace(SCIMConstants.DEFAULT_SCHEMA_PREFIX,
162 SCIMConstants.SCHEMA_URI_CORE);
163 xmlStreamWriter.writeNamespace("xsi", xsiURI);
164
165 xmlStreamWriter.writeStartElement(
166 SCIMConstants.SCHEMA_URI_CORE, "Errors");
167
168 xmlStreamWriter.writeStartElement(
169 SCIMConstants.SCHEMA_URI_CORE, "Error");
170
171 xmlStreamWriter.writeStartElement(
172 SCIMConstants.SCHEMA_URI_CORE, "code");
173 xmlStreamWriter.writeCharacters(String.valueOf(response.getStatusCode()));
174 xmlStreamWriter.writeEndElement();
175
176 final String description = response.getMessage();
177 if (description != null)
178 {
179 xmlStreamWriter.writeStartElement(
180 SCIMConstants.SCHEMA_URI_CORE, "description");
181 xmlStreamWriter.writeCharacters(description);
182 xmlStreamWriter.writeEndElement();
183 }
184
185 xmlStreamWriter.writeEndElement();
186 xmlStreamWriter.writeEndElement();
187 xmlStreamWriter.writeEndElement();
188 xmlStreamWriter.writeEndDocument();
189 }
190 catch (XMLStreamException e)
191 {
192 Debug.debugException(e);
193 throw new ServerErrorException(
194 "Cannot write error response: " + e.getMessage());
195 }
196 }
197
198
199
200 /**
201 * {@inheritDoc}
202 */
203 public void marshal(final Resources<? extends BaseResource> response)
204 throws SCIMException
205 {
206 try
207 {
208 xmlStreamWriter.writeStartDocument("UTF-8", "1.0");
209
210 xmlStreamWriter.setPrefix(SCIMConstants.DEFAULT_SCHEMA_PREFIX,
211 SCIMConstants.SCHEMA_URI_CORE);
212 xmlStreamWriter.setPrefix("xsi", xsiURI);
213 xmlStreamWriter.writeStartElement(SCIMConstants.SCHEMA_URI_CORE,
214 "Response");
215 xmlStreamWriter.writeNamespace(SCIMConstants.DEFAULT_SCHEMA_PREFIX,
216 SCIMConstants.SCHEMA_URI_CORE);
217 xmlStreamWriter.writeNamespace("xsi", xsiURI);
218
219 xmlStreamWriter.writeStartElement("totalResults");
220 xmlStreamWriter.writeCharacters(
221 Long.toString(response.getTotalResults()));
222 xmlStreamWriter.writeEndElement();
223
224 xmlStreamWriter.writeStartElement("itemsPerPage");
225 xmlStreamWriter.writeCharacters(
226 Integer.toString(response.getItemsPerPage()));
227 xmlStreamWriter.writeEndElement();
228
229 xmlStreamWriter.writeStartElement("startIndex");
230 xmlStreamWriter.writeCharacters(
231 Long.toString(response.getStartIndex()));
232 xmlStreamWriter.writeEndElement();
233
234 xmlStreamWriter.writeStartElement("Resources");
235
236 for (final BaseResource resource : response)
237 {
238 xmlStreamWriter.writeStartElement("Resource");
239 marshal(resource, xmlStreamWriter, xsiURI);
240 xmlStreamWriter.writeEndElement();
241 }
242
243 xmlStreamWriter.writeEndElement();
244
245 xmlStreamWriter.writeEndElement();
246 xmlStreamWriter.writeEndDocument();
247 }
248 catch (XMLStreamException e)
249 {
250 Debug.debugException(e);
251 throw new ServerErrorException(
252 "Cannot write resources: " + e.getMessage());
253 }
254 }
255
256
257
258 /**
259 * {@inheritDoc}
260 */
261 public void writeBulkStart(final int failOnErrors,
262 final Set<String> schemaURIs)
263 throws SCIMException
264 {
265 try
266 {
267 xmlStreamWriter.writeStartDocument("UTF-8", "1.0");
268
269 xmlStreamWriter.setPrefix(SCIMConstants.DEFAULT_SCHEMA_PREFIX,
270 SCIMConstants.SCHEMA_URI_CORE);
271 xmlStreamWriter.setPrefix("xsi", xsiURI);
272 xmlStreamWriter.writeStartElement(SCIMConstants.SCHEMA_URI_CORE,
273 "Bulk");
274 xmlStreamWriter.writeNamespace(SCIMConstants.DEFAULT_SCHEMA_PREFIX,
275 SCIMConstants.SCHEMA_URI_CORE);
276 xmlStreamWriter.writeNamespace("xsi", xsiURI);
277
278 if (failOnErrors >= 0)
279 {
280 xmlStreamWriter.writeStartElement("failOnErrors");
281 xmlStreamWriter.writeCharacters(
282 Integer.toString(failOnErrors));
283 xmlStreamWriter.writeEndElement();
284 }
285
286 xmlStreamWriter.writeStartElement("Operations");
287 }
288 catch (XMLStreamException e)
289 {
290 Debug.debugException(e);
291 throw new ServerErrorException(
292 "Cannot write start of bulk operations: " + e.getMessage());
293 }
294 }
295
296
297
298 /**
299 * {@inheritDoc}
300 */
301 public void writeBulkOperation(final BulkOperation o)
302 throws SCIMException
303 {
304 try
305 {
306 xmlStreamWriter.writeStartElement("Operation");
307 if (o.getMethod() != null)
308 {
309 xmlStreamWriter.writeStartElement("method");
310 xmlStreamWriter.writeCharacters(o.getMethod().toString());
311 xmlStreamWriter.writeEndElement();
312 }
313 if (o.getBulkId() != null)
314 {
315 xmlStreamWriter.writeStartElement("bulkId");
316 xmlStreamWriter.writeCharacters(o.getBulkId());
317 xmlStreamWriter.writeEndElement();
318 }
319 if (o.getVersion() != null)
320 {
321 xmlStreamWriter.writeStartElement("version");
322 xmlStreamWriter.writeCharacters(o.getVersion());
323 xmlStreamWriter.writeEndElement();
324 }
325 if (o.getPath() != null)
326 {
327 xmlStreamWriter.writeStartElement("path");
328 xmlStreamWriter.writeCharacters(o.getPath());
329 xmlStreamWriter.writeEndElement();
330 }
331 if (o.getLocation() != null)
332 {
333 xmlStreamWriter.writeStartElement("location");
334 xmlStreamWriter.writeCharacters(o.getLocation());
335 xmlStreamWriter.writeEndElement();
336 }
337 if (o.getData() != null)
338 {
339 xmlStreamWriter.writeStartElement("data");
340 marshal(o.getData(), xmlStreamWriter, xsiURI);
341 xmlStreamWriter.writeEndElement();
342 }
343 if (o.getStatus() != null)
344 {
345 xmlStreamWriter.writeStartElement("status");
346 xmlStreamWriter.writeStartElement("code");
347 xmlStreamWriter.writeCharacters(o.getStatus().getCode());
348 xmlStreamWriter.writeEndElement();
349 if (o.getStatus().getDescription() != null)
350 {
351 xmlStreamWriter.writeStartElement("description");
352 xmlStreamWriter.writeCharacters(o.getStatus().getDescription());
353 xmlStreamWriter.writeEndElement();
354 }
355 xmlStreamWriter.writeEndElement();
356 }
357 xmlStreamWriter.writeEndElement();
358 }
359 catch (XMLStreamException e)
360 {
361 Debug.debugException(e);
362 throw new ServerErrorException(
363 "Cannot write bulk operation: " + e.getMessage());
364 }
365 }
366
367
368 /**
369 * {@inheritDoc}
370 */
371 public void writeBulkFinish()
372 throws SCIMException
373 {
374 try
375 {
376 xmlStreamWriter.writeEndElement();
377 xmlStreamWriter.writeEndElement();
378 xmlStreamWriter.writeEndDocument();
379 }
380 catch (XMLStreamException e)
381 {
382 Debug.debugException(e);
383 throw new ServerErrorException(
384 "Cannot write end of bulk operations: " + e.getMessage());
385 }
386 }
387
388
389
390 /**
391 * {@inheritDoc}
392 */
393 public void bulkMarshal(final int failOnErrors,
394 final List<BulkOperation> operations)
395 throws SCIMException
396 {
397 writeBulkStart(failOnErrors, Collections.<String>emptySet());
398 for (final BulkOperation o : operations)
399 {
400 writeBulkOperation(o);
401 }
402 writeBulkFinish();
403 }
404
405
406
407 /**
408 * Write a SCIM object to an XML stream.
409 *
410 * @param resource The SCIM resource to be written.
411 * @param xmlStreamWriter The stream to which the SCIM object should be
412 * written.
413 * @param xsiURI The xsi URI to use for the type attribute.
414 * @throws XMLStreamException If the object could not be written.
415 */
416 private void marshal(final BaseResource resource,
417 final XMLStreamWriter xmlStreamWriter,
418 final String xsiURI)
419 throws XMLStreamException
420 {
421 final String resourceSchemaURI =
422 resource.getResourceDescriptor().getSchema();
423
424 int i = 1;
425 for (final String schemaURI :
426 resource.getResourceDescriptor().getAttributeSchemas())
427 {
428 if (schemaURI.equalsIgnoreCase(resourceSchemaURI))
429 {
430 final String prefix = SCIMConstants.DEFAULT_SCHEMA_PREFIX;
431 xmlStreamWriter.setPrefix(prefix, schemaURI);
432 xmlStreamWriter.writeNamespace(prefix, schemaURI);
433 }
434 else if (resource.getScimObject().hasSchema(schemaURI))
435 {
436 final String prefix = "ns" + String.valueOf(i++);
437 xmlStreamWriter.setPrefix(prefix, schemaURI);
438 xmlStreamWriter.writeNamespace(prefix, schemaURI);
439 }
440 }
441
442 if (xsiURI != null)
443 {
444 xmlStreamWriter.writeAttribute(xsiURI, "type",
445 SCIMConstants.DEFAULT_SCHEMA_PREFIX + ':' +
446 resource.getResourceDescriptor().getName());
447 }
448
449 // Write the resource attributes in the order defined by the
450 // resource descriptor.
451 for (final AttributeDescriptor attributeDescriptor :
452 resource.getResourceDescriptor().getAttributes())
453 {
454 final SCIMAttribute a =
455 resource.getScimObject().getAttribute(
456 attributeDescriptor.getSchema(), attributeDescriptor.getName());
457 if (a != null)
458 {
459 if (a.getAttributeDescriptor().isMultiValued())
460 {
461 writeMultiValuedAttribute(a, xmlStreamWriter);
462 }
463 else
464 {
465 writeSingularAttribute(a, xmlStreamWriter);
466 }
467 }
468 }
469 }
470
471
472
473 /**
474 * Write a multi-valued attribute to an XML stream.
475 *
476 * @param scimAttribute The attribute to be written.
477 * @param xmlStreamWriter The stream to which the attribute should be
478 * written.
479 * @throws XMLStreamException If the attribute could not be written.
480 */
481 private void writeMultiValuedAttribute(final SCIMAttribute scimAttribute,
482 final XMLStreamWriter xmlStreamWriter)
483 throws XMLStreamException
484 {
485 final SCIMAttributeValue[] values = scimAttribute.getValues();
486
487 writeStartElement(scimAttribute, xmlStreamWriter);
488
489 for (final SCIMAttributeValue value : values)
490 {
491 writeChildStartElement(scimAttribute, xmlStreamWriter);
492
493 // Write the subordinate attributes in the order defined by the schema.
494 for (final AttributeDescriptor descriptor :
495 scimAttribute.getAttributeDescriptor().getSubAttributes())
496 {
497 final SCIMAttribute a = value.getAttribute(descriptor.getName());
498 if (a != null)
499 {
500 if (a.getAttributeDescriptor().isMultiValued())
501 {
502 writeMultiValuedAttribute(a, xmlStreamWriter);
503 }
504 else
505 {
506 writeSingularAttribute(a, xmlStreamWriter);
507 }
508 }
509 }
510
511 xmlStreamWriter.writeEndElement();
512 }
513
514 xmlStreamWriter.writeEndElement();
515 }
516
517
518
519 /**
520 * Write a singular attribute to an XML stream.
521 *
522 * @param scimAttribute The attribute to be written.
523 * @param xmlStreamWriter The stream to which the attribute should be
524 * written.
525 * @throws XMLStreamException If the attribute could not be written.
526 */
527 private void writeSingularAttribute(final SCIMAttribute scimAttribute,
528 final XMLStreamWriter xmlStreamWriter)
529 throws XMLStreamException
530 {
531 final AttributeDescriptor attributeDescriptor =
532 scimAttribute.getAttributeDescriptor();
533
534 writeStartElement(scimAttribute, xmlStreamWriter);
535
536 final SCIMAttributeValue val = scimAttribute.getValue();
537
538 if (val.isComplex())
539 {
540 // Write the subordinate attributes in the order defined by the schema.
541 for (final AttributeDescriptor ad :
542 attributeDescriptor.getSubAttributes())
543 {
544 final SCIMAttribute a = val.getAttribute(ad.getName());
545 if (a != null)
546 {
547 writeSingularAttribute(a, xmlStreamWriter);
548 }
549 }
550 }
551 else
552 {
553 final String stringValue =
554 scimAttribute.getValue().getStringValue();
555 xmlStreamWriter.writeCharacters(stringValue);
556 }
557
558 xmlStreamWriter.writeEndElement();
559 }
560
561
562
563 /**
564 * Helper that writes namespace when needed.
565 * @param scimAttribute Attribute tag to write.
566 * @param xmlStreamWriter Writer to write with.
567 * @throws XMLStreamException thrown if error writing the tag element.
568 */
569 private void writeStartElement(final SCIMAttribute scimAttribute,
570 final XMLStreamWriter xmlStreamWriter)
571 throws XMLStreamException
572 {
573 if (scimAttribute.getSchema().equalsIgnoreCase(
574 SCIMConstants.SCHEMA_URI_CORE))
575 {
576 xmlStreamWriter.writeStartElement(scimAttribute.getName());
577 }
578 else
579 {
580 xmlStreamWriter.writeStartElement(scimAttribute.getSchema(),
581 scimAttribute.getName());
582 }
583 }
584
585
586
587
588
589 /**
590 * Helper that writes namespace when needed.
591 * @param scimAttribute Attribute tag to write.
592 * @param xmlStreamWriter Writer to write with.
593 * @throws XMLStreamException thrown if error writing the tag element.
594 */
595 private void writeChildStartElement(final SCIMAttribute scimAttribute,
596 final XMLStreamWriter xmlStreamWriter)
597 throws XMLStreamException
598 {
599 if (scimAttribute.getSchema().equalsIgnoreCase(
600 SCIMConstants.SCHEMA_URI_CORE))
601 {
602 xmlStreamWriter.writeStartElement(scimAttribute.getAttributeDescriptor().
603 getMultiValuedChildName());
604 }
605 else
606 {
607 xmlStreamWriter.writeStartElement(scimAttribute.getSchema(),
608 scimAttribute.getAttributeDescriptor().getMultiValuedChildName());
609 }
610 }
611 }