From fa0d7619769e14a2464cc6d59bba4f6f7bc284b4 Mon Sep 17 00:00:00 2001 From: Philip Helger Date: Wed, 27 Nov 2024 22:01:45 +0100 Subject: [PATCH] Added XML serialization of results --- README.md | 2 + .../phive/result/PhiveResultHelper.java | 201 ++++++ .../exception/PhiveRestoredException.java | 84 ++- .../phive/result/json/JsonErrorBuilder.java | 3 +- .../json/JsonValidationResultListHelper.java | 15 +- .../phive/result/json/PhiveJsonHelper.java | 127 ++-- .../phive/result/xml/PhiveXMLHelper.java | 676 ++++++++++++++++++ .../phive/result/xml/XMLErrorBuilder.java | 184 +++++ .../xml/XMLValidationResultListHelper.java | 264 +++++++ .../phive/result/xml/PhiveXMLHelperTest.java | 248 +++++++ .../ValidationExecutionManagerFuncTest.java | 16 + 11 files changed, 1718 insertions(+), 102 deletions(-) create mode 100644 phive-result/src/main/java/com/helger/phive/result/PhiveResultHelper.java create mode 100644 phive-result/src/main/java/com/helger/phive/result/xml/PhiveXMLHelper.java create mode 100644 phive-result/src/main/java/com/helger/phive/result/xml/XMLErrorBuilder.java create mode 100644 phive-result/src/main/java/com/helger/phive/result/xml/XMLValidationResultListHelper.java create mode 100644 phive-result/src/test/java/com/helger/phive/result/xml/PhiveXMLHelperTest.java diff --git a/README.md b/README.md index 42559016..07920dc5 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,8 @@ Please ensure that your stack size is at least 1MB (for Saxon). Using the Oracle # News and noteworthy +* v10.0.3 - work in progress + * Added default XML serialization of validation results * v10.0.2 - 2024-09-16 * Re-release due to Maven Central issue * v10.0.1 - 2024-09-16 diff --git a/phive-result/src/main/java/com/helger/phive/result/PhiveResultHelper.java b/phive-result/src/main/java/com/helger/phive/result/PhiveResultHelper.java new file mode 100644 index 00000000..1c0ec389 --- /dev/null +++ b/phive-result/src/main/java/com/helger/phive/result/PhiveResultHelper.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2014-2024 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phive.result; + +import java.net.MalformedURLException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import com.helger.commons.ValueEnforcer; +import com.helger.commons.annotation.Nonempty; +import com.helger.commons.error.level.EErrorLevel; +import com.helger.commons.error.level.IErrorLevel; +import com.helger.commons.io.resource.ClassPathResource; +import com.helger.commons.io.resource.FileSystemResource; +import com.helger.commons.io.resource.IReadableResource; +import com.helger.commons.io.resource.URLResource; +import com.helger.commons.io.resource.inmemory.IMemoryReadableResource; +import com.helger.commons.io.resource.wrapped.IWrappedReadableResource; +import com.helger.commons.state.ETriState; +import com.helger.commons.string.StringHelper; + +/** + * Contains stateless phive result helper methods. + * + * @author Philip Helger + */ +@Immutable +public final class PhiveResultHelper +{ + public static final String VALUE_ERRORLEVEL_SUCCESS = "SUCCESS"; + public static final String VALUE_ERRORLEVEL_WARN = "WARN"; + public static final String VALUE_ERRORLEVEL_ERROR = "ERROR"; + + public static final String VALUE_TRISTATE_TRUE = "TRUE"; + public static final String VALUE_TRISTATE_FALSE = "FALSE"; + public static final String VALUE_TRISTATE_UNDEFINED = "UNDEFINED"; + + private PhiveResultHelper () + {} + + public static boolean isConsideredError (@Nonnull final IErrorLevel aErrorLevel) + { + return aErrorLevel.isGE (EErrorLevel.ERROR); + } + + public static boolean isConsideredWarning (@Nonnull final IErrorLevel aErrorLevel) + { + return aErrorLevel.isGE (EErrorLevel.WARN); + } + + /** + * Get the string of the error level. One of "ERROR", + * "WARN" or "SUCCESS".
+ * See {@link #VALUE_ERRORLEVEL_SUCCESS}, {@link #VALUE_ERRORLEVEL_WARN}, + * {@link #VALUE_ERRORLEVEL_ERROR} + * + * @param aErrorLevel + * The error level to convert. May not be null. + * @return A non-null value string. + */ + @Nonnull + @Nonempty + public static String getErrorLevelValue (@Nonnull final IErrorLevel aErrorLevel) + { + ValueEnforcer.notNull (aErrorLevel, "ErrorLevel"); + + if (PhiveResultHelper.isConsideredError (aErrorLevel)) + return VALUE_ERRORLEVEL_ERROR; + if (PhiveResultHelper.isConsideredWarning (aErrorLevel)) + return VALUE_ERRORLEVEL_WARN; + return VALUE_ERRORLEVEL_SUCCESS; + } + + @Nullable + public static IErrorLevel getAsErrorLevel (@Nullable final String sErrorLevel) + { + if (VALUE_ERRORLEVEL_ERROR.equals (sErrorLevel)) + return EErrorLevel.ERROR; + if (VALUE_ERRORLEVEL_WARN.equals (sErrorLevel)) + return EErrorLevel.WARN; + if (VALUE_ERRORLEVEL_SUCCESS.equals (sErrorLevel)) + return EErrorLevel.SUCCESS; + return null; + } + + /** + * Get the tri-state representation of the provided value. Either + * {@link #VALUE_TRISTATE_TRUE} or {@link #VALUE_TRISTATE_FALSE}. + * + * @param b + * boolean value to get converted. + * @return A non-null value string. + * @see #getTriStateValue(ETriState) + */ + @Nonnull + @Nonempty + public static String getTriStateValue (final boolean b) + { + return b ? VALUE_TRISTATE_TRUE : VALUE_TRISTATE_FALSE; + } + + /** + * Get the tri-state representation of the provided value. Either + * {@link #VALUE_TRISTATE_TRUE}, {@link #VALUE_TRISTATE_FALSE} or + * {@link #VALUE_TRISTATE_UNDEFINED}. + * + * @param eTriState + * Tri-state value to get converted. May not be null. + * @return A non-null value string. + * @see #getTriStateValue(boolean) + */ + @Nonnull + public static String getTriStateValue (@Nonnull final ETriState eTriState) + { + ValueEnforcer.notNull (eTriState, "TriState"); + + if (eTriState.isUndefined ()) + return VALUE_TRISTATE_UNDEFINED; + return getTriStateValue (eTriState.isTrue ()); + } + + /** + * Convert the provided value into a tri-state value. Must be one of + * {@link #VALUE_TRISTATE_TRUE}, {@link #VALUE_TRISTATE_FALSE} or + * {@link #VALUE_TRISTATE_UNDEFINED}. + * + * @param sTriState + * Source value. May be null. + * @return null if the provided value is unknown. + */ + @Nullable + public static ETriState getAsTriState (@Nullable final String sTriState) + { + if (VALUE_TRISTATE_TRUE.equals (sTriState)) + return ETriState.TRUE; + if (VALUE_TRISTATE_FALSE.equals (sTriState)) + return ETriState.FALSE; + if (VALUE_TRISTATE_UNDEFINED.equals (sTriState)) + return ETriState.UNDEFINED; + return null; + } + + @Nonnull + @Nonempty + public static String getArtifactPathType (@Nonnull final IReadableResource aRes) + { + if (aRes instanceof ClassPathResource) + return "classpath"; + if (aRes instanceof FileSystemResource) + return "file"; + if (aRes instanceof URLResource) + return "url"; + if (aRes instanceof IMemoryReadableResource) + return "in-memory"; + if (aRes instanceof IWrappedReadableResource) + return "wrapped"; + return "unknown"; + } + + @Nullable + public static IReadableResource getAsValidationResource (@Nullable final String sArtefactPathType, + @Nullable final String sArtefactPath) + { + if (StringHelper.hasNoText (sArtefactPathType)) + return null; + if (StringHelper.hasNoText (sArtefactPath)) + return null; + + if ("file".equals (sArtefactPathType)) + return new FileSystemResource (sArtefactPath); + + if ("url".equals (sArtefactPathType)) + try + { + return new URLResource (sArtefactPath); + } + catch (final MalformedURLException ex) + { + return null; + } + + // Default to class path + return new ClassPathResource (sArtefactPath); + } +} diff --git a/phive-result/src/main/java/com/helger/phive/result/exception/PhiveRestoredException.java b/phive-result/src/main/java/com/helger/phive/result/exception/PhiveRestoredException.java index 019dfdde..0cdb2082 100644 --- a/phive-result/src/main/java/com/helger/phive/result/exception/PhiveRestoredException.java +++ b/phive-result/src/main/java/com/helger/phive/result/exception/PhiveRestoredException.java @@ -25,6 +25,9 @@ import com.helger.commons.string.StringHelper; import com.helger.json.IJsonObject; import com.helger.json.JsonObject; +import com.helger.xml.microdom.IMicroElement; +import com.helger.xml.microdom.MicroElement; +import com.helger.xml.microdom.util.MicroHelper; /** * This is a work around to read "exceptions" from external sources (like JSON) @@ -36,9 +39,26 @@ */ public class PhiveRestoredException extends Exception { - public static final String JSON_CLASS = "class"; - public static final String JSON_MESSAGE = "message"; - public static final String JSON_STACK_TRACE = "stackTrace"; + public static final String FIELD_CLASS = "class"; + /** + * @deprecated Use {@link #FIELD_CLASS} instead + */ + @Deprecated (forRemoval = true, since = "10.0.3") + public static final String JSON_CLASS = FIELD_CLASS; + + public static final String FIELD_MESSAGE = "message"; + /** + * @deprecated Use {@link #FIELD_MESSAGE} instead + */ + @Deprecated (forRemoval = true, since = "10.0.3") + public static final String JSON_MESSAGE = FIELD_MESSAGE; + + public static final String FIELD_STACK_TRACE = "stackTrace"; + /** + * @deprecated Use {@link #FIELD_STACK_TRACE} instead + */ + @Deprecated (forRemoval = true, since = "10.0.3") + public static final String JSON_STACK_TRACE = FIELD_STACK_TRACE; private final String m_sClassName; private final String m_sMessage; @@ -88,12 +108,40 @@ public ICommonsList getAllStackTraceLines () return m_aStackTraceLines.getClone (); } + @Nullable + public static IJsonObject getAsJson (@Nonnull final String sClassName, + @Nullable final String sMessage, + @Nonnull final String sStackTraceLines) + { + return new JsonObject ().add (FIELD_CLASS, sClassName) + .addIfNotNull (FIELD_MESSAGE, sMessage) + .add (FIELD_STACK_TRACE, sStackTraceLines); + } + @Nullable public IJsonObject getAsJson () { - return new JsonObject ().add (JSON_CLASS, m_sClassName) - .addIfNotNull (JSON_MESSAGE, m_sMessage) - .add (JSON_STACK_TRACE, StringHelper.getImploded ('\n', m_aStackTraceLines)); + return getAsJson (m_sClassName, m_sMessage, StringHelper.getImploded ('\n', m_aStackTraceLines)); + } + + @Nullable + public static IMicroElement getAsXML (@Nonnull final String sElementName, + @Nonnull final String sClassName, + @Nullable final String sMessage, + @Nonnull final String sStackTraceLines) + { + final IMicroElement ret = new MicroElement (sElementName); + ret.appendElement (FIELD_CLASS).appendText (sClassName); + if (sMessage != null) + ret.appendElement (FIELD_MESSAGE).appendText (sMessage); + ret.appendElement (FIELD_STACK_TRACE).appendText (sStackTraceLines); + return ret; + } + + @Nullable + public IMicroElement getAsXML (@Nonnull final String sElementName) + { + return getAsXML (sElementName, m_sClassName, m_sMessage, StringHelper.getImploded ('\n', m_aStackTraceLines)); } @Nullable @@ -102,9 +150,27 @@ public static PhiveRestoredException createFromJson (@Nullable final IJsonObject if (aObj == null) return null; - final String sClassName = aObj.getAsString (JSON_CLASS); - final String sMessage = aObj.getAsString (JSON_MESSAGE); - final ICommonsList aStackTraceLines = StringHelper.getExploded ('\n', aObj.getAsString (JSON_STACK_TRACE)); + final String sClassName = aObj.getAsString (FIELD_CLASS); + final String sMessage = aObj.getAsString (FIELD_MESSAGE); + final ICommonsList aStackTraceLines = StringHelper.getExploded ('\n', + aObj.getAsString (FIELD_STACK_TRACE)); + if (sClassName == null) + return null; + + return new PhiveRestoredException (sClassName, sMessage, aStackTraceLines); + } + + @Nullable + public static PhiveRestoredException createFromXML (@Nullable final IMicroElement aObj) + { + if (aObj == null) + return null; + + final String sClassName = MicroHelper.getChildTextContentTrimmed (aObj, FIELD_CLASS); + final String sMessage = MicroHelper.getChildTextContentTrimmed (aObj, FIELD_MESSAGE); + final ICommonsList aStackTraceLines = StringHelper.getExploded ('\n', + MicroHelper.getChildTextContentTrimmed (aObj, + FIELD_STACK_TRACE)); if (sClassName == null) return null; diff --git a/phive-result/src/main/java/com/helger/phive/result/json/JsonErrorBuilder.java b/phive-result/src/main/java/com/helger/phive/result/json/JsonErrorBuilder.java index 7e8b2c98..5b4a7d68 100644 --- a/phive-result/src/main/java/com/helger/phive/result/json/JsonErrorBuilder.java +++ b/phive-result/src/main/java/com/helger/phive/result/json/JsonErrorBuilder.java @@ -30,6 +30,7 @@ import com.helger.commons.location.ILocation; import com.helger.json.IJsonObject; import com.helger.json.JsonObject; +import com.helger.phive.result.PhiveResultHelper; /** * A flexible builder that can be used to convert data from an {@link IError} @@ -42,7 +43,7 @@ public class JsonErrorBuilder implements IBuilder { private LocalDateTime m_aErrorDateTime; private IErrorLevel m_aErrorLevel; - private Function m_aErrorLevelToJson = PhiveJsonHelper::getJsonErrorLevel; + private Function m_aErrorLevelToJson = PhiveResultHelper::getErrorLevelValue; private String m_sErrorID; private String m_sErrorFieldName; private ILocation m_aErrorLocation; diff --git a/phive-result/src/main/java/com/helger/phive/result/json/JsonValidationResultListHelper.java b/phive-result/src/main/java/com/helger/phive/result/json/JsonValidationResultListHelper.java index 8d133285..914e7dce 100644 --- a/phive-result/src/main/java/com/helger/phive/result/json/JsonValidationResultListHelper.java +++ b/phive-result/src/main/java/com/helger/phive/result/json/JsonValidationResultListHelper.java @@ -39,6 +39,7 @@ import com.helger.phive.api.executorset.IValidationExecutorSet; import com.helger.phive.api.result.ValidationResult; import com.helger.phive.api.validity.EExtendedValidity; +import com.helger.phive.result.PhiveResultHelper; /** * A helper class that allows to heavily customize the creation of validation @@ -51,8 +52,8 @@ public class JsonValidationResultListHelper { private IValidationExecutorSet m_aVES; private Function , IJsonObject> m_aVESToJson = PhiveJsonHelper::getJsonVES; - private Function m_aArtifactPathTypeToJson = PhiveJsonHelper::getArtifactPathType; - private Function m_aErrorLevelToJson = PhiveJsonHelper::getJsonErrorLevel; + private Function m_aArtifactPathTypeToJson = PhiveResultHelper::getArtifactPathType; + private Function m_aErrorLevelToJson = PhiveResultHelper::getErrorLevelValue; private BiFunction m_aErrorToJson = PhiveJsonHelper::getJsonError; private MutableInt m_aWarningCount; private MutableInt m_aErrorCount; @@ -121,7 +122,7 @@ public JsonValidationResultListHelper errorCount (@Nullable final MutableInt a) * "interrupted" : boolean, * "mostSevereErrorLevel" : string, * "results" : array { - * "success" : string, // as defined by {@link PhiveJsonHelper#getJsonTriState(ETriState)} + * "success" : string, // as defined by {@link PhiveResultHelper#getTriStateValue(ETriState)} * "artifactType" : string, * "artifactPathType" : string?, * "artifactPath" : string, @@ -171,13 +172,13 @@ public void applyTo (@Nonnull final IJsonObject aResponse, if (aVR.isSkipped ()) { bValidationInterrupted = true; - aVRT.add (PhiveJsonHelper.JSON_SUCCESS, PhiveJsonHelper.getJsonTriState (ETriState.UNDEFINED)); + aVRT.add (PhiveJsonHelper.JSON_SUCCESS, PhiveResultHelper.getTriStateValue (ETriState.UNDEFINED)); } else { // Backwards compatible decision final boolean bIsValid = aVR.getErrorList ().containsNoError (); - aVRT.add (PhiveJsonHelper.JSON_SUCCESS, PhiveJsonHelper.getJsonTriState (bIsValid)); + aVRT.add (PhiveJsonHelper.JSON_SUCCESS, PhiveResultHelper.getTriStateValue (bIsValid)); if (!bIsValid) eWorstValidity = EExtendedValidity.INVALID; } @@ -193,10 +194,10 @@ public void applyTo (@Nonnull final IJsonObject aResponse, if (aError.getErrorLevel ().isGT (aMostSevere)) aMostSevere = aError.getErrorLevel (); - if (PhiveJsonHelper.isConsideredError (aError.getErrorLevel ())) + if (PhiveResultHelper.isConsideredError (aError.getErrorLevel ())) nErrors++; else - if (PhiveJsonHelper.isConsideredWarning (aError.getErrorLevel ())) + if (PhiveResultHelper.isConsideredWarning (aError.getErrorLevel ())) nWarnings++; if (m_aErrorToJson != null) diff --git a/phive-result/src/main/java/com/helger/phive/result/json/PhiveJsonHelper.java b/phive-result/src/main/java/com/helger/phive/result/json/PhiveJsonHelper.java index dc6b7f92..cb41f6f0 100644 --- a/phive-result/src/main/java/com/helger/phive/result/json/PhiveJsonHelper.java +++ b/phive-result/src/main/java/com/helger/phive/result/json/PhiveJsonHelper.java @@ -16,7 +16,6 @@ */ package com.helger.phive.result.json; -import java.net.MalformedURLException; import java.time.LocalDateTime; import java.util.List; import java.util.Locale; @@ -39,12 +38,7 @@ import com.helger.commons.error.level.IErrorLevel; import com.helger.commons.error.list.ErrorList; import com.helger.commons.error.text.ConstantHasErrorText; -import com.helger.commons.io.resource.ClassPathResource; -import com.helger.commons.io.resource.FileSystemResource; import com.helger.commons.io.resource.IReadableResource; -import com.helger.commons.io.resource.URLResource; -import com.helger.commons.io.resource.inmemory.IMemoryReadableResource; -import com.helger.commons.io.resource.wrapped.IWrappedReadableResource; import com.helger.commons.lang.StackTraceHelper; import com.helger.commons.location.ILocation; import com.helger.commons.location.SimpleLocation; @@ -68,6 +62,7 @@ import com.helger.phive.api.result.ValidationResultList; import com.helger.phive.api.source.IValidationSource; import com.helger.phive.api.validity.EExtendedValidity; +import com.helger.phive.result.PhiveResultHelper; import com.helger.phive.result.exception.PhiveRestoredException; import com.helger.schematron.svrl.SVRLResourceError; @@ -83,13 +78,19 @@ @Immutable public final class PhiveJsonHelper { - public static final String JSON_ERRORLEVEL_SUCCESS = "SUCCESS"; - public static final String JSON_ERRORLEVEL_WARN = "WARN"; - public static final String JSON_ERRORLEVEL_ERROR = "ERROR"; - - public static final String JSON_TRISTATE_TRUE = "TRUE"; - public static final String JSON_TRISTATE_FALSE = "FALSE"; - public static final String JSON_TRISTATE_UNDEFINED = "UNDEFINED"; + @Deprecated (forRemoval = true, since = "10.0.3") + public static final String JSON_ERRORLEVEL_SUCCESS = PhiveResultHelper.VALUE_ERRORLEVEL_SUCCESS; + @Deprecated (forRemoval = true, since = "10.0.3") + public static final String JSON_ERRORLEVEL_WARN = PhiveResultHelper.VALUE_ERRORLEVEL_WARN; + @Deprecated (forRemoval = true, since = "10.0.3") + public static final String JSON_ERRORLEVEL_ERROR = PhiveResultHelper.VALUE_ERRORLEVEL_ERROR; + + @Deprecated (forRemoval = true, since = "10.0.3") + public static final String JSON_TRISTATE_TRUE = PhiveResultHelper.VALUE_TRISTATE_TRUE; + @Deprecated (forRemoval = true, since = "10.0.3") + public static final String JSON_TRISTATE_FALSE = PhiveResultHelper.VALUE_TRISTATE_FALSE; + @Deprecated (forRemoval = true, since = "10.0.3") + public static final String JSON_TRISTATE_UNDEFINED = PhiveResultHelper.VALUE_TRISTATE_UNDEFINED; public static final String JSON_RESOURCE_ID = "resource"; public static final String JSON_LINE_NUM = "line"; @@ -135,14 +136,16 @@ public final class PhiveJsonHelper private PhiveJsonHelper () {} + @Deprecated (forRemoval = true, since = "10.0.3") public static boolean isConsideredError (@Nonnull final IErrorLevel aErrorLevel) { - return aErrorLevel.isGE (EErrorLevel.ERROR); + return PhiveResultHelper.isConsideredError (aErrorLevel); } + @Deprecated (forRemoval = true, since = "10.0.3") public static boolean isConsideredWarning (@Nonnull final IErrorLevel aErrorLevel) { - return aErrorLevel.isGE (EErrorLevel.WARN); + return PhiveResultHelper.isConsideredWarning (aErrorLevel); } /** @@ -157,27 +160,17 @@ public static boolean isConsideredWarning (@Nonnull final IErrorLevel aErrorLeve */ @Nonnull @Nonempty + @Deprecated (forRemoval = true, since = "10.0.3") public static String getJsonErrorLevel (@Nonnull final IErrorLevel aErrorLevel) { - ValueEnforcer.notNull (aErrorLevel, "ErrorLevel"); - - if (isConsideredError (aErrorLevel)) - return JSON_ERRORLEVEL_ERROR; - if (isConsideredWarning (aErrorLevel)) - return JSON_ERRORLEVEL_WARN; - return JSON_ERRORLEVEL_SUCCESS; + return PhiveResultHelper.getErrorLevelValue (aErrorLevel); } @Nullable + @Deprecated (forRemoval = true, since = "10.0.3") public static IErrorLevel getAsErrorLevel (@Nullable final String sErrorLevel) { - if (JSON_ERRORLEVEL_ERROR.equals (sErrorLevel)) - return EErrorLevel.ERROR; - if (JSON_ERRORLEVEL_WARN.equals (sErrorLevel)) - return EErrorLevel.WARN; - if (JSON_ERRORLEVEL_SUCCESS.equals (sErrorLevel)) - return EErrorLevel.SUCCESS; - return null; + return PhiveResultHelper.getAsErrorLevel (sErrorLevel); } /** @@ -191,9 +184,10 @@ public static IErrorLevel getAsErrorLevel (@Nullable final String sErrorLevel) */ @Nonnull @Nonempty + @Deprecated (forRemoval = true, since = "10.0.3") public static String getJsonTriState (final boolean b) { - return b ? JSON_TRISTATE_TRUE : JSON_TRISTATE_FALSE; + return PhiveResultHelper.getTriStateValue (b); } /** @@ -207,13 +201,10 @@ public static String getJsonTriState (final boolean b) * @see #getJsonTriState(boolean) */ @Nonnull + @Deprecated (forRemoval = true, since = "10.0.3") public static String getJsonTriState (@Nonnull final ETriState eTriState) { - ValueEnforcer.notNull (eTriState, "TriState"); - - if (eTriState.isUndefined ()) - return JSON_TRISTATE_UNDEFINED; - return getJsonTriState (eTriState.isTrue ()); + return PhiveResultHelper.getTriStateValue (eTriState); } /** @@ -226,15 +217,10 @@ public static String getJsonTriState (@Nonnull final ETriState eTriState) * @return null if the provided value is unknown. */ @Nullable + @Deprecated (forRemoval = true, since = "10.0.3") public static ETriState getAsTriState (@Nullable final String sTriState) { - if (JSON_TRISTATE_TRUE.equals (sTriState)) - return ETriState.TRUE; - if (JSON_TRISTATE_FALSE.equals (sTriState)) - return ETriState.FALSE; - if (JSON_TRISTATE_UNDEFINED.equals (sTriState)) - return ETriState.UNDEFINED; - return null; + return PhiveResultHelper.getAsTriState (sTriState); } /** @@ -261,9 +247,9 @@ public static IJsonObject getJsonStackTrace (@Nullable final Throwable t) return null; if (t instanceof PhiveRestoredException) return ((PhiveRestoredException) t).getAsJson (); - return new JsonObject ().add (PhiveRestoredException.JSON_CLASS, t.getClass ().getName ()) - .addIfNotNull (PhiveRestoredException.JSON_MESSAGE, t.getMessage ()) - .add (PhiveRestoredException.JSON_STACK_TRACE, StackTraceHelper.getStackAsString (t)); + return PhiveRestoredException.getAsJson (t.getClass ().getName (), + t.getMessage (), + StackTraceHelper.getStackAsString (t)); } /** @@ -342,7 +328,6 @@ public static ILocation getAsErrorLocation (@Nullable final IJsonObject aObj) * @param t * The optional stack trace of the error. May be null. * @return The JSON object with the error. Never null. - * @see #getJsonErrorLevel(IErrorLevel) * @see #getJsonErrorLocation(ILocation) * @see #getJsonStackTrace(Throwable) * @see #getJsonError(IError, Locale) @@ -390,7 +375,6 @@ public static IJsonObject getJsonError (@Nonnull final IErrorLevel aErrorLevel, * The display locale to resolve the error text. May not be * null. * @return The JSON object with the error. Never null. - * @see #getJsonErrorLevel(IErrorLevel) * @see #getJsonStackTrace(Throwable) * @see #getJsonError(IErrorLevel, String, String, ILocation, String, String, * Throwable) @@ -445,7 +429,7 @@ public static JsonErrorBuilder jsonErrorBuilder (@Nonnull final IError aError, @ public static IError getAsIError (@Nonnull final IJsonObject aObj) { final LocalDateTime aErrorDT = PDTWebDateHelper.getLocalDateTimeFromXSD (aObj.getAsString (JSON_ERROR_DATETIME)); - final IErrorLevel aErrorLevel = getAsErrorLevel (aObj.getAsString (JSON_ERROR_LEVEL)); + final IErrorLevel aErrorLevel = PhiveResultHelper.getAsErrorLevel (aObj.getAsString (JSON_ERROR_LEVEL)); final String sErrorID = aObj.getAsString (JSON_ERROR_ID); final String sErrorFieldName = aObj.getAsString (JSON_ERROR_FIELD_NAME); // Try new structured version @@ -542,7 +526,7 @@ public static IJsonObject getJsonVES (@Nonnull final IValidationExecutorSet * "interrupted" : boolean, * "mostSevereErrorLevel" : string, * "results" : array { - * "success" : string, // as defined by {@link #getJsonTriState(ETriState)} + * "success" : string, // as defined by {@link PhiveResultHelper#getTriStateValue(ETriState)} * "artifactType" : string, * "artifactPath" : string, * "items" : array { @@ -569,7 +553,7 @@ public static void applyGlobalError (@Nonnull final IJsonObject aResponse, ValueEnforcer.isGE0 (nDurationMilliseconds, "DurationMilliseconds"); final IJsonArray aResultArray = new JsonArray (); - aResultArray.add (new JsonObject ().add (JSON_SUCCESS, getJsonTriState (false)) + aResultArray.add (new JsonObject ().add (JSON_SUCCESS, PhiveResultHelper.getTriStateValue (false)) .add (JSON_ARTIFACT_TYPE, ARTIFACT_TYPE_INPUT_PARAMETER) .add (JSON_ARTIFACT_PATH, ARTIFACT_PATH_NONE) .addJson (JSON_ITEMS, @@ -579,26 +563,17 @@ public static void applyGlobalError (@Nonnull final IJsonObject aResponse, aResponse.add (JSON_SUCCESS, false); aResponse.add (JSON_INTERRUPTED, false); - aResponse.add (JSON_MOST_SEVERE_ERROR_LEVEL, getJsonErrorLevel (EErrorLevel.ERROR)); + aResponse.add (JSON_MOST_SEVERE_ERROR_LEVEL, PhiveResultHelper.getErrorLevelValue (EErrorLevel.ERROR)); aResponse.addJson (JSON_RESULTS, aResultArray); aResponse.add (JSON_DURATION_MS, nDurationMilliseconds); } @Nonnull @Nonempty + @Deprecated (forRemoval = true, since = "10.0.3") public static String getArtifactPathType (@Nonnull final IReadableResource aRes) { - if (aRes instanceof ClassPathResource) - return "classpath"; - if (aRes instanceof FileSystemResource) - return "file"; - if (aRes instanceof URLResource) - return "url"; - if (aRes instanceof IMemoryReadableResource) - return "in-memory"; - if (aRes instanceof IWrappedReadableResource) - return "wrapped"; - return "unknown"; + return PhiveResultHelper.getArtifactPathType (aRes); } /** @@ -613,7 +588,7 @@ public static String getArtifactPathType (@Nonnull final IReadableResource aRes) * "interrupted" : boolean, * "mostSevereErrorLevel" : string, * "results" : array { - * "success" : string, // as defined by {@link #getJsonTriState(ETriState)} + * "success" : string, // as defined by {@link PhiveResultHelper#getTriStateValue(ETriState)} * "artifactType" : string, * "artifactPathType" : string?, * "artifactPath" : string, @@ -684,29 +659,11 @@ public static IValidationExecutorSet getAsVES } @Nullable + @Deprecated (forRemoval = true, since = "10.0.3") public static IReadableResource getAsValidationResource (@Nullable final String sArtefactPathType, @Nullable final String sArtefactPath) { - if (StringHelper.hasNoText (sArtefactPathType)) - return null; - if (StringHelper.hasNoText (sArtefactPath)) - return null; - - if ("file".equals (sArtefactPathType)) - return new FileSystemResource (sArtefactPath); - - if ("url".equals (sArtefactPathType)) - try - { - return new URLResource (sArtefactPath); - } - catch (final MalformedURLException ex) - { - return null; - } - - // Default to class path - return new ClassPathResource (sArtefactPath); + return PhiveResultHelper.getAsValidationResource (sArtefactPathType, sArtefactPath); } @Nullable @@ -748,7 +705,7 @@ public static ValidationResultList getAsValidationResultList (@Nullable final IJ { // Fall back to previous status final String sSuccess = aResultObj.getAsString (JSON_SUCCESS); - final ETriState eSuccess = getAsTriState (sSuccess); + final ETriState eSuccess = PhiveResultHelper.getAsTriState (sSuccess); if (eSuccess == null) { if (LOGGER.isDebugEnabled ()) @@ -781,7 +738,7 @@ public static ValidationResultList getAsValidationResultList (@Nullable final IJ } final String sArtefactPathType = aResultObj.getAsString (JSON_ARTIFACT_PATH_TYPE); final String sArtefactPath = aResultObj.getAsString (JSON_ARTIFACT_PATH); - final IReadableResource aRes = getAsValidationResource (sArtefactPathType, sArtefactPath); + final IReadableResource aRes = PhiveResultHelper.getAsValidationResource (sArtefactPathType, sArtefactPath); if (aRes == null) { if (LOGGER.isDebugEnabled ()) diff --git a/phive-result/src/main/java/com/helger/phive/result/xml/PhiveXMLHelper.java b/phive-result/src/main/java/com/helger/phive/result/xml/PhiveXMLHelper.java new file mode 100644 index 00000000..921fc7c1 --- /dev/null +++ b/phive-result/src/main/java/com/helger/phive/result/xml/PhiveXMLHelper.java @@ -0,0 +1,676 @@ +/* + * Copyright (C) 2014-2024 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phive.result.xml; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Locale; +import java.util.function.Function; + +import javax.annotation.Nonnegative; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.helger.commons.ValueEnforcer; +import com.helger.commons.annotation.Nonempty; +import com.helger.commons.datetime.PDTWebDateHelper; +import com.helger.commons.error.IError; +import com.helger.commons.error.SingleError; +import com.helger.commons.error.level.EErrorLevel; +import com.helger.commons.error.level.IErrorLevel; +import com.helger.commons.error.list.ErrorList; +import com.helger.commons.error.text.ConstantHasErrorText; +import com.helger.commons.io.resource.IReadableResource; +import com.helger.commons.lang.StackTraceHelper; +import com.helger.commons.location.ILocation; +import com.helger.commons.location.SimpleLocation; +import com.helger.commons.mutable.MutableInt; +import com.helger.commons.state.ETriState; +import com.helger.commons.string.StringHelper; +import com.helger.commons.string.StringParser; +import com.helger.diver.api.coord.DVRCoordinate; +import com.helger.phive.api.EValidationType; +import com.helger.phive.api.IValidationType; +import com.helger.phive.api.artefact.ValidationArtefact; +import com.helger.phive.api.executorset.IValidationExecutorSet; +import com.helger.phive.api.executorset.ValidationExecutorSetRegistry; +import com.helger.phive.api.executorset.status.IValidationExecutorSetStatus; +import com.helger.phive.api.result.ValidationResult; +import com.helger.phive.api.result.ValidationResultList; +import com.helger.phive.api.source.IValidationSource; +import com.helger.phive.api.validity.EExtendedValidity; +import com.helger.phive.result.PhiveResultHelper; +import com.helger.phive.result.exception.PhiveRestoredException; +import com.helger.schematron.svrl.SVRLResourceError; +import com.helger.xml.microdom.IMicroElement; +import com.helger.xml.microdom.MicroElement; +import com.helger.xml.microdom.util.MicroHelper; + +/** + * A utility class to create a common XML representation of a PHIVE result. Use + * {@link #applyGlobalError(IMicroElement, String, long)} or + * {@link #applyValidationResultList(IMicroElement, IValidationExecutorSet, List, Locale, long, MutableInt, MutableInt)} + * to add the result to an arbitrary {@link IMicroElement}. + * + * @author Philip Helger + * @since 10.0.3 + */ +@Immutable +public final class PhiveXMLHelper +{ + public static final String XML_RESOURCE_ID = "resource"; + public static final String XML_LINE_NUM = "line"; + public static final String XML_COLUMN_NUM = "col"; + + public static final String XML_ERROR_DATETIME = "errorDateTime"; + public static final String XML_ERROR_LEVEL = "errorLevel"; + public static final String XML_ERROR_ID = "errorID"; + public static final String XML_ERROR_FIELD_NAME = "errorFieldName"; + public static final String XML_ERROR_LOCATION_OBJ = "errorLocationObj"; + public static final String XML_ERROR_LOCATION_STR = "errorLocation"; + public static final String XML_ERROR_TEXT = "errorText"; + public static final String XML_EXCEPTION = "exception"; + public static final String XML_TEST = "test"; + + public static final String XML_VESID = "vesid"; + public static final String XML_NAME = "name"; + public static final String XML_DEPRECATED = "deprecated"; + public static final String XML_STATUS = "status"; + public static final String XML_STATUS_LAST_MODIFICATION = "lastModification"; + public static final String XML_STATUS_TYPE = "type"; + public static final String XML_STATUS_VALID_FROM = "validFrom"; + public static final String XML_STATUS_VALID_TO = "validTo"; + public static final String XML_STATUS_DEPRECATION_REASON = "deprecationReason"; + public static final String XML_STATUS_REPLACEMENT_VESID = "replacementVesid"; + + public static final String XML_SUCCESS = "success"; + public static final String XML_ARTIFACT_TYPE = "artifactType"; + public static final String XML_ARTIFACT_PATH_TYPE = "artifactPathType"; + public static final String XML_ARTIFACT_PATH = "artifactPath"; + public static final String XML_ITEM = "item"; + public static final String XML_INTERRUPTED = "interrupted"; + public static final String XML_MOST_SEVERE_ERROR_LEVEL = "mostSevereErrorLevel"; + public static final String XML_RESULT = "result"; + public static final String XML_DURATION_MS = "durationMS"; + public static final String XML_VES = "ves"; + + public static final String ARTIFACT_TYPE_INPUT_PARAMETER = "input-parameter"; + public static final String ARTIFACT_PATH_NONE = "none"; + + private static final Logger LOGGER = LoggerFactory.getLogger (PhiveXMLHelper.class); + + private PhiveXMLHelper () + {} + + /** + * Get the XML representation of a stack trace.
+ * + *
+   * <element>
+   *   <class>string</class>
+   *   <message>string</message>?,
+   *   <stackTrace>string</stackTrace>
+   * </element>
+   * 
+ * + * @param t + * The exception to convert to a XML object. May be null. + * @param sElementName + * The XML element name to use. May neither be null nor + * empty. + * @return null if the parameter is null, the XML + * object otherwise. + * @see PhiveRestoredException for a representation after reading + */ + @Nullable + public static IMicroElement getXMLStackTrace (@Nullable final Throwable t, + @Nonnull @Nonempty final String sElementName) + { + if (t == null) + return null; + if (t instanceof PhiveRestoredException) + return ((PhiveRestoredException) t).getAsXML (sElementName); + return PhiveRestoredException.getAsXML (sElementName, + t.getClass ().getName (), + t.getMessage (), + StackTraceHelper.getStackAsString (t)); + } + + /** + * Get the XML representation of a location.
+ * + *
+   * <element>
+   *   <resource>string</resource>?
+   *   <line>number</line>?
+   *   <col>number</col>?
+   * </element>
+   * 
+ * + * @param aLocation + * The location to convert to a XML object. May be null. + * @param sElementName + * The XML element name to use. May neither be null nor + * empty. + * @return null if the parameter is null, the XML + * object otherwise. + */ + @Nullable + public static IMicroElement getXMLErrorLocation (@Nullable final ILocation aLocation, + @Nonnull @Nonempty final String sElementName) + { + if (aLocation == null || !aLocation.isAnyInformationPresent ()) + return null; + final IMicroElement ret = new MicroElement (sElementName); + if (aLocation.hasResourceID ()) + ret.appendElement (XML_RESOURCE_ID).appendText (aLocation.getResourceID ()); + if (aLocation.hasLineNumber ()) + ret.appendElement (XML_LINE_NUM).appendText (Integer.toString (aLocation.getLineNumber ())); + if (aLocation.hasColumnNumber ()) + ret.appendElement (XML_COLUMN_NUM).appendText (Integer.toString (aLocation.getColumnNumber ())); + return ret; + } + + @Nullable + public static ILocation getAsErrorLocation (@Nullable final IMicroElement aObj) + { + if (aObj == null) + return null; + + final String sResourceID = MicroHelper.getChildTextContentTrimmed (aObj, XML_RESOURCE_ID); + final int nLineNumber = StringParser.parseInt (MicroHelper.getChildTextContentTrimmed (aObj, XML_LINE_NUM), + ILocation.ILLEGAL_NUMBER); + final int nColumnNumber = StringParser.parseInt (MicroHelper.getChildTextContentTrimmed (aObj, XML_COLUMN_NUM), + ILocation.ILLEGAL_NUMBER); + + if (StringHelper.hasNoText (sResourceID) && nLineNumber < 0 && nColumnNumber < 0) + return null; + + return new SimpleLocation (sResourceID, nLineNumber, nColumnNumber); + } + + /** + * Get the XML representation of an error.
+ * + *
+   * {
+   *   "errorLevel" : string,
+   *   "errorID" : string?,
+   *   "errorFieldName" : string?,
+   *   "errorLocation" : object?,
+   *   "test" : string?,
+   *   "errorText" : string,
+   *   "exception : object?
+   * }
+   * 
+ * + * @param aErrorLevel + * The error level to use. May not be null. + * @param sErrorID + * The ID of the error. May be null. + * @param sErrorFieldName + * The field name of the error. May be null. + * @param aErrorLocation + * The location of the error. May be null. + * @param sTest + * The performed test (e.g. for Schematrons). May be null. + * @param sErrorText + * The main error text. May not be null. + * @param t + * The optional stack trace of the error. May be null. + * @param sElementName + * The XML element name to use. May neither be null nor + * empty. + * @return The XML object with the error. Never null. + * @see #getXMLErrorLocation(ILocation, String) + * @see #getXMLStackTrace(Throwable, String) + * @see #getXMLError(IError, Locale,String) + */ + @Nonnull + public static IMicroElement getXMLError (@Nonnull final IErrorLevel aErrorLevel, + @Nullable final String sErrorID, + @Nullable final String sErrorFieldName, + @Nullable final ILocation aErrorLocation, + @Nullable final String sTest, + @Nonnull final String sErrorText, + @Nullable final Throwable t, + @Nonnull @Nonempty final String sElementName) + { + ValueEnforcer.notNull (aErrorLevel, "ErrorLevel"); + ValueEnforcer.notNull (sErrorText, "ErrorText"); + + return new XMLErrorBuilder (sElementName).errorLevel (aErrorLevel) + .errorID (sErrorID) + .errorFieldName (sErrorFieldName) + .errorLocation (aErrorLocation) + .test (sTest) + .errorText (sErrorText) + .exception (t) + .build (); + } + + /** + * Get the XML representation of an error.
+ * + *
+   * {
+   *   "errorLevel" : string,
+   *   "errorID" : string?,
+   *   "errorFieldName" : string?,
+   *   "errorLocation" : object?,
+   *   "test" : string?,
+   *   "errorText" : string,
+   *   "exception : object?
+   * }
+   * 
+ * + * @param aError + * The structured error. May not be null. + * @param aDisplayLocale + * The display locale to resolve the error text. May not be + * null. + * @param sElementName + * The XML element name to use. May neither be null nor + * empty. + * @return The XML object with the error. Never null. + * @see #getXMLStackTrace(Throwable, String) + * @see #getXMLError(IErrorLevel, String, String, ILocation, String, String, + * Throwable, String) + */ + @Nonnull + public static IMicroElement getXMLError (@Nonnull final IError aError, + @Nonnull final Locale aDisplayLocale, + @Nonnull @Nonempty final String sElementName) + { + return xmlErrorBuilder (aError, aDisplayLocale, sElementName).build (); + } + + /** + * Create an empty builder + * + * @param sElementName + * The XML element name to use. May neither be null nor + * empty. + * @return Never null. + */ + @Nonnull + public static XMLErrorBuilder xmlErrorBuilder (@Nonnull @Nonempty final String sElementName) + { + return new XMLErrorBuilder (sElementName); + } + + /** + * Create a builder that is pre-filled with the error details. + * + * @param aError + * The structured error. May not be null. + * @param aDisplayLocale + * The display locale to resolve the error text. May not be + * null. + * @param sElementName + * The XML element name to use. May neither be null nor + * empty. + * @return Never null. + */ + @Nonnull + public static XMLErrorBuilder xmlErrorBuilder (@Nonnull final IError aError, + @Nonnull final Locale aDisplayLocale, + @Nonnull @Nonempty final String sElementName) + { + ValueEnforcer.notNull (aError, "Error"); + ValueEnforcer.notNull (aDisplayLocale, "DisplayLocale"); + + return new XMLErrorBuilder (sElementName).errorDateTime (aError.getErrorDateTime ()) + .errorLevel (aError.getErrorLevel ()) + .errorID (aError.getErrorID ()) + .errorFieldName (aError.getErrorFieldName ()) + .errorLocation (aError.hasErrorLocation () ? aError.getErrorLocation () + : null) + .test (aError instanceof SVRLResourceError ? ((SVRLResourceError) aError).getTest () + : null) + .errorText (aError.getErrorText (aDisplayLocale)) + .exception (aError.getLinkedException ()); + } + + @Nonnull + public static IError getAsIError (@Nonnull final IMicroElement aObj) + { + final LocalDateTime aErrorDT = PDTWebDateHelper.getLocalDateTimeFromXSD (MicroHelper.getChildTextContentTrimmed (aObj, + XML_ERROR_DATETIME)); + final IErrorLevel aErrorLevel = PhiveResultHelper.getAsErrorLevel (MicroHelper.getChildTextContentTrimmed (aObj, + XML_ERROR_LEVEL)); + final String sErrorID = MicroHelper.getChildTextContentTrimmed (aObj, XML_ERROR_ID); + final String sErrorFieldName = MicroHelper.getChildTextContentTrimmed (aObj, XML_ERROR_FIELD_NAME); + // Try new structured version + final ILocation aErrorLocation = getAsErrorLocation (aObj.getFirstChildElement (XML_ERROR_LOCATION_OBJ)); + final String sErrorText = MicroHelper.getChildTextContentTrimmed (aObj, XML_ERROR_TEXT); + final String sTest = MicroHelper.getChildTextContent (aObj, XML_TEST); + final PhiveRestoredException aLinkedException = PhiveRestoredException.createFromXML (aObj.getFirstChildElement (XML_EXCEPTION)); + + if (sTest != null) + return new SVRLResourceError (aErrorDT, + aErrorLevel, + sErrorID, + sErrorFieldName, + aErrorLocation, + new ConstantHasErrorText (sErrorText), + aLinkedException, + sTest); + + return new SingleError (aErrorDT, + aErrorLevel, + sErrorID, + sErrorFieldName, + aErrorLocation, + new ConstantHasErrorText (sErrorText), + aLinkedException); + } + + /** + * Create the VES details as an XML Object.
+ * + *
+   * {
+   *   "vesid" : string,
+   *   "name" : string,
+   *   "deprecated" : boolean,
+   *   "status" : {
+   *     "lastModification" : dateTime
+   *     "type" : string,
+   *     "validFrom" : string?,
+   *     "validTo" : string?,
+   *     "deprecationReason" : string?
+   *     "replacementVesid" : string?
+   *   }
+   * }
+   * 
+ * + * @param aVES + * The VES to use. May not be null. + * @param sElementName + * The XML element name to use. May neither be null nor + * empty. + * @return The created XML object. + */ + @Nonnull + public static IMicroElement getXMLVES (@Nonnull final IValidationExecutorSet aVES, + @Nonnull @Nonempty final String sElementName) + { + ValueEnforcer.notNull (aVES, "VES"); + + final IValidationExecutorSetStatus aStatus = aVES.getStatus (); + final IMicroElement eStatus = new MicroElement (XML_STATUS); + eStatus.appendElement (XML_STATUS_LAST_MODIFICATION) + .appendText (PDTWebDateHelper.getAsStringXSD (aStatus.getStatusLastModification ())); + eStatus.appendElement (XML_STATUS_TYPE).appendText (aStatus.getType ().getID ()); + if (aStatus.hasValidFrom ()) + eStatus.appendElement (XML_STATUS_VALID_FROM) + .appendText (PDTWebDateHelper.getAsStringXSD (aStatus.getValidFrom ())); + if (aStatus.hasValidTo ()) + eStatus.appendElement (XML_STATUS_VALID_TO).appendText (PDTWebDateHelper.getAsStringXSD (aStatus.getValidTo ())); + if (aStatus.hasDeprecationReason ()) + eStatus.appendElement (XML_STATUS_DEPRECATION_REASON).appendText (aStatus.getDeprecationReason ()); + if (aStatus.hasReplacementVESID ()) + eStatus.appendElement (XML_STATUS_REPLACEMENT_VESID).appendText (aStatus.getReplacementVESID ().getAsSingleID ()); + + final IMicroElement ret = new MicroElement (sElementName); + ret.appendElement (XML_VESID).appendText (aVES.getID ().getAsSingleID ()); + ret.appendElement (XML_NAME).appendText (aVES.getDisplayName ()); + ret.appendElement (XML_DEPRECATED).appendText (Boolean.toString (aStatus.isDeprecated ())); + ret.appendChild (eStatus); + return ret; + } + + /** + * Add one global error to the response. Afterwards no validation results + * should be added. The layout of the response object is very similar to the + * one created by + * {@link #applyValidationResultList(IMicroElement, IValidationExecutorSet, List, Locale, long, MutableInt, MutableInt)}. + *
+ * + *
+   * {
+   *   "success" : boolean,
+   *   "interrupted" : boolean,
+   *   "mostSevereErrorLevel" : string,
+   *   "results" : array {
+   *     "success" : string,  // as defined by {@link PhiveResultHelper#getTriStateValue(ETriState)}
+   *     "artifactType" : string,
+   *     "artifactPath" : string,
+   *     "items" : array {
+   *       error structure as in {@link #getXMLError(IError, Locale, String)}
+   *     }
+   *   },
+   *   "durationMS" : number
+   * }
+   * 
+ * + * @param aResponse + * The response XML object to add to. May not be null. + * @param sErrorMsg + * The error message to use. May not be null. + * @param nDurationMilliseconds + * The duration of the validation in milliseconds. Must be ≥ 0. + */ + public static void applyGlobalError (@Nonnull final IMicroElement aResponse, + @Nonnull final String sErrorMsg, + @Nonnegative final long nDurationMilliseconds) + { + ValueEnforcer.notNull (aResponse, "Response"); + ValueEnforcer.notNull (sErrorMsg, "ErrorMsg"); + ValueEnforcer.isGE0 (nDurationMilliseconds, "DurationMilliseconds"); + + aResponse.appendElement (XML_SUCCESS).appendText (Boolean.FALSE.toString ()); + aResponse.appendElement (XML_INTERRUPTED).appendText (Boolean.FALSE.toString ()); + aResponse.appendElement (XML_MOST_SEVERE_ERROR_LEVEL) + .appendText (PhiveResultHelper.getErrorLevelValue (EErrorLevel.ERROR)); + + final IMicroElement aResult = aResponse.appendElement (XML_RESULT); + aResult.appendElement (XML_SUCCESS).appendText (PhiveResultHelper.getTriStateValue (false)); + aResult.appendElement (XML_ARTIFACT_TYPE).appendText (ARTIFACT_TYPE_INPUT_PARAMETER); + aResult.appendElement (XML_ARTIFACT_PATH).appendText (ARTIFACT_PATH_NONE); + aResult.appendChild (xmlErrorBuilder (XML_ITEM).errorLevel (EErrorLevel.ERROR).errorText (sErrorMsg).build ()); + + aResponse.appendElement (XML_DURATION_MS).appendText (Long.toString (nDurationMilliseconds)); + } + + /** + * Apply the results of a full validation onto a XML object.The layout of the + * response object is very similar to the one created by + * {@link #applyGlobalError(IMicroElement, String, long)}.
+ * + *
+    * {
+    *   "ves" : object as defined by {@link #getXMLVES(IValidationExecutorSet,String)}
+    *   "success" : boolean,
+    *   "interrupted" : boolean,
+    *   "mostSevereErrorLevel" : string,
+    *   "results" : array {
+    *     "success" : string,  // as defined by {@link PhiveResultHelper#getTriStateValue(ETriState)}
+    *     "artifactType" : string,
+    *     "artifactPathType" : string?,
+    *     "artifactPath" : string,
+    *     "items" : array {
+    *       error structure as in {@link #getXMLError(IError, Locale,String)}
+    *     }
+    *   },
+    *   "durationMS" : number
+    * }
+   * 
+ * + * @param aResponse + * The response XML object to add to. May not be null. + * @param aVES + * The Validation executor set that was used to perform validation. May + * be null. + * @param aValidationResultList + * The validation result list containing the validation results per + * layer. May not be null. + * @param aDisplayLocale + * The display locale to be used. May not be null. + * @param nDurationMilliseconds + * The duration of the validation in milliseconds. Must be ≥ 0. + * @param aWarningCount + * Optional callback value to store the overall warnings. If not + * null if will contain a ≥ 0 value afterwards. + * @param aErrorCount + * Optional callback value to store the overall errors. If not + * null if will contain a ≥ 0 value afterwards. + */ + public static void applyValidationResultList (@Nonnull final IMicroElement aResponse, + @Nullable final IValidationExecutorSet aVES, + @Nonnull final List aValidationResultList, + @Nonnull final Locale aDisplayLocale, + @Nonnegative final long nDurationMilliseconds, + @Nullable final MutableInt aWarningCount, + @Nullable final MutableInt aErrorCount) + { + new XMLValidationResultListHelper ().ves (aVES) + .warningCount (aWarningCount) + .errorCount (aErrorCount) + .applyTo (aResponse, + aValidationResultList, + aDisplayLocale, + nDurationMilliseconds); + } + + @Nullable + public static IValidationExecutorSet getAsVES (@Nonnull final ValidationExecutorSetRegistry aRegistry, + @Nullable final IMicroElement aXML) + { + ValueEnforcer.notNull (aRegistry, "Registry"); + if (aXML != null) + { + final IMicroElement eVes = aXML.getFirstChildElement (XML_VES); + if (eVes != null) + { + final String sVESID = MicroHelper.getChildTextContentTrimmed (eVes, XML_VESID); + if (StringHelper.hasText (sVESID)) + { + final DVRCoordinate aVESID = DVRCoordinate.parseOrNull (sVESID); + if (aVESID != null) + return aRegistry.getOfID (aVESID); + } + } + } + return null; + } + + @Nullable + public static ValidationResultList getAsValidationResultList (@Nullable final IMicroElement aXML) + { + // By default we're only resolving in the enum + return getAsValidationResultList (aXML, EValidationType::getFromIDOrNull); + } + + /** + * Try to parse a XML structure and convert it back to a + * {@link ValidationResultList}. + * + * @param aXML + * The XML to be read. May be null. + * @param aValidationTypeResolver + * The validation type resolver to be used. May not be + * null. + * @return null in case reverse operation fails. + */ + @Nullable + public static ValidationResultList getAsValidationResultList (@Nullable final IMicroElement aXML, + @Nonnull final Function aValidationTypeResolver) + { + ValueEnforcer.notNull (aValidationTypeResolver, "ValidationTypeResolver"); + + if (aXML == null) + return null; + + final ValidationResultList ret = new ValidationResultList (); + for (final IMicroElement eResult : aXML.getAllChildElements (XML_RESULT)) + { + // Fall back to previous status + final String sSuccess = MicroHelper.getChildTextContentTrimmed (eResult, XML_SUCCESS); + final ETriState eSuccess = PhiveResultHelper.getAsTriState (sSuccess); + if (eSuccess == null) + { + if (LOGGER.isDebugEnabled ()) + LOGGER.debug ("Failed to resolve TriState '" + sSuccess + "'"); + continue; + } + final EExtendedValidity eValidity; + switch (eSuccess) + { + case TRUE: + eValidity = EExtendedValidity.VALID; + break; + case FALSE: + eValidity = EExtendedValidity.INVALID; + break; + case UNDEFINED: + eValidity = EExtendedValidity.SKIPPED; + break; + default: + throw new IllegalStateException ("Oops"); + } + + final String sValidationType = MicroHelper.getChildTextContentTrimmed (eResult, XML_ARTIFACT_TYPE); + final IValidationType aValidationType = aValidationTypeResolver.apply (sValidationType); + if (aValidationType == null) + { + if (LOGGER.isDebugEnabled ()) + LOGGER.debug ("Failed to resolve ValidationType '" + sValidationType + "'"); + continue; + } + final String sArtefactPathType = MicroHelper.getChildTextContentTrimmed (eResult, XML_ARTIFACT_PATH_TYPE); + final String sArtefactPath = MicroHelper.getChildTextContentTrimmed (eResult, XML_ARTIFACT_PATH); + final IReadableResource aRes = PhiveResultHelper.getAsValidationResource (sArtefactPathType, sArtefactPath); + if (aRes == null) + { + if (LOGGER.isDebugEnabled ()) + LOGGER.debug ("Failed to resolve ValidationArtefact '" + + sArtefactPathType + + "' with path '" + + sArtefactPath + + "'"); + continue; + } + final ValidationArtefact aVA = new ValidationArtefact (aValidationType, aRes); + + if (eValidity.isSkipped ()) + { + // Ignored level + ret.add (ValidationResult.createSkippedResult (aVA)); + } + else + { + // We have results + final ErrorList aErrorList = new ErrorList (); + for (final IMicroElement eItem : eResult.getAllChildElements (XML_ITEM)) + { + final IError aError = getAsIError (eItem); + aErrorList.add (aError); + } + + final ValidationResult aVR = new ValidationResult (aVA, aErrorList); + ret.add (aVR); + } + } + return ret; + } +} diff --git a/phive-result/src/main/java/com/helger/phive/result/xml/XMLErrorBuilder.java b/phive-result/src/main/java/com/helger/phive/result/xml/XMLErrorBuilder.java new file mode 100644 index 00000000..41050ef3 --- /dev/null +++ b/phive-result/src/main/java/com/helger/phive/result/xml/XMLErrorBuilder.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2014-2024 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phive.result.xml; + +import java.time.LocalDateTime; +import java.util.function.Function; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.helger.commons.annotation.Nonempty; +import com.helger.commons.builder.IBuilder; +import com.helger.commons.datetime.PDTFactory; +import com.helger.commons.datetime.PDTWebDateHelper; +import com.helger.commons.error.IError; +import com.helger.commons.error.level.IErrorLevel; +import com.helger.commons.location.ILocation; +import com.helger.commons.string.StringHelper; +import com.helger.phive.result.PhiveResultHelper; +import com.helger.xml.microdom.IMicroElement; +import com.helger.xml.microdom.MicroElement; + +/** + * A flexible builder that can be used to convert data from an {@link IError} + * object to a XML representation with high degrees of freedom. + * + * @author Philip Helger + * @since 10.0.3 + */ +public class XMLErrorBuilder implements IBuilder +{ + private final String m_sElementName; + private LocalDateTime m_aErrorDateTime; + private IErrorLevel m_aErrorLevel; + private Function m_aErrorLevelToXML = PhiveResultHelper::getErrorLevelValue; + private String m_sErrorID; + private String m_sErrorFieldName; + private ILocation m_aErrorLocation; + private Function m_aErrorLocationToXML = x -> PhiveXMLHelper.getXMLErrorLocation (x, + PhiveXMLHelper.XML_ERROR_LOCATION_OBJ); + private String m_sTest; + private String m_sErrorText; + private Throwable m_aException; + private Function m_aExceptionToXML = t -> PhiveXMLHelper.getXMLStackTrace (t, + PhiveXMLHelper.XML_EXCEPTION); + + public XMLErrorBuilder (@Nonnull @Nonempty final String sElementName) + { + m_sElementName = sElementName; + } + + @Nonnull + public XMLErrorBuilder errorDateTimeNow () + { + return errorDateTime (PDTFactory.getCurrentLocalDateTime ()); + } + + @Nonnull + public XMLErrorBuilder errorDateTime (@Nullable final LocalDateTime a) + { + m_aErrorDateTime = a; + return this; + } + + @Nonnull + public XMLErrorBuilder errorLevel (@Nullable final IErrorLevel a) + { + m_aErrorLevel = a; + return this; + } + + @Nonnull + public XMLErrorBuilder errorLevelToXML (@Nullable final Function a) + { + m_aErrorLevelToXML = a; + return this; + } + + @Nonnull + public XMLErrorBuilder errorID (@Nullable final String s) + { + m_sErrorID = s; + return this; + } + + @Nonnull + public XMLErrorBuilder errorFieldName (@Nullable final String s) + { + m_sErrorFieldName = s; + return this; + } + + @Nonnull + public XMLErrorBuilder errorLocation (@Nullable final ILocation a) + { + m_aErrorLocation = a; + return this; + } + + @Nonnull + public XMLErrorBuilder errorLocationToXML (@Nullable final Function a) + { + m_aErrorLocationToXML = a; + return this; + } + + @Nonnull + public XMLErrorBuilder test (@Nullable final String s) + { + m_sTest = s; + return this; + } + + @Nonnull + public XMLErrorBuilder errorText (@Nullable final String s) + { + m_sErrorText = s; + return this; + } + + @Nonnull + public XMLErrorBuilder exception (@Nullable final Throwable a) + { + m_aException = a; + return this; + } + + @Nonnull + public XMLErrorBuilder exceptionToXML (@Nullable final Function a) + { + m_aExceptionToXML = a; + return this; + } + + @Nonnull + public IMicroElement build () + { + final IMicroElement ret = new MicroElement (m_sElementName); + if (m_aErrorDateTime != null) + ret.appendElement (PhiveXMLHelper.XML_ERROR_DATETIME) + .appendText (PDTWebDateHelper.getAsStringXSD (m_aErrorDateTime)); + if (m_aErrorLevel != null && m_aErrorLevelToXML != null) + { + final String sErrorLevel = m_aErrorLevelToXML.apply (m_aErrorLevel); + if (StringHelper.hasText (sErrorLevel)) + ret.appendElement (PhiveXMLHelper.XML_ERROR_LEVEL).appendText (sErrorLevel); + } + if (m_sErrorID != null) + ret.appendElement (PhiveXMLHelper.XML_ERROR_ID).appendText (m_sErrorID); + if (m_sErrorFieldName != null) + ret.appendElement (PhiveXMLHelper.XML_ERROR_FIELD_NAME).appendText (m_sErrorFieldName); + if (m_aErrorLocation != null && m_aErrorLocationToXML != null) + { + final IMicroElement eErrorLocation = m_aErrorLocationToXML.apply (m_aErrorLocation); + if (eErrorLocation != null) + ret.appendChild (eErrorLocation); + } + if (m_sTest != null) + ret.appendElement (PhiveXMLHelper.XML_TEST).appendText (m_sTest); + if (m_sErrorText != null) + ret.appendElement (PhiveXMLHelper.XML_ERROR_TEXT).appendText (m_sErrorText); + if (m_aException != null && m_aExceptionToXML != null) + { + final IMicroElement eEx = m_aExceptionToXML.apply (m_aException); + if (eEx != null) + ret.appendChild (eEx); + } + return ret; + } +} diff --git a/phive-result/src/main/java/com/helger/phive/result/xml/XMLValidationResultListHelper.java b/phive-result/src/main/java/com/helger/phive/result/xml/XMLValidationResultListHelper.java new file mode 100644 index 00000000..f6d0254b --- /dev/null +++ b/phive-result/src/main/java/com/helger/phive/result/xml/XMLValidationResultListHelper.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2014-2024 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phive.result.xml; + +import java.util.List; +import java.util.Locale; +import java.util.function.BiFunction; +import java.util.function.Function; + +import javax.annotation.Nonnegative; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.helger.commons.ValueEnforcer; +import com.helger.commons.error.IError; +import com.helger.commons.error.level.EErrorLevel; +import com.helger.commons.error.level.IErrorLevel; +import com.helger.commons.io.resource.IReadableResource; +import com.helger.commons.mutable.MutableInt; +import com.helger.commons.state.ETriState; +import com.helger.commons.string.StringHelper; +import com.helger.phive.api.executorset.IValidationExecutorSet; +import com.helger.phive.api.result.ValidationResult; +import com.helger.phive.api.validity.EExtendedValidity; +import com.helger.phive.result.PhiveResultHelper; +import com.helger.xml.microdom.IMicroElement; +import com.helger.xml.microdom.MicroElement; + +/** + * A helper class that allows to heavily customize the creation of validation + * result list JSONs + * + * @author Philip Helger + * @since 7.2.3 + */ +public class XMLValidationResultListHelper +{ + private static final Logger LOGGER = LoggerFactory.getLogger (XMLValidationResultListHelper.class); + + private IValidationExecutorSet m_aVES; + private Function , IMicroElement> m_aVESToXML = ves -> PhiveXMLHelper.getXMLVES (ves, + PhiveXMLHelper.XML_VES); + private Function m_aArtifactPathTypeToXML = PhiveResultHelper::getArtifactPathType; + private Function m_aErrorLevelToXML = PhiveResultHelper::getErrorLevelValue; + private BiFunction m_aErrorToXML = (err, + loc) -> PhiveXMLHelper.getXMLError (err, + loc, + PhiveXMLHelper.XML_ITEM); + private MutableInt m_aWarningCount; + private MutableInt m_aErrorCount; + + public XMLValidationResultListHelper () + {} + + @Nonnull + public XMLValidationResultListHelper ves (@Nullable final IValidationExecutorSet a) + { + m_aVES = a; + return this; + } + + @Nonnull + public XMLValidationResultListHelper vesToXML (@Nullable final Function , IMicroElement> a) + { + m_aVESToXML = a; + return this; + } + + @Nonnull + public XMLValidationResultListHelper artifactPathTypeToXML (@Nullable final Function a) + { + m_aArtifactPathTypeToXML = a; + return this; + } + + @Nonnull + public XMLValidationResultListHelper errorLevelToXML (@Nullable final Function a) + { + m_aErrorLevelToXML = a; + return this; + } + + @Nonnull + public XMLValidationResultListHelper errorToXML (@Nullable final BiFunction a) + { + m_aErrorToXML = a; + return this; + } + + @Nonnull + public XMLValidationResultListHelper warningCount (@Nullable final MutableInt a) + { + m_aWarningCount = a; + return this; + } + + @Nonnull + public XMLValidationResultListHelper errorCount (@Nullable final MutableInt a) + { + m_aErrorCount = a; + return this; + } + + /** + * Apply the results of a full validation onto a XML object.The layout of the + * response object is very similar to the one created by + * {@link PhiveXMLHelper#applyGlobalError(IMicroElement, String, long)}.
+ * + *
+    * {
+    *   "ves" : object as defined by {@link PhiveXMLHelper#getXMLVES(IValidationExecutorSet, String)}
+    *   "success" : boolean,
+    *   "interrupted" : boolean,
+    *   "mostSevereErrorLevel" : string,
+    *   "results" : array {
+    *     "success" : string,  // as defined by {@link PhiveResultHelper#getTriStateValue(ETriState)}
+    *     "artifactType" : string,
+    *     "artifactPathType" : string?,
+    *     "artifactPath" : string,
+    *     "items" : array {
+    *       error structure as in {@link PhiveXMLHelper#getXMLError(IError, Locale,String)}
+    *     }
+    *   },
+    *   "durationMS" : number
+    * }
+   * 
+ * + * @param aResponse + * The response JSON object to add to. May not be null. + * @param aValidationResultList + * The validation result list containing the validation results per + * layer. May not be null. + * @param aDisplayLocale + * The display locale to be used. May not be null. + * @param nDurationMilliseconds + * The duration of the validation in milliseconds. Must be ≥ 0. + */ + public void applyTo (@Nonnull final IMicroElement aResponse, + @Nonnull final List aValidationResultList, + @Nonnull final Locale aDisplayLocale, + @Nonnegative final long nDurationMilliseconds) + { + ValueEnforcer.notNull (aResponse, "Response"); + ValueEnforcer.notNull (aValidationResultList, "ValidationResultList"); + ValueEnforcer.notNull (aDisplayLocale, "DisplayLocale"); + ValueEnforcer.isGE0 (nDurationMilliseconds, "DurationMilliseconds"); + + if (m_aVES != null && m_aVESToXML != null) + { + final IMicroElement eVES = m_aVESToXML.apply (m_aVES); + if (eVES != null) + aResponse.appendChild (eVES); + } + + int nWarnings = 0; + int nErrors = 0; + { + boolean bValidationInterrupted = false; + IErrorLevel aMostSevere = EErrorLevel.LOWEST; + EExtendedValidity eWorstValidity = EExtendedValidity.VALID; + + // Calculate overalls + for (final ValidationResult aVR : aValidationResultList) + { + if (aVR.isSkipped ()) + { + bValidationInterrupted = true; + } + else + { + // Backwards compatible decision + final boolean bIsValid = aVR.getErrorList ().containsNoError (); + if (!bIsValid) + eWorstValidity = EExtendedValidity.INVALID; + } + + for (final IError aError : aVR.getErrorList ()) + { + if (aError.getErrorLevel ().isGT (aMostSevere)) + aMostSevere = aError.getErrorLevel (); + + if (PhiveResultHelper.isConsideredError (aError.getErrorLevel ())) + nErrors++; + else + if (PhiveResultHelper.isConsideredWarning (aError.getErrorLevel ())) + nWarnings++; + } + } + + // Success if the worst that happened is a warning + // This is an assumption atm + aResponse.appendElement (PhiveXMLHelper.XML_SUCCESS).appendText (Boolean.toString (eWorstValidity.isValid ())); + aResponse.appendElement (PhiveXMLHelper.XML_INTERRUPTED).appendText (Boolean.toString (bValidationInterrupted)); + if (m_aErrorLevelToXML != null) + { + final String sErrorLevel = m_aErrorLevelToXML.apply (aMostSevere); + if (StringHelper.hasText (sErrorLevel)) + aResponse.appendElement (PhiveXMLHelper.XML_MOST_SEVERE_ERROR_LEVEL).appendText (sErrorLevel); + } + } + + for (final ValidationResult aVR : aValidationResultList) + { + final IMicroElement aVRT = new MicroElement (PhiveXMLHelper.XML_RESULT); + // Success is only contained for backwards compatibility reasons. Validity + // now does the trick + if (aVR.isSkipped ()) + { + aVRT.appendElement (PhiveXMLHelper.XML_SUCCESS) + .appendText (PhiveResultHelper.getTriStateValue (ETriState.UNDEFINED)); + } + else + { + // Backwards compatible decision + final boolean bIsValid = aVR.getErrorList ().containsNoError (); + aVRT.appendElement (PhiveXMLHelper.XML_SUCCESS).appendText (PhiveResultHelper.getTriStateValue (bIsValid)); + } + aVRT.appendElement (PhiveXMLHelper.XML_ARTIFACT_TYPE) + .appendText (aVR.getValidationArtefact ().getValidationType ().getID ()); + if (m_aArtifactPathTypeToXML != null) + { + final String sPathType = m_aArtifactPathTypeToXML.apply (aVR.getValidationArtefact ().getRuleResource ()); + if (StringHelper.hasText (sPathType)) + aVRT.appendElement (PhiveXMLHelper.XML_ARTIFACT_PATH_TYPE).appendText (sPathType); + } + aVRT.appendElement (PhiveXMLHelper.XML_ARTIFACT_PATH) + .appendText (aVR.getValidationArtefact ().getRuleResourcePath ()); + + for (final IError aError : aVR.getErrorList ()) + { + final IMicroElement eError = m_aErrorToXML != null ? m_aErrorToXML.apply (aError, aDisplayLocale) : null; + if (eError != null) + aVRT.appendChild (eError); + else + LOGGER.warn ("Failed to convert IError to XML"); + } + aResponse.appendChild (aVRT); + } + aResponse.appendElement (PhiveXMLHelper.XML_DURATION_MS).appendText (Long.toString (nDurationMilliseconds)); + + // Set consumer values + if (m_aWarningCount != null) + m_aWarningCount.set (nWarnings); + if (m_aErrorCount != null) + m_aErrorCount.set (nErrors); + } +} diff --git a/phive-result/src/test/java/com/helger/phive/result/xml/PhiveXMLHelperTest.java b/phive-result/src/test/java/com/helger/phive/result/xml/PhiveXMLHelperTest.java new file mode 100644 index 00000000..f2e9675b --- /dev/null +++ b/phive-result/src/test/java/com/helger/phive/result/xml/PhiveXMLHelperTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2014-2024 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phive.result.xml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.Locale; + +import org.junit.Test; + +import com.helger.commons.collection.impl.CommonsArrayList; +import com.helger.commons.datetime.PDTFactory; +import com.helger.commons.datetime.PDTWebDateHelper; +import com.helger.commons.error.IError; +import com.helger.commons.error.SingleError; +import com.helger.commons.error.level.EErrorLevel; +import com.helger.commons.error.text.ConstantHasErrorText; +import com.helger.commons.io.resource.ClassPathResource; +import com.helger.commons.location.SimpleLocation; +import com.helger.commons.mock.CommonsTestHelper; +import com.helger.diver.api.coord.DVRCoordinate; +import com.helger.diver.api.version.DVRVersionException; +import com.helger.phive.api.execute.ValidationExecutionManager; +import com.helger.phive.api.executorset.IValidationExecutorSet; +import com.helger.phive.api.executorset.ValidationExecutorSet; +import com.helger.phive.api.executorset.ValidationExecutorSetRegistry; +import com.helger.phive.api.executorset.status.ValidationExecutorSetStatus; +import com.helger.phive.api.result.ValidationResultList; +import com.helger.phive.api.validity.IValidityDeterminator; +import com.helger.phive.result.exception.PhiveRestoredException; +import com.helger.phive.xml.source.IValidationSourceXML; +import com.helger.phive.xml.source.ValidationSourceXML; +import com.helger.phive.xml.xsd.ValidationExecutorXSD; +import com.helger.schematron.svrl.SVRLResourceError; +import com.helger.xml.microdom.IMicroElement; +import com.helger.xml.microdom.MicroElement; +import com.helger.xml.microdom.serialize.MicroWriter; +import com.helger.xml.serialize.write.EXMLSerializeIndent; +import com.helger.xml.serialize.write.XMLWriterSettings; + +/** + * Test class for class {@link PhiveXMLHelper}. + * + * @author Philip Helger + */ +public final class PhiveXMLHelperTest +{ + @Test + public void testGlobalError () + { + final IMicroElement aObj = new MicroElement ("root"); + PhiveXMLHelper.applyGlobalError (aObj, "My error", 123); + final String sXML = MicroWriter.getNodeAsString (aObj, + new XMLWriterSettings ().setIndent (EXMLSerializeIndent.NONE)); + assertEquals ("false" + + "false" + + "ERROR" + + "" + + "FALSE" + + "input-parameter" + + "none" + + "ERRORMy error" + + "" + + "123", + sXML); + } + + @Test + public void testValidationResults () throws DVRVersionException + { + final IMicroElement aObj = new MicroElement ("root"); + final Locale aDisplayLocale = Locale.US; + final DVRCoordinate aVESID = DVRCoordinate.create ("group", "art", "1.0"); + + final OffsetDateTime aNow = PDTFactory.getCurrentOffsetDateTimeMillisOnly (); + final IValidationExecutorSet aVES = new ValidationExecutorSet <> (aVESID, + "name", + ValidationExecutorSetStatus.createValidAt (aNow)); + PhiveXMLHelper.applyValidationResultList (aObj, aVES, new CommonsArrayList <> (), aDisplayLocale, 123, null, null); + final String sXML = MicroWriter.getNodeAsString (aObj, + new XMLWriterSettings ().setIndent (EXMLSerializeIndent.NONE)); + assertEquals ("" + + "group:art:1" + + "name" + + "false" + + "" + + "" + + PDTWebDateHelper.getAsStringXSD (aNow) + + "" + + "valid" + + "" + + "true" + + "false" + + "SUCCESS" + + "123" + + "", + sXML); + } + + @Test + public void testValidationResultsBackAndForth () throws DVRVersionException + { + // Register + final ValidationExecutorSetRegistry aRegistry = new ValidationExecutorSetRegistry <> (); + final DVRCoordinate aVESID = DVRCoordinate.create ("group", "art", "1.0"); + final ValidationExecutorSet aVES = new ValidationExecutorSet <> (aVESID, + "name", + ValidationExecutorSetStatus.createValidNow ()); + aVES.addExecutor (ValidationExecutorXSD.create (new ClassPathResource ("test/schema1.xsd"))); + aRegistry.registerValidationExecutorSet (aVES); + + // Validate + final ValidationResultList aVRL = ValidationExecutionManager.executeValidation (IValidityDeterminator.createDefault (), + aVES, + ValidationSourceXML.create (new ClassPathResource ("test/schema1.xml"))); + assertTrue (aVRL.containsAtLeastOneError ()); + + // To XML + final Locale aDisplayLocale = Locale.US; + final IMicroElement aObj = new MicroElement ("root"); + PhiveXMLHelper.applyValidationResultList (aObj, aVES, aVRL, aDisplayLocale, 123, null, null); + + // And back + final IValidationExecutorSet aVES2 = PhiveXMLHelper.getAsVES (aRegistry, aObj); + assertNotNull (aVES2); + assertSame (aVES, aVES2); + + final ValidationResultList aVRL2 = PhiveXMLHelper.getAsValidationResultList (aObj); + assertNotNull (aVRL2); + // direct equals doesn't work, because of the restored exception + assertEquals (aVRL.size (), aVRL2.size ()); + assertEquals (1, aVRL.size ()); + assertEquals (aVRL.get (0).getErrorList ().size (), aVRL2.get (0).getErrorList ().size ()); + + // and forth + final IMicroElement aObj2 = new MicroElement ("root"); + PhiveXMLHelper.applyValidationResultList (aObj2, aVES2, aVRL2, aDisplayLocale, 123, null, null); + + assertEquals (MicroWriter.getNodeAsString (aObj), MicroWriter.getNodeAsString (aObj2)); + } + + @Test + public void testException () + { + final Exception ex = new IllegalArgumentException ("bla foo"); + + // to XML + final IMicroElement aObj = PhiveXMLHelper.getXMLStackTrace (ex, "ex"); + assertNotNull (aObj); + } + + @Test + public void testError () + { + final IError aError = SingleError.builderError () + .errorID ("id1") + .errorText ("fla") + .errorLocation (new SimpleLocation ("res12", 3, 4)) + .build (); + final IMicroElement aXML = PhiveXMLHelper.getXMLError (aError, Locale.US, "err"); + assertNotNull (aXML); + + final IError aError2 = PhiveXMLHelper.getAsIError (aXML); + assertNotNull (aError2); + + CommonsTestHelper.testDefaultImplementationWithEqualContentObject (aError, aError2); + } + + @Test + public void testSVRLError () + { + final LocalDateTime aNow = PDTFactory.getCurrentLocalDateTimeMillisOnly (); + final IError aError = new SVRLResourceError (aNow, + EErrorLevel.ERROR, + "id2", + "field1", + new SimpleLocation ("res12", 3, 4), + new ConstantHasErrorText ("bla failed"), + null, + " my test <>"); + + // To Json + final IMicroElement aXML = PhiveXMLHelper.getXMLError (aError, Locale.US, "err"); + assertNotNull (aXML); + + // And back + final IError aError2 = PhiveXMLHelper.getAsIError (aXML); + assertNotNull (aError2); + + // And forth + final IMicroElement aXML2 = PhiveXMLHelper.getXMLError (aError2, Locale.US, "err"); + assertNotNull (aXML2); + + assertEquals (MicroWriter.getNodeAsString (aXML), MicroWriter.getNodeAsString (aXML2)); + CommonsTestHelper.testDefaultImplementationWithEqualContentObject (aError, aError2); + } + + @Test + public void testSVRLErrorWithException () + { + final LocalDateTime aNow = PDTFactory.getCurrentLocalDateTimeMillisOnly (); + final IError aError = new SVRLResourceError (aNow, + EErrorLevel.ERROR, + "id2", + "field1", + new SimpleLocation ("res12", 3, 4), + new ConstantHasErrorText ("bla failed"), + new IllegalStateException ("Sthg went wrong"), + " my test <>"); + + // To Json + final IMicroElement aXML = PhiveXMLHelper.getXMLError (aError, Locale.US, "err"); + assertNotNull (aXML); + + // And back + final IError aError2 = PhiveXMLHelper.getAsIError (aXML); + assertNotNull (aError2); + + // And forth + final IMicroElement aXML2 = PhiveXMLHelper.getXMLError (aError2, Locale.US, "err"); + assertNotNull (aXML2); + + assertEquals (MicroWriter.getNodeAsString (aXML), MicroWriter.getNodeAsString (aXML2)); + // The objects differ, because of the different exception types + assertTrue (aError2.getLinkedException () instanceof PhiveRestoredException); + if (false) + CommonsTestHelper.testDefaultImplementationWithEqualContentObject (aError, aError2); + } +} diff --git a/phive-xml/src/test/java/com/helger/phive/api/execute/ValidationExecutionManagerFuncTest.java b/phive-xml/src/test/java/com/helger/phive/api/execute/ValidationExecutionManagerFuncTest.java index cb18d33d..762b194d 100644 --- a/phive-xml/src/test/java/com/helger/phive/api/execute/ValidationExecutionManagerFuncTest.java +++ b/phive-xml/src/test/java/com/helger/phive/api/execute/ValidationExecutionManagerFuncTest.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2014-2024 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.helger.phive.api.execute; import static org.junit.Assert.assertEquals;