package com.thetransactioncompany.cors;

import java.util.Iterator;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Handles incoming cross-origin (CORS) requests according to the configured
 * access policy. Encapsulates the CORS processing logic as specified by the
 * W3C draft from
 * 2012-04-03.
 *
 *
Note that the actual CORS exception handling (which is outside the CORS
 * specification scope) is left to the invoking class to implement.
 *
 * @author Vladimir Dzhuvinov
 * @version $version$ (2012-10-19)
 */
public class CORSRequestHandler {

	/**
	 * The CORS filter configuration, detailing the cross-origin access
	 * policy.
	 */
	private CORSConfiguration config;

	/**
	 * Pre-computed string of the CORS supported methods.
	 */
	private String supportedMethods;

	/**
	 * Pre-computed string of the CORS supported headers.
	 */
	private String supportedHeaders;

	/**
	 * Pre-computed string of the CORS exposed headers.
	 */
	private String exposedHeaders;

	/**
	 * Creates a new CORS request handler.
	 *
	 * @param config Specifies the cross-origin access policy.
	 */
	public CORSRequestHandler(final CORSConfiguration config) {

		this.config = config;

		// Pre-compute response headers where possible
		supportedMethods = serialize(config.supportedMethods, ", ");
		supportedHeaders = serialize(config.supportedHeaders, ", ");
		exposedHeaders = serialize(config.exposedHeaders, ", ");
	}

	/**
	 * Serialises the items of a set into a string. Each item must have a
	 * meaningful {@code toString()} method.
	 *
	 * @param set The set to serialise.
	 * @param sep The string separator to apply.
	 *
	 * @return The serialised set as string.
	 */
	private static String serialize(final Set set, final String sep) {

		Iterator it = set.iterator();

		String s = "";

		while (it.hasNext()) {

			s = s + it.next().toString();

			if (it.hasNext())
				s = s + sep;
		}

		return s;
	}

	/**
	 * Parses a header value consisting of zero or more space / comma /
	 * space+comma separated strings. The input string is trimmed before
	 * splitting.
	 *
	 * @param headerValue The header value, may be {@code null}.
	 *
	 * @return A string array of the parsed string items, empty if none
	 *         were found or the input was {@code null}.
	 */
	private static String[] parseMultipleHeaderValues(final String headerValue) {

		if (headerValue == null)
			return new String[0];

		String trimmedHeaderValue = headerValue.trim();

		if (trimmedHeaderValue.isEmpty())
			return new String[0];

		return trimmedHeaderValue.split("\\s*,\\s*|\\s+");
	}

	/**
	 * Tags an HTTP servlet request to provide CORS information to
	 * downstream handlers.
	 *
	 *
Tagging is provided via {@code HttpServletRequest.setAttribute()}.
	 *
	 *

	 *
{@code cors.isCorsRequest} set to {@code true} or {@code false}.
	 *
{@code cors.origin} set to the value of the "Origin" header,
	 *         {@code null} if undefined.
	 *
{@code cors.requestType} set to "actual" or "preflight" (for
	 *         CORS requests).
	 *
{@code cors.requestHeaders} set to the value of the
	 *         "Access-Control-Request-Headers" or {@code null} if
	 *         undefined (added for preflight CORS requests only).
	 *

	 *
	 * @param request The servlet request to inspect and tag. Must not be
	 *                {@code null}.
	 */
	public void tagRequest(final HttpServletRequest request) {

		final CORSRequestType type = CORSRequestType.detect(request);

		switch (type) {

			case ACTUAL:
				request.setAttribute("cors.isCorsRequest", true);
				request.setAttribute("cors.origin", request.getHeader("Origin"));
				request.setAttribute("cors.requestType", "actual");
				break;

			case PREFLIGHT:
				request.setAttribute("cors.isCorsRequest", true);
				request.setAttribute("cors.origin", request.getHeader("Origin"));
				request.setAttribute("cors.requestType", "preflight");
				request.setAttribute("cors.requestHeaders", request.getHeader("Access-Control-Request-Headers"));
				break;

			case OTHER:
				request.setAttribute("cors.isCorsRequest", false);
		}
	}

	/**
	 * Handles a simple or actual CORS request.
	 *
	 *
CORS specification: Simple Cross-Origin Request, Actual Request, and Redirects
	 *
	 * @param request  The HTTP request.
	 * @param response The HTTP response.
	 *
	 * @throws InvalidCORSRequestException    If not a valid CORS simple/
	 *                                        actual request.
	 * @throws CORSOriginDeniedException      If the origin is not allowed.
	 * @throws UnsupportedHTTPMethodException If the requested HTTP method
	 *                                        is not supported by the CORS
	 *                                        policy.
	 */
	public void handleActualRequest(final HttpServletRequest request, final HttpServletResponse response)
		throws InvalidCORSRequestException,
		       CORSOriginDeniedException,
		       UnsupportedHTTPMethodException {

		if (CORSRequestType.detect(request) != CORSRequestType.ACTUAL)
			throw new InvalidCORSRequestException("Invalid simple/actual CORS request");

		// Check origin against allow list
		Origin requestOrigin = new Origin(request.getHeader("Origin"));

		if (! config.isAllowedOrigin(requestOrigin))
			throw new CORSOriginDeniedException("CORS origin denied", requestOrigin);

		// Check method

		HTTPMethod method = null;

		try {
			method = HTTPMethod.valueOf(request.getMethod());

		} catch (Exception e) {
			// Parse exception
			throw new UnsupportedHTTPMethodException("Unsupported HTTP method: " + request.getMethod());
		}

		if (! config.isSupportedMethod(method))
			throw new UnsupportedHTTPMethodException("Unsupported HTTP method", method);

		// Success, append response headers

		response.addHeader("Access-Control-Allow-Origin", requestOrigin.toString());

		if (config.supportsCredentials)
			response.addHeader("Access-Control-Allow-Credentials", "true");

		if (! exposedHeaders.isEmpty())
			response.addHeader("Access-Control-Expose-Headers", exposedHeaders);

		// Tag request
		request.setAttribute("cors.origin", requestOrigin.toString());
		request.setAttribute("cors.requestType", "actual");
	}

	/**
	 * Handles a preflight CORS request.
	 *
	 *
CORS specification: Preflight Request
	 *
	 * @param request  The HTTP request.
	 * @param response The HTTP response.
	 *
	 * @throws InvalidCORSRequestException    If not a valid CORS preflight
	 *                                        request.
	 * @throws CORSOriginDeniedException      If the origin is not allowed.
	 * @throws UnsupportedHTTPMethodException If the requested HTTP method
	 *                                        is not supported by the CORS
	 *                                        policy.
	 * @throws UnsupportedHTTPHeaderException If the requested HTTP header
	 *                                        is not supported by the CORS
	 *                                        policy.
	 */
	public void handlePreflightRequest(final HttpServletRequest request, final HttpServletResponse response)
		throws InvalidCORSRequestException,
		       CORSOriginDeniedException,
		       UnsupportedHTTPMethodException,
		       UnsupportedHTTPHeaderException {

		if (CORSRequestType.detect(request) != CORSRequestType.PREFLIGHT)
			throw new InvalidCORSRequestException("Invalid preflight CORS request");

		// Check origin against allow list
		Origin requestOrigin = new Origin(request.getHeader("Origin"));

		if (! config.isAllowedOrigin(requestOrigin))
			throw new CORSOriginDeniedException("CORS origin denied", requestOrigin);

		// Parse requested method
		// Note: method checking must be done after header parsing, see CORS spec

		String requestMethodHeader = request.getHeader("Access-Control-Request-Method");

		if (requestMethodHeader == null)
			throw new InvalidCORSRequestException("Invalid preflight CORS request: Missing Access-Control-Request-Method header");

		HTTPMethod requestedMethod = null;

		try {
			requestedMethod = HTTPMethod.valueOf(requestMethodHeader.toUpperCase());

		} catch (Exception e) {
			// Parse exception
			throw new UnsupportedHTTPMethodException("Unsupported HTTP method: " + requestMethodHeader);
		}

		// Parse custom headers

		final String[] requestHeaderValues = parseMultipleHeaderValues(request.getHeader("Access-Control-Request-Headers"));

		final HeaderFieldName[] requestHeaders = new HeaderFieldName[requestHeaderValues.length];

		for (int i=0; i<requestHeaders.length; i++) {

			try {
				requestHeaders[i] = new HeaderFieldName(requestHeaderValues[i]);

			} catch (IllegalArgumentException e) {
				// Invalid header name
				throw new InvalidCORSRequestException("Invalid preflight CORS request: Bad request header value");
			}
		}

		// Now, do method check
		if (! config.isSupportedMethod(requestedMethod))
			throw new UnsupportedHTTPMethodException("Unsupported HTTP method", requestedMethod);

		// Author request headers check

		for (int i=0; i<requestHeaders.length; i++) {

			if (! config.supportedHeaders.contains(requestHeaders[i]))
				throw new UnsupportedHTTPHeaderException("Unsupported HTTP request header", requestHeaders[i]);

		}

		// Success, append response headers

		if (config.supportsCredentials) {
			response.addHeader("Access-Control-Allow-Origin", requestOrigin.toString());
			response.addHeader("Access-Control-Allow-Credentials", "true");
		}
		else {
			if (config.allowAnyOrigin)
				response.addHeader("Access-Control-Allow-Origin", "*");
			else
				response.addHeader("Access-Control-Allow-Origin", requestOrigin.toString());
		}

		if (config.maxAge > 0)
			response.addHeader("Access-Control-Max-Age", Integer.toString(config.maxAge));

		response.addHeader("Access-Control-Allow-Methods", supportedMethods);

		if (! supportedHeaders.isEmpty())
			response.addHeader("Access-Control-Allow-Headers", supportedHeaders);
	}
}


package com.thetransactioncompany.cors;

/**
 * Represents an HTTP header field name. Provides an {@link #equals} method
 * to compare two header names using case-insensitive matching (RFC 2616,
 * section 4.2).
 *
 *
Header field name examples:
 *
 *

 *
Content-Type
 *
User-Agent
 *
X-Requested-With
 *

 *
 * @author Vladimir Dzhuvinov
 * @version $version$ (2012-10-19)
 */
public class HeaderFieldName {

	/**
	 * The header field name, formatted as {@code Aaa-Bbb-Ccc}.
	 */
	private final String name;

