diff --git a/phive-api/pom.xml b/phive-api/pom.xml index 9aabc1e4..c6a2e60b 100644 --- a/phive-api/pom.xml +++ b/phive-api/pom.xml @@ -22,7 +22,7 @@ com.helger.phive phive-parent-pom - 9.0.1-SNAPSHOT + 9.1.0-SNAPSHOT phive-api bundle diff --git a/phive-result/pom.xml b/phive-result/pom.xml index fb1d1275..13f2982e 100644 --- a/phive-result/pom.xml +++ b/phive-result/pom.xml @@ -22,7 +22,7 @@ com.helger.phive phive-parent-pom - 9.0.1-SNAPSHOT + 9.1.0-SNAPSHOT phive-result bundle diff --git a/phive-ves-engine/pom.xml b/phive-ves-engine/pom.xml index 5faf92a1..a7e25ee0 100644 --- a/phive-ves-engine/pom.xml +++ b/phive-ves-engine/pom.xml @@ -22,7 +22,7 @@ com.helger.phive phive-parent-pom - 9.0.1-SNAPSHOT + 9.1.0-SNAPSHOT phive-ves-engine bundle diff --git a/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/DefaultVESLoaderSchematron.java b/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/DefaultVESLoaderSchematron.java index d5578f93..757ef55f 100644 --- a/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/DefaultVESLoaderSchematron.java +++ b/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/DefaultVESLoaderSchematron.java @@ -44,6 +44,9 @@ */ public class DefaultVESLoaderSchematron implements IVESLoaderSchematron { + public static final String RESOURCE_TYPE_SCH = "sch"; + public static final String RESOURCE_TYPE_XSLT = "xslt"; + private static final Logger LOGGER = LoggerFactory.getLogger (DefaultVESLoaderSchematron.class); @Nonnull @@ -51,7 +54,7 @@ public IValidationExecutor loadSchematron (@Nonnull final @Nonnull final VesSchematronType aSCH, @Nonnull final ErrorList aErrorList) { - final RepoStorageKey aSCHKey = VESLoader.wrapKey (aSCH.getResource ()); + final RepoStorageKey aSCHKey = VESLoader.createRepoStorageKey (aSCH.getResource ()); // Read referenced Item final RepoStorageItem aSCHItem = aRepo.read (aSCHKey); @@ -74,7 +77,7 @@ public IValidationExecutor loadSchematron (@Nonnull final final String sResourceType = aSCH.getResource ().getType (); switch (sResourceType) { - case "sch": + case RESOURCE_TYPE_SCH: { // Resolve Schematron Engine final String sEngine = aSCH.getEngine (); @@ -110,7 +113,7 @@ public IValidationExecutor loadSchematron (@Nonnull final } break; } - case "xslt": + case RESOURCE_TYPE_XSLT: { // Indicate a potential error if (StringHelper.hasText (aSCH.getEngine ())) diff --git a/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/DefaultVESLoaderXSD.java b/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/DefaultVESLoaderXSD.java index 0f3f5b00..73b8e009 100644 --- a/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/DefaultVESLoaderXSD.java +++ b/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/DefaultVESLoaderXSD.java @@ -23,6 +23,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; import javax.xml.validation.Schema; import org.slf4j.Logger; @@ -42,6 +43,7 @@ import com.helger.commons.io.resource.IReadableResource; import com.helger.commons.io.resource.inmemory.ReadableResourceInputStream; import com.helger.commons.io.stream.NonBlockingByteArrayOutputStream; +import com.helger.commons.lang.EnumHelper; import com.helger.commons.string.StringHelper; import com.helger.commons.string.ToStringGenerator; import com.helger.diver.repo.IRepoStorageBase; @@ -68,6 +70,9 @@ */ public class DefaultVESLoaderXSD implements IVESLoaderXSD { + public static final String RESOURCE_TYPE_ZIP = "zip"; + public static final String RESOURCE_TYPE_XSD = "xsd"; + private static final Logger LOGGER = LoggerFactory.getLogger (DefaultVESLoaderXSD.class); /** @@ -75,10 +80,30 @@ public class DefaultVESLoaderXSD implements IVESLoaderXSD * * @author Philip Helger */ - private enum ECatalogType + public enum ECatalogType implements IHasID { - PUBLIC, - SYSTEM + PUBLIC ("public"), + SYSTEM ("system"); + + private final String m_sID; + + ECatalogType (@Nonnull @Nonempty final String sID) + { + m_sID = sID; + } + + @Nonnull + @Nonempty + public String getID () + { + return m_sID; + } + + @Nullable + public static ECatalogType getFromIDOrNull (@Nullable final String sID) + { + return EnumHelper.getFromIDOrNull (ECatalogType.class, sID); + } } /** @@ -86,7 +111,8 @@ private enum ECatalogType * * @author Philip Helger */ - private static final class CatalogEntry implements IHasID + @Immutable + public static final class CatalogEntry implements IHasID { private final ECatalogType m_eType; private final String m_sID; @@ -136,12 +162,13 @@ public String toString () @Nullable private static final String _unifyPath (@Nullable final String x) { + // Converty any "\" to "/" String ret = FilenameHelper.getPathUsingUnixSeparator (x); if (ret != null) { // Make absolute to simply LS resource resolving if (!ret.startsWith ("/")) - ret = "/" + ret; + ret = '/' + ret; } return ret; } @@ -151,7 +178,7 @@ public IValidationExecutor loadXSD (@Nonnull final IRepoS @Nonnull final VesXsdType aXSD, @Nonnull final ErrorList aErrorList) { - final RepoStorageKey aXSDKey = VESLoader.wrapKey (aXSD.getResource ()); + final RepoStorageKey aXSDKey = VESLoader.createRepoStorageKey (aXSD.getResource ()); // Read referenced Item final RepoStorageItem aXSDItem = aRepo.read (aXSDKey); @@ -176,12 +203,14 @@ public IValidationExecutor loadXSD (@Nonnull final IRepoS final VesXsdCatalogItemPublicType aPublic = (VesXsdCatalogItemPublicType) aItem; aEntry = new CatalogEntry (ECatalogType.PUBLIC, aPublic.getUri (), - VESLoader.wrapKey (aPublic.getResource ())); + VESLoader.createRepoStorageKey (aPublic.getResource ())); } else { final VesXsdCatalogItemSystemType aSystem = (VesXsdCatalogItemSystemType) aItem; - aEntry = new CatalogEntry (ECatalogType.SYSTEM, aSystem.getId (), VESLoader.wrapKey (aSystem.getResource ())); + aEntry = new CatalogEntry (ECatalogType.SYSTEM, + aSystem.getId (), + VESLoader.createRepoStorageKey (aSystem.getResource ())); } if (aCatalogEntries.containsKey (aEntry.getID ())) { @@ -204,7 +233,7 @@ public IValidationExecutor loadXSD (@Nonnull final IRepoS final String sResourceType = aXSD.getResource ().getType (); switch (sResourceType) { - case "xsd": + case RESOURCE_TYPE_XSD: { // Indicate a potential error if (StringHelper.hasText (aXSD.getMain ())) @@ -239,7 +268,7 @@ public IValidationExecutor loadXSD (@Nonnull final IRepoS aExecutorXSD = ValidationExecutorXSD.create (aRepoRes); break; } - case "zip": + case RESOURCE_TYPE_ZIP: { final String sMainUnified = _unifyPath (aXSD.getMain ()); if (StringHelper.hasNoText (sMainUnified)) @@ -258,7 +287,7 @@ public IValidationExecutor loadXSD (@Nonnull final IRepoS long nUnzippedLed = 0; final byte [] aBuffer = new byte [4096]; boolean bFoundMain = false; - try (ZipInputStream aZIS = new ZipInputStream (aRepoRes.getInputStream ())) + try (final ZipInputStream aZIS = new ZipInputStream (aRepoRes.getInputStream ())) { ZipEntry aEntry = null; while ((aEntry = aZIS.getNextEntry ()) != null) diff --git a/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/LoadedVES.java b/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/LoadedVES.java index c9a97c0b..064bd9c8 100644 --- a/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/LoadedVES.java +++ b/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/LoadedVES.java @@ -20,6 +20,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.NotThreadSafe; import com.helger.commons.ValueEnforcer; import com.helger.commons.annotation.Nonempty; @@ -42,6 +44,7 @@ import com.helger.phive.api.executorset.ValidationExecutorSet; import com.helger.phive.api.result.ValidationResultList; import com.helger.phive.api.source.IValidationSource; +import com.helger.phive.ves.model.v1.EVESSyntax; import com.helger.xml.namespace.MapBasedNamespaceContext; /** @@ -49,15 +52,10 @@ * * @author Philip Helger */ +@NotThreadSafe public final class LoadedVES { - public enum EVESSyntax - { - XSD, - SCHEMATRON, - EDIFACT - } - + @Immutable public static final class Header { private final VESID m_aVESID; @@ -110,6 +108,7 @@ public EVESSyntax getVESSyntax () * * @author Philip Helger */ + @Immutable public static final class Status { private final XMLOffsetDateTime m_aStatusLastMod; @@ -136,12 +135,12 @@ public XMLOffsetDateTime getStatusLastModification () return m_aStatusLastMod; } - public boolean isDTValidNow () + public boolean isDateTimeValidNow () { - return isDTValidAt (PDTFactory.getCurrentXMLOffsetDateTime ()); + return isDateTimeValidAt (PDTFactory.getCurrentXMLOffsetDateTime ()); } - public boolean isDTValidAt (@Nonnull final XMLOffsetDateTime aDT) + public boolean isDateTimeValidAt (@Nonnull final XMLOffsetDateTime aDT) { if (m_aValidFrom != null) { @@ -171,7 +170,7 @@ public boolean isExplicitlyDeprecated () public boolean isOverallValid () { - return isDTValidNow () && !isExplicitlyDeprecated (); + return isDateTimeValidNow () && !isExplicitlyDeprecated (); } @Nonnull @@ -187,6 +186,7 @@ public static Status createUndefined () * * @author Philip Helger */ + @NotThreadSafe public static final class OutputType { private final ICommonsMap m_aCustomErrorLevels = new CommonsHashMap <> (); @@ -202,6 +202,7 @@ public void addCustomErrorLevel (@Nonnull final String sID, @Nonnull final EErro * * @author Philip Helger */ + @NotThreadSafe public static final class Requirement { private final VESID m_aRequiredVESID; @@ -262,12 +263,18 @@ public boolean isStopOnError () m_aStatus = aStatus; } + /** + * @return The header of the loaded VES. Never null. + */ @Nonnull public Header getHeader () { return m_aHeader; } + /** + * @return The status information of the loaded VES. Never null. + */ @Nonnull public Status getStatus () { @@ -297,11 +304,18 @@ void setEagerRequires (@Nonnull final Requirement aRequirement, @Nonnull final L m_aRequiresLoader = null; } + /** + * @return true if a validation executor is present, + * false if not. + */ public boolean hasExecutor () { return m_aExecutor != null; } + /** + * @return The contained validation executor. May be null. + */ @Nullable public IValidationExecutor getExecutor () { @@ -319,7 +333,7 @@ private LoadedVES _getLoadedVESRequiresNotNull () LoadedVES ret = m_aLoadedRequires; if (ret == null) { - // TODO do something with the error list + // TODO do something better with the error list final ErrorList aErrorList = new ErrorList (); m_aLoadedRequires = ret = m_aRequiresLoader.deferredLoad (aErrorList); if (ret == null) @@ -353,12 +367,16 @@ private ICommonsList > _getValidationExe private boolean _isRecursivelyValid () { + // Local status first, because in case of failure, this is a quicker break + if (!m_aStatus.isOverallValid ()) + return false; + // No requirement if (m_aRequires == null) - return m_aStatus.isOverallValid (); + return true; // Requirement present - return _getLoadedVESRequiresNotNull ()._isRecursivelyValid () && m_aStatus.isOverallValid (); + return _getLoadedVESRequiresNotNull ()._isRecursivelyValid (); } public void applyValidation (@Nonnull final IValidationSource aValidationSource, @@ -369,7 +387,7 @@ public void applyValidation (@Nonnull final IValidationSource aValidationSource, ValueEnforcer.notNull (aValidationResultList, "ValidationResultList"); ValueEnforcer.notNull (aLocale, "Locale"); - if (m_aExecutor == null) + if (!hasExecutor ()) throw new VESLoadingException ("The loaded VES has no Executor Set and can therefore not be used for validating objects"); final boolean bIsValid = _isRecursivelyValid (); diff --git a/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/VESHelper.java b/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/VESHelper.java deleted file mode 100644 index 65c7ea21..00000000 --- a/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/VESHelper.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2023-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.phive.ves.engine.load; - -import java.time.Duration; -import java.util.Locale; - -import javax.annotation.Nonnull; -import javax.annotation.concurrent.Immutable; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.helger.commons.ValueEnforcer; -import com.helger.commons.error.list.ErrorList; -import com.helger.commons.timing.StopWatch; -import com.helger.diver.api.version.VESID; -import com.helger.diver.repo.IRepoStorageBase; -import com.helger.phive.api.result.ValidationResultList; -import com.helger.phive.api.source.IValidationSource; - -/** - * VES helper class - * - * @author Philip Helger - */ -@Immutable -public final class VESHelper -{ - private static final Logger LOGGER = LoggerFactory.getLogger (VESHelper.class); - - private VESHelper () - {} - - /** - * Load the validation rules from an external repository, identified by a - * VESID and apply the validation rules onto the provided data to be - * validated. All errors occurring are stored in the provided error list. - * - * @param aRepo - * Repository to load from - * @param aVESID - * The VESID of the artefacts to load - * @param aValidationSource - * The data to be validated - * @param aErrorList - * The error list to be filled, containing the loading errors. - * @return The validation result list. - * @throws VESLoadingException - * If anything goes wrong - */ - @Nonnull - public static ValidationResultList loadVESAndApplyValidation (@Nonnull final IRepoStorageBase aRepo, - @Nonnull final VESID aVESID, - @Nonnull final IValidationSource aValidationSource, - @Nonnull final ErrorList aErrorList) - { - ValueEnforcer.notNull (aRepo, "RepoChain"); - ValueEnforcer.notNull (aVESID, "VESID"); - ValueEnforcer.notNull (aValidationSource, "ValidationSource"); - ValueEnforcer.notNull (aErrorList, "ErrorList"); - - // load - final LoadedVES aLoadedVES = new VESLoader (aRepo).setUseEagerRequirementLoading (true) - .loadVESFromRepo (aVESID, aErrorList); - if (aLoadedVES == null) - throw new VESLoadingException ("Failed to load VES '" + aVESID.getAsSingleID () + "'"); - - if (LOGGER.isDebugEnabled ()) - LOGGER.debug ("Start validation of '" + aVESID.getAsSingleID () + "'"); - - // validate - final ValidationResultList aValidationResultList = new ValidationResultList (); - final Duration aDuration = StopWatch.runMeasured ( () -> { - aLoadedVES.applyValidation (aValidationSource, aValidationResultList, Locale.ENGLISH); - }); - - if (aDuration.compareTo (Duration.ofMillis (500)) > 0) - LOGGER.warn ("Finished validation of '" + aVESID.getAsSingleID () + "' after " + aDuration); - - return aValidationResultList; - } -} diff --git a/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/VESLoader.java b/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/VESLoader.java index 0c3e0555..053de774 100644 --- a/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/VESLoader.java +++ b/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/VESLoader.java @@ -16,6 +16,10 @@ */ package com.helger.phive.ves.engine.load; +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Locale; + import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -30,6 +34,7 @@ import com.helger.commons.collection.impl.ICommonsList; import com.helger.commons.collection.impl.ICommonsSet; import com.helger.commons.concurrent.SimpleReadWriteLock; +import com.helger.commons.datetime.PDTFactory; import com.helger.commons.error.SingleError; import com.helger.commons.error.level.EErrorLevel; import com.helger.commons.error.list.ErrorList; @@ -37,14 +42,17 @@ import com.helger.commons.state.ESuccess; import com.helger.commons.state.ETriState; import com.helger.commons.string.StringHelper; +import com.helger.commons.timing.StopWatch; import com.helger.diver.api.version.VESID; import com.helger.diver.repo.IRepoStorageBase; import com.helger.diver.repo.RepoStorageItem; -import com.helger.diver.repo.RepoStorageKey; +import com.helger.diver.repo.RepoStorageKeyOfArtefact; import com.helger.diver.repo.RepoStorageReadableResource; -import com.helger.phive.ves.engine.load.LoadedVES.EVESSyntax; +import com.helger.phive.api.result.ValidationResultList; +import com.helger.phive.api.source.IValidationSource; import com.helger.phive.ves.engine.load.LoadedVES.Requirement; import com.helger.phive.ves.engine.load.LoadedVES.Status; +import com.helger.phive.ves.model.v1.EVESSyntax; import com.helger.phive.ves.model.v1.VES1Marshaller; import com.helger.phive.ves.model.v1.VESStatus1Marshaller; import com.helger.phive.ves.v10.VesCustomErrorType; @@ -72,76 +80,10 @@ public final class VESLoader { public static final String FILE_EXT_VES = ".ves"; public static final String FILE_EXT_STATUS = ".status"; + public static final boolean DEFAULT_USE_EAGER_REQUIREMENT_LOADING = false; private static final Logger LOGGER = LoggerFactory.getLogger (VESLoader.class); - - @Nonnull - static VESID wrapID (@Nonnull final VesResourceType aVRT) - { - return new VESID (aVRT.getGroupId (), aVRT.getArtifactId (), aVRT.getVersion ()); - } - - @Nonnull - static RepoStorageKey wrapKey (@Nonnull final VesResourceType aVRT) - { - return RepoStorageKey.of (wrapID (aVRT), "." + aVRT.getType ()); - } - - static void wrap (@Nullable final VesNamespaceListType aNamespaces, @Nonnull final MapBasedNamespaceContext aNSCtx) - { - if (aNamespaces != null) - for (final VesNamespaceType aNamespace : aNamespaces.getNamespace ()) - { - final String sPrefix = aNamespace.getPrefix (); - if (aNamespace.isOverwrite ()) - { - // Simply overwrite - aNSCtx.addMapping (sPrefix, aNamespace.getUri ()); - } - else - { - // Error in case it already exists - if (aNSCtx.getNamespaceURI (sPrefix).length () > 0) - LOGGER.error ("The namespace prefix '" + sPrefix + "' is already mapped in the current namespace context"); - else - aNSCtx.addMapping (sPrefix, aNamespace.getUri ()); - } - } - } - - @Nonnull - private static MapBasedNamespaceContext _wrap (@Nullable final VesNamespaceListType aNamespaces) - { - final MapBasedNamespaceContext ret = new MapBasedNamespaceContext (); - wrap (aNamespaces, ret); - return ret; - } - - @Nonnull - private static EErrorLevel _wrap (@Nonnull final VesErrorLevelType e) - { - switch (e) - { - case INFO: - return EErrorLevel.INFO; - case WARN: - return EErrorLevel.WARN; - case ERROR: - return EErrorLevel.ERROR; - default: - throw new IllegalStateException ("Unsupported error level " + e); - } - } - - @Nonnull - private static LoadedVES.OutputType _wrap (@Nullable final VesOutputType aOutput) - { - final LoadedVES.OutputType ret = new LoadedVES.OutputType (); - if (aOutput != null) - for (final VesCustomErrorType aCustomError : aOutput.getCustomError ()) - ret.addCustomErrorLevel (aCustomError.getId (), _wrap (aCustomError.getLevel ())); - return ret; - } + private static final Duration DURATION_WARN = Duration.ofMillis (500); private final SimpleReadWriteLock m_aRWLock = new SimpleReadWriteLock (); private final IRepoStorageBase m_aRepo; @@ -152,7 +94,7 @@ private static LoadedVES.OutputType _wrap (@Nullable final VesOutputType aOutput @GuardedBy ("m_aRWLock") private IVESLoaderEdifact m_aLoaderEdifact = null; @GuardedBy ("m_aRWLock") - private boolean m_bUseEagerRequirementLoading = false; + private boolean m_bUseEagerRequirementLoading = DEFAULT_USE_EAGER_REQUIREMENT_LOADING; public VESLoader (@Nonnull final IRepoStorageBase aRepo) { @@ -160,12 +102,22 @@ public VESLoader (@Nonnull final IRepoStorageBase aRepo) m_aRepo = aRepo; } + /** + * @return The XSD loader to be used. May be null. + */ @Nullable public IVESLoaderXSD getLoaderXSD () { return m_aRWLock.readLockedGet ( () -> m_aLoaderXSD); } + /** + * Set the XSD loader to be used. + * + * @param aLoader + * The loader to be used. May be null. + * @return this for chaining + */ @Nonnull public VESLoader setLoaderXSD (@Nullable final IVESLoaderXSD aLoader) { @@ -173,12 +125,22 @@ public VESLoader setLoaderXSD (@Nullable final IVESLoaderXSD aLoader) return this; } + /** + * @return The Schematron loader to be used. May be null. + */ @Nullable public IVESLoaderSchematron getLoaderSchematron () { return m_aRWLock.readLockedGet ( () -> m_aLoaderSchematron); } + /** + * Set the Schematron loader to be used. + * + * @param aLoader + * The loader to be used. May be null. + * @return this for chaining + */ @Nonnull public VESLoader setLoaderSchematron (@Nullable final IVESLoaderSchematron aLoader) { @@ -186,12 +148,22 @@ public VESLoader setLoaderSchematron (@Nullable final IVESLoaderSchematron aLoad return this; } + /** + * @return The Edifact loader to be used. May be null. + */ @Nullable public IVESLoaderEdifact getLoaderEdifact () { return m_aRWLock.readLockedGet ( () -> m_aLoaderEdifact); } + /** + * Set the Edifact loader to be used. + * + * @param aLoader + * The loader to be used. May be null. + * @return this for chaining + */ @Nonnull public VESLoader setLoaderEdifact (@Nullable final IVESLoaderEdifact aLoader) { @@ -199,11 +171,23 @@ public VESLoader setLoaderEdifact (@Nullable final IVESLoaderEdifact aLoader) return this; } + /** + * @return true if eager requirement loading is enabled, + * false if lazy loading is enabled. + */ public boolean isUseEagerRequirementLoading () { return m_aRWLock.readLockedBoolean ( () -> m_bUseEagerRequirementLoading); } + /** + * User eager or lazy loading of dependent resources. + * + * @param b + * true for eager loading, false for lazy + * loading + * @return this for chaining + */ @Nonnull public VESLoader setUseEagerRequirementLoading (final boolean b) { @@ -235,11 +219,67 @@ public String getDepedencyChain (@Nullable final VESID aLastOne) } } + static void wrap (@Nullable final VesNamespaceListType aNamespaces, @Nonnull final MapBasedNamespaceContext aNSCtx) + { + if (aNamespaces != null) + for (final VesNamespaceType aNamespace : aNamespaces.getNamespace ()) + { + final String sPrefix = aNamespace.getPrefix (); + if (aNamespace.isOverwrite ()) + { + // Simply overwrite + aNSCtx.addMapping (sPrefix, aNamespace.getUri ()); + } + else + { + // Error in case it already exists + if (aNSCtx.getNamespaceURI (sPrefix).length () > 0) + LOGGER.error ("The namespace prefix '" + sPrefix + "' is already mapped in the current namespace context"); + else + aNSCtx.addMapping (sPrefix, aNamespace.getUri ()); + } + } + } + + @Nonnull + private static MapBasedNamespaceContext _wrap (@Nullable final VesNamespaceListType aNamespaces) + { + final MapBasedNamespaceContext ret = new MapBasedNamespaceContext (); + wrap (aNamespaces, ret); + return ret; + } + + @Nonnull + private static EErrorLevel _wrap (@Nonnull final VesErrorLevelType e) + { + switch (e) + { + case INFO: + return EErrorLevel.INFO; + case WARN: + return EErrorLevel.WARN; + case ERROR: + return EErrorLevel.ERROR; + default: + throw new IllegalStateException ("Unsupported error level " + e); + } + } + + @Nonnull + private static LoadedVES.OutputType _wrap (@Nullable final VesOutputType aOutput) + { + final LoadedVES.OutputType ret = new LoadedVES.OutputType (); + if (aOutput != null) + for (final VesCustomErrorType aCustomError : aOutput.getCustomError ()) + ret.addCustomErrorLevel (aCustomError.getId (), _wrap (aCustomError.getLevel ())); + return ret; + } + @Nullable public LoadedVES convertToLoadedVES (@Nonnull final LoadedVES.Status aStatus, @Nonnull final VesType aVES, @Nonnull final VESLoaderStatus aLoaderStatus, - @Nonnull final ErrorList aErrorList) + @Nonnull final ErrorList aErrorList) throws VESLoadingException { ValueEnforcer.notNull (aStatus, "Status"); ValueEnforcer.notNull (aVES, "VES"); @@ -300,7 +340,10 @@ public LoadedVES convertToLoadedVES (@Nonnull final LoadedVES.Status aStatus, if (aLoadedRequirement == null) throw new VESLoadingException ("Failed to load required VESID '" + aRequirement.getRequiredVESID ().getAsSingleID () + - "' [lazy]"); + "' [lazy]: " + + StringHelper.getImplodedMapped ("\n ", + aLocalErrorList, + x -> x.getAsString (Locale.ENGLISH))); return aLoadedRequirement; }); } @@ -404,12 +447,35 @@ public LoadedVES loadVESDirect (@Nonnull final LoadedVES.Status aStatus, return convertToLoadedVES (aStatus, aVES, aLoaderStatus, aErrorList); } + /** + * Load a VES by the provided VESID and fill all errors into the provided + * {@link ErrorList}. + * + * @param aVESID + * The VESID to load. May not be null. + * @param aErrorList + * The error list to be filled. May not be null. + * @return null if loading failed + */ @Nullable public LoadedVES loadVESFromRepo (@Nonnull final VESID aVESID, @Nonnull final ErrorList aErrorList) { return loadVESFromRepo (aVESID, new VESLoaderStatus (), aErrorList); } + /** + * Load a VES by the provided VESID and fill all errors into the provided + * {@link ErrorList}. + * + * @param aVESID + * The VESID to load. May not be null. + * @param aLoaderStatus + * The internal loader status to be used, to make sure no cycles etc. + * are contained + * @param aErrorList + * The error list to be filled. May not be null. + * @return null if loading failed + */ @Nullable public LoadedVES loadVESFromRepo (@Nonnull final VESID aVESID, @Nonnull final VESLoaderStatus aLoaderStatus, @@ -435,7 +501,7 @@ public LoadedVES loadVESFromRepo (@Nonnull final VESID aVESID, // Check if an explicit status is available final LoadedVES.Status aStatus; - final RepoStorageKey aRepoKeyStatus = RepoStorageKey.of (aVESID, FILE_EXT_STATUS); + final RepoStorageKeyOfArtefact aRepoKeyStatus = RepoStorageKeyOfArtefact.of (aVESID, FILE_EXT_STATUS); if (m_aRepo.exists (aRepoKeyStatus)) { final RepoStorageItem aRepoContentStatus = m_aRepo.read (aRepoKeyStatus); @@ -474,7 +540,7 @@ public LoadedVES loadVESFromRepo (@Nonnull final VESID aVESID, } // Read VES content from repo - final RepoStorageKey aRepoKeyVES = RepoStorageKey.of (aVESID, FILE_EXT_VES); + final RepoStorageKeyOfArtefact aRepoKeyVES = RepoStorageKeyOfArtefact.of (aVESID, FILE_EXT_VES); final RepoStorageItem aRepoContentVES = m_aRepo.read (aRepoKeyVES); if (aRepoContentVES == null) { @@ -501,4 +567,93 @@ public LoadedVES loadVESFromRepo (@Nonnull final VESID aVESID, // Now read into data model return convertToLoadedVES (aStatus, aVES, aLoaderStatus, aErrorList); } + + @Nonnull + static VESID createVESID (@Nonnull final VesResourceType aVRT) + { + // No classifier contained + return new VESID (aVRT.getGroupId (), aVRT.getArtifactId (), aVRT.getVersion ()); + } + + @Nonnull + static RepoStorageKeyOfArtefact createRepoStorageKey (@Nonnull final VesResourceType aVRT) + { + // File extension must start with a dot + return RepoStorageKeyOfArtefact.of (createVESID (aVRT), "." + aVRT.getType ()); + } + + /** + * Load the validation rules from an external repository, identified by a + * VESID and apply the validation rules onto the provided data to be + * validated. All errors occurring are stored in the provided error list. + * + * @param aRepo + * Repository to load from. Must not be null. + * @param aVESID + * The VESID of the artefacts to load. Must not be null. + * @param aValidationSource + * The data to be validated. Must not be null. + * @param aErrorList + * The error list to be filled, containing the loading errors. + * Validation errors will be returned in a separate + * {@link ValidationResultList}. Must not be null. + * @return The validation result + * @throws VESLoadingException + * If anything goes wrong + */ + @Nonnull + public static VESValidationResult loadVESAndApplyValidation (@Nonnull final IRepoStorageBase aRepo, + @Nonnull final VESID aVESID, + @Nonnull final IValidationSource aValidationSource, + @Nonnull final ErrorList aErrorList) throws VESLoadingException + { + ValueEnforcer.notNull (aRepo, "RepoChain"); + ValueEnforcer.notNull (aVESID, "VESID"); + ValueEnforcer.notNull (aValidationSource, "ValidationSource"); + ValueEnforcer.notNull (aErrorList, "ErrorList"); + + // Remember start date time + final OffsetDateTime aStartDT = PDTFactory.getCurrentOffsetDateTime (); + + // load + if (LOGGER.isDebugEnabled ()) + LOGGER.debug ("Start VES loading of '" + aVESID.getAsSingleID () + "'"); + + final StopWatch aSWLoad = StopWatch.createdStarted (); + final LoadedVES aLoadedVES = new VESLoader (aRepo).setUseEagerRequirementLoading (true) + .loadVESFromRepo (aVESID, aErrorList); + aSWLoad.stop (); + if (aLoadedVES == null) + { + throw new VESLoadingException ("Failed to load VES '" + + aVESID.getAsSingleID () + + "': " + + StringHelper.getImplodedMapped ("\n ", + aErrorList, + x -> x.getAsString (Locale.ENGLISH))); + } + + final Duration aLoadDuration = aSWLoad.getDuration (); + if (aLoadDuration.compareTo (DURATION_WARN) > 0) + LOGGER.warn ("Finished VES loading of '" + aVESID.getAsSingleID () + "' after " + aLoadDuration); + + // validate + if (LOGGER.isDebugEnabled ()) + LOGGER.debug ("Start validation of '" + aVESID.getAsSingleID () + "'"); + + final ValidationResultList aValidationResultList = new ValidationResultList (); + final Duration aValidateDuration = StopWatch.runMeasured ( () -> { + aLoadedVES.applyValidation (aValidationSource, aValidationResultList, Locale.ENGLISH); + }); + if (aValidateDuration.compareTo (DURATION_WARN) > 0) + LOGGER.warn ("Finished validation of '" + aVESID.getAsSingleID () + "' after " + aValidateDuration); + + // Collect all results + return new VESValidationResult (aVESID, + aValidationSource, + aStartDT, + aLoadDuration, + aValidateDuration, + aValidationResultList); + } } diff --git a/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/VESValidationResult.java b/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/VESValidationResult.java new file mode 100644 index 00000000..00913f35 --- /dev/null +++ b/phive-ves-engine/src/main/java/com/helger/phive/ves/engine/load/VESValidationResult.java @@ -0,0 +1,105 @@ +package com.helger.phive.ves.engine.load; + +import java.time.Duration; +import java.time.OffsetDateTime; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.NotThreadSafe; + +import com.helger.commons.ValueEnforcer; +import com.helger.commons.annotation.ReturnsMutableObject; +import com.helger.diver.api.version.VESID; +import com.helger.phive.api.result.ValidationResultList; +import com.helger.phive.api.source.IValidationSource; + +/** + * This class contains the collected validation results, including metadata on + * the execution. + * + * @author Philip Helger + */ +@NotThreadSafe +public final class VESValidationResult +{ + private final VESID m_aVESID; + private final IValidationSource m_aValidationSource; + private final OffsetDateTime m_aStartDateTime; + private final Duration m_aLoadingDuration; + private final Duration m_aValidationDuration; + private final ValidationResultList m_aValidationResultList; + + /** + * Constructor + * + * @param aVESID + * The VESID that was validated. May not be null. + * @param aValidationSource + * The source that was validated. May not be null. + * @param aStartDateTime + * When did it all start. This is the timestamp when loading begins. + * May not be null. + * @param aLoadingDuration + * The loading duration. May not be null. + * @param aValidationDuration + * The duration of the validation itself. May not be null. + * @param aValidationResultList + * The validation result list. May not be null. + */ + public VESValidationResult (@Nonnull final VESID aVESID, + @Nonnull final IValidationSource aValidationSource, + @Nonnull final OffsetDateTime aStartDateTime, + @Nonnull final Duration aLoadingDuration, + @Nonnull final Duration aValidationDuration, + @Nonnull final ValidationResultList aValidationResultList) + { + ValueEnforcer.notNull (aVESID, "VESID"); + ValueEnforcer.notNull (aValidationSource, "ValidationSource"); + ValueEnforcer.notNull (aStartDateTime, "StartDateTime"); + ValueEnforcer.notNull (aLoadingDuration, "LoadingDuration"); + ValueEnforcer.notNull (aValidationDuration, "ValidationDuration"); + ValueEnforcer.notNull (aValidationResultList, "ValidationResultList"); + m_aVESID = aVESID; + m_aValidationSource = aValidationSource; + m_aStartDateTime = aStartDateTime; + m_aLoadingDuration = aLoadingDuration; + m_aValidationDuration = aValidationDuration; + m_aValidationResultList = aValidationResultList; + } + + @Nonnull + public VESID getVESID () + { + return m_aVESID; + } + + @Nonnull + public IValidationSource getValidationSource () + { + return m_aValidationSource; + } + + @Nonnull + public OffsetDateTime getStartDateTime () + { + return m_aStartDateTime; + } + + @Nonnull + public Duration getLoadingDuration () + { + return m_aLoadingDuration; + } + + @Nonnull + public Duration getValidationDuration () + { + return m_aValidationDuration; + } + + @Nonnull + @ReturnsMutableObject + public ValidationResultList getValidationResultList () + { + return m_aValidationResultList; + } +} diff --git a/phive-ves-engine/src/test/java/com/helger/phive/ves/engine/ves/VESHelperTest.java b/phive-ves-engine/src/test/java/com/helger/phive/ves/engine/load/VESLoaderTest.java similarity index 85% rename from phive-ves-engine/src/test/java/com/helger/phive/ves/engine/ves/VESHelperTest.java rename to phive-ves-engine/src/test/java/com/helger/phive/ves/engine/load/VESLoaderTest.java index 91d12a2b..b70fbd26 100644 --- a/phive-ves-engine/src/test/java/com/helger/phive/ves/engine/ves/VESHelperTest.java +++ b/phive-ves-engine/src/test/java/com/helger/phive/ves/engine/load/VESLoaderTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.helger.phive.ves.engine.ves; +package com.helger.phive.ves.engine.load; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -40,69 +40,65 @@ import com.helger.diver.api.version.VESID; import com.helger.diver.repo.ERepoDeletable; import com.helger.diver.repo.ERepoWritable; +import com.helger.diver.repo.IRepoStorage; import com.helger.diver.repo.IRepoStorageBase; -import com.helger.diver.repo.RepoStorageChain; import com.helger.diver.repo.RepoStorageItem; -import com.helger.diver.repo.RepoStorageKey; +import com.helger.diver.repo.RepoStorageKeyOfArtefact; import com.helger.diver.repo.impl.RepoStorageInMemory; import com.helger.phive.api.result.ValidationResultList; -import com.helger.phive.ves.engine.load.LoadedVES; import com.helger.phive.ves.engine.load.LoadedVES.Status; -import com.helger.phive.ves.engine.load.VESHelper; -import com.helger.phive.ves.engine.load.VESLoader; import com.helger.phive.ves.engine.load.VESLoader.VESLoaderStatus; -import com.helger.phive.ves.engine.load.VESLoadingException; import com.helger.phive.ves.model.v1.VES1Marshaller; import com.helger.phive.ves.v10.VesType; import com.helger.phive.xml.source.IValidationSourceXML; import com.helger.phive.xml.source.ValidationSourceXML; -public final class VESHelperTest +public final class VESLoaderTest { - private static final Logger LOGGER = LoggerFactory.getLogger (VESHelperTest.class); + private static final Logger LOGGER = LoggerFactory.getLogger (VESLoaderTest.class); private static IRepoStorageBase s_aRepoStorage; - private static void _addResource (@Nonnull final RepoStorageInMemory aInMemoryRepo, + private static void _addResource (@Nonnull final IRepoStorage aRepo, @Nonnull final VESID aVESID, - @Nonnull @Nonempty final IReadableResource aPayload) + @Nonnull @Nonempty final IReadableResource aRulesPayload) { // Create StorageKey - final String sFileExt = "." + FilenameHelper.getExtension (aPayload.getPath ()); - final RepoStorageKey aKey = RepoStorageKey.of (aVESID, sFileExt); + final String sFileExt = "." + FilenameHelper.getExtension (aRulesPayload.getPath ()); + final RepoStorageKeyOfArtefact aKey = RepoStorageKeyOfArtefact.of (aVESID, sFileExt); // Read in the ves file from resources - final byte [] aData = StreamHelper.getAllBytes (aPayload); + final byte [] aData = StreamHelper.getAllBytes (aRulesPayload); assertNotNull (aData); - if (aInMemoryRepo.exists (aKey)) + if (aRepo.exists (aKey)) throw new IllegalStateException ("The key '" + aKey.getPath () + "' is already in the Repo"); // Write data to InMemoryRepo - final ESuccess eSuccess = aInMemoryRepo.write (aKey, RepoStorageItem.of (aData)); + final ESuccess eSuccess = aRepo.write (aKey, RepoStorageItem.of (aData)); assertNotNull (eSuccess); assertTrue (eSuccess.isSuccess ()); } - private static void _addVES (@Nonnull final RepoStorageInMemory aInMemoryRepo, - @Nonnull @Nonempty final IReadableResource aPayload) + private static void _addVES (@Nonnull final IRepoStorage aRepo, + @Nonnull @Nonempty final IReadableResource aVesPayload) { final ErrorList aErrorList = new ErrorList (); // Read VES as XML - final VesType aVES = new VES1Marshaller ().setCollectErrors (aErrorList).read (aPayload); + final VesType aVES = new VES1Marshaller ().setCollectErrors (aErrorList).read (aVesPayload); assertNotNull (aVES); // Convert to loaded VES - checking that it is okay - final LoadedVES aLoadedVES = new VESLoader (aInMemoryRepo).setUseEagerRequirementLoading (false) - .convertToLoadedVES (Status.createUndefined (), - aVES, - new VESLoaderStatus (), - aErrorList); + final LoadedVES aLoadedVES = new VESLoader (aRepo).setUseEagerRequirementLoading (false) + .convertToLoadedVES (Status.createUndefined (), + aVES, + new VESLoaderStatus (), + aErrorList); assertNotNull (aLoadedVES); assertEquals (aErrorList.getAllFailures ().toString (), 0, aErrorList.size ()); // Take VESID from the inside - _addResource (aInMemoryRepo, aLoadedVES.getHeader ().getVESID (), aPayload); + _addResource (aRepo, aLoadedVES.getHeader ().getVESID (), aVesPayload); } @BeforeClass @@ -155,8 +151,8 @@ public static void beforeClass () _addVES (aInMemoryRepo, new ClassPathResource ("ves/test3/xsd2.ves")); } - // Create RepoStorageChain with InMemoryRepo and return it - s_aRepoStorage = RepoStorageChain.of (aInMemoryRepo); + // No chain needed - use repo as is + s_aRepoStorage = aInMemoryRepo; } @Test @@ -167,10 +163,11 @@ public void test1LoadXSD1 () assertNotNull (aValidationSource.getNode ()); final ErrorList aErrorList = new ErrorList (); - final ValidationResultList aValidationResultList = VESHelper.loadVESAndApplyValidation (s_aRepoStorage, + final ValidationResultList aValidationResultList = VESLoader.loadVESAndApplyValidation (s_aRepoStorage, aVESID, aValidationSource, - aErrorList); + aErrorList) + .getValidationResultList (); assertEquals (aErrorList.toString (), 0, aErrorList.size ()); assertNotNull (aValidationResultList); @@ -186,10 +183,11 @@ public void test1LoadSCH1 () assertNotNull (aValidationSource.getNode ()); final ErrorList aErrorList = new ErrorList (); - final ValidationResultList aValidationResultList = VESHelper.loadVESAndApplyValidation (s_aRepoStorage, + final ValidationResultList aValidationResultList = VESLoader.loadVESAndApplyValidation (s_aRepoStorage, aVESID, aValidationSource, - aErrorList); + aErrorList) + .getValidationResultList (); assertEquals (aErrorList.toString (), 0, aErrorList.size ()); assertNotNull (aValidationResultList); @@ -205,10 +203,11 @@ public void test1LoadSCH2Valid () assertNotNull (aValidationSource.getNode ()); final ErrorList aErrorList = new ErrorList (); - final ValidationResultList aValidationResultList = VESHelper.loadVESAndApplyValidation (s_aRepoStorage, + final ValidationResultList aValidationResultList = VESLoader.loadVESAndApplyValidation (s_aRepoStorage, aVESID, aValidationSource, - aErrorList); + aErrorList) + .getValidationResultList (); assertEquals (aErrorList.toString (), 0, aErrorList.size ()); assertNotNull (aValidationResultList); @@ -225,10 +224,11 @@ public void test1LoadSCH3Valid () assertNotNull (aValidationSource.getNode ()); final ErrorList aErrorList = new ErrorList (); - final ValidationResultList aValidationResultList = VESHelper.loadVESAndApplyValidation (s_aRepoStorage, + final ValidationResultList aValidationResultList = VESLoader.loadVESAndApplyValidation (s_aRepoStorage, aVESID, aValidationSource, - aErrorList); + aErrorList) + .getValidationResultList (); assertEquals (aErrorList.toString (), 0, aErrorList.size ()); assertNotNull (aValidationResultList); @@ -246,10 +246,11 @@ public void test2LoadXSD1 () assertNotNull (aValidationSource.getNode ()); final ErrorList aErrorList = new ErrorList (); - final ValidationResultList aValidationResultList = VESHelper.loadVESAndApplyValidation (s_aRepoStorage, + final ValidationResultList aValidationResultList = VESLoader.loadVESAndApplyValidation (s_aRepoStorage, aVESID, aValidationSource, - aErrorList); + aErrorList) + .getValidationResultList (); assertEquals (aErrorList.toString (), 0, aErrorList.size ()); assertNotNull (aValidationResultList); diff --git a/phive-ves-model/pom.xml b/phive-ves-model/pom.xml index 7b37013d..92f907a6 100644 --- a/phive-ves-model/pom.xml +++ b/phive-ves-model/pom.xml @@ -22,7 +22,7 @@ com.helger.phive phive-parent-pom - 9.0.1-SNAPSHOT + 9.1.0-SNAPSHOT phive-ves-model bundle diff --git a/phive-ves-model/src/main/java/com/helger/phive/ves/model/v1/EVESSyntax.java b/phive-ves-model/src/main/java/com/helger/phive/ves/model/v1/EVESSyntax.java new file mode 100644 index 00000000..f35c145d --- /dev/null +++ b/phive-ves-model/src/main/java/com/helger/phive/ves/model/v1/EVESSyntax.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023-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.phive.ves.model.v1; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.helger.commons.annotation.Nonempty; +import com.helger.commons.id.IHasID; +import com.helger.commons.lang.EnumHelper; + +/** + * Contains the overall supported syntaxes for the VES model + * + * @author Philip Helger + */ +public enum EVESSyntax implements IHasID +{ + /** + * XML Schema + */ + XSD ("xsd"), + /** + * Schematron + */ + SCHEMATRON ("schematron"), + /** + * Edifact + */ + EDIFACT ("edifact"); + + private final String m_sID; + + EVESSyntax (@Nonnull @Nonempty final String sID) + { + m_sID = sID; + } + + @Nonnull + @Nonempty + public String getID () + { + return m_sID; + } + + @Nullable + public static EVESSyntax getFromIDOrNull (@Nullable final String sID) + { + return EnumHelper.getFromIDOrNull (EVESSyntax.class, sID); + } +} diff --git a/phive-xml/pom.xml b/phive-xml/pom.xml index a58f571b..631638dd 100644 --- a/phive-xml/pom.xml +++ b/phive-xml/pom.xml @@ -22,7 +22,7 @@ com.helger.phive phive-parent-pom - 9.0.1-SNAPSHOT + 9.1.0-SNAPSHOT phive-xml bundle diff --git a/pom.xml b/pom.xml index 35fd0344..4aabe12e 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ com.helger.phive phive-parent-pom - 9.0.1-SNAPSHOT + 9.1.0-SNAPSHOT pom phive-parent-pom Base POM to build phive - the Philip Helger Integrative Validation Engine @@ -95,7 +95,7 @@ com.helger.diver ph-diver-parent-pom - 1.0.2 + 1.1.0-SNAPSHOT pom import