All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.phive.ves.engine.load.VESLoader Maven / Gradle / Ivy

Go to download

phive - Philip Helger Integrative Validation Engine - VES - Validation Execution Set engine

There is a newer version: 10.1.0
Show newest version
/*
 * Copyright (C) 2023 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 javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.collection.impl.CommonsLinkedHashSet;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.commons.collection.impl.ICommonsSet;
import com.helger.commons.concurrent.SimpleReadWriteLock;
import com.helger.commons.error.SingleError;
import com.helger.commons.error.level.EErrorLevel;
import com.helger.commons.error.list.ErrorList;
import com.helger.commons.io.resource.IReadableResource;
import com.helger.commons.state.ESuccess;
import com.helger.commons.state.ETriState;
import com.helger.commons.string.StringHelper;
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.RepoStorageReadableResource;
import com.helger.phive.ves.engine.load.LoadedVES.EVESSyntax;
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.VES1Marshaller;
import com.helger.phive.ves.model.v1.VESStatus1Marshaller;
import com.helger.phive.ves.v10.VesCustomErrorType;
import com.helger.phive.ves.v10.VesErrorLevelType;
import com.helger.phive.ves.v10.VesNamespaceListType;
import com.helger.phive.ves.v10.VesNamespaceType;
import com.helger.phive.ves.v10.VesOutputType;
import com.helger.phive.ves.v10.VesRequiresType;
import com.helger.phive.ves.v10.VesResourceType;
import com.helger.phive.ves.v10.VesStatusType;
import com.helger.phive.ves.v10.VesType;
import com.helger.xml.namespace.MapBasedNamespaceContext;

/**
 * This class loads VES data into {@link LoadedVES} objects. It supports dynamic
 * rule creation for each syntax, using the {@link IVESLoaderXSD},
 * {@link IVESLoaderSchematron} and {@link IVESLoaderEdifact} classes. By
 * default only XSD and Schematron are support. Edifact needs to be implemented
 * manually.
 *
 * @author Philip Helger
 */
@ThreadSafe
public final class VESLoader
{
  public static final String FILE_EXT_VES = ".ves";
  public static final String FILE_EXT_STATUS = ".status";

  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 final SimpleReadWriteLock m_aRWLock = new SimpleReadWriteLock ();
  private final IRepoStorageBase m_aRepo;
  @GuardedBy ("m_aRWLock")
  private IVESLoaderXSD m_aLoaderXSD = new DefaultVESLoaderXSD ();
  @GuardedBy ("m_aRWLock")
  private IVESLoaderSchematron m_aLoaderSchematron = new DefaultVESLoaderSchematron ();
  @GuardedBy ("m_aRWLock")
  private IVESLoaderEdifact m_aLoaderEdifact = null;
  @GuardedBy ("m_aRWLock")
  private boolean m_bUseEagerRequirementLoading = false;

  public VESLoader (@Nonnull final IRepoStorageBase aRepo)
  {
    ValueEnforcer.notNull (aRepo, "Repo");
    m_aRepo = aRepo;
  }

  @Nullable
  public IVESLoaderXSD getLoaderXSD ()
  {
    return m_aRWLock.readLockedGet ( () -> m_aLoaderXSD);
  }

  @Nonnull
  public VESLoader setLoaderXSD (@Nullable final IVESLoaderXSD aLoader)
  {
    m_aRWLock.writeLocked ( () -> m_aLoaderXSD = aLoader);
    return this;
  }

  @Nullable
  public IVESLoaderSchematron getLoaderSchematron ()
  {
    return m_aRWLock.readLockedGet ( () -> m_aLoaderSchematron);
  }

  @Nonnull
  public VESLoader setLoaderSchematron (@Nullable final IVESLoaderSchematron aLoader)
  {
    m_aRWLock.writeLocked ( () -> m_aLoaderSchematron = aLoader);
    return this;
  }

  @Nullable
  public IVESLoaderEdifact getLoaderEdifact ()
  {
    return m_aRWLock.readLockedGet ( () -> m_aLoaderEdifact);
  }