	/**
	 * Applies a {@code Aaa-Bbb-Ccc} format to a header field name.
	 *
	 * @param name The header field name to format, must not be an empty
	 *             string or {@code null}.
	 *
	 * @return The formatted header field name.
	 *
	 * @throws IllegalArgumentException On a empty or invalid header field
	 *                                  name.
	 */
	protected static String formatCanonical(final String name) {

		String nameTrimmed = name.trim();

		if (nameTrimmed.isEmpty())
			throw new IllegalArgumentException("The header field name must not be an empty string");

		// Check for valid syntax: must begin with letter, then only word and dash chars allowed
		if (! nameTrimmed.matches("^[a-zA-Z][\\w-]*$"))
			throw new IllegalArgumentException("Invalid header field name syntax");

		String[] tokens = nameTrimmed.toLowerCase().split("-");

		String out = "";

		for (int i=0; i < tokens.length; i++) {

			char[] c = tokens[i].toCharArray();

			// Capitalise first char
			c[0] = Character.toUpperCase(c[0]);

			if (i >= 1)
				out = out + "-";

			out = out + new String(c);
		}

		return out;
	}

	/**
	 * Creates a new header field name from the specified string.
	 *
	 * @param name The header field name, must not be an empty strings or
	 *             {@code null}.
	 *
	 * @throws IllegalArgumentException On a empty or invalid header field
	 *                                  name.
	 */
	public HeaderFieldName (final String name) {

		this.name = formatCanonical(name);
	}

	/**
	 * Returns a string representation of a header field name in
	 * {@code Aaa-Bbb-Ccc} format.
	 *
	 * @return The header field name as string.
	 */
	public String toString() {

		return name;
	}

	/**
	 * Overrides {@code Object.hashCode}.
	 *
	 * @return The object hash code.
	 */
	public int hashCode() {

		return name.hashCode();
	}

	/**
	 * Overrides {@code Object.equals()}.
	 *
	 * @param object The object to compare to.
	 *
	 * @return {@code true} if the objects have the same value, otherwise
	 *         {@code false}.
	 */
	public boolean equals(Object object) {

		return object instanceof HeaderFieldName && name.equals(object.toString());
	}
}


/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * MinDeps.java
 * Copyright (C) 2017 FracPete
 */

package com.github.fracpete.deps4j;

import com.github.fracpete.processoutput4j.output.CollectingProcessOutput;
import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.ArgumentParser;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.Namespace;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Class for determining the minimum required classes from a small set
 * of classes and an associated classpath.
 */
public class MinDeps {

  /** the java home directory to use. */
  protected File m_JavaHome;

  /** the jdeps binary. */
  protected File m_Jdeps;

  /** the classpath to use. */
  protected String m_ClassPath;

  /** the file with classes to determine the minimum dependencies for. */
  protected File m_ClassesFile;

  /** the file with additional class names to include (optional). */
  protected File m_AdditionalFile;

  /** packages to keep. */
  protected List<String> m_Packages;

  /** the output file for the determined class names (optional). */
  protected File m_OutputFile;

  /** the classes to inspect. */
  protected List<String> m_Classes;

  /** the optional resource files. */
  protected List<String> m_Resources;

  /** the dependent classes. */
  protected Set<String> m_DependentClasses;

  /** the full dependencies. */
  protected List<String> m_Dependencies;

  public MinDeps() {
    super();

    m_JavaHome         = (System.getenv("JAVA_HOME") != null ? new File(System.getenv("JAVA_HOME")) : null);
    m_ClassesFile      = null;
    m_ClassPath        = null;
    m_AdditionalFile   = null;
    m_OutputFile       = null;
    m_Packages         = new ArrayList<>();
    m_Classes          = new ArrayList<>();
    m_Resources        = new ArrayList<>();
    m_DependentClasses = new HashSet<>();
    m_Dependencies     = new ArrayList<>();

  }

  /**
   * Sets the java home directory.
   *
   * @param value	the directory
   */
  public void setJavaHome(File value) {
    m_JavaHome = value;
  }

  /**
   * Returns the java home directory.
   *
   * @return		the directory
   */
  public File getJavaHome() {
    return m_JavaHome;
  }

  /**
   * Sets the classpath to use.
   *
   * @param value	the classpath
   */
  public void setClassPath(String value) {
    m_ClassPath = value;
  }

  /**
   * Returns the classpath to use.
   *
   * @return		the classpath, null if not set
   */
  public String getClassPath() {
    return m_ClassPath;
  }

  /**
   * Sets the file with the class files to inspect.
   *
   * @param value	the file
   */
  public void setClassesFile(File value) {
    m_ClassesFile = value;
  }

  /**
   * Returns the file with the class files to inspect.
   *
   * @return		the file, null if not set
   */
  public File getClassesFile() {
    return m_ClassesFile;
  }

  /**
   * Sets the file with the additional classnames to include (optional).
   *
   * @param value	the file
   */
  public void setAdditionalFile(File value) {
    m_AdditionalFile = value;
  }

  /**
   * Returns the file with the additional classnames to include (optional).
   *
   * @return		the file, null if not set
   */
  public File getAdditionalFile() {
    return m_AdditionalFile;
  }

  /**
   * Sets the packages to keep.
   *
   * @param value	the packages
   */
  public void setPackages(List<String> value) {
    m_Packages.addAll(value);
  }

  /**
   * Returns the packages to keep.
   *
   * @return		the packages
   */
  public List<String> getPackages() {
    return m_Packages;
  }

  /**
   * Sets the file for storing the determined class names in (optional).
   * Uses stdout if not set.
   *
   * @param value	the file
   */
  public void setOutputFile(File value) {
    m_OutputFile = value;
  }

  /**
   * Returns the file for storing the determined class names in (optional).
   * Uses stdout if not set.
   *
   * @return		the file, null if not set
   */
  public File getOutputFile() {
    return m_OutputFile;
  }

  /**
   * Sets the commandline options.
   *
   * @param options	the options to use
   * @return		true if successful
   * @throws Exception	in case of an invalid option
   */
  public boolean setOptions(String[] options) throws Exception {
    ArgumentParser parser;
    Namespace ns;

    parser = ArgumentParsers.newArgumentParser(MinDeps.class.getName());
    parser.addArgument("--java-home")
      .type(Arguments.fileType().verifyExists().verifyIsDirectory())
      .dest("javahome")
      .required(true)
      .help("The java home directory of the JDK that includes the jdeps binary, default is taken from JAVA_HOME environment variable.");
    parser.addArgument("--class-path")
      .dest("classpath")
      .required(true)
      .help("The CLASSPATH to use for jdeps.");
    parser.addArgument("--classes")
      .type(Arguments.fileType().verifyExists().verifyIsFile().verifyCanRead())
      .dest("classes")
      .required(true)
      .help("The file containing the classes to determine the dependencies for. Empty lines and lines starting with # get ignored.");
    parser.addArgument("--additional")
      .type(Arguments.fileType())
      .setDefault(new File("."))
      .required(false)
      .dest("additional")
      .help("The file with additional class names to just include.");
    parser.addArgument("--output")
      .type(Arguments.fileType())
      .setDefault(new File("."))
      .required(false)
      .dest("output")
      .help("The file for storing the determined class names in.");
    parser.addArgument("package")
      .dest("packages")
      .required(true)
      .nargs("+")
      .help("The packages to keep, eg 'weka'.");

    try {
      ns = parser.parseArgs(options);
    }
    catch (ArgumentParserException e) {
      parser.handleError(e);
      return false;
    }

    setJavaHome(ns.get("javahome"));
    setClassPath(ns.getString("classpath"));
    setClassesFile(ns.get("classes"));
    setAdditionalFile(ns.get("additional"));
    setPackages(ns.getList("packages"));
    setOutputFile(ns.get("output"));

    return true;
  }

  /**
   * Initializes the execution.
   */
  protected void initialize() {
    m_DependentClasses.clear();
    m_Resources.clear();
    m_Classes.clear();
    m_Dependencies.clear();
  }

  /**
   * Reads the file into the the provided list.
   * Skips empty lines and lines starting with #.
   *
   * @param file	the file to read
   * @param lines 	the lines to add the content to
   * @return 		null if successful, otherwise error message
   */
  protected String readFile(File file, List<String> lines) {
    int		i;

    try {
      lines.addAll(Files.readAllLines(file.toPath()));
      i = 0;
      while (i < lines.size()) {
        if (lines.get(i).trim().isEmpty()) {
          lines.remove(i);
          continue;
	}
	if (lines.get(i).startsWith("#")) {
          lines.remove(i);
          continue;
	}
	i++;
      }
    }
    catch (Exception e) {
      return "Failed to read file: " + file + "\n" + e;
    }

    return null;
  }

  /**
   * Performs some checks.
   *
   * @return		null if successful, otherwise error message
   */
  protected String check() {
    String		error;

    if (!m_JavaHome.exists())
      return "Java home directory does not exist: " + m_JavaHome;
    if (!m_JavaHome.isDirectory())
      return "Java home does not point to a directory: " + m_JavaHome;
    if (System.getProperty("os.name").toLowerCase().contains("windows"))
      m_Jdeps = new File(m_JavaHome.getAbsolutePath() + File.separator + "bin" + File.separator + "jdeps.exe");
    else
      m_Jdeps = new File(m_JavaHome.getAbsolutePath() + File.separator + "bin" + File.separator + "jdeps");
    if (!m_Jdeps.exists())
      return "jdeps binary does not exist: " + m_Jdeps;

    if (!m_ClassesFile.exists())
      return "File with class names does not exist: " + m_ClassesFile;
    if (m_ClassesFile.isDirectory())
      return "File with class names points to directory: " + m_ClassesFile;

    // read classes
    error = readFile(m_ClassesFile, m_Classes);
    if (error != null)
      return error;

    // read resources
    if ((m_AdditionalFile != null) && m_AdditionalFile.exists() && (!m_AdditionalFile.isDirectory())) {
      error = readFile(m_AdditionalFile, m_Resources);
      if (error != null)
	return error;
    }

    return null;
  }

  /**
   * Filters the list of strings with a regular expression.
   *
   * @param lines	the list to filter
   * @param regexp	the regular expression to use
   * @param invert	whether to invert the matching sense
   * @return		the filtered list
   */
  protected List<String> filter(List<String> lines, String regexp, boolean invert) {
    List<String>	result;
    Pattern 		pattern;

    result  = new ArrayList<>();
    pattern = Pattern.compile(regexp);

    for (String line: lines) {
      if (invert) {
	if (!pattern.matcher(line).matches())
	  result.add(line);
      }
      else {
	if (pattern.matcher(line).matches())
	  result.add(line);
      }
    }

    return result;
  }

