Java tutorial
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the Common Development and Distribution License, Version 1.0 * only (the "License"). You may not use this file except in compliance with the License. * * You can obtain a copy of the license at license/ESCIDOC.LICENSE or http://www.escidoc.de/license. See the License for * the specific language governing permissions and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each file and include the License file at * license/ESCIDOC.LICENSE. If applicable, add the following below this CDDL HEADER, with the fields enclosed by * brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2006-2011 Fachinformationszentrum Karlsruhe Gesellschaft fuer wissenschaftlich-technische Information mbH * and Max-Planck-Gesellschaft zur Foerderung der Wissenschaft e.V. All rights reserved. Use is subject to license * terms. */ package de.escidoc.core.common.util.xml; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.directory.NoSuchAttributeException; import javax.xml.stream.XMLEventWriter; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.Transformer; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.apache.commons.pool.impl.StackKeyedObjectPool; import org.codehaus.stax2.XMLOutputFactory2; import org.custommonkey.xmlunit.Diff; import org.custommonkey.xmlunit.XMLUnit; import org.escidoc.core.util.xml.internal.SchemaUtility; import org.escidoc.core.utils.io.Charsets; import org.escidoc.core.utils.io.IOUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import org.xml.sax.SAXException; import de.escidoc.core.common.business.Constants; import de.escidoc.core.common.business.fedora.resources.ResourceType; import de.escidoc.core.common.exceptions.application.invalid.XmlCorruptedException; import de.escidoc.core.common.exceptions.application.missing.MissingAttributeValueException; import de.escidoc.core.common.exceptions.system.EncodingSystemException; import de.escidoc.core.common.exceptions.system.WebserverSystemException; import de.escidoc.core.common.exceptions.system.XmlParserSystemException; import de.escidoc.core.common.util.configuration.EscidocConfiguration; import de.escidoc.core.common.util.stax.StaxParser; import de.escidoc.core.common.util.stax.handler.TaskParamHandler; import de.escidoc.core.common.util.string.StringUtility; import de.escidoc.core.common.util.xml.cache.SchemasCache; import de.escidoc.core.common.util.xml.stax.StaxAttributeEscapingWriterFactory; import de.escidoc.core.common.util.xml.stax.StaxTextEscapingWriterFactory; import de.escidoc.core.common.util.xml.stax.events.AbstractElement; import de.escidoc.core.common.util.xml.stax.events.StartElement; import de.escidoc.core.common.util.xml.stax.handler.DefaultHandler; import de.escidoc.core.common.util.xml.transformer.PoolableTransformerFactory; /** * Helper class to support Xml stuff in eSciDoc.<br> This class provides the validation of XML data using specified * schemas.<br> The schemas are specified by providing their schema URIs from that {@code Schema} objects are * created. These {@code Schema} objects are thread-safe and are cached to prevent unnecessary recreation. * * @author Torsten Tetteroo */ @Service("common.xml.XmlUtility") public final class XmlUtility { @Autowired @Qualifier("common.xml.SchemasCache") private SchemasCache schemasCache; private static final Logger LOGGER = LoggerFactory.getLogger(XmlUtility.class); /** * Pattern used to detect Object type is in resource type format, e.g. http://escidoc.de/core/01/resources/OrganizationalUnit */ public static final Pattern PATTERN_RESOURCE_OBJECT_TYPE = Pattern .compile('^' + Constants.RESOURCES_NS_URI + ".*$"); /** * The UTF-8 character encoding used in eSciDoc. */ public static final String CHARACTER_ENCODING = "UTF-8"; /** * The Mime-Type for XML. */ public static final String MIME_TYPE_XML = "text/xml"; /** * The XML version. */ private static final String XML_VERSION = "1.0"; /** * Head of document. */ public static final String DOCUMENT_START = "<?xml version=\"" + XML_VERSION + "\" encoding=\"" + CHARACTER_ENCODING + "\"?>\n"; /** * CDATA start. */ public static final String CDATA_START = "<![CDATA["; /** * CDATA end. */ public static final String CDATA_END = "]]>"; private static final String AMPERSAND = "&"; private static final String ESC_AMPERSAND = "&"; private static final String LESS_THAN = "<"; private static final String ESC_LESS_THAN = "<"; private static final String GREATER_THAN = ">"; private static final String ESC_GREATER_THAN = ">"; private static final String APOS = "'"; private static final String ESC_APOS = "'"; private static final String QUOT = "\""; private static final String ESC_QUOT = """; private static final Pattern PATTERN_ESCAPE_NEEDED = Pattern .compile(AMPERSAND + '|' + LESS_THAN + '|' + GREATER_THAN + '|' + QUOT + '|' + APOS); private static final Pattern PATTERN_UNESCAPE_NEEDED = Pattern.compile( ESC_AMPERSAND + '|' + ESC_LESS_THAN + '|' + ESC_GREATER_THAN + '|' + ESC_QUOT + '|' + ESC_APOS); private static final Pattern PATTERN_AMPERSAND = Pattern.compile('(' + AMPERSAND + ')'); private static final Pattern PATTERN_LESS_THAN = Pattern.compile('(' + LESS_THAN + ')'); private static final Pattern PATTERN_GREATER_THAN = Pattern.compile('(' + GREATER_THAN + ')'); private static final Pattern PATTERN_QUOT = Pattern.compile('(' + QUOT + ')'); private static final Pattern PATTERN_APOS = Pattern.compile('(' + APOS + ')'); private static final Pattern PATTERN_ESC_AMPERSAND = Pattern.compile('(' + ESC_AMPERSAND + ')'); private static final Pattern PATTERN_ESC_LESS_THAN = Pattern.compile('(' + ESC_LESS_THAN + ')'); private static final Pattern PATTERN_ESC_GREATER_THAN = Pattern.compile('(' + ESC_GREATER_THAN + ')'); private static final Pattern PATTERN_ESC_QUOT = Pattern.compile('(' + ESC_QUOT + ')'); private static final Pattern PATTERN_ESC_APOS = Pattern.compile('(' + ESC_APOS + ')'); private static String stylesheetDefinition; public static final String CDATA_END_QUOTED = "]]>"; public static final String NAME_ACTIVE = "active"; public static final String NAME_VALUE = "value"; public static final String NAME_TYPE = "type"; public static final String NAME_ASSIGNED_ON = "assigned-on"; public static final String NAME_COMPONENT = "component"; public static final String NAME_COMPONENTS = "components"; public static final String NAME_CREATED_BY = "created-by"; public static final String NAME_CREATION_DATE = "creation-date"; public static final String NAME_SPECIFICATION = "specification"; public static final String NAME_LOCK_OWNER = "lock-owner"; public static final String NAME_MEMBER = "member"; public static final String NAME_MODIFIED_BY = "modified-by"; public static final String NAME_CONTAINER_REF = "container-ref"; public static final String NAME_CONTENT_CATEGORY = "content-category"; public static final String NAME_ITEM_REF = "item-ref"; /** * The name of the last modification date attribute. */ public static final String NAME_LAST_MODIFICATION_DATE = "last-modification-date"; public static final String NAME_LATEST_RELEASE_NUMBER = "latest-release-number"; public static final String NAME_LATEST_VERSION_MODIFIED_BY = "latest-version-modified-by"; public static final String NAME_LATEST_VERSION_NUMBER = "latest-version-number"; public static final String NAME_LATEST_VERSION_STATUS = "latest-version-status"; public static final String NAME_LATEST_VERSION_VALID_STATUS = "latest-version-valid-status"; public static final String NAME_PUBLIC_STATUS = "public-status"; public static final String NAME_VALID_STATUS = "valid-status"; public static final String NAME_STATUS = "status"; public static final String NAME_VISIBILITY = "visibility"; public static final String NAME_ITEM = "item"; public static final String NAME_CONTEXT = "context"; public static final String NAME_CONTAINER = "container"; public static final String NAME_CONTENT_MODEL = "content-model"; public static final String NAME_CONTENT_RELATION = "content-relation"; public static final String NAME_USER_ACCOUNT = "user-account"; public static final String NAME_USER_GROUP = "user-group"; public static final String NAME_SET_DEFINITION = "set-definition"; public static final String NAME_ROLE = "role"; public static final String NAME_USER_ID = "userId"; public static final String NAME_GROUP_ID = "groupId"; public static final String NAME_ROLE_ID = "roleId"; public static final String NAME_OBJECT_ID = "objectId"; public static final String NAME_REVOCATION_DATE_FROM = "revocationDateFrom"; public static final String NAME_REVOCATION_DATE_TO = "revocationDateTo"; public static final String NAME_GRANTED_DATE_FROM = "grantedDateFrom"; public static final String NAME_GRANTED_DATE_TO = "grantedDateTo"; public static final String NAME_CREATOR_ID = "creatorId"; public static final String NAME_REVOKER_ID = "revokerId"; // Names of Statistic-Manager Resources public static final String NAME_STATISTIC_DATA = "statistic-data"; public static final String NAME_SCOPE = "scope"; public static final String NAME_AGGREGATION_DEFINITION = "aggregation-definition"; public static final String NAME_REPORT = "report"; public static final String NAME_REPORT_DEFINITION = "report-definition"; public static final String NAME_PREPROCESSING_INFORMATION = "preprocessing-information"; // ///////////////////////////////////////// public static final String NAME_OBJECT = "object"; public static final String NAME_ATTRIBUTES = "attributes"; public static final String NAME_ATTRIBUTE = "attribute"; private static final String NAME_OBJID = "objid"; public static final String NAME_NAME = "name"; public static final String NAME_PARAM = "param"; public static final String NAME_PID = "pid"; public static final String NAME_EXTERNAL_ID = "external-id"; public static final String NAME_PROPERTIES = "properties"; public static final String NAME_MDRECORDS = "md-records"; public static final String NAME_MDRECORD = "md-record"; private static final String NAME_RESOURCES = "resources"; public static final String NAME_ID = "id"; public static final String NAME_EMAIL = "email"; public static final String NAME_FILTER = "filter"; public static final String NAME_FORMAT = "format"; public static final String NAME_LOGIN_NAME = "login-name"; public static final String NAME_HANDLE = "handle"; private static final String NAME_HREF = "href"; public static final String NAME_DESCRIPTION = "description"; public static final String NAME_GENRE = "genre"; public static final String NAME_GRANT = "grant"; public static final String NAME_ORGANIZATIONAL_UNIT = "organizational-unit"; private static final String NAME_PARENT_OBJECTS = "parent-objects"; public static final String NAME_PARENTS = "parents"; public static final String NAME_PARENT = "parent"; private static final String NAME_CHILD_OBJECTS = "child-objects"; private static final String NAME_PATH_LIST = "path-list"; public static final String NAME_PREDECESSORS = "predecessors"; public static final String NAME_PREDECESSOR = "predecessor"; private static final String NAME_SUCCESSORS = "successors"; public static final String NAME_ORDER_BY = "order-by"; public static final String NAME_SORTING = "sorting"; public static final String NAME_LIMIT = "limit"; public static final String NAME_OFFSET = "offset"; public static final String NAME_UNSECURED_ACTION = "unsecured-action"; public static final String NAME_UNSECURED_ACTIONS = "unsecured-actions"; public static final String XPATH_USER_ACCOUNT_ORGANIZATIONAL_UNIT = "/user-account/properties/organizational-units/organizational-unit"; private static final String BASE_AA = "/aa/"; private static final String BASE_SM = "/statistic/"; private static final String BASE_OUM = "/oum/"; public static final String BASE_OM = "/ir/"; public static final String BASE_ORGANIZATIONAL_UNIT = BASE_OUM + NAME_ORGANIZATIONAL_UNIT + '/'; private static final String BASE_USER_ACCOUNT = BASE_AA + NAME_USER_ACCOUNT + '/'; private static final String BASE_USER_GROUP = BASE_AA + NAME_USER_GROUP + '/'; private static final String BASE_SET_DEFINITION = "/oai/" + NAME_SET_DEFINITION + '/'; public static final String BASE_ROLE = BASE_AA + NAME_ROLE + '/'; public static final String BASE_LOGIN = BASE_AA + "login" + '/'; private static final String BASE_SCOPE = BASE_SM + NAME_SCOPE + '/'; private static final String BASE_AGGREGATION_DEFINITION = BASE_SM + NAME_AGGREGATION_DEFINITION + '/'; private static final String BASE_REPORT_DEFINITION = BASE_SM + NAME_REPORT_DEFINITION + '/'; public static final String XPATH_USER_ACCOUNT_PROPERTIES = '/' + NAME_USER_ACCOUNT + '/' + NAME_PROPERTIES; /** * The thread-safe compiled Pattern used to extract the object id from an provided URI/Fedora id, e.g. from * <info:fedora/escidoc:1> or from http://www.escidoc.de/some/path/escidoc:1 */ private static final Pattern PATTERN_GET_ID_FROM_URI_OR_FEDORA_ID = Pattern.compile(".*/([^/>]+)>{0,1}"); /** * The thread-safe compiled pattern to extract an object id from an XML representation of a resource, either by * getting it from the attribute objid or extracting it from the attribute ...:href. */ private static final Pattern PATTERN_OBJID_FROM_XML = Pattern.compile("(.*?objid=\"|.*?:href=\".*/)(.*?)\".*"); /** * The thread-safe compiled pattern to extract a title from an XML representation of a resource. */ private static final Pattern PATTERN_NAME_FROM_XML = Pattern.compile( ".*?<.*?:" + NAME_NAME + ">(.*?)</.*?:" + NAME_NAME + ">.*", Pattern.DOTALL | Pattern.MULTILINE); private static final Pattern PATTERN_ID_WITHOUT_VERSION = Pattern.compile("([a-zA-Z]+:[a-zA-Z0-9]+):[0-9]+"); private static final Pattern PATTERN_VERSION_NUMBER = Pattern.compile("[a-zA-Z]+:[a-zA-Z0-9]+:([0-9]+)"); private static final String ERR_MSG_MISSING_ATTRIBUTE = "Missing attribute"; private static final StackKeyedObjectPool TRANSFORMER_POOL = new StackKeyedObjectPool( new PoolableTransformerFactory()); /** * Protected constructor to prevent instantiation outside of the Spring-context. */ protected XmlUtility() { } /** * Simple proxy method that can decide about the resource type and return the matching schema location. * * @param type The type of the resource. * @return Returns the location of the appropriate schema * @throws WebserverSystemException Thrown if retrieve of SchemaLocation failed. */ public static String getSchemaLocationForResource(final ResourceType type) throws WebserverSystemException { final String schemaLocation; switch (type) { case ITEM: schemaLocation = SchemaUtility.SCHEMA_LOCATIONS.get(SchemaUtility.ITEM_SCHEMA_LOCATION_NAME); break; case CONTAINER: schemaLocation = SchemaUtility.SCHEMA_LOCATIONS.get(SchemaUtility.CONTAINER_SCHEMA_LOCATION_NAME); break; case CONTEXT: schemaLocation = SchemaUtility.SCHEMA_LOCATIONS.get(SchemaUtility.CONTEXT_SCHEMA_LOCATION_NAME); break; case OU: schemaLocation = SchemaUtility.SCHEMA_LOCATIONS .get(SchemaUtility.ORGANIZATIONAL_UNIT_SCHEMA_LOCATION_NAME); break; case CONTENT_RELATION: schemaLocation = SchemaUtility.SCHEMA_LOCATIONS .get(SchemaUtility.CONTENT_RELATION_SCHEMA_LOCATION_NAME); break; case CONTENT_MODEL: schemaLocation = SchemaUtility.SCHEMA_LOCATIONS.get(SchemaUtility.CONTENT_MODEL_SCHEMA_LOCATION_NAME); break; default: throw new WebserverSystemException("Unknown schema location for resoure type " + type); } return schemaLocation; } /** * Gets the organizational unit href for the provided organizational unit id. * * @param organizationalUnitId The id of the organizational unit. * @return Returns the href for the provided organizational unit id. */ public static String getOrganizationalUnitHref(final String organizationalUnitId) { return BASE_ORGANIZATIONAL_UNIT + organizationalUnitId; } /** * Gets the container md records href for the provided organizational unit id. * * @param organizationalUnitId The id of organizational unit. * @return Returns the href for the md records of the provided organizational unit id. */ public static String getOrganizationalUnitMdRecordsHref(final String organizationalUnitId) { return getOrganizationalUnitHref(organizationalUnitId) + '/' + NAME_MDRECORDS; } /** * Gets the container properties href for the provided organizational unit id and name. * * @param organizationalUnitId The id of organizational unit. * @param name The name of the md record. * @return Returns the href for the md record of the provided organizational unit id and name. */ public static String getOrganizationalUnitMdRecordHref(final String organizationalUnitId, final String name) { return getOrganizationalUnitMdRecordsHref(organizationalUnitId) + '/' + NAME_MDRECORD + '/' + name; } /** * Gets the container href for the provided container id. * <p/> * Use the getHref() methods of the resource objects itself (Container). * * @param containerId The id of the container. * @return Returns the href for the provided container id. */ @Deprecated public static String getContainerHref(final String containerId) { return Constants.CONTAINER_URL_BASE + containerId; } /** * Gets the container properties href for the provided container id. * * @param containerHref The href of container. * @return Returns the href for the properties of the provided container id. */ public static String getContainerMdRecordsHref(final String containerHref) { return containerHref + '/' + NAME_MDRECORDS; } /** * Gets the container properties href for the provided container id. * * @param containerHref The href of the container. * @return Returns the href for the properties of the provided container id. */ public static String getContainerPropertiesHref(final String containerHref) { return containerHref + '/' + NAME_PROPERTIES; } /** * Gets the item href for the provided item id. * * @param itemId The id of the container. * @return Returns the href for the provided container id. */ public static String getItemHref(final String itemId) { return Constants.ITEM_URL_BASE + itemId; } /** * Gets the item parents href for the provided item href. * * @param itemHref The href of the item. * @return Returns the href for the data of the provided item id. */ public static String getItemParentsHref(final String itemHref) { return itemHref + '/' + NAME_RESOURCES + "/parents"; } /** * Gets the item href for the provided item id. * * @param itemId The id of the container. * @return Returns the href for the provided container id. */ public static String getComponentHref(final String itemId, final String componentId) { return Constants.ITEM_URL_BASE + itemId + Constants.COMPONENT_URL_PART + componentId; } /** * Gets the content-relation href for the provided content-relation id. * * @param contentRelationId The id of the content-relation. * @return Returns the href for the provided content-relation id. */ public static String getContentRelationHref(final String contentRelationId) { return Constants.CONTENT_RELATION_URL_BASE + contentRelationId; } /** * Gets the context href for the provided container id. * * @param contextId The id of the context. * @return Returns the href for the provided context id. */ public static String getContextHref(final String contextId) { return Constants.CONTEXT_URL_BASE + contextId; } /** * Get the properties href for the provided context. * * @param contextId The id of the context. * @return Returns the href of the properties for the provided context id. */ public static String getContextPropertiesHref(final String contextId) { return getContextHref(contextId) + '/' + NAME_PROPERTIES; } /** * Gets the context resources href for the provided context id. * * @param contextId The id of the context. * @return Returns the href for the data of the provided context id. */ public static String getContextResourcesHref(final String contextId) { return getContextHref(contextId) + '/' + NAME_RESOURCES; } /** * Gets the content model href for the provided content model id. * * @param contentModelId The id of the content model. * @return Returns the href for the provided content model id. */ public static String getContentModelHref(final String contentModelId) { return Constants.CONTENT_MODEL_URL_BASE + contentModelId; } /** * Gets the organizational unit properties href for the provided organizational unit id. * * @param organizationalUnitId The id of the organizational unit. * @return Returns the href for the properties of the provided organizational unit id. */ public static String getOrganizationalUnitPropertiesHref(final String organizationalUnitId) { return getOrganizationalUnitHref(organizationalUnitId) + '/' + NAME_PROPERTIES; } /** * Gets the organizational unit parent-ous href for the provided organizational unit id. * * @param organizationalUnitId The id of the organizational unit. * @return Returns the href for the parent-ous of the provided organizational unit id. */ public static String getOrganizationalUnitParentsHref(final String organizationalUnitId) { return getOrganizationalUnitHref(organizationalUnitId) + '/' + NAME_PARENTS; } /** * Get href of organizational unit predecessor OUs for the provided organizational unit id. * * @param organizationalUnitId The id of the organizational unit. * @return Returns the href for the predecessor OUs of the provided organizational unit id. */ public static String getOrganizationalUnitPredecessorsHref(final String organizationalUnitId) { return getOrganizationalUnitHref(organizationalUnitId) + '/' + NAME_PREDECESSORS; } /** * Get href of organizational unit successors for the provided organizational unit id. * * @param organizationalUnitId The id of the organizational unit. * @return Returns the href for the successor OUs of the provided organizational unit id. */ public static String getOrganizationalUnitSuccessorsHref(final String organizationalUnitId) { return getOrganizationalUnitHref(organizationalUnitId) + '/' + NAME_SUCCESSORS; } /** * Gets the container resources href for the provided container id. * * @param containerHref The href of the container. * @return Returns the href for the data of the provided container id. */ public static String getContainerResourcesHref(final String containerHref) { return containerHref + '/' + NAME_RESOURCES; } /** * Gets the container parents href for the provided container id. * * @param containerHref The href of the container. * @return Returns the href for the data of the provided container id. */ public static String getContainerParentsHref(final String containerHref) { return containerHref + '/' + NAME_RESOURCES + "/parents"; } /** * Gets the organizational unit resources href for the provided organizational unit id. * * @param organizationalUnitId The id of the organizational unit. * @return Returns the href for the data of the provided organizational unit id. */ public static String getOrganizationalUnitResourcesHref(final String organizationalUnitId) { return getOrganizationalUnitHref(organizationalUnitId) + '/' + NAME_RESOURCES; } /** * Gets the organizational unit virtual resource parents href for the provided organizational unit id. * * @param organizationalUnitId The id of the organizational unit. * @return Returns the href for the data of the provided organizational unit id. */ public static String getOrganizationalUnitResourcesParentObjectsHref(final String organizationalUnitId) { return getOrganizationalUnitResourcesHref(organizationalUnitId) + '/' + NAME_PARENT_OBJECTS; } /** * Gets the organizational unit virtual resource children href for the provided organizational unit id. * * @param organizationalUnitId The id of the organizational unit. * @return Returns the href for the data of the provided organizational unit id. */ public static String getOrganizationalUnitResourcesChildObjectsHref(final String organizationalUnitId) { return getOrganizationalUnitResourcesHref(organizationalUnitId) + '/' + NAME_CHILD_OBJECTS; } /** * Gets the organizational unit virtual resource path-list href for the provided organizational unit id. * * @param organizationalUnitId The id of the organizational unit. * @return Returns the href for the data of the provided organizational unit id. */ public static String getOrganizationalUnitResourcesPathListHref(final String organizationalUnitId) { return getOrganizationalUnitResourcesHref(organizationalUnitId) + '/' + NAME_PATH_LIST; } /** * Gets the organizational unit virtual resource successors href for the provided organizational unit id. * * @param organizationalUnitId The id of the organizational unit. * @return Returns the href for the data of the provided organizational unit id. */ public static String getOrganizationalUnitResourcesSuccessorsHref(final String organizationalUnitId) { return getOrganizationalUnitResourcesHref(organizationalUnitId) + '/' + NAME_SUCCESSORS; } /** * Gets the user account href for the provided user account id. * * @param userAccountId The id of the user account. * @return Returns the href for the provided user account id. */ public static String getUserAccountHref(final String userAccountId) { return BASE_USER_ACCOUNT + userAccountId; } /** * Gets the user group href for the provided user group id. * * @param userGroupId The id of the user group. * @return Returns the href for the provided user group id. */ public static String getUserGroupHref(final String userGroupId) { return BASE_USER_GROUP + userGroupId; } /** * Gets the set definition href for the provided set definition id. * * @param setDefinitionId The id of the set definition. * @return Returns the href for the provided set definition id. */ public static String getSetDefinitionHref(final String setDefinitionId) { return BASE_SET_DEFINITION + setDefinitionId; } /** * Gets the user group member href for the provided user group member id. * * @param userGroupHref The href of the user group member. * @param userGroupMemberId The id of the user group member. * @return Returns the href for the provided user group member id. */ public static String getUserGroupMemberHref(final String userGroupHref, final String userGroupMemberId) { return userGroupHref + "/selectors/selector/" + userGroupMemberId; } /** * Get the href for current grants. * * @param userAccountId objid of user account * @return href for the provided grant with user account id */ public static String getCurrentGrantsHref(final String userAccountId) { return getUserAccountHref(userAccountId) + "/resources/current-grants"; } public static String getPreferencesHref(final String userAccountId) { return getUserAccountHref(userAccountId) + "/resources/preferences"; } public static String getAttributesHref(final String userAccountId) { return getUserAccountHref(userAccountId) + "/resources/attributes"; } public static String getUserGroupCurrentGrantsHref(final String userGroupId) { return getUserGroupHref(userGroupId) + "/resources/current-grants"; } public static String getUserAccountResourcesHref(final String userAccountId) { return getUserAccountHref(userAccountId) + "/resources"; } public static String getUserGroupResourcesHref(final String userGroupId) { return getUserGroupHref(userGroupId) + "/resources"; } public static String getUserAccountGrantsHref(final String userAccountId) { return getUserAccountHref(userAccountId) + "/resources/grants"; } public static String getUserGroupGrantsHref(final String userGroupId) { return getUserGroupHref(userGroupId) + "/resources/grants"; } /** * Get the href to the specified role grant of the specified user. * * @param userAccountId The account of the user account owning the grant. * @param grantId The id of the grant. * @return The href of the provided role grant. */ public static String getUserAccountGrantHref(final String userAccountId, final String grantId) { return getUserAccountGrantsHref(userAccountId) + "/grant/" + grantId; } /** * Get the href to the specified role grant of the specified user group. * * @param userGroupId id of the user group owning the grant * @param grantId id of the grant * @return The href of the provided role grant. */ public static String getUserGroupGrantHref(final String userGroupId, final String grantId) { return getUserGroupGrantsHref(userGroupId) + "/grant/" + grantId; } /** * Get the href to the specified role. * * @param roleId The id of the role. * @return Returns the href to the role with the specified id. */ public static String getRoleHref(final String roleId) { return BASE_ROLE + roleId; } /** * Get the href to the specified scope. * * @param scopeId The id of the role. * @return Returns the href to the role with the specified id. */ public static String getScopeHref(final String scopeId) { return BASE_SCOPE + scopeId; } /** * Get the href to the specified aggregation-definition. * * @param aggregationDefinitionId The id of the aggregationDefinition. * @return Returns the href to the aggregationDefinitionId with the specified id. */ public static String getAggregationDefinitionHref(final String aggregationDefinitionId) { return BASE_AGGREGATION_DEFINITION + aggregationDefinitionId; } /** * Get the href to the specified report-definition. * * @param reportDefinitionId The id of the reportDefinition. * @return Returns the href to the reportDefinitionId with the specified id. */ public static String getReportDefinitionHref(final String reportDefinitionId) { return BASE_REPORT_DEFINITION + reportDefinitionId; } /** * Adds a new element to the provided {@code XMLStreamWriter} object containing a {@code String} value. * * @param writer The {@code XMLStreamWriter} object to add the element to. * @param elementName The name of the new element. * @param elementContent The {@code String} that shall be set as the value of the new element. * @param namespaceUri The namespace URI of the new element. * @param createEmpty Flag indicating if a an empty element shall be created if the provided data is * {@code null} ( {@code true} ), or if the element shall not be created ( * {@code false} ). * @throws XMLStreamException Thrown in case of an xml stream error. */ public static void addElement(final XMLStreamWriter writer, final String elementName, final String elementContent, final String namespaceUri, final boolean createEmpty) throws XMLStreamException { if (elementContent == null) { if (createEmpty) { writer.writeEmptyElement(namespaceUri, elementName); } } else { writer.writeStartElement(namespaceUri, elementName); writer.writeCharacters(elementContent); writer.writeEndElement(); } } /** * Adds a new element to the provided {@code XMLStreamWriter} object containing a date value. * * @param writer The {@code XMLStreamWriter} object to add the element to. * @param elementName The name of the new element. * @param elementContent The {@code Date} that shall be set as the value of the new element. * @param namespaceUri The namespace URI of the new element. * @param createEmpty Flag indicating if a an empty element shall be created if the provided data is * {@code null} ( {@code true} ), or if the element shall not be created ( * {@code false} ). * @throws XMLStreamException Thrown in case of an xml stream error. */ public static void addElement(final XMLStreamWriter writer, final String elementName, final DateTime elementContent, final String namespaceUri, final boolean createEmpty) throws XMLStreamException { if (elementContent == null) { if (createEmpty) { writer.writeEmptyElement(namespaceUri, elementName); } } else { writer.writeStartElement(namespaceUri, elementName); writer.writeCharacters(elementContent.withZone(DateTimeZone.UTC).toString(Constants.TIMESTAMP_FORMAT)); writer.writeEndElement(); } } /** * Adds the "last-modification-date" attribute to the provided {@code XMLStreamWriter}.<br> The value of the * attribute is set to the value of the provided date.<br> If no date is provided, nothing is added. * * @param writer The {@code XMLStreamWriter} object to add the attribute to. * @param modifiedDate The date to set as the last modified date. * @throws XMLStreamException Thrown in case of an xml stream error. */ public static void addLastModificationDateAttribute(final XMLStreamWriter writer, final DateTime modifiedDate) throws XMLStreamException { if (modifiedDate == null) { return; } writer.writeAttribute("last-modification-date", modifiedDate.withZone(DateTimeZone.UTC).toString(Constants.TIMESTAMP_FORMAT)); } /** * Adds the provided object id to the {@code XMLStreamWriter} that has been provided.<br> The object id is * added as the attribute "objid". * * @param writer The {@code XMLStreamWriter} object to add the attribute to. * @param objId The object id to add. * @throws XMLStreamException Thrown in case of an xml stream error. */ public static void addObjectId(final XMLStreamWriter writer, final String objId) throws XMLStreamException { writer.writeAttribute("objid", objId); } /** * Adds a new element to the provided {@code XMLStreamWriter} containing a simple xlink with the provided * values. The new element is empty. * * @param writer The {@code XMLStreamWriter} object to add the element to. * @param elementName The name of the new element. * @param xlinkTitle The title of the xlink contained in the new element. * @param xlinkHref The href of the xlink contained in the new element. * @param namespaceUri The namespace URI of the new element. * @throws XMLStreamException Thrown in case of an xml stream error. */ public static void addReferencingElement(final XMLStreamWriter writer, final String elementName, final String xlinkTitle, final String xlinkHref, final String namespaceUri) throws XMLStreamException { writer.writeStartElement(namespaceUri, elementName); addXlinkAttributes(writer, xlinkTitle, xlinkHref); writer.writeEndElement(); } /** * Adds the xlink attributes to the provided {@code Element}.<br> The attribute "xlink:type" is set to * "simple", the attributes "xlink:title" and "xlink:href" to the respective provided values.<br> If the provided * title is {@code null}. the title attribute is skipped. * * @param writer The {@code XMLStreamWriter} object to add the attributes to. * @param xlinkTitle The title of the xlink. * @param xlinkHref The href of the xlink. * @throws XMLStreamException Thrown in case of an xml stream error. */ public static void addXlinkAttributes(final XMLStreamWriter writer, final String xlinkTitle, final String xlinkHref) throws XMLStreamException { writer.writeAttribute(Constants.XLINK_NS_URI, "type", "simple"); if (xlinkTitle != null) { writer.writeAttribute(Constants.XLINK_NS_URI, "title", xlinkTitle); } writer.writeAttribute(Constants.XLINK_NS_URI, "href", xlinkHref); } /** * Adds the "xml:base" attribute to the provided {@code XMLStreamWriter}.<br> The value of the attribute is set * to the value of the configuration property {@code escidoc.baseurl}. * * @param writer The {@code XMLStreamWriter} object to add the attribute to. * @throws IOException Thrown if the base url cannot be determined. * @throws XMLStreamException Thrown in case of an xml stream error. */ public static void addXmlBaseAttribute(final XMLStreamWriter writer) throws XMLStreamException { writer.writeAttribute(Constants.XML_NS_URI, "base", EscidocConfiguration.getInstance().get(EscidocConfiguration.ESCIDOC_CORE_BASEURL)); } /** * Converts the provided String to a {@code ByteArrayInputStream}. * * @param str The string to get as {@code ByteArrayInputStream}. * @return Returns the {@code ByteArrayInputStream} for the provided string. */ public static ByteArrayInputStream convertToByteArrayInputStream(final String str) { try { return new ByteArrayInputStream(str.getBytes(CHARACTER_ENCODING)); } catch (final UnsupportedEncodingException e) { // this should not happen return new ByteArrayInputStream("".getBytes(Charsets.UTF8_CHARSET)); } } /** * Converts the provided String to a {@code ByteArrayOutputStream}. * * @param str The string to get as {@code ByteArrayOutputStream}. * @return Returns the {@code ByteArrayOutputStream} for the provided string. */ public static ByteArrayOutputStream convertToByteArrayOutputStream(final String str) { final ByteArrayOutputStream stream = new ByteArrayOutputStream(); try { stream.write(str.getBytes(CHARACTER_ENCODING)); } catch (final UnsupportedEncodingException e) { if (LOGGER.isWarnEnabled()) { LOGGER.warn("Error on writing to stream."); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Error on writing to stream.", e); } } catch (final IOException e) { if (LOGGER.isWarnEnabled()) { LOGGER.warn("Error on writing to stream."); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Error on writing to stream.", e); } } return stream; } /** * Get the objid from an URI/Fedora identifier, e.g. from <info:fedora/escidoc:1><br/> If the provided value * does not match the expected pattern, it is returned as provided. Otherwise, the objid is extracted from it and * returned. * * @param uri The value to get the objid from * @return Returns the extracted objid or the provided value. */ public static String getIdFromURI(final String uri) { if (uri == null) { return null; } final Matcher matcher = PATTERN_GET_ID_FROM_URI_OR_FEDORA_ID.matcher(uri); return matcher.find() ? matcher.group(1) : uri; } /** * Extracts the objid from the provided resource XML representation.<br/> Either the first occurence of objid="..." * is searched and the value is returned, or the first occurence of :href="..." is searched and from this value the * objid is extracted and returned. * * @param resourceXml The XML representation of the resource to get the objid from. * @return Returns the extracted objid or {@code null}. */ public static String getIdFromXml(final CharSequence resourceXml) { final Matcher matcher = PATTERN_OBJID_FROM_XML.matcher(resourceXml); return matcher.find() ? matcher.group(2) : null; } /** * Extracts the objid from the provided element.<br/> Either the id is fetched from the attribute objid of the * provided element. If this fails, it is extracted from the attribute href. If this fials, too, an exception is * thrown. * * @param element The element to get the objid from. * @return Returns the objid value. * @throws MissingAttributeValueException Thrown if neither an objid nor an href attribute exists. */ public static String getIdFromStartElement(final StartElement element) throws MissingAttributeValueException { try { return element.indexOfAttribute(null, NAME_OBJID) == -1 ? getIdFromURI(element.getAttributeValue(Constants.XLINK_NS_URI, NAME_HREF)) : element.getAttributeValue(null, NAME_OBJID); } catch (final NoSuchAttributeException e) { throwMissingAttributeValueException(element, NAME_OBJID + '|' + NAME_HREF); return null; } } /** * Remove version information from given objid. * * @param objid The objid. * @return The objid without version information. */ public static String getObjidWithoutVersion(final String objid) { String result = objid; final Matcher m = PATTERN_ID_WITHOUT_VERSION.matcher(objid); if (m.find()) { result = m.group(1); } return result; } /** * Extract version number from objid. * * @param objid The objid. * @return The number of version or null. */ public static String getVersionNumberFromObjid(final CharSequence objid) { String version = null; final Matcher m = PATTERN_VERSION_NUMBER.matcher(objid); if (m.find()) { version = m.group(1); } return version; } /** * Extracts the name from the provided resource XML representation.<br> The first occurence of * <...:name>...</...:name> is searched and the value is returned. * * @param resourceXml The XML representation of the resource to get the name from. * @return Returns the extracted name (trimmed) or {@code null}. */ public static String extractNameFromXml(final CharSequence resourceXml) { final Matcher matcher = PATTERN_NAME_FROM_XML.matcher(resourceXml); return matcher.find() ? matcher.group(1).trim() : null; } /** * Convert the given date into a XML compliant format in UTC. * * @param date date * @return normalized date string */ public static String normalizeDate(final Date date) { return new DateTime(date).withZone(DateTimeZone.UTC).toString(Constants.TIMESTAMP_FORMAT); } /** * Convert the given date string into a XML compliant format in UTC. * * @param date date string * @return normalized date string */ public static String normalizeDate(final String date) { return new DateTime(date).withZone(DateTimeZone.UTC).toString(Constants.TIMESTAMP_FORMAT); } /** * Parse the task parameter structure. * * @param param The parameter structure. * @return The handler holding the extracted values. * @throws EncodingSystemException If a wrong Encoding is detected. * @throws XmlCorruptedException If the given XML is not valid. */ public static TaskParamHandler parseTaskParam(final String param) throws XmlCorruptedException, EncodingSystemException { return parseTaskParam(param, true); } public static TaskParamHandler parseTaskParam(final String param, final boolean checkLastModificationDate) throws XmlCorruptedException, EncodingSystemException { final StaxParser staxParser = new StaxParser(); final TaskParamHandler result = new TaskParamHandler(staxParser); if (param != null) { result.setCheckLastModificationDate(checkLastModificationDate); final ByteArrayInputStream xmlDataIs; try { xmlDataIs = new ByteArrayInputStream(param.getBytes(CHARACTER_ENCODING)); } catch (final UnsupportedEncodingException e) { throw new EncodingSystemException(e.getMessage(), e); } final List<DefaultHandler> handlerChain = new ArrayList<DefaultHandler>(); handlerChain.add(result); staxParser.setHandlerChain(handlerChain); try { staxParser.parse(xmlDataIs); } catch (final Exception e) { throw new XmlCorruptedException(e.getMessage(), e); } staxParser.clearHandlerChain(); } return result; } /** * Retrieves the base url for XML schemas for internal validation (i.e. escidoc-core.selfurl + * escidoc-core.xsd-path.<br> * * @return Returns the base url. * @throws WebserverSystemException In case of an error. */ public static String getSchemaBaseUrl() { return EscidocConfiguration.getInstance().appendToSelfURL( EscidocConfiguration.getInstance().get(EscidocConfiguration.ESCIDOC_CORE_XSD_PATH) + '/'); } /** * Gets the stylesheet definition. * * @return Returns the stylesheet definition. This may be an empty string, if the xslt has not been defined with the * eSciDoc configuration property escidoc.xslt.std. * @throws WebserverSystemException In case of an error. */ public static String getStylesheetDefinition() { if (stylesheetDefinition == null) { String xslt = EscidocConfiguration.getInstance().get(EscidocConfiguration.ESCIDOC_CORE_XSLT_STD); if (xslt == null || xslt.isEmpty()) { // FIXME a non-existing values should be null and not an empty string stylesheetDefinition = ""; } else { // add baseurl, if xslt is not http or https protocol /* * taking base url from configuration is actually an hacking, because the servlet context is missing in the * deep of these methods. */ if (!(xslt.startsWith("http://") || xslt.startsWith("https://"))) { String baseurl = EscidocConfiguration.getInstance() .get(EscidocConfiguration.ESCIDOC_CORE_BASEURL); if (!baseurl.endsWith("/")) { baseurl += "/"; } if (xslt.startsWith("./")) { xslt = baseurl + xslt.substring(2, xslt.length()); } else if (xslt.startsWith("/")) { xslt = baseurl + xslt.substring(1, xslt.length()); } } stylesheetDefinition = "<?xml-stylesheet type=\"text/xsl\" href=\"" + xslt + "\"?>\n"; } } return stylesheetDefinition; } /** * Replace forbidden characters in xml content with their escape sequence.<br/> This method escapes &, <, and > in * attributes and text content. In attributes, it additionally escapes " and '. * * @param xmlText The xml text. * @return The resulting text with escaped characters. */ public static String escapeForbiddenXmlCharacters(final String xmlText) { String result = xmlText; if (result != null && PATTERN_ESCAPE_NEEDED.matcher(result).find()) { result = PATTERN_AMPERSAND.matcher(result).replaceAll(ESC_AMPERSAND); result = PATTERN_LESS_THAN.matcher(result).replaceAll(ESC_LESS_THAN); result = PATTERN_GREATER_THAN.matcher(result).replaceAll(ESC_GREATER_THAN); result = PATTERN_QUOT.matcher(result).replaceAll(ESC_QUOT); result = PATTERN_APOS.matcher(result).replaceAll(ESC_APOS); } return result; } /** * Replace all escape sequences for forbidden charcters with their readable. * * @param xmlText The xml text with escape sequences. * @return The resulting text with unescaped characters. */ public static String unescapeForbiddenXmlCharacters(final String xmlText) { String result = xmlText; if (result != null && PATTERN_UNESCAPE_NEEDED.matcher(result).find()) { result = PATTERN_ESC_LESS_THAN.matcher(result).replaceAll(LESS_THAN); result = PATTERN_ESC_GREATER_THAN.matcher(result).replaceAll(GREATER_THAN); result = PATTERN_ESC_QUOT.matcher(result).replaceAll(QUOT); result = PATTERN_ESC_APOS.matcher(result).replaceAll(APOS); result = PATTERN_ESC_AMPERSAND.matcher(result).replaceAll(AMPERSAND); } return result; } /** * Throw a uniform escidoc system exception in case of an unexpected exception from the stax parser. * * @param message A handler specific message added to thrown exception message. * @param e The unexcepcted exception. * @throws XmlParserSystemException Thrown in case of an internal system error. */ public static void handleUnexpectedStaxParserException(final String message, final Exception e) throws XmlParserSystemException { final String text = message != null ? message + e.getMessage() : e.getMessage(); throw new XmlParserSystemException(text, e); } /** * FIXME Often this method is not used but get(ESCIDOC_CORE_BASEURL) directly. And/or there is no such method for * every other property!? * * @return Return the configured escidoc baseurl. * @throws WebserverSystemException If an error occurs accessing the escidoc configuration */ public static String getEscidocBaseUrl() { return EscidocConfiguration.getInstance().get(EscidocConfiguration.ESCIDOC_CORE_BASEURL); } /** * Gets an initilized {@code XMLOutputFactory2} instance.<br/> The returned instance is initialized as follows: * <ul> <li>If the provided parameter is set to {@code true}, IS_REPAIRING_NAMESPACES is set to true, i.e. the * created writers will automatically repair the namespaces, see {@code XMLOutputFactory} for details.</li> * <li>For writing escaped attribute values, the {@link StaxAttributeEscapingWriterFactory} is used<./li> <li>For * writing escaped text content, the {@link StaxTextEscapingWriterFactory} is used.</li> </ul> * * @param repairing Flag indicating if the factory shall create namespace repairing writers ({@code true}) or * non repairing writers ({@code false}). * @return Returns the initalized {@code XMLOutputFactory} instance. */ private static XMLOutputFactory getInitilizedXmlOutputFactory(final boolean repairing) { final XMLOutputFactory xmlof = XMLOutputFactory.newInstance(); xmlof.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, repairing); if (repairing) { xmlof.setProperty(XMLOutputFactory2.P_AUTOMATIC_NS_PREFIX, "ext"); } xmlof.setProperty(XMLOutputFactory2.P_ATTR_VALUE_ESCAPER, new StaxAttributeEscapingWriterFactory()); xmlof.setProperty(XMLOutputFactory2.P_TEXT_ESCAPER, new StaxTextEscapingWriterFactory()); return xmlof; } /** * Creates an {@code XMLStreamWriter} for the provided {@code OutputStream}. * * @param out The {@code OutputStream} to get the writer for. * @return Returns the {@code XMLStreamWriter}. * @throws XMLStreamException Thrown in case of an error during creating the writer. */ public static XMLStreamWriter createXmlStreamWriter(final OutputStream out) throws XMLStreamException { return getInitilizedXmlOutputFactory(false).createXMLStreamWriter(out, CHARACTER_ENCODING); } /** * Creates a namespace repairing {@code XMLStreamWriter} for the provided {@code OutputStream}. * * @param out The {@code OutputStream} to get the writer for. * @return Returns the {@code XMLStreamWriter}. * @throws XMLStreamException Thrown in case of an error during creating the writer. */ public static XMLStreamWriter createXmlStreamWriterNamespaceRepairing(final OutputStream out) throws XMLStreamException { return getInitilizedXmlOutputFactory(true).createXMLStreamWriter(out, CHARACTER_ENCODING); } /** * Creates an {@code XMLStreamWriter} for the provided {@code OutputStream}. * * @param writer The {@code Writer} to get the writer for. * @return Returns the {@code XMLStreamWriter}. * @throws XMLStreamException Thrown in case of an error during creating the writer. */ public static XMLStreamWriter createXmlStreamWriter(final Writer writer) throws XMLStreamException { return getInitilizedXmlOutputFactory(false).createXMLStreamWriter(writer); } /** * Creates an {@code XMLEventWriter} for the provided {@code Writer}. * * @param writer The {@code Writer} to get the writer for. * @return Returns the {@code XMLEventWriter}. * @throws XMLStreamException Thrown in case of an error during creating the writer. */ public static XMLEventWriter createXmlEventWriter(final Writer writer) throws XMLStreamException { return getInitilizedXmlOutputFactory(false).createXMLEventWriter(writer); } /** * Throws an {@code MissingAttributeValueException}. * * @param element The element in that the attribute is missing. * @param attributeName The name of the missing attribute. * @throws MissingAttributeValueException Throws created exception. */ public static void throwMissingAttributeValueException(final AbstractElement element, final String attributeName) throws MissingAttributeValueException { throw new MissingAttributeValueException(StringUtility.format(ERR_MSG_MISSING_ATTRIBUTE, element.getPath(), attributeName, element.getLocationString())); } /** * Create the content of the DC datastream to store in Fedora. * * @param nsUri nsUri of the md record. Through this URI is the mapping schema selected. * @param mdRecordXml Xml representation of the md record to parse. * @param objID The objid of the Fedora object. A triple is created with this objid. * @return The content of the DC datastream or null if content is empty. * @throws WebserverSystemException If an error occurs. */ public static String createDC(final String nsUri, final String mdRecordXml, final String objID) throws WebserverSystemException { return createDC(nsUri, mdRecordXml, objID, null); } /** * Create the content of the DC datastream to store in Fedora. * * @param nsUri nsUri of the md record. Through this URI is the mapping schema selected. * @param mdRecordXml Xml representation of the md record to parse. * @param objID The objid of the Fedora object. A triple is created with this objid. * @param contentModelID The objid of the content-model. * @return The content of the DC datastream or null if content is empty. * @throws WebserverSystemException If an error occurs. */ public static String createDC(final String nsUri, final String mdRecordXml, final CharSequence objID, final String contentModelID) throws WebserverSystemException { String result = null; Transformer t = null; final String transformerKey = nsUri + ';' + contentModelID; try { t = (Transformer) TRANSFORMER_POOL.borrowObject(transformerKey); if (objID != null && objID.length() > 0) { t.setParameter("ID", objID); } else { t.clearParameters(); } final ByteArrayOutputStream out = new ByteArrayOutputStream(); t.transform(new StreamSource(new ByteArrayInputStream(mdRecordXml.getBytes(CHARACTER_ENCODING))), new StreamResult(out)); result = out.toString(CHARACTER_ENCODING).trim(); } catch (final Exception e) { throw new WebserverSystemException("Mapping of Metadata to DC failed.", e); } finally { try { TRANSFORMER_POOL.returnObject(transformerKey, t); } catch (final Exception e) { if (LOGGER.isWarnEnabled()) { LOGGER.warn("Returning transformer to pool failed."); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Returning transformer to pool failed.", e); } } } // check if result is empty if (result != null && result.length() == 0) { result = null; } return result; } public static boolean isIdentical(final byte[] xml1, final byte[] xml2) { if (xml1 == null) { return xml2 == null; } if (xml2 == null) { return xml1 == null; } return isIdentical(IOUtils.newStringFromBytes(xml1), IOUtils.newStringFromBytes(xml2)); } public static boolean isIdentical(final String xml1, final String xml2) { if (xml1 == null) { return xml2 == null; } if (xml2 == null) { return xml1 == null; } XMLUnit.setIgnoreComments(true); XMLUnit.setIgnoreAttributeOrder(true); XMLUnit.setIgnoreWhitespace(true); try { final Diff diff = XMLUnit.compareXML(xml1, xml2); return diff.identical(); } catch (SAXException e) { throw new RuntimeException("Error on comparing XML.", e); } catch (IOException e) { throw new RuntimeException("Error on comparing XML.", e); } } }