com.helger.peppol.sbdh.read.PeppolSBDHDocumentReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of peppol-sbdh Show documentation
Show all versions of peppol-sbdh Show documentation
Library for reading and writing OASIS Standard Business Document Header (SBDH) documents conforming to the PEPPOL specifications
/**
* Copyright (C) 2014-2019 Philip Helger
* 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.peppol.sbdh.read;
import java.io.InputStream;
import java.time.LocalDateTime;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.WillClose;
import javax.annotation.concurrent.NotThreadSafe;
import org.unece.cefact.namespaces.sbdh.BusinessScope;
import org.unece.cefact.namespaces.sbdh.DocumentIdentification;
import org.unece.cefact.namespaces.sbdh.PartnerIdentification;
import org.unece.cefact.namespaces.sbdh.Scope;
import org.unece.cefact.namespaces.sbdh.StandardBusinessDocument;
import org.unece.cefact.namespaces.sbdh.StandardBusinessDocumentHeader;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.OverrideOnDemand;
import com.helger.commons.equals.EqualsHelper;
import com.helger.commons.io.resource.IReadableResource;
import com.helger.commons.io.stream.StreamHelper;
import com.helger.commons.string.StringHelper;
import com.helger.datetime.util.PDTXMLConverter;
import com.helger.peppol.sbdh.CPeppolSBDH;
import com.helger.peppol.sbdh.PeppolSBDHAdditionalAttributes;
import com.helger.peppol.sbdh.PeppolSBDHDocument;
import com.helger.peppolid.factory.IIdentifierFactory;
import com.helger.peppolid.factory.SimpleIdentifierFactory;
import com.helger.peppolid.peppol.PeppolIdentifierHelper;
import com.helger.sbdh.SBDMarshaller;
/**
* Main class to read standard business documents and extract the PEPPOL
* required data out of it.
*
* @author Philip Helger
*/
@NotThreadSafe
public class PeppolSBDHDocumentReader
{
private final IIdentifierFactory m_aIdentifierFactory;
public PeppolSBDHDocumentReader ()
{
this (SimpleIdentifierFactory.INSTANCE);
}
public PeppolSBDHDocumentReader (@Nonnull final IIdentifierFactory aIdentifierFactory)
{
m_aIdentifierFactory = ValueEnforcer.notNull (aIdentifierFactory, "IdentifierFactory");
}
/**
* Check if the passed header version is valid or not. By default is must
* match {@link CPeppolSBDH#HEADER_VERSION}. Override this method to allow for
* other schemes as well.
*
* @param sHeaderVersion
* The value to be checked. This is the content of the XML element
* HeaderVersion
. May be null
.
* @return true
if the value is valid, false
* otherwise.
*/
@OverrideOnDemand
protected boolean isValidHeaderVersion (@Nullable final String sHeaderVersion)
{
return CPeppolSBDH.HEADER_VERSION.equals (sHeaderVersion);
}
/**
* Check if the passed sender authority is valid or not. By default is must
* match {@link PeppolIdentifierHelper#DEFAULT_PARTICIPANT_SCHEME}. Override
* this method to allow for other schemes as well.
*
* @param sSenderAuthority
* The value to be checked. This is the content of the XML attribute
* Sender/Identifier/@Authority
. May be null
.
* @return true
if the value is valid, false
* otherwise.
*/
@OverrideOnDemand
protected boolean isValidSenderAuthority (@Nullable final String sSenderAuthority)
{
return PeppolIdentifierHelper.DEFAULT_PARTICIPANT_SCHEME.equals (sSenderAuthority);
}
/**
* Check if the passed sender identifier is valid or not. By default is must
* not be empty. Override this method to perform further checks.
*
* @param sSenderAuthority
* The authority of the sender that was already validated with
* {@link #isValidSenderAuthority(String)}. This parameter is present
* to allow for different identifier checks for different authorities.
* May be null
.
* @param sSenderIdentifier
* The value to be checked. This conforms to the XML element value of
* Sender/Identifier
. May be null
.
* @return true
if the value is valid for the given authority,
* false
otherwise.
*/
@OverrideOnDemand
protected boolean isValidSenderIdentifier (@Nullable final String sSenderAuthority,
@Nullable final String sSenderIdentifier)
{
return StringHelper.hasText (sSenderIdentifier);
}
/**
* Check if the passed receiver authority is valid or not. By default is must
* match {@link PeppolIdentifierHelper#DEFAULT_PARTICIPANT_SCHEME}. Override
* this method to allow for other schemes as well.
*
* @param sReceiverAuthority
* The value to be checked. This is the content of the XML attribute
* Receiver/Identifier/@Authority
. May be
* null
.
* @return true
if the value is valid, false
* otherwise.
*/
@OverrideOnDemand
protected boolean isValidReceiverAuthority (@Nullable final String sReceiverAuthority)
{
return PeppolIdentifierHelper.DEFAULT_PARTICIPANT_SCHEME.equals (sReceiverAuthority);
}
/**
* Check if the passed receiver identifier is valid or not. By default is must
* not be empty. Override this method to perform further checks.
*
* @param sReceiverAuthority
* The authority of the receiver that was already validated with
* {@link #isValidReceiverAuthority(String)}. This parameter is present
* to allow for different identifier checks for different authorities.
* May be null
.
* @param sReceiverIdentifier
* The value to be checked. This conforms to the XML element value of
* Receiver/Identifier
. May be null
.
* @return true
if the value is valid for the given authority,
* false
otherwise.
*/
@OverrideOnDemand
protected boolean isValidReceiverIdentifier (@Nullable final String sReceiverAuthority,
@Nullable final String sReceiverIdentifier)
{
return StringHelper.hasText (sReceiverIdentifier);
}
/**
* Check if the passed document type identifier is valid or not. By default it
* must not be empty. Override this method to perform further checks.
*
* @param sDocumentTypeIdentifier
* The value to be checked excluding the PEPPOL identifier scheme. This
* conforms to the XML element value of
* BusinessScope/Scope[Type/text()="DOCUMENTID"]/InstanceIdentifier
* . May be null
.
* @return true
if the value is valid, false
* otherwise.
*/
@OverrideOnDemand
protected boolean isValidDocumentTypeIdentifier (@Nullable final String sDocumentTypeIdentifier)
{
return StringHelper.hasText (sDocumentTypeIdentifier);
}
/**
* Check if the passed process identifier is valid or not. By default is must
* not be empty. Override this method to perform further checks.
*
* @param sProcessIdentifier
* The value to be checked excluding the PEPPOL identifier scheme. This
* conforms to the XML element value of
* BusinessScope/Scope[Type/text()="PROCESSID"]/InstanceIdentifier
* . May be null
.
* @return true
if the value is valid, false
* otherwise.
*/
@OverrideOnDemand
protected boolean isValidProcessIdentifier (@Nullable final String sProcessIdentifier)
{
return StringHelper.hasText (sProcessIdentifier);
}
/**
* Check if the passed business message is valid or not. By default this
* method always returns true
since the element is never
* null
and no UBL specific checks are performed. Override this
* method to perform further or other checks.
*
* @param aBusinessMessage
* The business message element to check against. Never
* null
.
* @return true
if the value is valid, false
* otherwise.
*/
@OverrideOnDemand
protected boolean isValidBusinessMessage (@Nonnull final Element aBusinessMessage)
{
return true;
}
/**
* Check if the passed document identification standard is valid or not. By
* default this checks if the standard is the same as the namespace URI of the
* business message root element. Override this method to perform further or
* other checks.
*
* @param sStandard
* The value to be checked. This corresponds to the field
* "DocumentIdentification/Standard". May be null
.
* @param aBusinessMessage
* The business message element to check against. Never
* null
.
* @return true
if the value is valid, false
* otherwise.
*/
@OverrideOnDemand
protected boolean isValidStandard (@Nullable final String sStandard, @Nonnull final Element aBusinessMessage)
{
return EqualsHelper.equals (sStandard, aBusinessMessage.getNamespaceURI ());
}
/**
* Check if the passed document identification type version is valid or not.
* By default this refers to the UBL version and must either be "2.0" or
* "2.1". Override this method to perform further or other checks.
*
* @param sTypeVersion
* The value to be checked. This corresponds to the field
* "DocumentIdentification/TypeVersion". May be null
.
* @param aBusinessMessage
* The business message element to check against. Never
* null
.
* @return true
if the value is valid, false
* otherwise.
*/
@OverrideOnDemand
protected boolean isValidTypeVersion (@Nullable final String sTypeVersion, @Nonnull final Element aBusinessMessage)
{
return CPeppolSBDH.TYPE_VERSION_20.equals (sTypeVersion) ||
CPeppolSBDH.TYPE_VERSION_21.equals (sTypeVersion) ||
CPeppolSBDH.TYPE_VERSION_22.equals (sTypeVersion);
}
/**
* Check if the passed document identification type is valid or not. By
* default this checks if the type is the same as the local name of the
* business message root element. Override this method to perform further or
* other checks.
*
* @param sType
* The value to be checked. This corresponds to the field
* "DocumentIdentification/Type". May be null
.
* @param aBusinessMessage
* The business message element to check against. Never
* null
.
* @return true
if the value is valid, false
* otherwise.
*/
@OverrideOnDemand
protected boolean isValidType (@Nullable final String sType, @Nonnull final Element aBusinessMessage)
{
return EqualsHelper.equals (sType, aBusinessMessage.getLocalName ());
}
/**
* Check if the passed document identification instance identifier is valid or
* not. By default all non-empty values are valid. Override this method to
* perform further or other checks.
*
* @param sInstanceIdentifier
* The value to be checked. This corresponds to the field
* "DocumentIdentification/InstanceIdentifier". May be
* null
.
* @return true
if the value is valid, false
* otherwise.
*/
@OverrideOnDemand
protected boolean isValidInstanceIdentifier (@Nullable final String sInstanceIdentifier)
{
return StringHelper.hasText (sInstanceIdentifier);
}
/**
* Check if the passed document identification creation date time is valid or
* not. By default all values are valid as they cannot be null
.
* Override this method to perform further or other checks.
*
* @param aCreationDateTime
* The value to be checked. This corresponds to the field
* "DocumentIdentification/CreationDateAndTime". Is never
* null
.
* @return true
if the value is valid, false
* otherwise.
*/
@OverrideOnDemand
protected boolean isValidCreationDateTime (@Nonnull final LocalDateTime aCreationDateTime)
{
return true;
}
/**
* Create a new SBD marshaller used for reading SBD documents. Override this
* method to customize reading.
*
* @return An instance of the {@link SBDMarshaller} and never
* null
.
*/
@Nonnull
@OverrideOnDemand
protected SBDMarshaller createSBDMarshaller ()
{
final SBDMarshaller ret = new SBDMarshaller ();
// Simply swallow all error messages where possible
ret.setValidationEventHandlerFactory (x -> null);
return ret;
}
/**
* Extract the document data from the Standard Business Document represents by
* the passed parameter.
*
* @param aStandardBusinessDocument
* The input stream to read from. Will be closed by this method. May
* not be null
.
* @return The document data and never null
.
* @throws PeppolSBDHDocumentReadException
* In case the passed Standard Business Document does not conform to
* the PEPPOL rules.
*/
@Nonnull
public PeppolSBDHDocument extractData (@Nonnull @WillClose final InputStream aStandardBusinessDocument) throws PeppolSBDHDocumentReadException
{
ValueEnforcer.notNull (aStandardBusinessDocument, "StandardBusinessDocument");
try
{
// Convert to domain object
final StandardBusinessDocument aSBD = createSBDMarshaller ().read (aStandardBusinessDocument);
if (aSBD == null)
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_SBD_XML);
return extractData (aSBD);
}
finally
{
StreamHelper.close (aStandardBusinessDocument);
}
}
/**
* Extract the document data from the Standard Bussiness Document represents
* by the passed parameter.
*
* @param aStandardBusinessDocument
* The resource to read from. May not be null
.
* @return The document data and never null
.
* @throws PeppolSBDHDocumentReadException
* In case the passed Standard Business Document does not conform to
* the PEPPOL rules.
*/
@Nonnull
public PeppolSBDHDocument extractData (@Nonnull final IReadableResource aStandardBusinessDocument) throws PeppolSBDHDocumentReadException
{
ValueEnforcer.notNull (aStandardBusinessDocument, "StandardBusinessDocument");
// Convert to domain object
final StandardBusinessDocument aSBD = createSBDMarshaller ().read (aStandardBusinessDocument);
if (aSBD == null)
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_SBD_XML);
return extractData (aSBD);
}
/**
* Extract the document data from the Standard Bussiness Document represents
* by the passed parameter.
*
* @param aStandardBusinessDocument
* The DOM node to read from. May not be null
.
* @return The document data and never null
.
* @throws PeppolSBDHDocumentReadException
* In case the passed Standard Business Document does not conform to
* the PEPPOL rules.
*/
@Nonnull
public PeppolSBDHDocument extractData (@Nonnull final Node aStandardBusinessDocument) throws PeppolSBDHDocumentReadException
{
ValueEnforcer.notNull (aStandardBusinessDocument, "StandardBusinessDocument");
// Convert to domain object
final StandardBusinessDocument aSBD = createSBDMarshaller ().read (aStandardBusinessDocument);
if (aSBD == null)
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_SBD_XML);
return extractData (aSBD);
}
/**
* Extract the document data from the Standard Bussiness Document represents
* by the passed parameter.
*
* @param aStandardBusinessDocument
* The domain object to read from. May not be null
.
* @return The document data and never null
.
* @throws PeppolSBDHDocumentReadException
* In case the passed Standard Business Document does not conform to
* the PEPPOL rules.
*/
@Nonnull
public PeppolSBDHDocument extractData (@Nonnull final StandardBusinessDocument aStandardBusinessDocument) throws PeppolSBDHDocumentReadException
{
ValueEnforcer.notNull (aStandardBusinessDocument, "StandardBusinessDocument");
final PeppolSBDHDocument ret = new PeppolSBDHDocument (m_aIdentifierFactory);
// Grab the header
final StandardBusinessDocumentHeader aSBDH = aStandardBusinessDocument.getStandardBusinessDocumentHeader ();
if (aSBDH == null)
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.MISSING_SBDH);
// Check that the header version is correct
if (!isValidHeaderVersion (aSBDH.getHeaderVersion ()))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_HEADER_VERSION,
aSBDH.getHeaderVersion ());
// Check sender
{
if (aSBDH.getSenderCount () != 1)
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_SENDER_COUNT,
Integer.toString (aSBDH.getSenderCount ()));
// Identifier is mandatory
final PartnerIdentification aSenderIdentification = aSBDH.getSenderAtIndex (0).getIdentifier ();
if (!isValidSenderAuthority (aSenderIdentification.getAuthority ()))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_SENDER_AUTHORITY,
aSenderIdentification.getAuthority ());
// Check sender identifier value
if (!isValidSenderIdentifier (aSenderIdentification.getAuthority (), aSenderIdentification.getValue ()))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_SENDER_VALUE,
aSenderIdentification.getValue ());
// Remember sender
ret.setSender (aSenderIdentification.getAuthority (), aSenderIdentification.getValue ());
}
// Check receiver
{
if (aSBDH.getReceiverCount () != 1)
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_RECEIVER_COUNT,
Integer.toString (aSBDH.getReceiverCount ()));
// Identifier is mandatory
final PartnerIdentification aReceiverIdentification = aSBDH.getReceiverAtIndex (0).getIdentifier ();
if (!isValidReceiverAuthority (aReceiverIdentification.getAuthority ()))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_RECEIVER_AUTHORITY,
aReceiverIdentification.getAuthority ());
// Check receiver identifier value
if (!isValidReceiverIdentifier (aReceiverIdentification.getAuthority (), aReceiverIdentification.getValue ()))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_RECEIVER_VALUE,
aReceiverIdentification.getValue ());
ret.setReceiver (aReceiverIdentification.getAuthority (), aReceiverIdentification.getValue ());
}
// Document type identifier and process identifier
{
final BusinessScope aBusinessScope = aSBDH.getBusinessScope ();
if (aBusinessScope == null)
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.BUSINESS_SCOPE_MISSING);
// Check that at least 2 "Scope" elements are present
if (aBusinessScope.getScopeCount () < 2)
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_SCOPE_COUNT,
Integer.toString (aBusinessScope.getScopeCount ()));
boolean bFoundDocumentIDScope = false;
boolean bFoundProcessIDScope = false;
for (final Scope aScope : aBusinessScope.getScope ())
{
final String sType = aScope.getType ();
final String sInstanceIdentifier = aScope.getInstanceIdentifier ();
if (CPeppolSBDH.SCOPE_DOCUMENT_TYPE_ID.equals (sType))
{
if (!isValidDocumentTypeIdentifier (sInstanceIdentifier))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_DOCUMENT_TYPE_IDENTIFIER,
sInstanceIdentifier);
// The scheme was added in Spec v1.1
String sScheme = aScope.getIdentifier ();
if (sScheme == null)
sScheme = PeppolIdentifierHelper.DEFAULT_DOCUMENT_TYPE_SCHEME;
ret.setDocumentType (sScheme, sInstanceIdentifier);
bFoundDocumentIDScope = true;
}
else
if (CPeppolSBDH.SCOPE_PROCESS_ID.equals (sType))
{
if (!isValidProcessIdentifier (sInstanceIdentifier))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_PROCESS_IDENTIFIER,
sInstanceIdentifier);
// The scheme was added in Spec v1.1
String sScheme = aScope.getIdentifier ();
if (sScheme == null)
sScheme = PeppolIdentifierHelper.DEFAULT_PROCESS_SCHEME;
ret.setProcess (sScheme, sInstanceIdentifier);
bFoundProcessIDScope = true;
}
else
// read as additional attributes
if (!PeppolSBDHAdditionalAttributes.isReservedAttributeName (sType))
{
if (StringHelper.hasText (sInstanceIdentifier))
{
// Name and value
ret.additionalAttributes ().add (sType, sInstanceIdentifier);
}
else
{
// Name only
// The problem is that InstanceIdentifier is a mandatory element
// and therefore there is no way to differentiate between empty
// string and not available
ret.additionalAttributes ().add (sType, (String) null);
}
}
else
{
// Reserved for future use
}
}
if (!bFoundDocumentIDScope)
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.MISSING_DOCUMENT_TYPE_IDENTIFIER);
if (!bFoundProcessIDScope)
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.MISSING_PROCESS_IDENTIFIER);
}
// Check document and metadata
{
// Extract the main business message first - cannot be null and must be an
// Element!
final Element aBusinessMessage = (Element) aStandardBusinessDocument.getAny ();
if (!isValidBusinessMessage (aBusinessMessage))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_BUSINESS_MESSAGE);
// Set the main business message to the return data
ret.setBusinessMessage (aBusinessMessage);
// This field is mandatory in XML
final DocumentIdentification aDI = aSBDH.getDocumentIdentification ();
final String sNamespaceURI = aDI.getStandard ();
if (!isValidStandard (sNamespaceURI, aBusinessMessage))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_STANDARD,
sNamespaceURI,
aBusinessMessage.getNamespaceURI ());
final String sUBLVersion = aDI.getTypeVersion ();
if (!isValidTypeVersion (sUBLVersion, aBusinessMessage))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_TYPE_VERSION, sUBLVersion);
final String sLocalName = aDI.getType ();
if (!isValidType (sLocalName, aBusinessMessage))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_TYPE,
sLocalName,
aBusinessMessage.getLocalName ());
// The unique message ID
final String sSBDHID = aDI.getInstanceIdentifier ();
if (!isValidInstanceIdentifier (sSBDHID))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_INSTANCE_IDENTIFIER, sSBDHID);
// Mandatory date and time (cannot be null)
final LocalDateTime aCreationDateAndTime = PDTXMLConverter.getLocalDateTime (aDI.getCreationDateAndTime ());
if (!isValidCreationDateTime (aCreationDateAndTime))
throw new PeppolSBDHDocumentReadException (EPeppolSBDHDocumentReadError.INVALID_CREATION_DATE_TIME,
String.valueOf (aCreationDateAndTime));
ret.setDocumentIdentification (sNamespaceURI, sUBLVersion, sLocalName, sSBDHID, aCreationDateAndTime);
}
return ret;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy