Package org.sonatype.nexus.configuration

Source Code of org.sonatype.nexus.configuration.ModelUtils$MissingModelVersionException

/*
* Sonatype Nexus (TM) Open Source Version
* Copyright (c) 2007-2014 Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
* which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
* of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
* Eclipse Foundation. All other trademarks are the property of their respective owners.
*/
package org.sonatype.nexus.configuration;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.StandardCopyOption;
import java.util.Map;
import java.util.Objects;

import org.sonatype.nexus.util.file.DirSupport;
import org.sonatype.sisu.goodies.common.Loggers;
import org.sonatype.sisu.goodies.common.io.FileReplacer;
import org.sonatype.sisu.goodies.common.io.FileReplacer.ContentWriter;

import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import org.slf4j.Logger;

import static com.google.common.base.Preconditions.checkNotNull;

/**
* Generic "model" utility to handle model IO independent of what technology is used for persisting POJOs to model
* and other way around, while retaining all the benefits of proper error detection and backup files.
*
* @author cstamas
* @since 2.7.0
*/
public class ModelUtils
{
  private static final Logger log = Loggers.getLogger(ModelUtils.class);

  private ModelUtils() {
    // no instance
  }

  // Stream IO

  /**
   * Model reader.
   */
  public static interface ModelReader<E>
  {
    E read(InputStream input)
        throws IOException, CorruptModelException;
  }

  /**
   * Versioned, a source of the model version that might come from it's content but does not have to.
   */
  public static interface Versioned
  {
    String readVersion(InputStream input)
        throws IOException, CorruptModelException;
  }

  /**
   * Model writer.
   */
  public static interface ModelWriter<E>
  {
    void write(OutputStream output, E model)
        throws IOException;
  }

  /**
   * Model upgrader.
   */
  public static abstract class ModelUpgrader
  {
    private final String fromVersion;

    private final String toVersion;

    public ModelUpgrader(final String fromVersion, final String toVersion) {
      this.fromVersion = checkNotNull(fromVersion);
      this.toVersion = checkNotNull(toVersion);
    }

    public final String fromVersion() {
      return fromVersion;
    }

    public final String toVersion() {
      return toVersion;
    }

    public abstract void upgrade(InputStream input, OutputStream output)
        throws IOException, CorruptModelException;
  }

  // Character Streams

  /**
   * Character models are preferred as UTF-8.
   */
  public static final Charset DEFAULT_CHARSET = Charsets.UTF_8;

  /**
   * Character model reader.
   */
  public static abstract class CharacterModelReader<E>
      implements ModelReader<E>
  {
    protected final Charset charset;

    protected CharacterModelReader() {
      this(DEFAULT_CHARSET);
    }

    protected CharacterModelReader(final Charset charset) {
      this.charset = checkNotNull(charset);
    }

    @Override
    public E read(final InputStream input) throws IOException, CorruptModelException {
      return read(new InputStreamReader(input, charset));
    }

    public abstract E read(Reader reader)
        throws IOException, CorruptModelException;
  }

  /**
   * Character model writer.
   */
  public static abstract class CharacterModelWriter<E>
      implements ModelWriter<E>
  {
    private final Charset charset;

    protected CharacterModelWriter() {
      this(DEFAULT_CHARSET);
    }

    protected CharacterModelWriter(final Charset charset) {
      this.charset = checkNotNull(charset);
    }

    @Override
    public void write(final OutputStream output, final E model) throws IOException {
      write(new OutputStreamWriter(output, charset), model);
    }

    public abstract void write(Writer writer, E model)
        throws IOException;
  }