  @Nonnull
  public VESLoader setLoaderEdifact (@Nullable final IVESLoaderEdifact aLoader)
  {
    m_aRWLock.writeLocked ( () -> m_aLoaderEdifact = aLoader);
    return this;
  }

  public boolean isUseEagerRequirementLoading ()
  {
    return m_aRWLock.readLockedBoolean ( () -> m_bUseEagerRequirementLoading);
  }

  @Nonnull
  public VESLoader setUseEagerRequirementLoading (final boolean b)
  {
    m_aRWLock.writeLocked ( () -> m_bUseEagerRequirementLoading = b);
    return this;
  }

  @ThreadSafe
  public static final class VESLoaderStatus
  {
    private final SimpleReadWriteLock m_aRWLock = new SimpleReadWriteLock ();
    @GuardedBy ("m_aRWLock")
    private final ICommonsSet  m_aLoaded = new CommonsLinkedHashSet <> ();

    @Nonnull
    public ESuccess addVESID (@Nonnull final VESID aVESID)
    {
      return ESuccess.valueOf (m_aRWLock.writeLockedBoolean ( () -> m_aLoaded.add (aVESID)));
    }

    @Nonnull
    @Nonempty
    public String getDepedencyChain (@Nullable final VESID aLastOne)
    {
      final ICommonsList  aList = m_aLoaded.getCopyAsList ();
      if (aLastOne != null)
        aList.add (aLastOne);
      return StringHelper.imploder ().source (aList, x -> "'" + x.getAsSingleID () + "'").separator (" -> ").build ();
    }
  }

  @Nullable
  public LoadedVES convertToLoadedVES (@Nonnull final LoadedVES.Status aStatus,
                                       @Nonnull final VesType aVES,
                                       @Nonnull final VESLoaderStatus aLoaderStatus,
                                       @Nonnull final ErrorList aErrorList)
  {
    ValueEnforcer.notNull (aStatus, "Status");
    ValueEnforcer.notNull (aVES, "VES");
    ValueEnforcer.notNull (aLoaderStatus, "LoaderStatus");
    ValueEnforcer.notNull (aErrorList, "ErrorList");

    final EVESSyntax eSyntax = aVES.getXsd () != null ? EVESSyntax.XSD : aVES.getSchematron () != null
                                                                                                       ? EVESSyntax.SCHEMATRON
                                                                                                       : EVESSyntax.EDIFACT;

    // Extract data
    final LoadedVES.Header aHeader = new LoadedVES.Header (new VESID (aVES.getGroupId (),
                                                                      aVES.getArtifactId (),
                                                                      aVES.getVersion ()),
                                                           aVES.getName (),
                                                           aVES.getReleased (),
                                                           eSyntax);
    final LoadedVES ret = new LoadedVES (aHeader, aStatus);
    if (aVES.getRequires () != null)
    {
      final VesRequiresType aReq = aVES.getRequires ();
      final Requirement aRequirement = new LoadedVES.Requirement (new VESID (aReq.getGroupId (),
                                                                             aReq.getArtifactId (),
                                                                             aReq.getVersion ()),
                                                                  _wrap (aReq.getNamespaces ()),
                                                                  _wrap (aReq.getOutput ()),
                                                                  aReq.isStopOnError ());
      if (isUseEagerRequirementLoading ())
      {
        // Eager loading

        // Recursive load required artefact; required to have this in scope
        final LoadedVES aLoadedRequirement = loadVESFromRepo (aRequirement.getRequiredVESID (),
                                                              aLoaderStatus,
                                                              aErrorList);
        if (aLoadedRequirement == null)
        {
          aErrorList.add (SingleError.builderError ()
                                     .errorText ("Failed to load required VESID '" +
                                                 aRequirement.getRequiredVESID ().getAsSingleID () +
                                                 "' [eager]")
                                     .build ());
          return null;
        }

        ret.setEagerRequires (aRequirement, aLoadedRequirement);
      }
      else
      {
        // Lazy loading
        ret.setLazyRequires (aRequirement, aLocalErrorList -> {
          // Use this deferred loader, to ensure the same surrounding VESLoader
          // instance is used
          // Recursive load required artefact; required to have this in scope
          final LoadedVES aLoadedRequirement = loadVESFromRepo (aRequirement.getRequiredVESID (),
                                                                aLoaderStatus,
                                                                aLocalErrorList);
          if (aLoadedRequirement == null)
            throw new VESLoadingException ("Failed to load required VESID '" +
                                           aRequirement.getRequiredVESID ().getAsSingleID () +
                                           "' [lazy]");
          return aLoadedRequirement;
        });
      }
    }
    if (aVES.getXsd () != null)
    {
      // XSD
      final IVESLoaderXSD aLoader = getLoaderXSD ();
      if (aLoader != null)
        ret.setExecutor (aLoader.loadXSD (m_aRepo, aVES.getXsd (), aErrorList));
      else
        aErrorList.add (SingleError.builderError ()
                                   .errorText ("The VES contains an XSD element, but no XSD loader is present")
                                   .build ());
    }
    else
      if (aVES.getSchematron () != null)
      {
        // Schematron
        final IVESLoaderSchematron aLoader = getLoaderSchematron ();
        if (aLoader != null)
          ret.setExecutor (aLoader.loadSchematron (m_aRepo, aVES.getSchematron (), aErrorList));
        else
          aErrorList.add (SingleError.builderError ()
                                     .errorText ("The VES contains a Schematron element, but no Schematron loader is present")
                                     .build ());
      }
      else
        if (aVES.getEdifact () != null)
        {
          // EDIFACT
          final IVESLoaderEdifact aLoader = getLoaderEdifact ();
          if (aLoader != null)
            ret.setExecutor (aLoader.loadEdifact (m_aRepo, aVES.getEdifact (), aErrorList));
          else
            aErrorList.add (SingleError.builderError ()
                                       .errorText ("The VES contains an Edifact element, but no Edifact loader is present")
                                       .build ());
        }
        else
          throw new IllegalStateException ("Unsupported base syntax");

    if (!ret.hasExecutor ())
    {
      aErrorList.add (SingleError.builderError ()
                                 .errorText ("The loaded VES contains no Validation Executor and is therefore not usable")
                                 .build ());
      return null;
    }

    return ret;
  }