  /**
   * Builds the regular expression for the packages to keep.
   *
   * @return		the regexp
   */
  protected String packagesRegExp() {
    StringBuilder	result;
    int			i;
    String		pkg;

    result = new StringBuilder();
    result.append(".* (");
    for (i = 0; i < m_Packages.size(); i++) {
      if (i > 0)
        result.append("|");
      pkg = m_Packages.get(i);
      if (!pkg.endsWith("."))
        pkg = pkg + ".";
      pkg = pkg.replace(".", "\\.");
      result.append(pkg);
    }
    result.append(").*$");

    return result.toString();
  }

  /**
   * Determines the dependencies.
   *
   * @return		null if successful, otherwise error message
   */
  protected String determine() {
    String[] 			cmd;
    ProcessBuilder 		builder;
    CollectingProcessOutput 	output;
    List<String>		lines;
    int				i;
    String			line;

    for (String cls: m_Classes) {
      // progress
      System.err.println(cls);

      cmd = new String[]{
        m_Jdeps.getAbsolutePath(),
	"-cp",
	m_ClassPath,
	"-recursive",
	"-verbose:class",
	cls
      };
      builder = new ProcessBuilder();
      builder.command(cmd);
      output = new CollectingProcessOutput();
      try {
	output.monitor(builder);
      }
      catch (Exception e) {
        return "Failed to execute: " + builder.toString() + "\n" + e;
      }

      // filter output
      lines = new ArrayList<>(Arrays.asList(output.getStdOut().replace("\r", "").split("\n")));
      lines = filter(lines, packagesRegExp(), false);
      lines = filter(lines, ".*\\$.*", true);
      lines = filter(lines, ".*\\.jar\\)", true);

      // clean up
      for (i = 0; i < lines.size(); i++) {
        line = lines.get(i);
        line = line.replaceFirst(".* -> ", "");
        line = line.replaceFirst(" .*$", "");
        line = line.trim();
        lines.set(i, line);
      }

      // add to result
      m_DependentClasses.addAll(lines);
      System.err.println("--> " + m_DependentClasses.size());
    }

    return null;
  }

  /**
   * Determines the dependencies.
   *
   * @return		null if successful, otherwise error message
   */
  public String execute() {
    String		result;

    initialize();

    result = check();

    if (result == null)
      result = determine();

    if (result == null) {
      m_Dependencies = new ArrayList<>();
      m_Dependencies.addAll(m_Classes);
      for (String cls: m_DependentClasses) {
        if (!m_Dependencies.contains(cls))
	  m_Dependencies.add(cls);
      }
      for (String cls: m_Resources) {
        if (!m_Dependencies.contains(cls))
	  m_Dependencies.add(cls);
      }
      Collections.sort(m_Dependencies);
    }

    return result;
  }

  /**
   * Outputs the dependencies on stdout.
   */
  public void output() {
    if ((m_OutputFile == null || m_OutputFile.isDirectory())) {
      for (String dep : m_Dependencies)
	System.out.println(dep);
    }
    else {
      try {
	Files.write(m_OutputFile.toPath(), m_Dependencies, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
      }
      catch (Exception e) {
        System.err.println("Failed to write dependencies to: " + m_OutputFile);
        e.printStackTrace();
      }
    }
  }

  /**
   * Returns the dependencies collected.
   *
   * @return		the dependencies
   * @see		#execute()
   */
  public List<String> getDependencies() {
    return m_Dependencies;
  }

  public static void main(String[] args) throws Exception {
    MinDeps	mindeps;
    String	error;

    mindeps = new MinDeps();
    if (mindeps.setOptions(args)) {
      error = mindeps.execute();
      if (error != null) {
        System.err.println(error);
	System.exit(2);
      }
      else {
        mindeps.output();
      }
    }
    else {
      System.exit(1);
    }
  }
}


/*
 Copyright 2015 eBusiness Information
 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 mobi.designmyapp.common.instance.provider;

import mobi.designmyapp.common.instance.model.Instance;
import mobi.designmyapp.common.instance.model.Status;

import java.util.List;

/**
 * This class represents an instance provider, a provider manage
 * instances (@see mobi.designmyapp.common.instance.model.Instance)
 * Created by Jean Blanchard on 28/10/14.
 */
public abstract class InstanceProvider implements Comparable<InstanceProvider> {

  protected List<Instance> instances;
  protected Integer poolSize;
  protected Integer ttl;
  protected Integer priority;

  /**
   * Retrieve deployed instances.
   * @return deployed instances
   */
  public abstract List<Instance> getInstances();

  /**
   * Refresh the status of the instances.
   */
  public abstract void refreshInstancesState();

  /**
   * Retrieve a single instance by its id, null if the id is not found.
   * @param instanceId instance id to retrieve
   * @return the instance or null if not found
   */
  public abstract Instance getInstance(String instanceId);

  public Integer getPoolSize(){
    return poolSize;
  }

  public void setPoolSize(Integer poolSize){
    this.poolSize = poolSize;
  }

  public Integer getPriority() {
    return priority;
  }

  public void setPriority(Integer priority) {
    this.priority = priority;
  }

  /**
   * Friendly name for this provider.
   * @return the name of the provider
   */
  public abstract String getName();

  /**
   * Start a new instance.
   * @param imageName String representing the kind of instance to launch
   * @param options List representing the option to use when starting the instance
   * @param args List representing the args to use when starting the instance
   * @return the started instance if no errors append
   */
  public abstract Instance start(String imageName, List<String> options, List<String> args);

  /**
   * Terminate an instance.
   * @param instanceId instance id to terminate
   */
  public abstract void stop(String instanceId);

  /**
   * Restart an instance.
   * @param instanceId instance Id to restart
   * @return the restarted instance
   */
  public abstract Instance restart(String instanceId);

  /**
   * Retrieve the status of this InstanceProvider.
   * @return InstanceProvider status @see mobi.designmyapp.common.instance.model.Status
   */
  public abstract Status getStatus();

  /**
   * Indicate if the InstanceProvider can create instances checking poolSize
   * and running instances List.
   * @return boolean indicating whether a new instance can be started.
   */
  public abstract boolean canCreateInstance();

  /**
   * Retrieve the number of RUNNING instances.
   * @return number of RUNNING instances
   */
  public abstract int getActiveCount();

  /**
   * Return the provider-level time-to-live for default instances.
   * @return time-to-live
   */
  public Integer getTtl() {
    return ttl;
  }

  /**
   * Default implementation of compareTo for the InstanceManager.
   */
  @Override
  public int compareTo(InstanceProvider instanceProvider) {
    if (this.getPriority().equals(instanceProvider.getPriority())) {
      return this.getName().compareTo(instanceProvider.getName());
    } else {
      return this.getPriority().compareTo(instanceProvider.getPriority());
    }
  }

}


package com.azaptree.services.command.impl;

/*
 * #%L
 * AZAPTREE-COMMAND-SERVICE
 * %%
 * Copyright (C) 2012 AZAPTREE.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.
 * #L%
 */

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.annotation.PostConstruct;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.azaptree.services.command.CommandCatalog;
import com.azaptree.services.command.CommandException;
import com.azaptree.services.command.CommandKey;
import com.azaptree.services.command.CommandService;
import com.azaptree.services.command.CommandServiceJmxApi;
import com.azaptree.services.commons.validation.ValidationException;
import com.google.common.collect.ImmutableMap;

/**
 *
 *
 * @author Alfio Zappala
 *
 */
@Service
@ManagedResource
public class CommandServiceImpl implements CommandService, CommandServiceJmxApi {

	@Autowired
	private List<CommandCatalog> catalogs;

	private Map<String, CommandCatalog> commandCatalogs;
	private Map<CommandKey, Command> commands;

	@Override
	public void execute(final CommandKey key, final Context ctx) throws CommandException {
		Assert.notNull(key, "key is required");
		Assert.notNull(ctx, "ctx is required");

		final org.apache.commons.chain.Command command = getCommonsChainCommand(key);
		final CommandExcecutionMetric metric = new CommandExcecutionMetric(key);
		try {
			command.execute(ctx);
			metric.succeeded();
		} catch (final CommandException | ValidationException | IllegalArgumentException e) {
			metric.failed(e);
			throw e;
		} catch (final Throwable t) {
			metric.failed(t);
			throw new CommandException(t);
		} finally {
			commandMetricLog.info(metric.toString());
		}
	}

	private org.apache.commons.chain.Command getCommonsChainCommand(final CommandKey key) {
		Assert.notNull(key, "key is required");
		final org.apache.commons.chain.Command command = commands.get(key);
		if (command == null) {
			throw new IllegalArgumentException(String.format("Unknown command: %s -> %s", key.getCatalogName(), key.getCommandName()));
		}

		return command;
	}

	@Override
	public com.azaptree.services.command.Command getCommand(final CommandKey key) {
		return (com.azaptree.services.command.Command) getCommonsChainCommand(key);
	}

	@Override
	public CommandCatalog getCommandCatalog(final String catalogName) {
		Assert.hasText(catalogName);
		return commandCatalogs.get(catalogName);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.command.impl.CommandServiceJmxApi#getCommandCatalogNames()
	 */
	@ManagedAttribute
	@Override
	public String[] getCommandCatalogNames() {
		final Set<String> catalogNames = new TreeSet<>(commandCatalogs.keySet());
		return catalogNames.toArray(new String[catalogNames.size()]);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.command.impl.CommandServiceJmxApi#getCommandNames(java.lang.String)
	 */
	@Override
	@ManagedOperation
	@ManagedOperationParameters({ @ManagedOperationParameter(name = "catalogName", description = "Catalog name") })
	public String[] getCommandNames(final String catalogName) {
		Assert.hasText(catalogName, "catalogName is required");
		final CommandCatalog catalog = getCommandCatalog(catalogName);
		return catalog.getCommandNames();
	}

	@PostConstruct
	public void init() {
		Assert.notEmpty(catalogs, "No catalogs were found");

		final Map<String, CommandCatalog> catMap = new HashMap<>();
		final Map<CommandKey, Command> cmdMap = new HashMap<>();
		for (final CommandCatalog catalog : catalogs) {
			log.info("registering catalog : {}", catalog.getName());
			if (catMap.get(catalog.getName()) != null) {
				throw new IllegalArgumentException("Duplicate Catalog name found: " + catalog.getName());
			}
			catMap.put(catalog.getName(), catalog);

			for (final String commandName : catalog.getCommandNames()) {
				final Command command = catalog.getCommand(commandName);
				Assert.notNull(command);
				final CommandKey commandKey = new CommandKey(catalog.getName(), commandName);
				cmdMap.put(commandKey, command);
				log.info("registered CommandKey: {}", commandKey);
			}
		}
		catalogs = null; // no longer needed

		commandCatalogs = ImmutableMap.<String, CommandCatalog> builder().putAll(catMap).build();
		commands = ImmutableMap.<CommandKey, Command> builder().putAll(cmdMap).build();

	}
}


/*
 Copyright 2015 eBusiness Information
 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 mobi.designmyapp.common.util;

import mobi.designmyapp.common.engine.service.PricingService;
import mobi.designmyapp.common.engine.service.ResourceService;
import mobi.designmyapp.common.instance.service.InstanceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Factory to retrieve Utility and Service classes
 * Created by Alexandre Nunesse on 06/01/15.
 */
public class UtilsFactory {

  private static final Logger LOGGER = LoggerFactory.getLogger(UtilsFactory.class);

  private static final String IO_UTILS = "mobi.designmyapp.common.util.IOUtilsImpl";
  private static final String IMAGE_UTILS = "mobi.designmyapp.common.util.ImageUtilsImpl";
  private static final String PRICE_UTILS = "mobi.designmyapp.common.util.PriceUtilsImpl";
  private static final String RESOURCE_UTILS = "mobi.designmyapp.common.util.ResourceUtilsImpl";
  private static final String ZIP_UTILS = "mobi.designmyapp.common.util.ZipUtilsImpl";
  private static final String DIGEST_UTILS = "mobi.designmyapp.common.util.DigestUtilsImpl";
  private static final String STRING_UTILS = "mobi.designmyapp.common.util.StringUtilsImpl";

  private static final String RESOURCE_SERVICE = "mobi.designmyapp.engine.service.ResourceServiceImpl";
  private static final String PRICING_SERVICE = "mobi.designmyapp.engine.service.PricingServiceImpl";
  private static final String INSTANCE_SERVICE = "mobi.designmyapp.engine.service.InstanceServiceImpl";

  private static IOUtils ioUtils;
  private static ImageUtils imageUtils;
  private static PriceUtils priceUtils;
  private static ResourceUtils resourceUtils;
  private static ZipUtils zipUtils;
  private static DigestUtils digestUtils;
  private static StringUtils stringUtils;

  private static ResourceService resourceService;
  private static PricingService pricingService;
  private static InstanceService instanceService;

  private UtilsFactory() {

  }

  /**
   * Retrieve IOUtils implementation
   * @return IOUtils instance
   */
  public static IOUtils getIOUtils() {
    if (ioUtils == null) {
      try {
        Class clazz = Class.forName(IO_UTILS);
        ioUtils = (IOUtils) clazz.newInstance();
      } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        LOGGER.warn("Cannot instanciate util: {}", e.getMessage());
        throw new IllegalStateException(e);
      }
    }
    return ioUtils;
  }

  /**
   * Retrieve ImageUtils implementation
   * @return ImageUtils instance
   */
  public static ImageUtils getImageUtils() {
    if (imageUtils == null) {
      try {
        Class clazz = Class.forName(IMAGE_UTILS);
        imageUtils = (ImageUtils) clazz.newInstance();
      } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        LOGGER.warn("Cannot instanciate util: {}", e.getMessage());
        throw new IllegalStateException(e);
      }
    }
    return imageUtils;
  }

  /**
   * Retrieve PriceUtils implementation
   * @return PriceUtils instance
   */
  public static PriceUtils getPriceUtils() {
    if (priceUtils == null) {
      try {
        Class clazz = Class.forName(PRICE_UTILS);
        priceUtils = (PriceUtils) clazz.newInstance();
      } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        LOGGER.warn("Cannot instanciate util: {}", e.getMessage());
        throw new IllegalStateException(e);
      }
    }
    return priceUtils;
  }

  /**
   * Retrieve ResourceUtils implementation
   * @return ResourceUtils instance
   */
  public static ResourceUtils getResourceUtils() {
    if (resourceUtils == null) {
      try {
        Class clazz = Class.forName(RESOURCE_UTILS);
        resourceUtils = (ResourceUtils) clazz.newInstance();
      } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        LOGGER.warn("Cannot instanciate util: {}", e.getMessage());
        throw new IllegalStateException(e);
      }
    }
    return resourceUtils;
  }

  /**
   * Retrieve ZipUtils implementation
   * @return ZipUtils instance
   */
  public static ZipUtils getZipUtils() {
    if (zipUtils == null) {
      try {
        Class clazz = Class.forName(ZIP_UTILS);
        zipUtils = (ZipUtils) clazz.newInstance();
      } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        LOGGER.warn("Cannot instanciate util: {}",e.getMessage());
        throw new IllegalStateException(e);
      }
    }
    return zipUtils;
  }

  /**
   * Retrieve DigestUtils implementation
   * @return DigestUtils instance
   */
  public static DigestUtils getDigestUtils() {
    if (digestUtils == null) {
      try {
        Class clazz = Class.forName(DIGEST_UTILS);
        digestUtils = (DigestUtils) clazz.newInstance();
      } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        LOGGER.warn("Cannot instanciate util: {}", e.getMessage());
        throw new IllegalStateException(e);
      }
    }
    return digestUtils;
  }

  /**
   * Retrieve StringUtils implementation
   * @return StringUtils instance
   */
  public static StringUtils getStringUtils() {
    if (stringUtils == null) {
      try {
        Class clazz = Class.forName(STRING_UTILS);
        stringUtils = (StringUtils) clazz.newInstance();
      } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        LOGGER.warn("Cannot instanciate util: {}", e.getMessage());
        throw new IllegalStateException(e);
      }
    }
    return stringUtils;
  }

  /**
   * Retrieve ResourceService implementation
   * @return ResourceService instance
   */
  public static ResourceService getResourceService() {
    if (resourceService == null) {
      try {
        Class clazz = Class.forName(RESOURCE_SERVICE);
        resourceService = (ResourceService) clazz.newInstance();
      } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        LOGGER.warn("Cannot instanciate util: {}", e.getMessage());
        throw new IllegalStateException(e);
      }
    }
    return resourceService;
  }

  /**
   * Retrieve PricingService implementation
   * @return PricingService instance
   */
  public static PricingService getPricingService() {
    if (pricingService == null) {
      try {
        Class clazz = Class.forName(PRICING_SERVICE);
        pricingService = (PricingService) clazz.newInstance();
      } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        LOGGER.warn("Cannot instanciate util: {}", e.getMessage());
        throw new IllegalStateException(e);
      }
    }
    return pricingService;
  }

  /**
   * Retrieve InstanceService implementation
   * @return InstanceService instance
   */
  public static InstanceService getInstanceService() {
    if (instanceService == null) {
      try {
        Class clazz = Class.forName(INSTANCE_SERVICE);
        instanceService = (InstanceService) clazz.newInstance();
      } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        LOGGER.warn("Cannot instanciate util: {}", e.getMessage());
        throw new IllegalStateException(e);
      }
    }
    return instanceService;
  }
}


package org.sbolstandard.core.io.rdf;

import java.net.URI;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.stream.events.XMLEvent;

import org.sbolstandard.core.datatree.*;
import org.sbolstandard.core.datatree.Datatree.NamedProperties;
import org.sbolstandard.core.datatree.Datatree.NamespaceBindings;
import org.sbolstandard.core.io.CoreIoException;
import org.sbolstandard.core.io.IoReader;
import org.sbolstandard.core.io.IoWriter;
import static org.sbolstandard.core.io.rdf.RdfTerms.*;

/**
 * The IO layer to read and write {@link DocumentRoot}s using RDF/XML.
 *

 * Documents are serialised using nesting, in which {@link TopLevelDocument}s embed {@link NestedDocument}s.
 *

 *
 *

 * Both {@link TopLevelDocument}s and {@link NestedDocument}s are represented as RDF resources, and
 * {@link NamedProperty} objects are serialised as statements for these RDF resources.
 *

 */
public class RdfIo{

  /**
   * Creates an {@link IoWriter} using the given {@link XMLStreamWriter} object.
   *
   *
This {@link IoWriter} provides a method to write {@link DocumentRoot} objects in RDF/XML format.
   * During the serialisation, the RDF namespace is added if it is not provided in the {@link NamespaceBinding}s property of a {@link DocumentRoot}.
   *

   * @param writer The {@link XMLStreamWriter} writer to serialise a {@link DocumentRoot}.
   * @return {@link IoWriter}
   */
  public IoWriter<QName> createIoWriter(final XMLStreamWriter writer)
  {
    return new IoWriter<QName>() {

      @Override
      public void write(DocumentRoot<QName> document) throws CoreIoException {
        try
        {
          writer.writeStartDocument();

          writeStartElement(RDF);
          setPrefix(rdf);

          if(!document.getNamespaceBindings().contains(rdf)) {
            writeNamespace(rdf);
          }

          for(NamespaceBinding nb : document.getNamespaceBindings()) {
            setPrefix(nb);
            writeNamespace(nb);
          }

          for (TopLevelDocument<QName> child : document.getTopLevelDocuments())
          {
            write(child);
          }

          writer.writeEndElement();
          writer.writeEndDocument();
        }
        catch(XMLStreamException xse)
        {
          throw new CoreIoException(xse);
        }

      }

      private void write(IdentifiableDocument<QName> doc) throws XMLStreamException {
        writeStartElement(doc.getType());
        writeAttribute(rdfAbout, doc.getIdentity().toString());

        for (NamedProperty<QName> property : doc.getProperties()) {
          write(property);
        }

        writer.writeEndElement();
      }

      private void write(final NamedProperty<QName> property) {
        new PropertyValue.Visitor<QName>() {
          @Override
          public void visit(NestedDocument<QName> v) throws XMLStreamException {
            writeStartElement(property.getName());
            write(v);
            writer.writeEndElement();
          }

          @Override
          public void visit(Literal<QName> v) throws XMLStreamException {
            if(isEmptyElementValue(v)) {
              writeEmptyElement(property.getName());
              write(v);
            } else {
              writeStartElement(property.getName());
              write(v);
              writer.writeEndElement();
            }
          }
        }.visit(property.getValue());
      }

      private boolean isEmptyElementValue(Literal<QName> literal) {
        return /* literal instanceof Literal.QNameLiteral || */ literal instanceof Literal.UriLiteral;
      }

      private void write(Literal<QName> literal) {
        new Literal.Visitor<QName>() {
          @Override
          public void visit(Literal.StringLiteral<QName> l) throws XMLStreamException {
            writer.writeCharacters(l.getValue());
          }

          @Override
          public void visit(Literal.UriLiteral<QName> l) throws XMLStreamException {
            writeAttribute(rdfResource, l.getValue().toString());
          }

          @Override
          public void visit(Literal.IntegerLiteral<QName> l) throws XMLStreamException {
            writer.writeCharacters(l.getValue().toString());
          }

          @Override
          public void visit(Literal.DoubleLiteral<QName> l) throws XMLStreamException {
            writer.writeCharacters(l.getValue().toString());
          }

          @Override
          public void visit(Literal.TypedLiteral<QName> l) throws XMLStreamException {
            writer.writeCharacters(l.getValue() + "^^" + l.getType().getPrefix() + ":" + l.getType().getLocalPart());
          }

          @Override
          public void visit(Literal.BooleanLiteral<QName> l) throws XMLStreamException {
            writer.writeCharacters(l.getValue().toString());
          }
        }.visit(literal);
      }

      private void writeEmptyElement(QName tagName) throws XMLStreamException {
        writer.writeEmptyElement(tagName.getPrefix(), tagName.getLocalPart(), tagName.getNamespaceURI());
      }

      private void writeStartElement(QName tagName) throws XMLStreamException {
        writer.writeStartElement(tagName.getPrefix(), tagName.getLocalPart(), tagName.getNamespaceURI());
      }

      private void setPrefix(NamespaceBinding binding) throws XMLStreamException {
        writer.setPrefix(binding.getPrefix(), binding.getNamespaceURI());
      }

      private void writeNamespace(NamespaceBinding binding) throws XMLStreamException {
        writer.writeNamespace(binding.getPrefix(), binding.getNamespaceURI());
      }

      private void writeAttribute(QName attrName, String attrValue) throws XMLStreamException {
        writer.writeAttribute(
                attrName.getPrefix(),
                attrName.getNamespaceURI(),
                attrName.getLocalPart(),
                attrValue);
      }

    };
  }

  /**
   * Creates an {@link IoReader} using the given {@link XMLStreamReader}.
   *

   * This {@link IoReader} provides a method to read data in RDF/XML format and deserialise it into a {@link DocumentRoot} object.
   *

   * @param xmlReader The {@link XMLStreamReader} reader to read RDF/XML data.
   * @return {@link IoReader}
   * @throws XMLStreamException  when the underlying reader raises an exception
   */
  public IoReader<QName> createIoReader(final XMLStreamReader xmlReader) throws XMLStreamException
  {
    return new IoReader<QName>() {

      @Override
      public DocumentRoot<QName> read () throws CoreIoException
      {
        try {
          while (xmlReader.hasNext())
          {
            int eventType = xmlReader.next();
            switch (eventType) {
              case  XMLEvent.START_ELEMENT:
                NamespaceBindings bindings = readBindings();
                Datatree.TopLevelDocuments<QName> topLevelDocuments= readTopLevelDocuments();
                return Datatree.DocumentRoot(bindings, topLevelDocuments);
            }
          }
        } catch (XMLStreamException e) {
          throw new CoreIoException(e);
        }
        return null;
      }

      private Datatree.NamespaceBindings readBindings() throws XMLStreamException {
        NamespaceBinding[] bindings = new NamespaceBinding[xmlReader.getNamespaceCount()];

        for(int i = 0; i < xmlReader.getNamespaceCount(); i++) {
          bindings[i] = Datatree.NamespaceBinding(xmlReader.getNamespaceURI(i), xmlReader.getNamespacePrefix(i));
        }
        return Datatree.NamespaceBindings(bindings);
      }

      /**
       * Used to store documents and properties when reading XML in the readTopLevelDocuments method
       */
      private Stack<Object> documentStack=new Stack<Object>() ;

      /**
       * Used to store the TopLevelDocument objects in the readTopLevelDocuments method
       */
      private List<TopLevelDocument<QName>> topLevelDocuments=null;

      /**
       * Reads RDF document and returns TopLevelDocuments.
       *
       *

       * Properties and
       * documents are stored in a Stack object and populated as more data
       * become available The stack object holds one TopLevelDocument at a
       * time. Once a TopLevelDocument is read it is added to the
       * topLevelDocuments collection. For triples within a
       * TopLevelDocument the following rules apply:
       *

       * Starting tags:
       *

       * If a triple contains rdf:about attribute it is assumed that the
       * tag is the start of a NestedDocument. An empty Nested document is
       * added to the stack.
       * If a triple contains rdf:resource, a NamedProperty with a URI
       * value is created and added to the stack.
       * Otherwise a NamedProperty without a value is added to the stack
       *

       *
       * End tags:
       *

       * For each end tag, an object is taken from the stack.
       * If the object is a property The property is removed from the
       * stack The XML value (if the value exists) is used to set the
       * value of that property. The property is then added to the recent
       * document in the stack. This document can be a NestedDocument or a
       * TopLevelDocument.
       * If the object is a NestedDocument, the document is removed from
       * the stack. The property identifying the document in this case is
       * the most recent object in the stack and it is also removed from
       * the stack. The NestedDocument is then added to the parent
       * document (it can be a another NestedDocument or a
       * TopLevelDocument using the property relationship. This parent
       * document is the most recent object after removing the property.
       * If the object is TopLevelDocument, the object is removed from the
       * stack and added to the topLevelDocuments collection
       *

       *
       * @return
       * @throws XMLStreamException
       */
      private Datatree.TopLevelDocuments<QName> readTopLevelDocuments()
              throws XMLStreamException {

        //Used as a buffer to read XML characters in the readTopLevelDocuments method
        StringBuilder currentText=null;

    	topLevelDocuments = new ArrayList<TopLevelDocument<QName>>();
        while (xmlReader.hasNext()) {

          int eventType = xmlReader.next();
          switch (eventType) {
            case XMLEvent.START_ELEMENT:
              currentText = new StringBuilder(256);
              QName elementURI = Datatree.QName(xmlReader.getNamespaceURI(), xmlReader.getLocalName(), xmlReader.getPrefix());
              addToStack(elementURI);
              break;
            case XMLEvent.END_ELEMENT:
              String literalValue = null;
              if (currentText != null) {
                literalValue = currentText.toString();
                currentText = null;
              }
              updateDocumentInStack(literalValue);
              break;
            case XMLEvent.CHARACTERS:
              String characters = xmlReader.getText();
              if (currentText != null) {
                currentText.append(characters);
              }
              break;
          }
        }
        Datatree.TopLevelDocuments<QName> documents = Datatree
                .TopLevelDocuments(topLevelDocuments.toArray(new TopLevelDocument[topLevelDocuments.size()]));
        return documents;
        // return Datatree.<QName> TopLevelDocuments();
      }

      private void addToStack(QName elementURI) throws XMLStreamException
      {
        URI identity = null;
        URI resourceURI = null;

        int attributes = xmlReader.getAttributeCount();
        for (int i = 0; i < attributes; ++i)
        {
          if (rdfAbout.getLocalPart().equals(xmlReader.getAttributeLocalName(i)) && rdfAbout.getNamespaceURI().equals(xmlReader.getAttributeNamespace(i)))
          {
            identity = URI.create(xmlReader.getAttributeValue(i));
          }
          if (rdfResource.getLocalPart().equals(xmlReader.getAttributeLocalName(i)) && rdfResource.getNamespaceURI().equals(xmlReader.getAttributeNamespace(i)))
          {
            resourceURI = URI.create(xmlReader.getAttributeValue(i));
          }
        }

        if (identity != null)
        {
          Datatree.NamespaceBindings bindings = readBindings();
          IdentifiableDocument<QName> document = null;
          if (documentStack.isEmpty())
          {
            document = Datatree.TopLevelDocument(bindings, elementURI, identity);
          }
          else
          {
            document = Datatree.NestedDocument(bindings, elementURI, identity);
          }
          documentStack.push(document);
        }
        else
        {
          NamedProperty<QName> property = null;
          if (resourceURI != null)
          {
            property = Datatree.NamedProperty(elementURI, resourceURI);
          }
          else
          {
            // TODO Make sure this is ok. The value type is not known yet!
            property = Datatree.NamedProperty(elementURI, "");
          }
          documentStack.push(property);
        }
      }

      private void updateDocumentInStack(String literalValue) throws XMLStreamException
      {
        // Get the object in the stack
        if (!documentStack.isEmpty())
        {
          Object stackObject = documentStack.pop();

          if (stackObject instanceof NamedProperty)
          {
            NamedProperty<QName> property = (NamedProperty<QName>) stackObject;
            // Set its property value
            if (literalValue != null && literalValue.length() > 0)
            {
              property = Datatree.NamedProperty(property.getName(), literalValue);
            }
            updateDocumentInStackWithProperty(property);
          }
          else if (stackObject instanceof NestedDocument)
          {
            NestedDocument<QName> document = (NestedDocument<QName>) stackObject;

            // Get the property for the nested document
            NamedProperty<QName> property = (NamedProperty<QName>) documentStack
                    .pop();
            property = Datatree.NamedProperty(property.getName(), document);
            updateDocumentInStackWithProperty(property);

            // Skip the ending of the property tag. The nested
            // document is attached to the parent using the property
            // already.
            while (xmlReader.hasNext())
            {
              int eventType = xmlReader.next();

              if (eventType == XMLEvent.END_ELEMENT)
              {
                String elementURI = xmlReader.getNamespaceURI() + xmlReader.getLocalName();
                if (elementURI.equals(property.getName().getNamespaceURI() + property.getName().getLocalPart()))
                {
                  break;
                }
              }
            }
          }
          else if (stackObject instanceof TopLevelDocument)
          {
            topLevelDocuments.add((TopLevelDocument<QName>) stackObject);
          }
        }
      }

      private void updateDocumentInStackWithProperty(NamedProperty<QName> property)
      {
        //Add it to the document in the stack
        IdentifiableDocument<QName> documentInStack=(IdentifiableDocument<QName>)documentStack.pop();
        documentInStack=addProperty(documentInStack, property);

        //Put the document back to the stack
        documentStack.push(documentInStack);
      }

      private IdentifiableDocument<QName> addProperty(
              IdentifiableDocument<QName> document, NamedProperty<QName> property)
      {

        List<NamedProperty<QName>> properties = new ArrayList<>();
        if (document.getProperties() == null || document.getProperties().size() == 0)
        {
          properties = Datatree.NamedProperties(property).getProperties();
        }
        else
        {
          properties.addAll(document.getProperties());
          // TODO if the Property value is a NestedDocument then add
          // the property using the same property key, still works without this though!
          properties.add(property);
        }
        NamedProperty<QName>[] propertyArray = properties.toArray(new NamedProperty[properties.size()]);
        NamedProperties<QName> namedProperties = Datatree.NamedProperties(propertyArray);
        NamespaceBindings bindings = Datatree.NamespaceBindings(
                (NamespaceBinding[]) document.getNamespaceBindings().toArray(new NamespaceBinding[document.getNamespaceBindings().size()]));

        if (document instanceof TopLevelDocument)
        {
          document = Datatree.TopLevelDocument(bindings,
                  document.getType(),
                  document.getIdentity(),
                  namedProperties);
        }
        else
        {
          document = Datatree.NestedDocument(
                  bindings,
                  document.getType(),
                  document.getIdentity(),
                  namedProperties);
        }

        return document;
      }

    };
  }
}


