com.helger.jaxb.IJAXBWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ph-jaxb Show documentation
Show all versions of ph-jaxb Show documentation
Special Java 1.8+ Library with extended JAXB support
/*
* Copyright (C) 2014-2024 Philip Helger (www.helger.com)
* philip[at]helger[dot]com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.helger.jaxb;
import java.io.File;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.file.Path;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.WillClose;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.helger.commons.io.EAppend;
import com.helger.commons.io.file.FileHelper;
import com.helger.commons.io.resource.IWritableResource;
import com.helger.commons.io.stream.ByteBufferOutputStream;
import com.helger.commons.io.stream.NonBlockingByteArrayInputStream;
import com.helger.commons.io.stream.NonBlockingStringWriter;
import com.helger.commons.io.stream.StreamHelper;
import com.helger.commons.state.ESuccess;
import com.helger.commons.string.StringHelper;
import com.helger.commons.system.ENewLineMode;
import com.helger.xml.XMLFactory;
import com.helger.xml.microdom.IMicroDocument;
import com.helger.xml.microdom.IMicroElement;
import com.helger.xml.microdom.serialize.MicroSAXHandler;
import com.helger.xml.serialize.write.EXMLIncorrectCharacterHandling;
import com.helger.xml.serialize.write.EXMLSerializeIndent;
import com.helger.xml.serialize.write.IXMLWriterSettings;
import com.helger.xml.serialize.write.SafeXMLStreamWriter;
import com.helger.xml.serialize.write.XMLWriterSettings;
import com.helger.xml.transform.TransformResultFactory;
/**
* Interface for writing JAXB documents to various destinations.
*
* @author Philip Helger
* @param
* The JAXB type to be written
*/
public interface IJAXBWriter
{
/**
* Use the {@link SafeXMLStreamWriter} where applicable to ensure valid XML is
* created? This is a work around for
* https://github.com/eclipse-ee4j/jaxb-ri/issues/614 and
* https://github.com/eclipse-ee4j/jaxb-ri/issues/960
* Note: these bugs are still open for JAXB 4.0.0
*/
boolean USE_JAXB_CHARSET_FIX = true;
/**
* @return The special JAXB namespace context to be used. May be
* null
.
* @since 8.5.3 in this interface
*/
@Nullable
NamespaceContext getNamespaceContext ();
/**
* @return true
if the JAXB output should be formatted. Default
* is false
.
* @since 8.5.3 in this interface
*/
boolean isFormattedOutput ();
/**
* @return The special JAXB Charset to be used for writing. null
* by default.
* @since 8.5.3 in this interface
*/
@Nullable
Charset getCharset ();
default boolean hasCharset ()
{
return getCharset () != null;
}
/**
* @return The JAXB indentation string to be used for writing.
* null
by default. Only used when formatted output is
* used.
* @since 8.5.3 in this interface
*/
@Nullable
String getIndentString ();
default boolean hasIndentString ()
{
return StringHelper.hasText (getIndentString ());
}
/**
* @return true
if an eventually configured XML Schema should be
* used, false
to explicitly disable the usage of XML
* Schema.
* @since 11.0.3
*/
boolean isUseSchema ();
/**
* @return The schema location to be used for writing. null
by
* default.
* @since 8.6.0
*/
@Nullable
String getSchemaLocation ();
default boolean hasSchemaLocation ()
{
return StringHelper.hasText (getSchemaLocation ());
}
/**
* @return The no namespace schema location to be used for writing.
* null
by default.
* @since 9.0.0
*/
@Nullable
String getNoNamespaceSchemaLocation ();
default boolean hasNoNamespaceSchemaLocation ()
{
return StringHelper.hasText (getNoNamespaceSchemaLocation ());
}
/**
* @return The XML writer settings to be used based on this writer settings.
* Never null
.
*/
@Nonnull
default IXMLWriterSettings getXMLWriterSettings ()
{
final XMLWriterSettings ret = new XMLWriterSettings ().setNamespaceContext (getNamespaceContext ())
.setIndent (isFormattedOutput () ? EXMLSerializeIndent.INDENT_AND_ALIGN
: EXMLSerializeIndent.NONE);
if (hasIndentString ())
ret.setIndentationString (getIndentString ());
if (hasCharset ())
ret.setCharset (getCharset ());
return ret.setNewLineMode (ENewLineMode.DEFAULT)
.setIncorrectCharacterHandling (EXMLIncorrectCharacterHandling.DO_NOT_WRITE_LOG_WARNING);
}
/**
* Write the passed object to a {@link File}.
*
* @param aObject
* The object to be written. May not be null
.
* @param aResultFile
* The result file to be written to. May not be null
.
* @return {@link ESuccess}
*/
@Nonnull
default ESuccess write (@Nonnull final JAXBTYPE aObject, @Nonnull final File aResultFile)
{
if (USE_JAXB_CHARSET_FIX)
{
final OutputStream aOS = FileHelper.getBufferedOutputStream (aResultFile);
if (aOS == null)
return ESuccess.FAILURE;
return write (aObject, aOS);
}
return write (aObject, TransformResultFactory.create (aResultFile));
}
/**
* Write the passed object to a {@link Path}.
*
* @param aObject
* The object to be written. May not be null
.
* @param aResultPath
* The result path to be written to. May not be null
.
* @return {@link ESuccess}
*/
@Nonnull
default ESuccess write (@Nonnull final JAXBTYPE aObject, @Nonnull final Path aResultPath)
{
if (USE_JAXB_CHARSET_FIX)
return write (aObject, aResultPath.toFile ());
return write (aObject, TransformResultFactory.create (aResultPath));
}
/**
* Write the passed object to an {@link OutputStream}.
*
* @param aObject
* The object to be written. May not be null
.
* @param aOS
* The output stream to write to. Will always be closed. May not be
* null
.
* @return {@link ESuccess}
*/
@Nonnull
default ESuccess write (@Nonnull final JAXBTYPE aObject, @Nonnull @WillClose final OutputStream aOS)
{
try
{
if (USE_JAXB_CHARSET_FIX)
{
return write (aObject, SafeXMLStreamWriter.create (aOS, getXMLWriterSettings ()));
}
return write (aObject, TransformResultFactory.create (aOS));
}
finally
{
// Needs to be manually closed
StreamHelper.close (aOS);
}
}
/**
* Write the passed object to a {@link Writer}.
*
* @param aObject
* The object to be written. May not be null
.
* @param aWriter
* The writer to write to. Will always be closed. May not be
* null
.
* @return {@link ESuccess}
*/
@Nonnull
default ESuccess write (@Nonnull final JAXBTYPE aObject, @Nonnull @WillClose final Writer aWriter)
{
try
{
if (USE_JAXB_CHARSET_FIX)
{
return write (aObject, SafeXMLStreamWriter.create (aWriter, getXMLWriterSettings ()));
}
return write (aObject, TransformResultFactory.create (aWriter));
}
finally
{
// Needs to be manually closed
StreamHelper.close (aWriter);
}
}
/**
* Write the passed object to a {@link ByteBuffer}.
*
* @param aObject
* The object to be written. May not be null
.
* @param aBuffer
* The byte buffer to write to. If the buffer is too small, it is
* automatically extended. May not be null
.
* @return {@link ESuccess}
* @throws BufferOverflowException
* If the ByteBuffer is too small
*/
@Nonnull
default ESuccess write (@Nonnull final JAXBTYPE aObject, @Nonnull final ByteBuffer aBuffer)
{
return write (aObject, new ByteBufferOutputStream (aBuffer, false));
}
/**
* Write the passed object to an {@link IWritableResource}.
*
* @param aObject
* The object to be written. May not be null
.
* @param aResource
* The result resource to be written to. May not be null
.
* @return {@link ESuccess}
*/
@Nonnull
default ESuccess write (@Nonnull final JAXBTYPE aObject, @Nonnull final IWritableResource aResource)
{
if (USE_JAXB_CHARSET_FIX)
{
final OutputStream aOS = aResource.getOutputStream (EAppend.TRUNCATE);
if (aOS == null)
return ESuccess.FAILURE;
return write (aObject, aOS);
}
return write (aObject, TransformResultFactory.create (aResource));
}
/**
* Convert the passed object to XML.
*
* @param aObject
* The object to be converted. May not be null
.
* @param aMarshallerFunc
* The marshalling function. May not be null
.
* @return {@link ESuccess}
*/
@Nonnull
ESuccess write (@Nonnull JAXBTYPE aObject, @Nonnull IJAXBMarshaller aMarshallerFunc);
/**
* Convert the passed object to XML. This method is potentially dangerous,
* when using StreamResult because it may create invalid XML. Only when using
* the {@link SafeXMLStreamWriter} it is ensured that only valid XML is
* created!
*
* @param aObject
* The object to be converted. May not be null
.
* @param aResult
* The result object holder. May not be null
. Usually
* SAXResult, DOMResult and StreamResult are supported.
* @return {@link ESuccess}
*/
@Nonnull
default ESuccess write (@Nonnull final JAXBTYPE aObject, @Nonnull final Result aResult)
{
if (USE_JAXB_CHARSET_FIX && aResult instanceof StreamResult)
{
LoggerFactory.getLogger (IJAXBWriter.class)
.warn ("Potentially invalid XML is created by using StreamResult object: {}", aResult);
}
return write (aObject, (m, e) -> m.marshal (e, aResult));
}
/**
* Convert the passed object to XML.
*
* @param aObject
* The object to be converted. May not be null
.
* @param aHandler
* XML will be sent to this handler as SAX2 events. May not be
* null
.
* @return {@link ESuccess}
*/
@Nonnull
default ESuccess write (@Nonnull final JAXBTYPE aObject, @Nonnull final org.xml.sax.ContentHandler aHandler)
{
// No need for charset fix, because it is up to the ContentHandler, if it is
// converting to a byte[] or not.
return write (aObject, (m, e) -> m.marshal (e, aHandler));
}
/**
* Convert the passed object to XML.
*
* @param aObject
* The object to be converted. May not be null
.
* @param aWriter
* XML will be sent to this writer. May not be null
.
* @return {@link ESuccess}
*/
@Nonnull
default ESuccess write (@Nonnull final JAXBTYPE aObject,
@Nonnull @WillClose final javax.xml.stream.XMLStreamWriter aWriter)
{
// No need for charset fix, because it is up to the XMLStreamWriter, if it
// is converting to a byte[] or not.
final ESuccess ret = write (aObject, (m, e) -> m.marshal (e, aWriter));
// Needs to be manually flushed and closed
try
{
aWriter.flush ();
}
catch (final XMLStreamException ex)
{
throw new IllegalStateException ("Failed to flush XMLStreamWriter", ex);
}
try
{
aWriter.close ();
}
catch (final XMLStreamException ex)
{
throw new IllegalStateException ("Failed to close XMLStreamWriter", ex);
}
return ret;
}
/**
* Convert the passed object to a new DOM document (write).
*
* @param aObject
* The object to be converted. May not be null
.
* @return null
if converting the document failed.
*/
@Nullable
default Document getAsDocument (@Nonnull final JAXBTYPE aObject)
{
// No need for charset fix, because the document is returned in an internal
// representation with String content
final Document aDoc = XMLFactory.newDocument ();
return write (aObject, TransformResultFactory.create (aDoc)).isSuccess () ? aDoc : null;
}
/**
* Convert the passed object to a new DOM document and return the document
* element (write).
*
* @param aObject
* The object to be converted. May not be null
.
* @return null
if converting the document failed.
* @since 11.0.2
*/
@Nullable
default Element getAsElement (@Nonnull final JAXBTYPE aObject)
{
final Document aDoc = getAsDocument (aObject);
return aDoc == null ? null : aDoc.getDocumentElement ();
}
/**
* Convert the passed object to a new micro document (write).
*
* @param aObject
* The object to be converted. May not be null
.
* @return null
if converting the document failed.
*/
@Nullable
default IMicroDocument getAsMicroDocument (@Nonnull final JAXBTYPE aObject)
{
// No need for charset fix, because the document is returned in an internal
// representation with String content
final MicroSAXHandler aHandler = new MicroSAXHandler (false, null, true);
return write (aObject, aHandler).isSuccess () ? aHandler.getDocument () : null;
}
/**
* Convert the passed object to a new micro document and return only the root
* element (write).
*
* @param aObject
* The object to be converted. May not be null
.
* @return null
if converting the document failed.
*/
@Nullable
default IMicroElement getAsMicroElement (@Nonnull final JAXBTYPE aObject)
{
final IMicroDocument aDoc = getAsMicroDocument (aObject);
if (aDoc == null)
return null;
final IMicroElement ret = aDoc.getDocumentElement ();
// Important to detach from document - otherwise the element cannot be
// re-added somewhere else
ret.detachFromParent ();
return ret;
}
/**
* Utility method to directly convert the passed domain object to an XML
* string (write).
*
* @param aObject
* The domain object to be converted. May not be null
.
* @return null
if the passed domain object could not be
* converted because of validation errors.
*/
@Nullable
default String getAsString (@Nonnull final JAXBTYPE aObject)
{
try (final NonBlockingStringWriter aSW = new NonBlockingStringWriter ())
{
if (USE_JAXB_CHARSET_FIX)
{
return write (aObject,
SafeXMLStreamWriter.create (aSW, getXMLWriterSettings ())).isSuccess () ? aSW.getAsString ()
: null;
}
return write (aObject, TransformResultFactory.create (aSW)).isSuccess () ? aSW.getAsString () : null;
}
}
/**
* Write the passed object to a {@link ByteBuffer} and return it (write).
*
* @param aObject
* The object to be written. May not be null
.
* @return null
if the passed domain object could not be
* converted because of validation errors.
*/
@Nullable
default ByteBuffer getAsByteBuffer (@Nonnull final JAXBTYPE aObject)
{
try (final ByteBufferOutputStream aBBOS = new ByteBufferOutputStream ())
{
if (USE_JAXB_CHARSET_FIX)
{
return write (aObject,
SafeXMLStreamWriter.create (aBBOS, getXMLWriterSettings ())).isFailure () ? null
: aBBOS.getBuffer ();
}
return write (aObject, aBBOS).isFailure () ? null : aBBOS.getBuffer ();
}
}
/**
* Write the passed object to a byte array and return the created byte array
* (write).
*
* @param aObject
* The object to be written. May not be null
.
* @return null
if the passed domain object could not be
* converted because of validation errors.
*/
@Nullable
default byte [] getAsBytes (@Nonnull final JAXBTYPE aObject)
{
try (final ByteBufferOutputStream aBBOS = new ByteBufferOutputStream ())
{
if (USE_JAXB_CHARSET_FIX)
{
return write (aObject,
SafeXMLStreamWriter.create (aBBOS, getXMLWriterSettings ())).isFailure () ? null
: aBBOS.getAsByteArray ();
}
return write (aObject, aBBOS).isFailure () ? null : aBBOS.getAsByteArray ();
}
}
/**
* Write the passed object to a byte array and return the input stream on that
* array.
*
* @param aObject
* The object to be written. May not be null
.
* @return null
if the passed domain object could not be
* converted because of validation errors.
* @since 9.1.8
*/
@Nullable
default NonBlockingByteArrayInputStream getAsInputStream (@Nonnull final JAXBTYPE aObject)
{
final byte [] aBytes = getAsBytes (aObject);
return aBytes == null ? null : new NonBlockingByteArrayInputStream (aBytes);
}
}