  /**
   * Character model upgrader.
   */
  public static abstract class CharacterModelUpgrader
      extends ModelUpgrader
  {
    private final Charset charset;

    protected CharacterModelUpgrader(final String fromVersion, final String toVersion) {
      this(fromVersion, toVersion, DEFAULT_CHARSET);
    }

    protected CharacterModelUpgrader(final String fromVersion, final String toVersion, final Charset charset) {
      super(fromVersion, toVersion);
      this.charset = checkNotNull(charset);
    }

    public void upgrade(InputStream input, OutputStream output)
        throws IOException, CorruptModelException
    {
      upgrade(new InputStreamReader(input, charset), new OutputStreamWriter(output, charset));
    }

    public abstract void upgrade(Reader reader, Writer writer)
        throws IOException, CorruptModelException;
  }

  // ==

  /**
   * Exception indicating that model is unreadable by {@link ModelReader}.
   */
  public static class CorruptModelException
      extends RuntimeException
  {
    public CorruptModelException(final String message) {
      super(message);
    }

    public CorruptModelException(final String message, final Throwable cause) {
      super(message, cause);
    }
  }

  /**
   * Exception indicating that model that is {@link Versioned} lacks the version (is non existent or is empty).
   */
  public static class MissingModelVersionException
      extends CorruptModelException
  {
    public MissingModelVersionException(final String message) {
      super(message);
    }
  }

  /**
   * Adapter for {@link ModelUpgrader} to be used with {@link FileReplacer}.
   */
  private static class ModelUpgraderAdapter
      implements ContentWriter
  {
    private final File file;

    private final ModelUpgrader modelUpgrader;

    private ModelUpgraderAdapter(final File file, final ModelUpgrader modelUpgrader) {
      this.file = checkNotNull(file);
      this.modelUpgrader = checkNotNull(modelUpgrader);
    }

    @Override
    public void write(final BufferedOutputStream output) throws IOException {
      try (final InputStream input = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
        modelUpgrader.upgrade(input, output);
      }
    }
  }

  /**
   * Loads a model from a file using given reader. Also, checks the file version and if does not match with given
   * {@code currentModelVersion} will attempt upgrade using passed in upgraders.
   * <p/>
   * Note: this method method assumes few things about model: it is suited for versioned models,
   * They are expected to have a "version". The versions are opaque, they are not sorted and such, they are checked
   * for plain equality. Models <em>without</em> "version" will be rejected with CorruptModelException, kinda
   * considered corrupt.
   * <p/>
   * Also, be aware that this method, even while loading the file and converting it into POJOs, will not
   * perform any semantic validation of it, that's the caller's duty to perform. In case of IO problem, or
   * corruption failures, proper exception is thrown.
   * <p/>
   * Concurrency note: if concurrent invocation of this (thread safe method) is possible at client side,
   * it's is caller role to ensure synchronization in caller code and make sure this method is not called
   * concurrently, as IO side effects will have unexpected results. Invoking it multiple times is fine,
   * but simultaneous invocation from same component (working on same file) should not happen.
   *
   * @throws CorruptModelException if the model to be load is detected as corrupted (while loading or upgrading).
   * @throws IOException           if during load of the model an IO problem happens.
   * @since 2.7.0
   */
  public static <E> E load(final String currentModelVersion,
                           final File file,
                           final ModelReader<E> reader,
                           final ModelUpgrader... upgraders)
      throws CorruptModelException, IOException
  {
    checkNotNull(currentModelVersion, "currentModelVersion");
    checkNotNull(file, "file");
    checkNotNull(reader, "reader");
    checkNotNull(upgraders, "upgraders");
    log.info("Loading model {}", file.getAbsoluteFile(), currentModelVersion);

    try {
      if (reader instanceof Versioned) {
        final String originalFileVersion;
        try (final InputStream input = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
          originalFileVersion = ((Versioned) reader).readVersion(input);
        }

        if (Strings.isNullOrEmpty(originalFileVersion)) {
          throw new MissingModelVersionException("Passed in model has null version");
        }

        if (!Objects.equals(currentModelVersion, originalFileVersion)) {
          log.info("Upgrading model {} from version {} to {}", file.getAbsoluteFile(), originalFileVersion, currentModelVersion);
          String currentFileVersion = originalFileVersion;
          final Map<String, ModelUpgrader> upgradersMap = Maps.newHashMapWithExpectedSize(upgraders.length);
          for (ModelUpgrader upgrader : upgraders) {
            upgradersMap.put(upgrader.fromVersion(), upgrader);
          }
          final FileReplacer fileReplacer = new FileReplacer(file);
          fileReplacer.setDeleteBackupFile(true);
          ModelUpgrader upgrader = upgradersMap.get(currentFileVersion);
          // backup old version
          Files.copy(file.toPath(), new File(file.getParentFile(), file.getName() + ".old").toPath(),
              StandardCopyOption.REPLACE_EXISTING);
          while (upgrader != null && !Objects.equals(currentModelVersion, currentFileVersion)) {
            try {
              fileReplacer.replace(new ModelUpgraderAdapter(file, upgrader));
            }
            catch (CorruptModelException e) {
              final CorruptModelException ex = new CorruptModelException(String
                  .format("Model %s detected as corrupt during upgrade from version %s to version %s",
                      file.getAbsolutePath(), upgrader.fromVersion(),
                      upgrader.toVersion()), e);
              throw ex;
            }
            catch (IOException e) {
              final IOException ex = new IOException(String
                  .format("IO problem during upgrade from version %s to version %s of %s", upgrader.fromVersion(),
                      upgrader.toVersion(), file.getAbsolutePath()), e);
              throw ex;
            }
            currentFileVersion = upgrader.toVersion();
            upgrader = upgradersMap.get(currentFileVersion);
          }

          if (!Objects.equals(currentModelVersion, currentFileVersion)) {
            // upgrade failed
            throw new IOException(String
                .format(
                    "Could not upgrade model %s to version %s, is upgraded to %s, originally was %s, available upgraders exists for versions %s",
                    file.getAbsolutePath(), currentModelVersion, currentFileVersion, originalFileVersion,
                    upgradersMap.keySet()));
          }
        }
      }

      try (final InputStream input = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
        E model = reader.read(input);
        // model.setVersion(currentModelVersion);
        return model;
      }
    }
    catch (NoSuchFileException e) {
      // TODO: "translate" to old FileNotFoundEx as we have existing code relying on this exception
      // Having the new NoSuchFileEx does not buy us much, as two classes are almost identical
      final FileNotFoundException fnf = new FileNotFoundException(e.getMessage());
      fnf.initCause(e);
      throw fnf;
    }
  }

  /**
   * Saves a model to a file using given writer, keeping the a backup of the file.
   * <p/>
   * Concurrency note: if concurrent invocation of this (thread safe method) is possible at client side,
   * it's is caller role to ensure synchronization in caller code and make sure this method is not called
   * concurrently, as IO side effects will have unexpected results. Invoking it multiple times is fine,
   * but simultaneous invocation from same component (working on same file) should not happen.
   *
   * @since 2.7.0
   */
  public static <E> void save(final E model, final File file, final ModelWriter<E> writer) throws IOException {
    checkNotNull(model, "model");
    checkNotNull(file, "File");
    checkNotNull(writer, "ModelWriter");
    log.info("Saving model {}", file.getAbsoluteFile());
    DirSupport.mkdir(file.getParentFile().toPath());
    final File backupFile = new File(file.getParentFile(), file.getName() + ".bak");
    final FileReplacer fileReplacer = new FileReplacer(file);
    fileReplacer.setDeleteBackupFile(false);
    fileReplacer.replace(new ContentWriter()
    {
      @Override
      public void write(final BufferedOutputStream output) throws IOException {
        writer.write(output, model);
        output.flush();
      }
    });
    if (Files.exists(fileReplacer.getBackupFile().toPath())) {
      Files.copy(fileReplacer.getBackupFile().toPath(), backupFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
      Files.delete(fileReplacer.getBackupFile().toPath());
    }
  }
}
TOP

Related Classes of org.sonatype.nexus.configuration.ModelUtils$MissingModelVersionException

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.