package com.azaptree.services.executor;

/*
 * #%L
 * AZAPTREE-EXECUTOR-SERVICE
 * %%
 * Copyright (C) 2012 AZAPTREE.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.
 * #L%
 */

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import javax.annotation.PreDestroy;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Component;

@Component
@ManagedResource
public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor implements PausableThreadPoolExecutor, BeanNameAware, ThreadPoolExecutorJmxApi {
	private String name;

	private volatile boolean paused;

	private final ReentrantLock pauseLock = new ReentrantLock();
	private final Condition unpaused = pauseLock.newCondition();

	/**
	 * same as : ThreadPoolExecutor(new ThreadPoolConfig());
	 */
	public ThreadPoolExecutor() {
		this(new ThreadPoolConfig());
		logInfo();
	}

	/**
	 * same as : ThreadPoolExecutor(new ThreadPoolConfig(name));
	 *
	 * @param name
	 */
	public ThreadPoolExecutor(final String name) {
		this(new ThreadPoolConfig(name));
		logInfo();
	}

	public ThreadPoolExecutor(final ThreadPoolConfig config) {
		super(config.getCorePoolSize(), config.getMaximumPoolSize(),
		        config.getKeepAliveTime(), config.getKeepAliveTimeUnit(),
		        config.getWorkQueue(), config.getThreadFactory(), config.getHandler());
		allowCoreThreadTimeOut(config.isAllowCoreThreadTimeOut());
		logInfo();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#allowCoreThreadTimeOut(boolean)
	 */
	@ManagedOperation(
	        description = "Sets the policy governing whether core threads may time out and terminate if no tasks arrive within the keep-alive time, being replaced if needed when new tasks arrive.")
	@ManagedOperationParameters({ @ManagedOperationParameter(name = "value", description = "true if should time out, else false") })
	@Override
	public void allowCoreThreadTimeOut(final boolean value) {
		super.allowCoreThreadTimeOut(value);
	}

	@Override
	protected void beforeExecute(final Thread t, final Runnable r) {
		super.beforeExecute(t, r);
		while (paused) {
			pauseLock.lock();
			try {
				while (paused) {
					unpaused.await();
				}
			} catch (final InterruptedException ie) {
				t.interrupt();
			} finally {
				pauseLock.unlock();
			}
		}
	}

	@PreDestroy
	public void destroy() {
		final Logger log = LoggerFactory.getLogger(ThreadPoolExecutor.class);
		log.info("SHUTTING DOWN : {}", name);
		shutdown();
		try {
			while (!awaitTermination(60l, TimeUnit.SECONDS)) {
				log.info("WAITING FOR SHUTDOWN TO COMPLETE : {}", name);
			}
		} catch (final InterruptedException e) {
			log.info("Interrupted while waiting for shutdown to complete : {}", name);
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#getActiveCount()
	 */
	@ManagedAttribute(description = "Returns the approximate number of threads that are actively executing tasks.")
	@Override
	public int getActiveCount() {
		return super.getActiveCount();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#getCompletedTaskCount()
	 */
	@Override
	@ManagedAttribute(description = "Returns the approximate total number of tasks that have completed execution.")
	public long getCompletedTaskCount() {
		return super.getCompletedTaskCount();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#getCorePoolSize()
	 */
	@ManagedAttribute(description = "Returns the core number of threads.")
	@Override
	public int getCorePoolSize() {
		return super.getCorePoolSize();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#getLargestPoolSize()
	 */
	@Override
	@ManagedAttribute(description = "Returns the largest number of threads that have ever simultaneously been in the pool.")
	public int getLargestPoolSize() {
		return super.getLargestPoolSize();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#getMaximumPoolSize()
	 */
	@Override
	@ManagedAttribute(description = "Returns the maximum allowed number of threads.")
	public int getMaximumPoolSize() {
		return super.getMaximumPoolSize();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#getName()
	 */
	@Override
	@ManagedAttribute
	public String getName() {
		return name;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#getPoolSize()
	 */
	@Override
	@ManagedAttribute(description = "Returns the current number of threads in the pool.")
	public int getPoolSize() {
		return super.getPoolSize();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#getTaskCount()
	 */
	@Override
	@ManagedAttribute(description = "Returns the approximate total number of tasks that have ever been scheduled for execution.")
	public long getTaskCount() {
		return super.getTaskCount();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#isCoreThreadTimeOutAllowed()
	 */
	@Override
	@ManagedAttribute(
	        description = "Returns true if this pool allows core threads to time out and terminate if no tasks arrive within the keepAlive time, being replaced if needed when new tasks arrive.")
	public boolean isCoreThreadTimeOutAllowed() {
		return super.allowsCoreThreadTimeOut();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#isPaused()
	 */
	@Override
	@ManagedAttribute
	public boolean isPaused() {
		return paused;
	}

	private void logInfo() {
		LoggerFactory.getLogger(ThreadPoolExecutor.class).info("{}", this);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#pause()
	 */
	@Override
	@ManagedOperation
	public void pause() {
		paused = true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#purge()
	 */
	@ManagedOperation(description = "Tries to remove from the work queue all Future tasks that have been cancelled.")
	@Override
	public void purge() {
		super.purge();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#resume()
	 */
	@Override
	@ManagedOperation
	public void resume() {
		paused = false;
		pauseLock.lock();
		try {
			unpaused.signalAll();
		} finally {
			pauseLock.unlock();
		}
	}

	@Override
	public void setBeanName(final String beanName) {
		name = beanName;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#setCorePoolSize(int)
	 */
	@ManagedOperation(
	        description = "Sets the core number of threads. If the new value is smaller than the current value, excess existing threads will be terminated when they next become idle. If larger, new threads will, if needed, be started to execute any queued tasks")
	@ManagedOperationParameters({ @ManagedOperationParameter(name = "corePoolSize", description = "the new core size") })
	@Override
	public void setCorePoolSize(final int corePoolSize) {
		super.setCorePoolSize(corePoolSize);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#setKeepAliveTimeSecs(int)
	 */
	@Override
	@ManagedOperation(description = "Sets the time limit in seconds for which threads may remain idle before being terminated.")
	@ManagedOperationParameters({ @ManagedOperationParameter(name = "time",
	        description = "the time in seconds to wait. A time value of zero will cause excess threads to terminate immediately after executing tasks.") })
	public void setKeepAliveTimeSecs(final int time) {
		super.setKeepAliveTime(time, TimeUnit.SECONDS);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.azaptree.services.executor.ThreadPoolExecutorJmxApi#setMaximumPoolSize(int)
	 */
	@ManagedOperation(description = "Sets the maximum allowed number of threads.")
	@ManagedOperationParameters({ @ManagedOperationParameter(name = "maximumPoolSize", description = "the new maximum") })
	@Override
	public void setMaximumPoolSize(final int maximumPoolSize) {
		super.setMaximumPoolSize(maximumPoolSize);
	}

	@Override
	public String toString() {
		final ToStringBuilder sb = new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE);
		sb.append("activeCount", getActiveCount());
		sb.append("corePoolSize", getCorePoolSize());
		sb.append("largestPoolSize", getLargestPoolSize());
		sb.append("maximumPoolSize", getMaximumPoolSize());
		sb.append("poolSize", getPoolSize());
		sb.append("completedTaskCount", getCompletedTaskCount());
		sb.append("keepAliveTimeSecs", getKeepAliveTime(TimeUnit.SECONDS));
		sb.append("name", getName());
		sb.append("taskCount", getTaskCount());
		sb.append("allowsCoreThreadTimeOut", allowsCoreThreadTimeOut());
		return sb.toString();
	}

}


/*
 * The Fascinator - Plugin - Harvester - Workflows
 * Copyright (C) 2010-2011 University of Southern Queensland
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package com.googlecode.fascinator.harvester.workflow;

import com.googlecode.fascinator.api.harvester.HarvesterException;
import com.googlecode.fascinator.api.storage.DigitalObject;
import com.googlecode.fascinator.api.storage.StorageException;
import com.googlecode.fascinator.common.JsonObject;
import com.googlecode.fascinator.common.JsonSimple;
import com.googlecode.fascinator.common.harvester.impl.GenericHarvester;
import com.googlecode.fascinator.common.storage.StorageUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *

 * This plugin is a basic harvester for ingesting uploaded content into
 * workflows. It creates the DigitalObject of a source object for the standard
 * harvest/transform/index stack.
 *

 *
 *

 * A trimmed down version of the file-system harvester but doesn't need
 * recursion or caching.
 *

 *
 *
Configuration

 *

 * Sample configuration file for workflow harvester: workflow-harvester.json
 *

 *
 *
 *
Examples

 *

 *

 * Below is the example of workflow stages:
 *
 *

 *   "stages": [
 *         {
 *             "name": "pending",
 *             "label": "Pending",
 *             "security": ["metadata", "admin"],
 *             "visibility": ["metadata", "editor", "admin"]
 *         },
 *         {
 *             "name": "metadata",
 *             "label": "Basic Metadata Check",
 *             "security": ["editor", "admin"],
 *             "visibility": ["metadata", "editor", "admin"],
 *             "template": "workflows/basic-init"
 *         },
 *         {
 *             "name": "live",
 *             "label": "Live",
 *             "security": ["editor", "admin"],
 *             "visibility": ["guest"],
 *             "template": "workflows/basic-live"
 *         }
 *     ]
 *

 *
 *

 *

 *
 *
Rule file

 *

 * Sample rule file for the workflow harvester: workflow-harvester.py
 *

 *
 *
Wiki Link

 *

 * https://fascinator.usq.edu.au/trac/wiki/Fascinator/Documents/Plugins/
 * Harvester/Workflow
 *

 *
 * @author Greg Pendlebury
 */
public class WorkflowHarvester extends GenericHarvester {

    /** logging */
    @SuppressWarnings("unused")
	private Logger log = LoggerFactory.getLogger(WorkflowHarvester.class);

    /** flag for forcing local storage */
    private boolean forceLocalStorage;

    /** flag for forcing local update */
    private boolean forceUpdate;

    /** Render chains */
    private Map<String, Map<String, List<String>>> renderChains;

    /**
     * Basic constructor.
     *
     */
    public WorkflowHarvester() {
        // Just provide GenericHarvester our identity.
        super("workflow-harvester", "Workflow Harvester");
    }

    /**
     * Basic init() function. Notice the lack of parameters. This is not part
     * of the Plugin API but from the GenericHarvester implementation. It will
     * be called following the constructor verifies configuration is available.
     *
     * @throws HarvesterException : If there are problems during instantiation
     */
    @Override
    public void init() throws HarvesterException {
        forceLocalStorage = getJsonConfig().getBoolean(true,
                "harvester", "workflow-harvester", "force-storage");
        forceUpdate = getJsonConfig().getBoolean(false,
                "harvester", "workflow-harvester", "force-update");

        // Order is significant
        renderChains = new LinkedHashMap<String, Map<String, List<String>>>();
        JsonObject renderTypes = getJsonConfig().getObject("renderTypes");
        if (renderTypes != null) {
            for (Object name : renderTypes.keySet()) {
                Map<String, List<String>> details = new HashMap<String, List<String>>();
                JsonObject chain = (JsonObject) renderTypes.get(name);
                details.put("fileTypes",
                        JsonSimple.getStringList(chain, "fileTypes"));
                details.put("harvestQueue",
                        JsonSimple.getStringList(chain, "harvestQueue"));
                details.put("indexOnHarvest",
                        JsonSimple.getStringList(chain, "indexOnHarvest"));
                details.put("renderQueue",
                        JsonSimple.getStringList(chain, "renderQueue"));
                renderChains.put((String) name, details);
            }
        }
    }

    /**
     * Gets a list of digital object IDs. If there are no objects, this method
     * should return an empty list, not null.
     *
     * @return a list of object IDs, possibly empty
     * @throws HarvesterException if there was an error retrieving the objects
     */
    @Override
    public Set<String> getObjectIdList() throws HarvesterException {
        return Collections.emptySet();
    }

    /**
     * Get an individual uploaded file as a digital object. For consistency this
     * should be in a list.
     *
     * @return a list of one object ID
     * @throws HarvesterException if there was an error retrieving the objects
     */
    @Override
    public Set<String> getObjectId(File uploadedFile) throws HarvesterException {
        Set<String> objectIds = new HashSet<String>();
        try {
            objectIds.add(createDigitalObject(uploadedFile));
        } catch (StorageException se) {
            throw new HarvesterException(se);
        }
        return objectIds;
    }

    /**
     * Tests whether there are more objects to retrieve. This method should
     * return true if called before getObjects.
     *
     * @return true if there are more objects to retrieve, false otherwise
     */
    @Override
    public boolean hasMoreObjects() {
        return false;
    }

    /**
     * Store the provided file in storage. Ensure proper queue routing is set
     * in object properties.
     *
     * @param file : The file to store
     * @return String : The OID of the stored object
     * @throws HarvesterException : if there was an error accessing storage
     * @throws StorageException : if there was an error writing to storage
     */
    private String createDigitalObject(File file) throws HarvesterException,
            StorageException {
        String objectId;
        DigitalObject object;
        if (forceUpdate) {
            object = StorageUtils.storeFile(getStorage(), file,
                    !forceLocalStorage);
        } else {
            String oid = StorageUtils.generateOid(file);
            String pid = StorageUtils.generatePid(file);
            object = getStorage().createObject(oid);
            if (forceLocalStorage) {
                try {
                    object.createStoredPayload(pid, new FileInputStream(file));
                } catch (FileNotFoundException ex) {
                    throw new HarvesterException(ex);
                }
            } else {
                object.createLinkedPayload(pid, file.getAbsolutePath());
            }

        }
        // update object metadata
        Properties props = object.getMetadata();
        props.setProperty("render-pending", "true");
        props.setProperty("file.path",
                FilenameUtils.separatorsToUnix(file.getAbsolutePath()));
        objectId = object.getId();

        // Store rendition information if we have it
        String ext = FilenameUtils.getExtension(file.getName());
        for (String chain : renderChains.keySet()) {
            Map<String, List<String>> details = renderChains.get(chain);
            if (details.get("fileTypes").contains(ext)) {
                storeList(props, details, "harvestQueue");
                storeList(props, details, "indexOnHarvest");
                storeList(props, details, "renderQueue");
            }
        }

        object.close();
        return objectId;
    }

    /**
     * Take a list of strings from a Java Map, concatenate the values together
     * and store them in a Properties object using the Map's original key.
     *
     * @param props : Properties object to store into
     * @param details : The full Java Map
     * @param field : The key to use in both objects
     */
    private void storeList(Properties props, Map<String, List<String>> details,
            String field) {
        Set<String> valueSet = new LinkedHashSet<String>();
        // merge with original property value if exists
        String currentValue = props.getProperty(field, "");
        if (!"".equals(currentValue)) {
            String[] currentList = currentValue.split(",");
            valueSet.addAll(Arrays.asList(currentList));
        }
        valueSet.addAll(details.get(field));
        String joinedList = StringUtils.join(valueSet, ",");
        props.setProperty(field, joinedList);
    }
}


package hudson.plugins.dry;

import hudson.Extension;
import hudson.plugins.analysis.core.PluginDescriptor;
import hudson.util.FormValidation;

import org.kohsuke.stapler.QueryParameter;

/**
 * Descriptor for the class {@link DryPublisher}. Used as a singleton. The
 * class is marked as public so that it can be accessed from views.
 *
 * @author Ulli Hafner
 */
@Extension(ordinal = 100)
public final class DryDescriptor extends PluginDescriptor {
    /** The ID of this plug-in is used as URL. */
    static final String PLUGIN_ID = "dry";
    /** The URL of the result action. */
    static final String RESULT_URL = PluginDescriptor.createResultUrlName(PLUGIN_ID);
    /** Icons prefix. */
    static final String ICON_URL_PREFIX = "/plugin/dry/icons/";
    /** Icon to use for the result and project action. */
    static final String ICON_URL = ICON_URL_PREFIX + "dry-24x24.png";

    private static final ThresholdValidation VALIDATION = new ThresholdValidation();

    /**
     * Creates a new instance of {@link DryDescriptor}.
     */
    public DryDescriptor() {
        super(DryPublisher.class);
    }

    @Override
    public String getDisplayName() {
        return Messages.DRY_Publisher_Name();
    }

    @Override
    public String getPluginName() {
        return PLUGIN_ID;
    }

    @Override
    public String getIconUrl() {
        return ICON_URL;
    }

    @Override
    public String getSummaryIconUrl() {
        return ICON_URL_PREFIX + "dry-48x48.png";
    }

    /**
     * Performs on-the-fly validation on threshold for high warnings.
     *
     * @param highThreshold
     *            the threshold for high warnings
     * @param normalThreshold
     *            the threshold for normal warnings
     * @return the validation result
     */
    public FormValidation doCheckHighThreshold(@QueryParameter final String highThreshold, @QueryParameter final String normalThreshold) {
        return VALIDATION.validateHigh(highThreshold, normalThreshold);
    }

    /**
     * Performs on-the-fly validation on threshold for normal warnings.
     *
     * @param highThreshold
     *            the threshold for high warnings
     * @param normalThreshold
     *            the threshold for normal warnings
     * @return the validation result
     */
    public FormValidation doCheckNormalThreshold(@QueryParameter final String highThreshold, @QueryParameter final String normalThreshold) {
        return VALIDATION.validateNormal(highThreshold, normalThreshold);
    }
}


/**
 * Copyright (C) 2012 Ness Computing, Inc.
 *
 * 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.nesscomputing.migratory.metadata;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Maps.EntryTransformer;

import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.Query;
import org.skife.jdbi.v2.Update;
import org.skife.jdbi.v2.exceptions.CallbackFailedException;
import org.skife.jdbi.v2.tweak.HandleCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.nesscomputing.migratory.MigratoryConfig;
import com.nesscomputing.migratory.MigratoryContext;
import com.nesscomputing.migratory.MigratoryException;
import com.nesscomputing.migratory.MigratoryException.Reason;
import com.nesscomputing.migratory.MigratoryOption;
import com.nesscomputing.migratory.migration.DbMigrator;
import com.nesscomputing.migratory.migration.MigrationManager;
import com.nesscomputing.migratory.migration.MigrationPlanner;
import com.nesscomputing.migratory.migration.MigrationPlanner.MigrationDirection;
import com.nesscomputing.migratory.migration.MigrationResult;

public class MetadataManager
{
    private static final Logger LOG = LoggerFactory.getLogger(MetadataManager.class);

    public static final String METADATA_MIGRATION_NAME = "migratory_metadata";

    public static final String METADATA_PREFIX = "#metadata:";

    private final MigratoryContext migratoryContext;
    private final MigratoryConfig migratoryConfig;

    private Handle transactionHandle = null;

    public MetadataManager(final MigratoryContext migratoryContext)
    {
        this.migratoryContext = migratoryContext;
        this.migratoryConfig = migratoryContext.getConfig();
    }

    /**
     * Make sure that the metadata exists. If it does not exist, create
     * the metadata table from scratch and register its creation.
     * @return A migration result if the table was created or null if it already existed.
     */
    public List<MetadataInfo> ensureMetadata(final MigratoryOption [] options)
        throws MigratoryException
    {
        if (migratoryContext.getDbSupport().tableExists(migratoryConfig.getMetadataTableName()))
        {
            return null;
        }

        if (migratoryConfig.isReadOnly()) {
            throw new MigratoryException(Reason.IS_READONLY);
        }
        // Table does not exist. The way we get one, is that we run a special migration for the internal metadata schema
        final MigrationPlanner migrationPlanner = new MigrationPlanner(new MigrationManager(migratoryContext, METADATA_MIGRATION_NAME), 0, Integer.MAX_VALUE);

        migrationPlanner.plan();

        if (migrationPlanner.getDirection() != MigrationDirection.FORWARD) {
            throw new MigratoryException(Reason.INTERNAL, "Migration planner could not plan a migration for the metadata table!");
        }

        final DbMigrator migrator = new DbMigrator(migratoryContext, migrationPlanner);

        try {
            lock(METADATA_MIGRATION_NAME);
            final List<MigrationResult> results = migrator.migrate(options);
            return commit(results);
        } catch (MigratoryException e) {
            rollback();
            throw e;
        } catch (RuntimeException e) {
            rollback();
            throw e;
        }
    }

    public void lock(final String personalityName)
    {
        transactionHandle = migratoryContext.getDBI().open();
        transactionHandle.begin();

        if (METADATA_MIGRATION_NAME.equals(personalityName)) {
            // Can't lock the actual metadata migration, because the table might not exist yet.
            return;
        }
        if (migratoryContext.getDbSupport().supportsLocking()) {
            transactionHandle.createStatement(METADATA_PREFIX + "lock")
                .bind("personality_name", personalityName)
                .execute();
        }
    }

    public List<MetadataInfo> commit(final List<MigrationResult> migrationResults)
    {
        if (transactionHandle == null) {
            throw new MigratoryException(Reason.INTERNAL, "commit before lock!");
        }

        try {
            final List<MetadataInfo> results = Lists.newArrayList();

            if (migrationResults != null) {
                for (MigrationResult migrationResult : migrationResults) {
                    final MetadataInfo metadataInfo = new MetadataInfo(migrationResult);

                    final Update update = metadataInfo.bindToHandle(transactionHandle.createStatement(METADATA_PREFIX + "insert_metadata"));
                    final int count = update.execute();
                    LOG.debug("{} rows changed by inserting {}.", count, metadataInfo);
                    results.add(metadataInfo);
                }
            }
            LOG.debug("Metadata Insert: {}", results.size());
            return results;
        }
        finally {
            transactionHandle.commit();
            transactionHandle.close();
            transactionHandle = null;
        }
    }

    public void rollback()
    {
        if (transactionHandle == null) {
            LOG.warn("Got a late rollback after commit/rollback. Ignoring!");
        }
        else {
            transactionHandle.rollback();
            transactionHandle.close();
            transactionHandle = null;
        }
    }

    public Map<String, List<MetadataInfo>> getHistory(final Collection<String> personalities, final MigratoryOption [] options)
    {
        return performCallback(new HandleCallback<Map<String, List<MetadataInfo>>>() {
                @Override
                public Map<String, List<MetadataInfo>> withHandle(final Handle handle) throws SQLException {
                    final Map<String, List<MetadataInfo>> results = Maps.newTreeMap();

                    if (personalities != null) {
                        for (final String personality : personalities) {
                            final List<MetadataInfo> result =
                                new HistoryCallback(personality, options).withHandle(handle);

                            if (!result.isEmpty()) {
                                results.put(personality, result);
                            }
                        }
                    }
                    else {
                        final List<MetadataInfo> result = HistoryCallback.getHistoryQuery(handle, null, options).list();

                        for (final MetadataInfo metadataInfo : result) {
                            List<MetadataInfo> resultList = results.get(metadataInfo.getPersonalityName());
                            if (resultList == null) {
                                resultList = new ArrayList<MetadataInfo>();
                                results.put(metadataInfo.getPersonalityName(), resultList);
                            }
                            resultList.add(metadataInfo);
                        }
                    }

                    return results;
                }
            });
    }

    public Map<String, MetadataInfo> getStatus(final Collection<String> personalities, final MigratoryOption ... options)
    {
        return performCallback(new HandleCallback<Map<String, MetadataInfo>>() {
                @Override
                public Map<String, MetadataInfo> withHandle(final Handle handle) throws SQLException {

                    final Map<String, MetadataInfo> results = Maps.newHashMap();

                    if (personalities != null) {
                        for (final String personality : personalities) {
                            final MetadataInfo result =
                                new StatusCallback(personality, options).withHandle(handle);

                            if (result != null) {
                                results.put(personality, result);
                            }
                        }
                    }
                    else {
                        final List<MetadataInfo> result = StatusCallback.getStatusQuery(handle, null, options).list();

                        for (final MetadataInfo metadataInfo : result) {
                            results.put(metadataInfo.getPersonalityName(), metadataInfo);
                        }
                    }

                    return results;
                }
            });
    }

    private <T> T performCallback(final HandleCallback<T> callback) {
        if (transactionHandle != null) {
            try {
                return callback.withHandle(transactionHandle);
            } catch (Exception e) {
                throw new CallbackFailedException(e);
            }
        }
        else {
            return migratoryContext.getDBI().withHandle(callback);
        }
    }

    public Map<String, Integer> retrieveCurrentVersions(final MigratoryOption ... options)
    {
        return Maps.transformEntries(getStatus(null, options), new EntryTransformer<String, MetadataInfo, Integer>() {
                @Override
                public Integer transformEntry(final String name, final MetadataInfo info) {
                    return info.getEndVersion();
                }
            });
    }

    public Collection<String> retrieveExistingPersonalities(final MigratoryOption ... options)
    {
        return getStatus(null, options).keySet();
    }

    public MetadataInfo getPersonalityStatus(final String personality, final MigratoryOption ... options)
    {
        return performCallback(new StatusCallback(personality, options));
    }

    public List<MetadataInfo> getPersonalityHistory(final String personality, final MigratoryOption ... options)
    {
        return performCallback(new HistoryCallback(personality, options));
    }

    public Integer getCurrentVersion(final String personality, final MigratoryOption ... options)
    {
        final MetadataInfo metadataInfo = getPersonalityStatus(personality, options);
        return (metadataInfo == null) ? null : metadataInfo.getEndVersion();
    }

    private static class StatusCallback implements HandleCallback<MetadataInfo>
    {
        private final String personality;
        private final MigratoryOption [] options;

        private StatusCallback(final String personality, final MigratoryOption [] options)
        {
            this.personality = personality;
            this.options = options;
        }

        @Override
        public MetadataInfo withHandle(final Handle handle) throws SQLException {
            return getStatusQuery(handle, personality, options).first();
        }

        private static Query<MetadataInfo> getStatusQuery(final Handle handle, final String personality, final MigratoryOption [] options)
        {
            final Query<MetadataInfo> query = handle.createQuery(METADATA_PREFIX + "query_status")
                .define("include_internal", MigratoryOption.containsOption(MigratoryOption.INCLUDE_INTERNAL, options))
                .define("include_failed", MigratoryOption.containsOption(MigratoryOption.INCLUDE_FAILED, options))
                .define("personality_name", personality)
                .bind("personality_name", personality)
                .map(MetadataInfo.MAPPER);

            return query;
        }
    }

    private static class HistoryCallback implements HandleCallback<List<MetadataInfo>>
    {
        private final String personality;
        private final MigratoryOption [] options;

        private HistoryCallback(final String personality, final MigratoryOption [] options)
        {
            this.personality = personality;
            this.options = options;
        }

        @Override
        public List<MetadataInfo> withHandle(final Handle handle) throws SQLException {
            return getHistoryQuery(handle, personality, options).list();
        }

        private static Query<MetadataInfo> getHistoryQuery(final Handle handle, final String personality, final MigratoryOption [] options)
        {
            final Query<MetadataInfo> query = handle.createQuery(METADATA_PREFIX + "query_history")
                .define("include_internal", MigratoryOption.containsOption(MigratoryOption.INCLUDE_INTERNAL, options))
                .define("personality_name", personality)
                .bind("personality_name", personality)
                .map(MetadataInfo.MAPPER);

            return query;
        }
    }
}


package hudson.plugins.dry;

import hudson.model.AbstractBuild;
import hudson.plugins.analysis.util.model.AnnotationContainer;
import hudson.plugins.analysis.util.model.FileAnnotation;
import hudson.plugins.analysis.views.DetailFactory;
import hudson.plugins.analysis.views.SourceDetail;
import hudson.plugins.analysis.views.TabDetail;
import hudson.plugins.dry.parser.DuplicateCode;

import java.util.Collection;

import org.apache.commons.lang.StringUtils;

/**
 * A detail builder for dry annotations capable of showing details of linked annotations.
 *
 * @author Ulli Hafner
 */
public class DryDetailBuilder extends DetailFactory {
    @Override
    public Object createDetails(final String link, final AbstractBuild<?, ?> owner,
            final AnnotationContainer container, final String defaultEncoding, final String displayName) {
        if (link.startsWith("link.")) {
            String suffix = StringUtils.substringAfter(link, "link.");
            String[] fromToStrings = StringUtils.split(suffix, ".");
            if (fromToStrings.length == 2) {
                return createDrySourceDetail(owner, container, defaultEncoding, fromToStrings[0], fromToStrings[1]);
            }
            return null;
        }
        return super.createDetails(link, owner, container, defaultEncoding, displayName);
    }

    /**
     * Creates the dry source detail view.
     *
     * @param owner
     *            the owner
     * @param container
     *            the container
     * @param defaultEncoding
     *            the default encoding
     * @param fromString
     *            from ID
     * @param toString
     *            to ID
     * @return the detail view or null
     */
    private Object createDrySourceDetail(final AbstractBuild<?, ?> owner,
            final AnnotationContainer container, final String defaultEncoding,
            final String fromString, final String toString) {
        long from = Long.parseLong(fromString);
        long to = Long.parseLong(toString);

        FileAnnotation fromAnnotation = container.getAnnotation(from);
        if (fromAnnotation instanceof DuplicateCode) {
            return new SourceDetail(owner, ((DuplicateCode)fromAnnotation).getLink(to), defaultEncoding);
        }
        return null;
    }

    @Override
    protected TabDetail createTabDetail(final AbstractBuild<?, ?> owner, final Collection<FileAnnotation> annotations,
            final String url, final String defaultEncoding) {
        return new DryTabDetail(owner, this, annotations, url, defaultEncoding);
    }
}