  @Nullable
  public LoadedVES loadVESDirect (@Nonnull final IReadableResource aVESRes, @Nonnull final ErrorList aErrorList)
  {
    return loadVESDirect (Status.createUndefined (), aVESRes, new VESLoaderStatus (), aErrorList);
  }

  @Nullable
  public LoadedVES loadVESDirect (@Nonnull final LoadedVES.Status aStatus,
                                  @Nonnull final IReadableResource aVESRes,
                                  @Nonnull final VESLoaderStatus aLoaderStatus,
                                  @Nonnull final ErrorList aErrorList)
  {
    ValueEnforcer.notNull (aStatus, "Status");
    ValueEnforcer.notNull (aVESRes, "VESRes");
    ValueEnforcer.notNull (aLoaderStatus, "LoaderStatus");
    ValueEnforcer.notNull (aErrorList, "ErrorList");

    // Read VES as XML
    final VesType aVES = new VES1Marshaller ().setCollectErrors (aErrorList).read (aVESRes);
    if (aVES == null)
    {
      // Error in XML
      aErrorList.add (SingleError.builderError ()
                                 .errorFieldName (aVESRes.getPath ())
                                 .errorText ("Failed to read VES as XML.")
                                 .build ());
      return null;
    }

    // Build VESID
    final VESID aVESID = new VESID (aVES.getGroupId (), aVES.getArtifactId (), aVES.getVersion ());

    LOGGER.info ("Trying to read VESID '" + aVESID.getAsSingleID () + "' directly");

    // Ensure the VESID is not yet in the loader chain
    if (aLoaderStatus.addVESID (aVESID).isFailure ())
    {
      aErrorList.add (SingleError.builderError ()
                                 .errorText ("The VESID '" +
                                             aVESID.getAsSingleID () +
                                             "' was already loaded. It seems like you have a circular dependency: " +
                                             aLoaderStatus.getDepedencyChain (aVESID))
                                 .build ());
      return null;
    }

    // Convert to loaded VES
    return convertToLoadedVES (aStatus, aVES, aLoaderStatus, aErrorList);
  }

  @Nullable
  public LoadedVES loadVESFromRepo (@Nonnull final VESID aVESID, @Nonnull final ErrorList aErrorList)
  {
    return loadVESFromRepo (aVESID, new VESLoaderStatus (), aErrorList);
  }

  @Nullable
  public LoadedVES loadVESFromRepo (@Nonnull final VESID aVESID,
                                    @Nonnull final VESLoaderStatus aLoaderStatus,
                                    @Nonnull final ErrorList aErrorList)
  {
    ValueEnforcer.notNull (aVESID, "VESID");
    ValueEnforcer.notNull (aLoaderStatus, "LoaderStatus");
    ValueEnforcer.notNull (aErrorList, "ErrorList");

    LOGGER.info ("Trying to read VESID '" + aVESID.getAsSingleID () + "' from repository");

    // Ensure the VESID is not yet in the loader chain
    if (aLoaderStatus.addVESID (aVESID).isFailure ())
    {
      aErrorList.add (SingleError.builderError ()
                                 .errorText ("The VESID '" +
                                             aVESID.getAsSingleID () +
                                             "' was already loaded. It seems like you have a circular dependency: " +
                                             aLoaderStatus.getDepedencyChain (aVESID))
                                 .build ());
      return null;
    }

    // Check if an explicit status is available
    final LoadedVES.Status aStatus;
    final RepoStorageKey aRepoKeyStatus = RepoStorageKey.of (aVESID, FILE_EXT_STATUS);
    if (m_aRepo.exists (aRepoKeyStatus))
    {
      final RepoStorageItem aRepoContentStatus = m_aRepo.read (aRepoKeyStatus);
      // it's okay, if it does not exist
      if (aRepoContentStatus != null)
      {
        // Read VES Status as XML
        final VesStatusType aVESStatus = new VESStatus1Marshaller ().setCollectErrors (aErrorList)
                                                                    .read (new RepoStorageReadableResource (aRepoKeyStatus,
                                                                                                            aRepoContentStatus));
        if (aVESStatus == null)
        {
          // Error in Status XML - breaking error
          aErrorList.add (SingleError.builderError ()
                                     .errorFieldName (aRepoKeyStatus.getPath ())
                                     .errorText ("Failed to read VES Status as XML.")
                                     .build ());
          return null;
        }

        aStatus = new LoadedVES.Status (aVESStatus.getStatusLastModified (),
                                        aVESStatus.getValidFrom (),
                                        aVESStatus.getValidTo (),
                                        ETriState.valueOf (aVESStatus.isDeprecated ()));
      }
      else
      {
        // Status is supposed to exist, but does not
        aStatus = LoadedVES.Status.createUndefined ();
      }
    }
    else
    {
      // Status does not exist
      aStatus = LoadedVES.Status.createUndefined ();
    }

    // Read VES content from repo
    final RepoStorageKey aRepoKeyVES = RepoStorageKey.of (aVESID, FILE_EXT_VES);
    final RepoStorageItem aRepoContentVES = m_aRepo.read (aRepoKeyVES);
    if (aRepoContentVES == null)
    {
      aErrorList.add (SingleError.builderError ()
                                 .errorFieldName (aRepoKeyVES.getPath ())
                                 .errorText ("Failed to resolve VES from repository.")
                                 .build ());
      return null;
    }

    // Read VES as XML
    final VesType aVES = new VES1Marshaller ().setCollectErrors (aErrorList)
                                              .read (new RepoStorageReadableResource (aRepoKeyVES, aRepoContentVES));
    if (aVES == null)
    {
      // Error in XML
      aErrorList.add (SingleError.builderError ()
                                 .errorFieldName (aRepoKeyVES.getPath ())
                                 .errorText ("Failed to read VES as XML.")
                                 .build ());
      return null;
    }

    // Now read into data model
    return convertToLoadedVES (aStatus, aVES, aLoaderStatus, aErrorList);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy