Maven migration (first draft)

This commit is contained in:
Graham Triggs 2015-11-19 23:47:12 +00:00
parent 5e0329908c
commit e1ff94ccaf
2866 changed files with 1112 additions and 616 deletions

View file

@ -0,0 +1,192 @@
=begin
--------------------------------------------------------------------------------
This script will try to show that the JARs in a list are not needed for VIVO
(or Vitro).
Given a list of JARs, it will determine what packages the JAR contains, and will
search the source files of the project for references to those packages.
The list of JARs should be created by running the jarlist target of the build file.
That will use JarJar to create a list of JAR file that are not directly referenced
by the classes in the build. But what about JARs that are needed by a call using
reflection? Like Class.forName("this.that.theOther"). We know this is how the MySQL
driver is loaded (so we need mysql-connector-java-5.1.16-bin.jar). Are there any
others? This script will try to find them.
The jarlist target includes a list of JARs that we know are needed, such as the MySQL
connector. If this script finds any JARs that are needed, they should be added to that
list, and the target run again, in case they depend on other JARs in turn.
--------------------------------------------------------------------------------
One of the tricks that this script uses is to prune the list of packages to
search for. If a JAR contains both "this.that.other" and "this.that", then we
only need to search for "this.that", since it will reveal uses of "this.that.other"
as well.
--------------------------------------------------------------------------------
pass the name of the file that contains the JAR names
pass the root directory of the combined vitro/vivo distro
(appbase)
--------------------------------------------------------------------------------
Search all of the *.java, *.jsp, *.js, *.tld, *.xml files for mention of these package names
For each hit, print the file path, line number, the package name and the JAR name.
To search files:
find -X . -name \*.db -or -name \*pot | xargs grep 'org\.package\.name'
grep -H -n -f file_with_package_patterns
or can we do this all with grep on each string?
grep -r --include=*.jsp
grep -H -n -r --include=*.javaxd --include=*.jsp org\.package\.name .
and precede the output with a header that lists the package
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
=end
# Just say what we're doing.
#
def print_args
puts "Scanning for JAR usage."
puts "Base directory is #{@scan_base_directory}"
puts "JAR list is in #{@jar_names_file}"
puts
end
# Build a Hash of JAR names mapped to (reduced) arrays of package names.
#
def figure_package_names_from_jars
@packages_for_jars = {}
File.open(@jar_names_file) do |file|
file.each do |line|
jar_name = line.strip
@packages_for_jars[jar_name] = figure_package_names_for_jar(jar_name)
end
end
end
# Figure out the reduced set of package names for this JAR
#
def figure_package_names_for_jar(jar_name)
jar_path = "#{@scan_base_directory}/lib/#{jar_name}"
jar_contents = `jar -tf '#{jar_path}'`
packages = analyze_jar_contents(jar_contents)
reduced = reduce_package_names(packages)
end
# Ask the JAR what it contains. Keep only the names of populated packages.
# Ignore packages that are not at least 2 levels deep.
#
def analyze_jar_contents(jar_contents)
packages = []
jar_contents.lines do |line|
line.strip!
if line.end_with?('/')
elsif line.start_with?('META-INF')
elsif line.count('/') < 2
else
package = line[0...line.rindex('/')].tr('/', '.')
packages << package
end
end
packages.uniq.sort!
end
# Remove the names of any sub-packages. Searching for the parent package will be sufficient.
#
def reduce_package_names(packages)
reduced = []
packages.each do |candidate|
redundant = FALSE
reduced.each do |result|
redundant = TRUE if candidate.start_with?(result)
end
reduced << candidate unless redundant
end
reduced
end
# Show what packages we will search for, and for which JAR
#
def print_package_names_for_jars
puts "Packages for each jar"
@packages_for_jars.each do |jar_name, package_array|
puts " #{jar_name}"
package_array.each do |package_name|
puts " #{package_name}"
end
puts
end
end
#
#
def show_packages_in_source_files
@packages_for_jars.each do |jar_name, package_array|
show_packages_for_jar_in_source_files(jar_name, package_array)
end
end
#
#
def show_packages_for_jar_in_source_files(jar_name, package_array)
puts "------------------------------- #{jar_name} ------------------------------"
package_array.each do |package_name|
show_package_in_source_files(package_name)
end
end
#
#
def show_package_in_source_files(package_name)
puts "#{package_name}"
include_parms = build_include_parms(["*.java", "*.jsp", "*.xml", "*.tld", "*.js" ])
package_name_pattern = package_name.sub(/\./, "\\.")
system "grep -H -n -r #{include_parms} #{package_name_pattern} '#{@scan_base_directory}'"
puts
end
#
#
def build_include_parms(file_specs)
"--include=" + file_specs.join(" --include=")
end
#
#
# ------------------------------------------------------------------------------------
# Standalone calling.
#
# Do this if this program was called from the command line. That is, if the command
# expands to the path of this file.
# ------------------------------------------------------------------------------------
#
if File.expand_path($0) == File.expand_path(__FILE__)
if ARGV.length != 2
raise("usage is: ruby jarUsageScanner.rb <jar_names_file> <scan_base_directory>")
end
@jar_names_file, @scan_base_directory = ARGV
if !File.file?(@jar_names_file)
raise "File does not exist: '#{@jar_names_file}'."
end
if !File.directory?(@scan_base_directory)
raise "Directory does not exist: '#{@scan_base_directory}'."
end
print_args
figure_package_names_from_jars
print_package_names_for_jars
show_packages_in_source_files
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,93 @@
/**
* Copyright 2007 Google 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.
*/
/**
* This class is created to work around a known bug in JarJar which did not get fixed in release 1.1.
* See the comments in edu.cornell.mannlib.vitro.utilities.jarlist.JarLister
*/
package com.tonicsystems.jarjar;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import com.tonicsystems.jarjar.asm.ClassReader;
import com.tonicsystems.jarjar.ext_util.ClassHeaderReader;
import com.tonicsystems.jarjar.ext_util.ClassPathEntry;
import com.tonicsystems.jarjar.ext_util.ClassPathIterator;
import com.tonicsystems.jarjar.ext_util.RuntimeIOException;
public class KlugedDepFind {
private File curDir = new File(System.getProperty("user.dir"));
public void setCurrentDirectory(File curDir) {
this.curDir = curDir;
}
public void run(String from, String to, DepHandler handler)
throws IOException {
try {
ClassHeaderReader header = new ClassHeaderReader();
Map<String, String> classes = new HashMap<String, String>();
ClassPathIterator cp = new ClassPathIterator(curDir, to, null);
try {
while (cp.hasNext()) {
ClassPathEntry entry = cp.next();
InputStream in = entry.openStream();
try {
header.read(in);
classes.put(header.getClassName(), entry.getSource());
} catch (Exception e) {
System.err.println("Error reading " + entry.getName()
+ ": " + e.getMessage());
} finally {
in.close();
}
}
} finally {
cp.close();
}
handler.handleStart();
cp = new ClassPathIterator(curDir, from, null);
try {
while (cp.hasNext()) {
ClassPathEntry entry = cp.next();
InputStream in = entry.openStream();
try {
new ClassReader(in).accept(new DepFindVisitor(classes,
entry.getSource(), handler),
ClassReader.SKIP_DEBUG
| ClassReader.EXPAND_FRAMES);
} catch (Exception e) {
System.err.println("Error reading " + entry.getName()
+ ": " + e.getMessage());
} finally {
in.close();
}
}
} finally {
cp.close();
}
handler.handleEnd();
} catch (RuntimeIOException e) {
throw (IOException) e.getCause();
}
}
}

View file

@ -0,0 +1,435 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.containerneutral;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpServlet;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* Look at web.xml, and check for conditions that violate the Servlet 2.4 spec,
* but that might not be noticed because Tomcat doesn't complain.
*
* ------
*
* Values of the <dispatcher/> tag:
*
* The spec permits only these values: "FORWARD", "REQUEST", "INCLUDE", "ERROR",
* but Tomcat also allows the lower-case equivalents. GlassFish or WebLogic will
* barf on lower-case.
*
* Check to see that only the upper-case values are used.
*
* ------
*
* Existence of Servlet classes:
*
* The spec allows the container to either load all servlets at startup, or to
* load them when requested. Since Tomcat only loads servlet when requested, it
* doesn't notice or complain if web.xml cites a <servlet-class/> that doesn't
* exist, as long as it is never invoked. On the other hand, WebLogic loads all
* serlvets at startup, and will barf if the class is not found.
*
* Check each <servlet-class/> to insure that the class can be loaded and
* instantiated and assigned to HttpServlet.
*
* ------
*
* Embedded URIs in taglibs.
*
* I can't find this definitively in the JSP spec, but some containers complain
* if web.xml specifies a <taglib-uri/> that conflicts with the <uri/> embedded
* in the taglib itself. As far as I can see in the spec, the embedded <uri/>
* tag is not required or referenced unless we are using
* "Implicit Map Entries From TLDs", which in turn is only relevant for TLDs
* packaged in JAR files. So, I can't find support for this complaint, but it
* seems a reasonable one.
*
* Check each <taglib/> specified in web.xml. If the taglib has an embedded
* <uri/> tag, it should match the <taglib-uri/> from web.xml.
*
* ------
*
* Existence of Listener and Filter classes.
*
* As far as I can tell, there is no ambiguity here, and every container will
* complain if any of the <listener-class/> or <filter-class/> entries are
* unsuitable. I check them anyway, since the mechanism was already assembled
* for checking <servlet-class/> entries.
*
* Check each <listener-class/> to insure that the class can be loaded and
* instantiated and assigned to ServletContextListener.
*
* Check each <filter-class/> to insure that the class can be loaded and
* instantiated and assigned to Filter.
*
* ------
*
* A <servlet/> tag for every <servlet-mapping/> tag
*
* I can't find a mention of this in the spec, but Tomcat complains and refuses
* to load the app if there is a <servlet-mapping/> tag whose <servlet-name/> is
* not matched by a <servlet-name/> in a <servlet/> tag.
*
* Get sets of all <servlet-name/> tags that are specified in <servlet/> and
* <servlet-mapping/> tags. There should not be any names in the
* servlet-mappings that are not in the servlets.
*
* ---------------------------------------------------------------------
*
* Although this class is executed as a JUnit test, it doesn't have the usual
* structure for a unit test.
*
* In order to produce the most diagnostic information, the test does not abort
* on the first failure. Rather, failure messages are accumulated until all
* checks have been performed, and the test list all such messages on failure.
*
* ---------------------------------------------------------------------
*
* Since this is not executed as part of the standard Vitro unit tests, it also
* cannot use the standard logging mechanism. Log4J has not been initialized.
*
*/
public class CheckContainerNeutrality {
private static final String PROPERTY_WEBAPP_DIR = "CheckContainerNeutrality.webapp.dir";
private static DocumentBuilder docBuilder;
private static XPath xpath;
@BeforeClass
public static void createDocBuilder() {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
factory.setNamespaceAware(true); // never forget this!
docBuilder = factory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
@BeforeClass
public static void createXPath() {
xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(new StupidNamespaceContext());
}
private File webappDir;
private File webXmlFile;
private Document webXmlDoc;
private List<String> messages;
@Before
public void setup() throws SAXException, IOException {
String webappDirPath = System.getProperty(PROPERTY_WEBAPP_DIR);
if (webappDirPath == null) {
fail("System property '" + PROPERTY_WEBAPP_DIR
+ "' was not provided.");
}
webappDir = new File(webappDirPath);
if (!webappDir.isDirectory()) {
fail("'" + webappDirPath + "' is not a directory");
}
webXmlFile = new File(webappDir, "WEB-INF/web.xml");
if (!webXmlFile.isFile()) {
fail("Can't find '" + webXmlFile.getAbsolutePath() + "'");
}
webXmlDoc = docBuilder.parse(webXmlFile);
messages = new ArrayList<String>();
}
// ----------------------------------------------------------------------
// Tests
// ----------------------------------------------------------------------
@Test
public void checkAll() throws IOException {
checkDispatcherValues();
checkServletClasses();
checkListenerClasses();
checkFilterClasses();
checkTaglibLocations();
checkServletNames();
if (!messages.isEmpty()) {
fail("Found these problems with '" + webXmlFile.getCanonicalPath()
+ "'\n " + StringUtils.join(messages, "\n "));
}
}
private void checkDispatcherValues() {
List<String> okValues = Arrays.asList(new String[] { "FORWARD",
"REQUEST", "INCLUDE", "ERROR" });
for (Node n : findNodes("//j2ee:dispatcher")) {
String text = n.getTextContent();
if (!okValues.contains(text)) {
messages.add("<dispatcher>" + text
+ "</dispatcher> is not valid. Acceptable values are "
+ okValues);
}
}
}
private void checkServletClasses() {
for (Node n : findNodes("//j2ee:servlet-class")) {
String text = n.getTextContent();
String problem = confirmClassNameIsValid(text, HttpServlet.class);
if (problem != null) {
messages.add("<servlet-class>" + text
+ "</servlet-class> is not valid: " + problem);
}
}
}
private void checkListenerClasses() {
for (Node n : findNodes("//j2ee:listener-class")) {
String text = n.getTextContent();
String problem = confirmClassNameIsValid(text,
ServletContextListener.class);
if (problem != null) {
messages.add("<listener-class>" + text
+ "</listener-class> is not valid: " + problem);
}
}
}
private void checkFilterClasses() {
for (Node n : findNodes("//j2ee:filter-class")) {
String text = n.getTextContent();
String problem = confirmClassNameIsValid(text, Filter.class);
if (problem != null) {
messages.add("<filter-class>" + text
+ "</filter-class> is not valid: " + problem);
}
}
}
private void checkTaglibLocations() {
for (Node n : findNodes("//j2ee:jsp-config/j2ee:taglib")) {
String taglibUri = findNode("j2ee:taglib-uri", n).getTextContent();
String taglibLocation = findNode("j2ee:taglib-location", n)
.getTextContent();
// System.out.println("taglibUri='" + taglibUri
// + "', taglibLocation='" + taglibLocation + "'");
String message = checkTaglibUri(taglibUri, taglibLocation);
if (message != null) {
messages.add(message);
}
}
}
private void checkServletNames() {
Set<String> servletNames = new HashSet<String>();
for (Node n : findNodes("//j2ee:servlet/j2ee:servlet-name")) {
servletNames.add(n.getTextContent());
}
Set<String> servletMappingNames = new HashSet<String>();
for (Node n : findNodes("//j2ee:servlet-mapping/j2ee:servlet-name")) {
servletMappingNames.add(n.getTextContent());
}
servletMappingNames.removeAll(servletNames);
for (String name : servletMappingNames) {
messages.add("There is a <servlet-mapping> tag for <servlet-name>"
+ name + "</servlet-name>, but there is "
+ "no matching <servlet> tag.");
}
}
private String checkTaglibUri(String taglibUri, String taglibLocation) {
File taglibFile = new File(webappDir, taglibLocation);
if (!taglibFile.isFile()) {
return "File '" + taglibLocation + "' can't be found ('"
+ taglibFile.getAbsolutePath() + "')";
}
Document taglibDoc;
try {
taglibDoc = docBuilder.parse(taglibFile);
} catch (SAXException e) {
return "Failed to parse the taglib file '" + taglibFile + "': " + e;
} catch (IOException e) {
return "Failed to parse the taglib file '" + taglibFile + "': " + e;
}
List<Node> uriNodes = findNodes("/j2ee:taglib/j2ee:uri",
taglibDoc.getDocumentElement());
// System.out.println("uriNodes: " + uriNodes);
if (uriNodes.isEmpty()) {
return null;
}
if (uriNodes.size() > 1) {
return "taglib '" + taglibLocation + "' contains "
+ uriNodes.size()
+ " <uri> nodes. Expecting no more than 1";
}
String embeddedUri = uriNodes.get(0).getTextContent();
if (taglibUri.equals(embeddedUri)) {
return null;
} else {
return "URI in taglib doesn't match the one in web.xml: taglib='"
+ taglibLocation + "', internal URI='"
+ uriNodes.get(0).getTextContent()
+ "', URI from web.xml='" + taglibUri + "'";
}
}
// ----------------------------------------------------------------------
// Helper methods
// ----------------------------------------------------------------------
/**
* Search for an Xpath in web.xml, returning a handy list.
*/
private List<Node> findNodes(String pattern) {
return findNodes(pattern, webXmlDoc.getDocumentElement());
}
/**
* Search for an Xpath within a node of web.xml, returning a handy list.
*/
private List<Node> findNodes(String pattern, Node context) {
try {
XPathExpression xpe = xpath.compile(pattern);
NodeList nodes = (NodeList) xpe.evaluate(context,
XPathConstants.NODESET);
List<Node> list = new ArrayList<Node>();
for (int i = 0; i < nodes.getLength(); i++) {
list.add(nodes.item(i));
}
return list;
} catch (XPathExpressionException e) {
throw new RuntimeException(e);
}
}
/**
* Search for an Xpath within a node of web.xml, returning a single node or
* throwing an exception.
*/
private Node findNode(String pattern, Node context) {
List<Node> list = findNodes(pattern, context);
if (list.size() != 1) {
throw new RuntimeException("Expecting 1 node, but found "
+ list.size() + " nodes using '" + pattern + "'");
} else {
return list.get(0);
}
}
/**
* Check that the supplied className can be instantiated with a
* zero-argument constructor, and assigned to a variable of the target
* class.
*/
private String confirmClassNameIsValid(String className,
Class<?> targetClass) {
try {
Class<?> specifiedClass = Class.forName(className);
Object o = specifiedClass.newInstance();
if (!targetClass.isInstance(o)) {
return specifiedClass.getSimpleName()
+ " is not a subclass of "
+ targetClass.getSimpleName() + ".";
}
} catch (ClassNotFoundException e) {
return "The class does not exist.";
} catch (InstantiationException e) {
return "The class does not have a public constructor "
+ "that takes zero arguments.";
} catch (IllegalAccessException e) {
return "The class does not have a public constructor "
+ "that takes zero arguments.";
}
return null;
}
/**
* Dump the first 20 nodes of an XML context, excluding comments and blank
* text nodes.
*/
@SuppressWarnings("unused")
private int dumpXml(Node xmlNode, int... parms) {
int remaining = (parms.length == 0) ? 20 : parms[0];
int level = (parms.length < 2) ? 1 : parms[1];
Node n = xmlNode;
if (Node.COMMENT_NODE == n.getNodeType()) {
return 0;
}
if (Node.TEXT_NODE == n.getNodeType()) {
if (StringUtils.isBlank(n.getTextContent())) {
return 0;
}
}
int used = 1;
System.out.println(StringUtils.repeat("-->", level) + n);
NodeList nl = n.getChildNodes();
for (int i = 0; (i < nl.getLength() && remaining > used); i++) {
used += dumpXml(nl.item(i), remaining - used, level + 1);
}
return used;
}
// ----------------------------------------------------------------------
// Helper classes
// ----------------------------------------------------------------------
private static class StupidNamespaceContext implements NamespaceContext {
@Override
public String getNamespaceURI(String prefix) {
if ("j2ee".equals(prefix)) {
return "http://java.sun.com/xml/ns/j2ee";
} else {
throw new UnsupportedOperationException();
}
}
@Override
public String getPrefix(String namespaceURI) {
throw new UnsupportedOperationException();
}
@Override
public Iterator<?> getPrefixes(String namespaceURI) {
throw new UnsupportedOperationException();
}
}
}

View file

@ -0,0 +1,198 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.jarlist;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import com.tonicsystems.jarjar.AbstractDepHandler;
import com.tonicsystems.jarjar.DepHandler;
import com.tonicsystems.jarjar.KlugedDepFind;
/**
* This takes the place of the JarJar main routine, in doing a Find operation.
*
* One thing this lets us do is to call KlugedDepFind instead of DepFind.
* KlugedDepFind was created because JarJar had a known bug that wasn't fixed in
* the latest release. (see http://code.google.com/p/jarjar/issues/detail?id=6).
* I had to put KlugedDepFind into the com.tonicsystems.jarjar package so it
* wauld have access to DepFindVisitor, which is package-private.
*
* The other thing we can do is to provide a custom DepHandler which records the
* dependencies directly instead of writing them to a file which we would need
* to parse. Since we have the dependencies in a data structure, it's easy to
* walk the tree and find out what JARs are required, even through several
* layers of dependency.
*
* When calling this, pass 2 arguments. The first is the path to the JAR which
* contains the Vitro (or VIVO) classes. The second is the path to the directory
* that contains the JARs. (shouldn't end with a slash)
*
* There is a list of JARs which we know we need but which aren't shown by the
* analysis. For example, the MySQL driver is loaded dynamically by name, so an
* analysis of the class files in the JARs won't show that it is used. For now,
* these known dependencies are hard-coded, but it would be nice to read them
* from a data file instead.
*/
public class JarLister {
/**
* <pre>
*
* What I originally wanted to do was this:
*
* <target name="jarlist" depends="jar" description="Figure out what JARs are needed">
* <java classname="com.tonicsystems.jarjar.Main" fork="no" failonerror="true">
* <classpath refid="utility.run.classpath" />
* <arg value="find" />
* <arg value="jar" />
* <arg value="${build.dir}/${ant.project.name}.jar" />
* <arg value="${appbase.dir}/lib/*" />
* </java>
* </target>
*
* I ended up with this instead:
*
* <target name="jarlist" depends="jar" description="Figure out what JARs are needed">
* <java classname="edu.cornell.mannlib.vitro.utilities.jarlist.JarLister" fork="no" failonerror="true">
* <classpath refid="utility.run.classpath" />
* <arg value="${build.dir}/${ant.project.name}.jar" />
* <arg value="${appbase.dir}/lib" />
* <arg value="${appbase.dir}/config/jarlist/known_dependencies.txt" />
* </java>
* </target>
*
* </pre>
*/
private final String topJar;
private final String libDirectory;
private final List<String> knownDependencies;
private final Map<String, Set<String>> dependencyMap = new HashMap<String, Set<String>>();
private final Set<String> dependencySet = new TreeSet<String>();
public JarLister(String[] args) throws IOException {
topJar = args[0];
libDirectory = args[1];
knownDependencies = Collections
.unmodifiableList(readKnownDependencies(args[2]));
}
private List<String> readKnownDependencies(String knownDependenciesFilename)
throws IOException {
List<String> list = new ArrayList<String>();
BufferedReader r = new BufferedReader(new FileReader(
knownDependenciesFilename));
String line;
while (null != (line = r.readLine())) {
line = line.trim();
if (!(line.startsWith("#") || line.isEmpty())) {
list.add(line);
}
}
return list;
}
public void runDepFind() throws IOException {
DepHandler handler = new AbstractDepHandler(DepHandler.LEVEL_JAR) {
@Override
public void handle(String from, String to) {
addToMap(from, to);
}
};
String fullPath = topJar + ":" + libDirectory + "/*";
new KlugedDepFind().run(fullPath, fullPath, handler);
}
private void addToMap(String fromPath, String toPath) {
String fromName = new File(fromPath).getName();
String toName = new File(toPath).getName();
if (!dependencyMap.containsKey(fromName)) {
dependencyMap.put(fromName, new HashSet<String>());
}
dependencyMap.get(fromName).add(toName);
// System.out.println("Adding " + fromName + " ==> " + toName);
}
public void populateDependencySet() {
String topJarName = new File(topJar).getName();
addDependenciesFor(topJarName);
for (String known : knownDependencies) {
dependencySet.add(known);
addDependenciesFor(known);
}
}
private void addDependenciesFor(String name) {
if (!dependencyMap.containsKey(name)) {
return;
}
for (String depend : dependencyMap.get(name)) {
if (!dependencySet.contains(depend)) {
dependencySet.add(depend);
// System.out.println("Depend: " + depend);
addDependenciesFor(depend);
}
}
}
public void dumpDependencySet(PrintWriter w) {
w.println("--------------------");
w.println("Known required JARs");
w.println("--------------------");
for (String d : knownDependencies) {
w.println(" " + d);
}
w.println();
w.println("--------------------");
w.println("Dependent JARs");
w.println("--------------------");
for (String d : dependencySet) {
w.println(" " + d);
}
w.println();
File libDir = new File(libDirectory);
SortedSet<String> unused = new TreeSet<String>(Arrays.asList(libDir
.list()));
unused.removeAll(dependencySet);
w.println("--------------------");
w.println("Unused JARs");
w.println("--------------------");
for (String d : unused) {
w.println(" " + d);
}
w.println();
}
public static void main(String[] args) throws IOException {
JarLister jl = new JarLister(args);
jl.runDepFind();
jl.populateDependencySet();
PrintWriter output = new PrintWriter(System.out, true);
jl.dumpDependencySet(output);
output.close();
}
}

View file

@ -0,0 +1,161 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.revisioninfo;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A harness that runs a system-level command.
*
* No provision is made for standard input.
*
* The standard output and standard error streams are asynchronously read, so
* the sub-process will not block on full buffers. Warning: if either of these
* streams contain more data than can fit into a String, then we will have a
* problem.
*/
public class ProcessRunner {
private int returnCode;
private String stdOut = "";
private String stdErr = "";
private File workingDirectory;
private final Map<String, String> environmentAdditions = new HashMap<String, String>();
/** Set the directory that the command will run in. */
public void setWorkingDirectory(File workingDirectory) {
this.workingDirectory = workingDirectory;
}
/** Add (or replace) any environment variable. */
public void setEnvironmentVariable(String key, String value) {
this.environmentAdditions.put(key, value);
}
/**
* Run the command.
*
* @param command
* a list containing the operating system program and its
* arguments. See
* {@link java.lang.ProcessBuilder#ProcessBuilder(List)}.
*/
public void run(List<String> command) throws ProcessException {
try {
ProcessBuilder builder = new ProcessBuilder(command);
if (workingDirectory != null) {
builder.directory(workingDirectory);
}
if (!environmentAdditions.isEmpty()) {
builder.environment().putAll(this.environmentAdditions);
}
Process process = builder.start();
StreamEater outputEater = new StreamEater(process.getInputStream());
StreamEater errorEater = new StreamEater(process.getErrorStream());
this.returnCode = process.waitFor();
outputEater.join();
this.stdOut = outputEater.getContents();
errorEater.join();
this.stdErr = errorEater.getContents();
} catch (IOException e) {
throw new ProcessException("Exception when handling sub-process:",
e);
} catch (InterruptedException e) {
throw new ProcessException("Exception when handling sub-process:",
e);
}
}
public int getReturnCode() {
return returnCode;
}
public String getStdErr() {
return stdErr;
}
public String getStdOut() {
return stdOut;
}
/**
* A thread that reads an InputStream until it reaches end of file, then
* closes the stream.
*/
private static class StreamEater extends Thread {
private final InputStream stream;
private final StringWriter contents = new StringWriter();
private final byte[] buffer = new byte[4096];
public StreamEater(InputStream stream) {
this.stream = stream;
this.start();
}
@Override
public void run() {
try {
int howMany = 0;
while (true) {
howMany = stream.read(buffer);
if (howMany > 0) {
contents.write(new String(buffer, 0, howMany));
} else if (howMany == 0) {
Thread.yield();
} else {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String getContents() {
return contents.toString();
}
}
/**
* Indicates a problem when dealing with a spawned sub-process.
*/
public static class ProcessException extends Exception {
public ProcessException() {
}
public ProcessException(String message) {
super(message);
}
public ProcessException(Throwable cause) {
super(cause);
}
public ProcessException(String message, Throwable cause) {
super(message, cause);
}
}
}

View file

@ -0,0 +1,253 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.revisioninfo;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
import edu.cornell.mannlib.vitro.utilities.revisioninfo.ProcessRunner.ProcessException;
/**
* Get release and revision information to display on screen. Put this
* information into a single line and append it to the specified file.
*
* Ask Git for the information. If Git is available, and if this is a working
* directory, then we can build the info from the responses we get from
* "git describe", "git symbolic-ref" and "git log".
*
* If that doesn't work, read the information from the "revisionInfo" file in
* the product directory. Presumably, that file was created when the source was
* exported from Git.
*
* If that doesn't work either, return something like this:
* "productName ~ unknown ~ unknown"
*/
public class RevisionInfoBuilder {
/**
* Indicates a problem with the command-line arguments.
*/
private static class UsageException extends Exception {
UsageException(String message) {
super(message);
}
}
/**
* An object that holds the revision information and a message about how we
* obtained it.
*/
private static class Results {
final String message;
final String infoLine;
Results(String message, String infoLine) {
this.message = message;
this.infoLine = infoLine;
}
@Override
public String toString() {
return message + ": " + infoLine;
}
}
private static final String GIT_DIRECTORY_NAME = ".git";
private static final String[] GIT_DESCRIBE_COMMAND = { "git", "describe" };
private static final String[] GIT_SREF_COMMAND = { "git", "symbolic-ref",
"HEAD" };
private static final String[] GIT_LOG_COMMAND = { "git", "log",
"--pretty=format:%h", "-1" };
private static final String INFO_LINE_DELIMITER = " ~ ";
private static final String REVISION_INFO_FILENAME = "revisionInfo";
private final String productName;
private final File productDirectory;
private final File resultFile;
private Results results;
public RevisionInfoBuilder(String[] args) throws UsageException {
if (args.length != 3) {
throw new UsageException(
"RevisionInfoBuilder requires 3 arguments, not "
+ args.length);
}
productName = args[0];
productDirectory = new File(args[1]);
resultFile = new File(args[2]);
if (!productDirectory.isDirectory()) {
throw new UsageException("Directory '"
+ productDirectory.getAbsolutePath() + "' does not exist.");
}
if (!resultFile.getParentFile().exists()) {
throw new UsageException("Result file '"
+ resultFile.getAbsolutePath()
+ "' does not exist, and we can't create it "
+ "because it's parent directory doesn't exist either.");
}
}
private void buildInfo() {
results = buildInfoFromGit();
if (results == null) {
results = buildInfoFromFile();
}
if (results == null) {
results = buildDummyInfo();
}
}
private Results buildInfoFromGit() {
if (!isThisAGitWorkspace()) {
System.out.println("Not a git workspace");
return null;
}
String release = assembleReleaseNameFromGit();
if (release == null) {
System.out.println("Couldn't get release name from Git");
}
String revision = obtainCommitIdFromGit();
if (revision == null) {
System.out.println("Couldn't get commit ID from Git");
}
if ((revision == null) && (release == null)) {
return null;
}
return new Results("Info from Git", buildLine(release, revision));
}
private boolean isThisAGitWorkspace() {
File gitDirectory = new File(productDirectory, GIT_DIRECTORY_NAME);
return gitDirectory.isDirectory();
}
private String assembleReleaseNameFromGit() {
String describeResponse = runSubProcess(GIT_DESCRIBE_COMMAND);
String srefResponse = runSubProcess(GIT_SREF_COMMAND);
return parseReleaseName(describeResponse, srefResponse);
}
private String obtainCommitIdFromGit() {
String logResponse = runSubProcess(GIT_LOG_COMMAND);
return parseLogResponse(logResponse);
}
private String parseReleaseName(String describeResponse, String srefResponse) {
if (describeResponse != null) {
return describeResponse.trim() + " tag";
} else if (srefResponse != null) {
return srefResponse.substring(srefResponse.lastIndexOf('/') + 1)
.trim() + " branch";
} else {
return null;
}
}
private String parseLogResponse(String logResponse) {
return logResponse;
}
private String runSubProcess(String[] cmdArray) {
List<String> command = Arrays.asList(cmdArray);
try {
ProcessRunner runner = new ProcessRunner();
runner.setWorkingDirectory(productDirectory);
runner.run(command);
int rc = runner.getReturnCode();
if (rc != 0) {
throw new ProcessRunner.ProcessException("Return code from "
+ command + " was " + rc);
}
String output = runner.getStdOut();
// System.err.println(command + " response was '" + output + "'");
return output;
} catch (ProcessException e) {
// System.out.println(e);
return null;
}
}
private Results buildInfoFromFile() {
try {
File revisionInfoFile = new File(productDirectory,
REVISION_INFO_FILENAME);
BufferedReader reader = new BufferedReader(new FileReader(
revisionInfoFile));
String release = reader.readLine();
if (release == null) {
throw new EOFException("No release line in file.");
}
String revision = reader.readLine();
if (revision == null) {
throw new EOFException("No revision line in file.");
}
return new Results("Info from file", buildLine(release, revision));
} catch (IOException e) {
System.out.println("No information from file: " + e);
return null;
}
}
private Results buildDummyInfo() {
String line = buildLine(null, null);
return new Results("Using dummy info", line);
}
private String buildLine(String release, String revision) {
if (release == null) {
release = "unknown";
}
if (revision == null) {
revision = "unknown";
}
return productName + INFO_LINE_DELIMITER + release.trim()
+ INFO_LINE_DELIMITER + revision.trim();
}
private void writeLine() throws IOException {
Writer writer = null;
writer = new FileWriter(resultFile, true);
writer.write(results.infoLine + "\n");
writer.close();
System.out.println(results);
}
public static void main(String[] args) {
try {
RevisionInfoBuilder builder = new RevisionInfoBuilder(args);
builder.buildInfo();
builder.writeLine();
} catch (UsageException e) {
System.err.println(e);
System.err.println("usage: RevisionInfoBuilder [product_name] "
+ "[product_directory] [output_file]");
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}

View file

@ -0,0 +1,346 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testing;
import static edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel.BRIEF;
import static edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel.FULL;
import static edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel.MORE;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel;
/**
* Listen to events as they come from the JUnit test runner. The events from the
* lifecycle methods are broken down into semantic chunks and executed. Three
* levels of output are available.
*
* On the surface, JUnit treats "failures" (failed assertions) the same as
* "errors" (unexpected exceptions). We're going to distinguish between them.
*
* @author jeb228
*/
public class VitroTestRunListener extends RunListener {
private final ReportLevel reportLevel;
private int classCount;
private int testsTotal;
private int errorsTotal;
private int failuresTotal;
private int ignoresTotal;
private long overallStartTime;
private Class<?> currentClass;
private int testsCurrentClass;
private int errorsCurrentClass;
private int failuresCurrentClass;
private int ignoresCurrentClass;
private long classStartTime;
private String currentTest;
private boolean testHadError;
private boolean testFailed;
private boolean testIgnored;
private long testStartTime;
public VitroTestRunListener(ReportLevel reportLevel) {
this.reportLevel = reportLevel;
}
/** Did any of the tests fail or have errors? */
public boolean didEverythingPass() {
return (failuresTotal == 0) && (errorsTotal == 0);
}
// -------------------------------------------------------------------------
// Life-cycle methods that will be called by the test runner.
// -------------------------------------------------------------------------
@Override
public void testRunStarted(Description description) throws Exception {
openTestRun();
reportTestRunStart();
}
@Override
public void testStarted(Description description) throws Exception {
if (currentClass != description.getTestClass()) {
if (currentClass != null) {
closeCurrentClass();
reportCurrentClass();
}
openCurrentClass(description);
reportCurrentClassStart();
}
openCurrentTest(description);
}
@Override
public void testAssumptionFailure(Failure failure) {
if (isError(failure)) {
testHadError = true;
reportError(failure);
} else {
testFailed = true;
reportFailure(failure);
}
}
@Override
public void testFailure(Failure failure) throws Exception {
if (isError(failure)) {
testHadError = true;
reportError(failure);
} else {
testFailed = true;
reportFailure(failure);
}
}
@Override
public void testFinished(Description description) throws Exception {
closeCurrentTest();
reportCurrentTest();
}
@Override
public void testIgnored(Description description) throws Exception {
testStarted(description);
testIgnored = true;
testFinished(description);
}
@Override
public void testRunFinished(Result result) throws Exception {
if (currentClass != null) {
closeCurrentClass();
reportCurrentClass();
}
closeTestRun();
reportTestRun();
System.out.println();
}
// -------------------------------------------------------------------------
// Handling the logical events.
// -------------------------------------------------------------------------
private void openTestRun() {
overallStartTime = System.currentTimeMillis();
}
private void closeTestRun() {
// Nothing to close.
}
private void reportTestRunStart() {
if (reportLevel == FULL) {
System.out
.println("Starting test run at " + time(overallStartTime));
System.out.println();
}
if (reportLevel == MORE) {
System.out
.println("Starting test run at " + time(overallStartTime));
System.out.println();
System.out.println("Tests Pass Error Fail Ignore Seconds");
}
}
private void reportTestRun() {
int successes = testsTotal - errorsTotal - failuresTotal - ignoresTotal;
if (reportLevel != BRIEF) {
System.out.println();
}
System.out.format(
"Tests Pass Error Fail Ignore Seconds TOTAL (%d classes)\n",
classCount);
System.out.format(" %4d %4d %4d %4d %4d %6s\n", testsTotal,
successes, errorsTotal, failuresTotal, ignoresTotal,
elapsed(overallStartTime));
if (reportLevel != BRIEF) {
System.out.println("Ending test run at "
+ time(System.currentTimeMillis()));
}
}
private void openCurrentClass(Description description) {
currentClass = description.getTestClass();
classStartTime = System.currentTimeMillis();
testsCurrentClass = 0;
errorsCurrentClass = 0;
failuresCurrentClass = 0;
ignoresCurrentClass = 0;
}
private void closeCurrentClass() {
classCount++;
testsTotal += testsCurrentClass;
errorsTotal += errorsCurrentClass;
failuresTotal += failuresCurrentClass;
ignoresTotal += ignoresCurrentClass;
}
private void reportCurrentClassStart() {
if (reportLevel == FULL) {
System.out.format("Tests Pass Error Fail Ignore Seconds %s\n",
currentClass.getName());
}
}
private void reportCurrentClass() {
int successes = testsCurrentClass - errorsCurrentClass
- failuresCurrentClass - ignoresCurrentClass;
if (reportLevel == MORE) {
System.out.format(" %4d %4d %4d %4d %4d %6s %s\n",
testsCurrentClass, successes, errorsCurrentClass,
failuresCurrentClass, ignoresCurrentClass,
elapsed(classStartTime), currentClass.getSimpleName());
}
if (reportLevel == FULL) {
System.out.println("-----------------------------------");
System.out.format(" %4d %4d %4d %4d %4d %6s\n",
testsCurrentClass, successes, errorsCurrentClass,
failuresCurrentClass, ignoresCurrentClass,
elapsed(classStartTime));
System.out.println();
}
}
private void openCurrentTest(Description description) {
currentTest = description.getMethodName();
testHadError = false;
testFailed = false;
testIgnored = false;
testStartTime = System.currentTimeMillis();
}
private void closeCurrentTest() {
if (testHadError) {
errorsCurrentClass++;
}
if (testFailed) {
failuresCurrentClass++;
}
if (testIgnored) {
ignoresCurrentClass++;
}
testsCurrentClass++;
}
private boolean isError(Failure failure) {
Throwable throwable = failure.getException();
return (throwable != null) && !(throwable instanceof AssertionError);
}
private void reportError(Failure error) {
Description description = error.getDescription();
String methodName = description.getMethodName();
String className = description.getTestClass().getName();
String message = error.getMessage();
System.out.format("EXCEPTION: test %s() in %s: %s\n",
methodName, className, message);
System.out.println(formatStackTrace(error.getException()));
}
private void reportFailure(Failure failure) {
Description description = failure.getDescription();
String methodName = description.getMethodName();
String className = description.getTestClass().getName();
String message = failure.getMessage();
System.out.format("TEST FAILED: test %s() in %s: %s\n", methodName,
className, message);
}
private void reportCurrentTest() {
if (reportLevel == FULL) {
char passFlag = (testIgnored | testFailed | testHadError) ? ' '
: '1';
char errorFlag = testHadError ? '1' : ' ';
char failFlag = testFailed ? '1' : ' ';
char ignoreFlag = testIgnored ? '1' : ' ';
System.out.format(
" %c %c %c %c %6s %s()\n",
passFlag, errorFlag, failFlag, ignoreFlag,
elapsed(testStartTime), currentTest);
}
}
// -------------------------------------------------------------------------
// Formatting methods.
// -------------------------------------------------------------------------
private final SimpleDateFormat formatter = new SimpleDateFormat(
"HH:mm:ss 'on' MMM dd, yyyy");
private String time(long time) {
return formatter.format(new Date(time));
}
/** Show elapsed time in 6 columns. */
private String elapsed(long start) {
long interval = System.currentTimeMillis() - start;
return String.format("%6.2f", ((float) interval) / 1000.0);
}
/**
* Trim the stack trace: don't show the line saying "23 more", and don't
* show the lines about org.junit or java.lang.reflect or sun.reflect.
*
* Once we hit some "client code", we won't trim any futher lines even if
* they belong to org.junit, or the others.
*
* If we have nested exceptions, the process repeats for each "Caused by"
* section.
*/
private String formatStackTrace(Throwable throwable) {
StringWriter w = new StringWriter();
throwable.printStackTrace(new PrintWriter(w));
String[] lineArray = w.toString().split("\\n");
List<String> lines = new ArrayList<String>(Arrays.asList(lineArray));
boolean removing = true;
for (int i = lines.size() - 1; i > 0; i--) {
String line = lines.get(i);
if (removing) {
if (line.matches("\\s*[\\.\\s\\d]+more\\s*")
|| line.contains("at "
+ VitroTestRunner.class.getName())
|| line.contains("at org.junit.")
|| line.contains("at java.lang.reflect.")
|| line.contains("at sun.reflect.")) {
lines.remove(line);
} else {
removing = false;
}
} else {
if (line.contains("Caused by: ")) {
removing = true;
}
}
}
StringBuilder result = new StringBuilder();
for (String line : lines) {
result.append(line).append('\n');
}
return result.toString().trim();
}
}

View file

@ -0,0 +1,153 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testing;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import org.junit.runner.JUnitCore;
/**
* A Java application that will run the Vitro unit tests. It searches for unit
* tests in the supplied source directory (any file whose name is *Test.Java).
* It runs the tests with a variety of reporting detail, depending on the level
* selected. If the level selector is absent or unrecognized, the medium level
* is used.
*
* @author jeb228
*/
public class VitroTestRunner {
public enum ReportLevel {
/** Report only the one-line summary. */
BRIEF,
/** Report times and statistics for each test class. */
MORE,
/** Report times and statistics for each test method. */
FULL
}
private final List<Class<?>> classes;
private final VitroTestRunListener listener;
/**
* Locate the test classes. Initialize the test listener.
*/
public VitroTestRunner(File sourceRootDir, ReportLevel reportLevel) {
List<String> classNames = getListOfTestClassNames(sourceRootDir);
this.classes = getTestClasses(classNames);
this.listener = new VitroTestRunListener(reportLevel);
}
/**
* Start a recursive search through the source directory.
*/
private List<String> getListOfTestClassNames(File sourceRootDir) {
SortedSet<String> names = new TreeSet<String>();
searchForTestClasses(names, "", sourceRootDir);
return new ArrayList<String>(names);
}
/**
* Recursively search the directory for files in the form "*Test.java".
* Ignore any files or directories whose names start with a "."
*/
private void searchForTestClasses(SortedSet<String> names, String prefix,
File directory) {
for (File file : directory.listFiles()) {
String filename = file.getName();
if (filename.startsWith(".")) {
// ignore .svn, etc.
} else if (file.isDirectory()) {
searchForTestClasses(names, prefix + filename + ".", file);
} else if (filename.endsWith("Test.java")) {
String classname = filename.substring(0, filename.length() - 5);
names.add(prefix + classname);
}
}
}
/**
* Instantiate a class for each test class name.
*/
private List<Class<?>> getTestClasses(List<String> classNames) {
List<Class<?>> classes = new ArrayList<Class<?>>();
for (String classname : classNames) {
try {
classes.add(Class.forName(classname));
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Can't load test class: "
+ classname, e);
}
}
return classes;
}
/**
* We've located all of the test clases. Now run them.
*/
private void run() {
JUnitCore junitCore = new JUnitCore();
junitCore.addListener(this.listener);
junitCore.run(this.classes.toArray(new Class<?>[0]));
}
/**
* Did any of the tests fail?
*/
private boolean didEverythingPass() {
return this.listener.didEverythingPass();
}
/**
* <p>
* You must provide a path to the source directory of the test classes.
* </p>
* <p>
* You may also provide a reporting level of "BRIEF", "MORE", or "FULL". If
* no level is provided, or if it is not recognized, "BRIEF" is used.
* </p>
*/
public static void main(String[] args) {
if ((args.length < 1) || (args.length > 2)) {
usage("Wrong number of arguments: expecting 1 or 2, but found "
+ args.length + ".");
}
File sourceRootDir = new File(args[0]);
if (!sourceRootDir.exists()) {
usage(sourceRootDir + " does not exist.");
}
if (!sourceRootDir.isDirectory()) {
usage(sourceRootDir + " is not a directory.");
}
ReportLevel reportLevel = ReportLevel.MORE;
if (args.length == 2) {
for (ReportLevel level : ReportLevel.values()) {
if (level.toString().equalsIgnoreCase(args[1])) {
reportLevel = level;
}
}
}
VitroTestRunner runner = new VitroTestRunner(sourceRootDir, reportLevel);
runner.run();
if (!runner.didEverythingPass()) {
System.exit(1);
}
}
/**
* Tell them how it should have been done.
*/
private static void usage(String message) {
System.out.println(message);
System.out.println("usage: " + VitroTestRunner.class.getSimpleName()
+ " sourceRootDirectory [ BRIEF | MORE | FULL ]");
System.exit(1);
}
}

View file

@ -0,0 +1,300 @@
=begin
--------------------------------------------------------------------------------
Scan the source directory, checking for expected "magic license tags",
or
Copy the source directory, inserting licensing information into the files.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
=end
$: << File.dirname(File.expand_path(__FILE__))
require 'date'
require 'fileutils'
require 'pathname'
require 'property_file_reader'
require 'licenser_stats'
class Licenser
MAGIC_STRING = '$This file is distributed under the terms of the license in /doc/license.txt$'
# ------------------------------------------------------------------------------------
private
# ------------------------------------------------------------------------------------
#
# Some paths in the properties file, if they are relative, should be relative to the
# properties file itself.
def relative_to_properties(properties, key)
path = properties[key]
base = File.dirname(properties['properties_file_path'])
return nil if path == nil
return File.expand_path(path) if Pathname.new(path).absolute?
return File.expand_path(File.join(base, path))
end
# Some paths in the properties file, if they are relative, should be relative to the
# source directory.
def relative_to_source(properties, key)
path = properties[key]
base = @source_dir ? @source_dir : ''
return nil if path == nil
return path if Pathname.new(path).absolute?
return File.expand_path(File.join(base, path))
end
# Confirm that the parameters are reasonable.
#
def sanity_checks_on_parameters()
# Check that all necessary properties are here.
raise("Properties file must contain a value for 'source_dir'") if @source_dir == nil
raise("Properties file must contain a value for 'known_exceptions'") if @known_exceptions_file == nil
raise("Properties file must contain a value for 'skip_directories'") if @skip_directories_list == nil
raise("Properties file must contain a value for 'file_matchers'") if @file_match_list == nil
raise("Properties file must contain a value for 'report_level'") if @report_level_string == nil
if !File.exist?(@source_dir)
raise "Source directory does not exist: #{@source_dir}"
end
if !File.exist?(@known_exceptions_file)
raise "Known exceptions file does not exist: #{@known_exceptions_file}"
end
end
# The globs in the exceptions file are assumed to be
# relative to the source directory. Make them explicitly so.
#
# Ignore any blank lines or lines that start with a '#'
#
def prepare_exception_globs(exceptions_file, source_dir)
source_path = File.expand_path(source_dir)
globs = []
File.open(exceptions_file) do |file|
file.each do |line|
glob = line.strip
if (glob.length > 0) && (glob[0..0] != '#')
globs << "#{source_path}/#{glob}".gsub('//', '/')
end
end
end
return globs
end
# Recursively scan this directory, and copy if we are not scan-only.
#
def scan_dir(source_dir, target_dir)
@stats.enter_directory(source_dir)
Dir.foreach(File.join(@source_dir, source_dir)) do |filename|
source_path_relative = File.join(source_dir, filename)
source_path = File.join(@source_dir, source_path_relative)
target_path_relative = File.join(target_dir, filename)
target_path = File.join(@target_dir, target_path_relative)
# What kind of beast is this?
if filename == '.' || filename == '..'
is_skipped_directory = true
else
if File.directory?(source_path)
if (path_matches_skipped?(source_path_relative))
is_skipped_directory = true
else
is_directory = true
end
else
if filename_matches_pattern?(filename)
if path_matches_exception?(source_path_relative)
is_exception = true
else
is_match = true
end
else
is_ignored = true
end
end
end
if is_skipped_directory
# do nothing
elsif is_directory
scan_dir(source_path_relative, target_path_relative)
elsif is_match
@stats.record_scan_matching(filename)
scan_file(source_path, filename)
elsif is_exception
@stats.record_known_exception(filename)
else # not a match
@stats.record_scan_non_matching(filename)
end
end
end
# Is this directory one of the skipped?
#
def path_matches_skipped?(relative_path)
@skip_directories.each do |glob|
return true if File.fnmatch(glob, relative_path)
end
return false
end
# Does this file path match any of the exceptions?
#
def path_matches_exception?(relative_path)
path = File.expand_path(File.join(@source_dir, relative_path))
@known_exceptions.each do |pattern|
return true if File.fnmatch(pattern, path)
end
return false
end
# Does this filename match any of the patterns?
#
def filename_matches_pattern?(filename)
@file_matchers.each do |pattern|
return true if File.fnmatch(pattern, filename)
end
return false
end
# This file should contain a license tag.
#
def scan_file(source_path, filename)
found = 0
File.open(source_path) do |source_file|
source_file.each do |line|
if line.include?(MAGIC_STRING)
found += 1
end
end
end
if found == 0
@stats.record_no_tag(filename, source_path)
elsif found == 1
@stats.record_tag(filename)
else
raise("File contains #{found} license lines: #{source_path}")
end
end
# ------------------------------------------------------------------------------------
public
# ------------------------------------------------------------------------------------
# Setup and get ready to process.
#
# * properties is a map of keys to values, probably parsed from a properties file.
#
def initialize(properties)
@file_match_list = properties['file_matchers']
@skip_directories_list = properties['skip_directories']
@report_level_string = properties['report_level']
# These properties contain paths, and if they are relative paths, they
# should be relative to the properties file itself.
@source_dir = relative_to_properties(properties, 'source_dir')
@target_dir = relative_to_properties(properties, 'target_dir')
# These properties contain paths, and if they are relative paths, they
# should be relative to the source_directory.
@license_file = relative_to_source(properties, 'license_file')
@known_exceptions_file = relative_to_source(properties, 'known_exceptions')
sanity_checks_on_parameters()
@full_report = @report_level_string === 'full'
@short_report = @report_level_string === 'short'
@file_matchers = @file_match_list.strip.split(/,\s*/)
@skip_directories = @skip_directories_list.strip.split(/,\s*/)
@known_exceptions = prepare_exception_globs(@known_exceptions_file, @source_dir)
@stats = LicenserStats.new(@source_dir, @file_matchers, @full_report)
end
# Start the recursive scanning (and copying).
def process()
scan_dir('.', '.')
end
# Report the summary statistics
def report(properties)
if (@short_report)
tags = 0
@stats.tagged_files.each {|line| tags += line[1] }
known = 0
@stats.known_exceptions.each {|line| known += line[1] }
missing = 0
@stats.missing_tags.each {|line| missing += line[1] }
puts "Licenser: scanned #{@stats.file_count} files in #{@stats.dir_count} directories."
printf(" Licensed files: %5d\n", tags)
printf(" Known exceptions: %5d\n", known)
printf(" Missing tags: %5d\n", missing)
else
puts "Licenser: run completed at #{DateTime.now.strftime("%H:%M:%S on %b %d, %Y")}"
puts " scanned #{@stats.file_count} files in #{@stats.dir_count} directories."
puts
puts 'Licensed files'
@stats.tagged_files.sort.each do |line|
printf("%5d %s\n", line[1], line[0])
end
puts
puts 'Known non-licensed files'
@stats.known_exceptions.sort.each do |line|
printf("%5d %s\n", line[1], line[0])
end
puts
puts 'Missing tags'
@stats.missing_tags.sort.each do |line|
printf("%5d %s\n", line[1], line[0])
end
puts
puts 'properties:'
properties.each do |key, value|
puts " #{key} = #{value}"
end
end
end
# Were we successful or not?
def success?
return @stats.missing_tags.empty?
end
end
#
#
# ------------------------------------------------------------------------------------
# Standalone calling.
#
# Do this if this program was called from the command line. That is, if the command
# expands to the path of this file.
# ------------------------------------------------------------------------------------
#
if File.expand_path($0) == File.expand_path(__FILE__)
if ARGV.length == 0
raise("No arguments - usage is: ruby licenser.rb <properties_file>")
end
if !File.file?(ARGV[0])
raise "File does not exist: '#{ARGV[0]}'."
end
properties = PropertyFileReader.read(ARGV[0])
l = Licenser.new(properties)
l.process
l.report(properties)
if l.success?
puts "Licenser was successful."
exit 0
else
puts "Licenser found problems."
exit 1
end
end

View file

@ -0,0 +1,95 @@
=begin
--------------------------------------------------------------------------------
Collect the statistics of a licenser run.
--------------------------------------------------------------------------------
=end
class LicenserStats
attr_reader :tagged_files
attr_reader :missing_tags
attr_reader :known_exceptions
attr_reader :file_count
attr_reader :dir_count
# ------------------------------------------------------------------------------------
private
# ------------------------------------------------------------------------------------
#
def which_match(filename)
@file_matchers.each do |matcher|
return matcher if File.fnmatch(matcher, filename)
end
raise("filename matches no matchers!: #{filename}")
end
# ------------------------------------------------------------------------------------
public
# ------------------------------------------------------------------------------------
def initialize(root_dir, file_matchers, full)
@root_dir = "#{root_dir}/".gsub('//', '/')
@file_matchers = file_matchers
@full = full
# keep track of how many tags are found in all file types
@tagged_files = Hash.new()
file_matchers.each do |matcher|
@tagged_files[matcher] = 0
end
# keep track of missing tags, only in file types that have missing tags
@missing_tags = Hash.new(0)
# keep track of how many known non-licensed files we encounter, and what types.
@known_exceptions = Hash.new(0)
# keep track of how many files are copied
@file_count = 0
#keep track of how many directories are copied
@dir_count = 0
end
def enter_directory(path)
@dir_count += 1
puts "Entering directory: #{path}" if @full
end
def record_scan_non_matching(filename)
@file_count += 1
puts " Scan without mods: #{filename}" if @full
end
def record_copy_non_matching(filename)
@file_count += 1
puts " Copy without mods: #{filename}" if @full
end
def record_scan_matching(filename)
@file_count += 1
puts " Scan with mods: #{filename}" if @full
end
def record_copy_matching(filename)
@file_count += 1
puts " Copy with mods: #{filename}" if @full
end
def record_known_exception(filename)
@file_count += 1
puts " Known exception: #{filename}" if @full
@known_exceptions[which_match(filename)] += 1
end
def record_tag(filename)
puts " Found license tag into #{filename}" if @full
@tagged_files[which_match(filename)] += 1
end
def record_no_tag(filename, source_path)
puts "ERROR: Found no license tag in #{source_path.sub(@root_dir, '')}"
@missing_tags[which_match(filename)] += 1
end
end

View file

@ -0,0 +1,39 @@
=begin
--------------------------------------------------------------------------------
A utility class that reads a properties file and returns a hash containing the
properties.
--------------------------------------------------------------------------------
=end
class PropertyFileReader
# Read a properties file and return a hash.
#
# Parameters: the path to the properties file
#
# The hash includes the special property "properties_file_path", which holds
# the path to the properties file.
#
def self.read(file_path)
properties = {}
properties["properties_file_path"] = File.expand_path(file_path)
File.open(file_path) do |file|
file.each_line do |line|
line.strip!
if line.length == 0 || line[0] == ?# || line[0] == ?!
# ignore blank lines, and lines starting with '#' or '!'.
elsif line =~ /(.*?)\s*[=:]\s*(.*)/
# key and value are separated by '=' or ':' and optional whitespace.
properties[$1.strip] = $2
else
# No '=' or ':' means that the value is empty.
properties[line] = ''
end
end
end
return properties
end
end

View file

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<!-- ======================================================================
A tool to migrate RDB data into TDB.
====================================================================== -->
<project name="RDB-migration" default="describe">
<property name="working.dir" location=".work" />
<property name="src.dir" location="src" />
<property name="lib.dir" location="lib" />
<!-- =================================
target: describe
================================= -->
<target name="describe"
description="--> Describe the targets (this is the default).">
<echo>
all - Compiles and runs the RDB migration tool.
</echo>
</target>
<!-- =================================
target: all
================================= -->
<target name="all"
depends="clean, run"
description="Build from scratch and run the migration.">
</target>
<!-- - - - - - - - - - - - - - - - - -
target: clean
- - - - - - - - - - - - - - - - - -->
<target name="clean">
<delete dir="${working.dir}" />
</target>
<!-- - - - - - - - - - - - - - - - - -
target: setup
- - - - - - - - - - - - - - - - - -->
<target name="setup" depends="getBuildProperties, getRuntimeProperties">
<mkdir dir="${working.dir}" />
</target>
<!-- - - - - - - - - - - - - - - - - -
target: getBuildProperties
- - - - - - - - - - - - - - - - - -->
<target name="getBuildProperties">
<property name="vivo.distribution.dir" location="../.." />
<property name="build.properties.file"
location="${vivo.distribution.dir}/build.properties" />
<fail message="You must create a &quot;${build.properties.file}&quot; file.">
<condition>
<not>
<available file="${build.properties.file}" />
</not>
</condition>
</fail>
<property file="${build.properties.file}" />
<fail unless="vitro.core.dir"
message="${build.properties.file} must contain a value for vitro.core.dir" />
<fail unless="vitro.home"
message="${build.properties.file} must contain a value for vitro.home" />
</target>
<!-- - - - - - - - - - - - - - - - - -
target: getRuntimeProperties
- - - - - - - - - - - - - - - - - -->
<target name="getRuntimeProperties">
<property name="runtime.properties.file"
location="${vitro.home}/runtime.properties" />
<fail message="You must create a &quot;${runtime.properties.file}&quot; file.">
<condition>
<not>
<available file="${runtime.properties.file}" />
</not>
</condition>
</fail>
<property file="${runtime.properties.file}" />
<fail unless="VitroConnection.DataSource.url"
message="${runtime.properties.file} must contain a value for VitroConnection.DataSource.url" />
<fail unless="VitroConnection.DataSource.username"
message="${runtime.properties.file} must contain a value for VitroConnection.DataSource.username" />
<fail unless="VitroConnection.DataSource.password"
message="${runtime.properties.file} must contain a value for VitroConnection.DataSource.password" />
</target>
<!-- - - - - - - - - - - - - - - - - -
target: compile
- - - - - - - - - - - - - - - - - -->
<target name="compile" depends="setup">
<path id="main.compile.classpath">
<fileset dir="${lib.dir}" includes="*.jar" />
</path>
<javac srcdir="${src.dir}"
destdir="${working.dir}"
debug="true"
deprecation="true"
encoding="UTF8"
includeantruntime="false"
optimize="true"
source="1.7">
<classpath refid="main.compile.classpath" />
</javac>
</target>
<!-- - - - - - - - - - - - - - - - - -
target: run
- - - - - - - - - - - - - - - - - -->
<target name="run" depends="compile">
<path id="migrate.run.classpath">
<pathelement location="${working.dir}" />
<path refid="main.compile.classpath" />
</path>
<java classname="edu.cornell.mannlib.vivo.utilities.rdbmigration.RdbMigrator"
fork="no"
failonerror="true">
<arg value="${vitro.home}" />
<arg value="${VitroConnection.DataSource.url}" />
<arg value="${VitroConnection.DataSource.username}" />
<arg value="${VitroConnection.DataSource.password}" />
<jvmarg value="-Xms512m"/>
<jvmarg value="-Xmx2048m"/>
<classpath refid="migrate.run.classpath" />
</java>
</target>
</project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,362 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.utilities.rdbmigration;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import com.hp.hpl.jena.db.DBConnection;
import com.hp.hpl.jena.db.GraphRDB;
import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.query.Dataset;
import com.hp.hpl.jena.sparql.core.DatasetGraph;
import com.hp.hpl.jena.tdb.TDBFactory;
/**
* A free-standing application that walks the user through the process of
* copying VIVO configuration data from RDB to TDB.
*/
public class RdbMigrator {
private static final String DIRECTORY_TDB = "tdbModels";
private static final String TABLE_RDB = "jena_graph";
private static final String TABLE_MIGRATED = "vivo_rdb_migrated";
private static final List<String> EXPECTED_MODELS = Arrays
.asList(new String[] {
"http://vitro.mannlib.cornell.edu/default/vitro-kb-displayMetadata",
"http://vitro.mannlib.cornell.edu/default/vitro-kb-displayMetadata-displayModel",
"http://vitro.mannlib.cornell.edu/default/vitro-kb-displayMetadataTBOX",
"http://vitro.mannlib.cornell.edu/default/vitro-kb-userAccounts" });
private final String vivoHomeDir;
private final String jdbcUrl;
private final String username;
private final String password;
private File targetDir;
private boolean alreadyMigrated;
private List<String> modelsToCopy;
/**
* Confirm all of the parameters. Ask the user for approval. Do the
* migration.
*
* @throws UserDeclinedException
* If the user decides not to continue.
*/
public RdbMigrator(String vivoHomeDir, String jdbcUrl, String username,
String password) throws UserDeclinedException, IOException,
SQLException {
this.vivoHomeDir = vivoHomeDir;
this.jdbcUrl = jdbcUrl;
this.username = username;
this.password = password;
testDbConnection();
checkThatRdbExists();
confirmTargetDirectory();
if (doesTdbExist()) {
askContinueOverTdb();
}
if (isAlreadyMigrated()) {
askMigrateAgain();
}
getListOfRdbModels();
if (isUnexpectedModels()) {
askMigrateAllModels();
}
askApprovalForMigrationPlan();
migrate();
}
private void testDbConnection() {
try (Connection conn = getSqlConnection()) {
// Just open and close it.
} catch (SQLException e) {
quit("Can't log in to database: '" + jdbcUrl + "', '" + username
+ "', '" + password + "'\n" + e.getMessage());
}
}
private void checkThatRdbExists() throws SQLException {
try (Connection conn = getSqlConnection()) {
DatabaseMetaData md = conn.getMetaData();
try (ResultSet rs = md.getTables(null, null, TABLE_RDB, null);) {
if (!rs.next()) {
quit("The database at '" + jdbcUrl
+ "' contains no RDB tables.");
}
}
}
}
private void confirmTargetDirectory() {
File vivoHome = new File(vivoHomeDir);
if (!vivoHome.isDirectory()) {
quit("'" + vivoHome + "' is not a directory.");
}
if (!vivoHome.canWrite()) {
quit("Can't write to '" + vivoHome + "'.");
}
targetDir = new File(vivoHome, DIRECTORY_TDB);
}
private boolean doesTdbExist() {
if (!targetDir.exists()) {
return false;
}
if (!targetDir.isDirectory()) {
quit("'" + targetDir + "' is not a directory.");
}
if (!targetDir.canWrite()) {
quit("Can't write to '" + targetDir + "'.");
}
String[] filenames = targetDir.list();
if (filenames == null || filenames.length == 0) {
return false;
}
return true;
}
private void askContinueOverTdb() throws UserDeclinedException, IOException {
ask("A directory of TDB files exists at '" + targetDir + "'.\n"
+ " Migration will replace the existing triples.\n"
+ "Continue? (y/n)");
}
private boolean isAlreadyMigrated() throws SQLException {
try (Connection conn = getSqlConnection()) {
DatabaseMetaData md = conn.getMetaData();
try (ResultSet rs = md.getTables(null, null, TABLE_MIGRATED, null);) {
if (rs.next()) {
alreadyMigrated = true;
announceMigrationDate(conn);
return true;
} else {
return false;
}
}
}
}
private void announceMigrationDate(Connection conn) {
String migrationDate = "UNKNOWN DATE";
String query = String.format("SELECT date FROM %s LIMIT 1",
TABLE_MIGRATED);
try (Statement stmt = conn.createStatement();
java.sql.ResultSet rs = stmt.executeQuery(query)) {
if (rs.next()) {
migrationDate = rs.getString("DATE");
}
} catch (SQLException e) {
// go with default answer.
}
System.out.println("It looks like this RDB data has already been "
+ "migrated to TDB, on " + migrationDate
+ "\n (found a table named '" + TABLE_MIGRATED + "')");
}
private void askMigrateAgain() throws UserDeclinedException, IOException {
ask("Migrate again? (y/n)");
}
private void getListOfRdbModels() throws SQLException {
try (Connection conn = getSqlConnection();
ClosingDBConnection rdb = new ClosingDBConnection(conn)) {
modelsToCopy = rdb.getAllModelNames().toList();
}
}
private boolean isUnexpectedModels() {
List<String> unexpectedModels = new ArrayList<>(modelsToCopy);
unexpectedModels.removeAll(EXPECTED_MODELS);
if (unexpectedModels.isEmpty()) {
return false;
}
System.out
.println("VIVO requires only these models from RDB:\n "
+ StringUtils.join(EXPECTED_MODELS, "\n ")
+ "\nYour RDB triple-store contains these additional models:\n "
+ StringUtils.join(unexpectedModels, "\n "));
return true;
}
private void askMigrateAllModels() throws IOException, UserDeclinedException {
System.out.println("Migrate all models, or only the required models?\n"
+ " Enter 'all' or 'only', or anything else to quit.");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
if (s != null) {
if (s.trim().toLowerCase().equals("all")) {
return;
} else if (s.trim().toLowerCase().equals("only")) {
modelsToCopy = EXPECTED_MODELS;
return;
}
}
throw new UserDeclinedException("Enter 'all' or 'only'.");
}
private void askApprovalForMigrationPlan() throws SQLException,
UserDeclinedException, IOException {
int modelCount = 0;
int tripleCount = 0;
try (Connection conn = getSqlConnection();
ClosingDBConnection rdb = new ClosingDBConnection(conn)) {
for (String modelName : modelsToCopy) {
modelCount++;
try (ClosingGraphRDB graph = new ClosingGraphRDB(rdb, modelName)) {
tripleCount += graph.size();
}
}
}
String warning = alreadyMigrated ? " Existing triples will be over-written.\n"
: "";
String question = String.format("Migrating %d triples in %d models "
+ "to TDB files in '%s'\n%sContinue? (y/n)", tripleCount,
modelCount, targetDir, warning);
ask(question);
}
private void quit(String message) {
throw new IllegalArgumentException(
"--------------------------------------------------------------\n"
+ message
+ "\n--------------------------------------------------------------");
}
private void ask(String string) throws UserDeclinedException, IOException {
System.out.println(string);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
if ((s == null) || (!s.trim().toLowerCase().equals("y"))) {
throw new UserDeclinedException("OK.");
}
}
private static class UserDeclinedException extends Exception {
public UserDeclinedException(String message) {
super(message);
}
}
public void migrate() throws SQLException {
copyData();
writeMigrationRecord();
}
private void copyData() throws SQLException {
try (Connection conn = getSqlConnection();
ClosingDBConnection rdb = new ClosingDBConnection(conn)) {
Dataset tdbDataset = TDBFactory.createDataset(targetDir
.getAbsolutePath());
copyGraphs(rdb, tdbDataset);
}
}
@SuppressWarnings("deprecation")
private void copyGraphs(ClosingDBConnection rdb, Dataset tdbDataset) {
DatasetGraph tdbDsGraph = tdbDataset.asDatasetGraph();
for (String modelName : modelsToCopy) {
try (ClosingGraphRDB graph = new ClosingGraphRDB(rdb, modelName)) {
tdbDsGraph.addGraph(Node.createURI(modelName), graph);
System.out
.println(String.format(" copied %4d triples from %s",
graph.size(), modelName));
}
}
}
private void writeMigrationRecord() throws SQLException {
String createTable = String.format("CREATE TABLE %s (date DATE)",
TABLE_MIGRATED);
String deleteOldDates = String.format("DELETE FROM %s", TABLE_MIGRATED);
String insertDate = String.format("INSERT INTO %s (date) VALUES (?)",
TABLE_MIGRATED);
try (Connection conn = getSqlConnection();
Statement stmt = conn.createStatement();
PreparedStatement pstmt = conn.prepareStatement(insertDate)) {
if (alreadyMigrated) {
stmt.executeUpdate(deleteOldDates);
} else {
stmt.executeUpdate(createTable);
}
pstmt.setDate(1, new Date(System.currentTimeMillis()));
pstmt.executeUpdate();
}
}
private Connection getSqlConnection() throws SQLException {
Properties connectionProps;
connectionProps = new Properties();
connectionProps.put("user", username);
connectionProps.put("password", password);
return DriverManager.getConnection(jdbcUrl, connectionProps);
}
private static class ClosingDBConnection extends DBConnection implements
AutoCloseable {
ClosingDBConnection(Connection sqlConnection) {
super(sqlConnection, "MySQL");
}
}
private static class ClosingGraphRDB extends GraphRDB implements
AutoCloseable {
ClosingGraphRDB(DBConnection rdb, String modelName) {
super(rdb, modelName, null,
GraphRDB.OPTIMIZE_ALL_REIFICATIONS_AND_HIDE_NOTHING, false);
}
}
// ----------------------------------------------------------------------
// Main routine
// ----------------------------------------------------------------------
@SuppressWarnings("unused")
public static void main(String[] args) {
if (args.length != 4) {
System.out.println("Usage: RdbMigrator vivoHomeDir, jdbcUrl, "
+ "username, password");
}
try {
new RdbMigrator(args[0], args[1], args[2], args[3]);
} catch (IllegalArgumentException | UserDeclinedException e) {
System.out.println(e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,19 @@
A tool to convert triples from SDB to TDB.
Unlike the RDF-migration tool, this tool is not tied to the VIVO
properties files. Instead, you must provide:
* The URL for the SDB, including username and password parameters
* The directory path for the TDB.
The directory must exist.
The directory must be empty, unless the "force" optiopn is used.
Examples:
java -jar sdb2tdb.jar \
jdbc:mysql://localhost/vitrodb?user=vivoUser&password=vivoPass \
/usr/local/my/tdb
java -Xms512m -Xmx4096m -jar .work/sdb2tdb.jar \
'jdbc:mysql://localhost/weill17?user=vivoUser&password=vivoPass' \
/Users/jeb228/Testing/instances/weill-develop/vivo_home/contentTdb \
force

View file

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<!-- ======================================================================
A tool to convert SDB data into TDB.
====================================================================== -->
<project name="SDB-to-TDB" default="describe">
<property name="working.dir" location=".work" />
<property name="src.dir" location="src" />
<property name="lib.dir" location="lib" />
<property name="classes.dir" location="${working.dir}/classes" />
<property name="jar.file" location="${working.dir}/sdb2tdb.jar" />
<!-- =================================
target: describe
================================= -->
<target name="describe"
description="--> Describe the targets (this is the default).">
<echo>
all - Compiles the tool ad packs it into a JAR
</echo>
</target>
<!-- =================================
target: all
================================= -->
<target name="all"
depends="clean, jar"
description="Build from scratch and run the migration.">
</target>
<!-- - - - - - - - - - - - - - - - - -
target: clean
- - - - - - - - - - - - - - - - - -->
<target name="clean">
<delete dir="${working.dir}" />
</target>
<!-- - - - - - - - - - - - - - - - - -
target: prepare
- - - - - - - - - - - - - - - - - -->
<target name="prepare">
<mkdir dir="${working.dir}" />
<mkdir dir="${classes.dir}" />
</target>
<!-- - - - - - - - - - - - - - - - - -
target: compile
- - - - - - - - - - - - - - - - - -->
<target name="compile" depends="prepare">
<path id="main.compile.classpath">
<fileset dir="${lib.dir}" includes="*.jar" />
</path>
<javac srcdir="${src.dir}"
destdir="${classes.dir}"
debug="true"
deprecation="true"
encoding="UTF8"
includeantruntime="false"
optimize="true"
source="1.7">
<classpath refid="main.compile.classpath" />
</javac>
</target>
<!-- - - - - - - - - - - - - - - - - -
target: jar
- - - - - - - - - - - - - - - - - -->
<target name="jar" depends="compile">
<jar destfile="${jar.file}">
<manifest>
<attribute name="Main-Class"
value="edu.cornell.mannlib.vitro.utilities.sdb2tdb.Sdb2Tdb" />
</manifest>
<fileset dir="${classes.dir}" />
<archives>
<zips>
<fileset dir="${lib.dir}" includes="**/*.jar" />
</zips>
</archives>
</jar>
</target>
</project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,291 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.sdb2tdb;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.apache.jena.riot.RDFDataMgr;
import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.query.Dataset;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.sdb.SDB;
import com.hp.hpl.jena.sdb.SDBFactory;
import com.hp.hpl.jena.sdb.Store;
import com.hp.hpl.jena.sdb.StoreDesc;
import com.hp.hpl.jena.sdb.store.DatabaseType;
import com.hp.hpl.jena.sdb.store.LayoutType;
import com.hp.hpl.jena.tdb.TDBFactory;
/**
* Copy all of the data from an SDB triple-store to a TDB triple-store. See
* README.txt for more details.
*
* Examples of invoking it:
*
* <pre>
* java -jar sdb2tdb.jar \
* 'jdbc:mysql://localhost/vitrodb?user=vivoUser&password=vivoPass'\
* /usr/local/my/tdb
*
* java -Xms2048m -Xmx2048m -jar .work/sdb2tdb.jar \
* 'jdbc:mysql://localhost/weill17?user=vivoUser&password=vivoPass' \
* /Users/jeb228/Testing/instances/weill-develop/vivo_home/contentTdb \
* force
* </pre>
*
* Each graph is copied separately. Small graphs are simply loaded into memory
* and transferred. Large graphs are read to produce a streaming result set
* which is written to a temporary file. That file is then read into a TDB
* model.
*
* This has been tested with graphs up to 6 million triples without crashing.
*/
public class Sdb2Tdb {
private static final int LARGE_MODEL_THRESHOLD = 500_000;
private final String driverClassName;
private final String jdbcUrl;
private final String destination;
private final boolean force;
private Dataset sdbDataset;
private Dataset tdbDataset;
public Sdb2Tdb(List<String> hardArgs) throws UsageException {
List<String> args = new ArrayList<>(hardArgs);
if (!args.isEmpty() && args.indexOf("force") == (args.size() - 1)) {
this.force = true;
args.remove(args.size() - 1);
} else {
this.force = false;
}
if (args.size() == 3) {
this.driverClassName = args.remove(0);
} else if (args.size() == 2) {
this.driverClassName = "com.mysql.jdbc.Driver";
} else {
throw new UsageException("Wrong number of arguments: "
+ hardArgs.size());
}
this.jdbcUrl = args.get(0);
this.destination = args.get(1);
checkDriverClass();
checkJdbcUrl();
checkDestination();
}
private void checkDriverClass() throws UsageException {
try {
Class.forName(this.driverClassName).newInstance();
} catch (Exception e) {
throw new UsageException("Can't instantiate JDBC driver: "
+ this.driverClassName);
}
}
private void checkJdbcUrl() {
if ((!this.jdbcUrl.matches("\\buser\\b"))
|| (!this.jdbcUrl.matches("\\bpassword\\b"))) {
System.out.println("\nWARNING: The JDBC url probably should "
+ "contain values for user and password.\n");
}
}
private void checkDestination() throws UsageException {
File destDir = new File(this.destination);
if (!destDir.isDirectory()) {
throw new UsageException(
"The destination directory does not exist: '"
+ this.destination + "'");
}
if (!destDir.canWrite()) {
throw new UsageException("Cannot write to '" + this.destination
+ "'");
}
if (!(this.force || getDestinationFilenames().isEmpty())) {
throw new UsageException("The destination directory is not empty. "
+ "Choose another destination, or use the 'force' option");
}
}
private List<String> getDestinationFilenames() {
File destDir = new File(this.destination);
String[] filenames = destDir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return !(name.equals(".") || name.equals(".."));
}
});
return Arrays.asList(filenames);
}
private void translate() throws SQLException, IOException {
try {
sdbDataset = openSdbDataset();
tdbDataset = openTdbDataset();
copyGraphs();
} finally {
if (tdbDataset != null) {
tdbDataset.close();
}
if (sdbDataset != null) {
sdbDataset.close();
}
}
}
private Dataset openSdbDataset() throws SQLException {
Connection conn = DriverManager.getConnection(this.jdbcUrl);
Store store = SDBFactory.connectStore(conn, makeSdbStoreDesc());
SDB.getContext().set(SDB.jdbcStream, Boolean.TRUE);
SDB.getContext().set(SDB.jdbcFetchSize, Integer.MIN_VALUE);
return SDBFactory.connectDataset(store);
}
private StoreDesc makeSdbStoreDesc() {
return new StoreDesc(LayoutType.LayoutTripleNodesHash,
DatabaseType.MySQL);
}
private Dataset openTdbDataset() {
return TDBFactory.createDataset(new File(this.destination)
.getAbsolutePath());
}
private void copyGraphs() throws IOException {
for (Iterator<String> modelNames = sdbDataset.listNames(); modelNames
.hasNext();) {
String modelName = modelNames.next();
Model model = sdbDataset.getNamedModel(modelName);
if (model.size() < LARGE_MODEL_THRESHOLD) {
copySmallModel(modelName, model);
} else {
copyLargeModel(modelName, model);
}
}
}
private void copySmallModel(String modelName, Model model) {
System.out.println(String.format("Copying %6d triples: %s",
model.size(), modelName));
tdbDataset.addNamedModel(modelName, model);
}
private void copyLargeModel(String modelName, Model model)
throws IOException {
File tempFile = File.createTempFile("sdb-", ".n3");
System.out.println(String.format("Copying %6d triples: %s %s",
model.size(), modelName, tempFile.getAbsolutePath()));
model.close();
try (OutputStream os = new FileOutputStream(tempFile);
GraphToTriples trips = new GraphToTriples(this, modelName)) {
RDFDataMgr.writeTriples(os, trips);
}
System.out.println("Wrote it.");
try (InputStream is = new FileInputStream(tempFile)) {
tdbDataset.getNamedModel(modelName).read(is, null, "N-TRIPLE");
}
System.out.println("Read it.");
}
public static void main(String[] args) {
try {
Sdb2Tdb sdb2tdb = new Sdb2Tdb(Arrays.asList(args));
sdb2tdb.translate();
} catch (UsageException e) {
System.out.println();
System.out.println(e.getMessage());
System.out.println(e.getProperUsage());
System.out.println();
} catch (SQLException | IOException e) {
e.printStackTrace();
}
}
private static class UsageException extends Exception {
public UsageException(String message) {
super(message);
}
public String getProperUsage() {
return "Usage is: java -jar sdb2tdb [driver_class] <jdbcUrl> <destination_directory> [force]";
}
}
private static class GraphToTriples implements Iterator<Triple>,
AutoCloseable {
private static final String QUERY_TEMPLATE = "" //
+ "SELECT ?s ?p ?o \n" //
+ "WHERE { \n" //
+ " GRAPH <%s> { \n" //
+ " ?s ?p ?o . \n" //
+ " } \n" //
+ "}";
private final QueryExecution qe;
private final ResultSet results;
GraphToTriples(Sdb2Tdb parent, String graphUri) {
String qStr = String.format(QUERY_TEMPLATE, graphUri);
Query q = QueryFactory.create(qStr);
qe = QueryExecutionFactory.create(q, parent.sdbDataset);
results = qe.execSelect();
}
@Override
public boolean hasNext() {
return results.hasNext();
}
@Override
public Triple next() {
QuerySolution solution = results.nextSolution();
Node s = solution.get("s").asNode();
Node p = solution.get("p").asNode();
Node o = solution.get("o").asNode();
return new Triple(s, p, o);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
qe.close();
}
}
}

View file

@ -0,0 +1,9 @@
log4j.appender.AllAppender=org.apache.log4j.ConsoleAppender
log4j.appender.AllAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.AllAppender.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] %m%n
# log4j.appender.AllAppender.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{1}] %m%n
log4j.rootLogger=INFO, AllAppender
# Make all of the Solr classes quieter...
log4j.logger.org.apache.solr=WARN

View file

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<!-- ======================================================================
Integration tests to be sure that the Solr configuration does what we
want.
====================================================================== -->
<project name="solr-tester" default="describe">
<property name="src.dir" location="src" />
<property name="vitro.libs.dir" location="../../webapp/lib" />
<property name="solr.home.template"
location="../../solr/homeDirectoryTemplate" />
<property name="solr.war.file" location="../../solr/solr-4.7.2.war" />
<property name="working.dir" location=".work" />
<property name="classes.dir" location="${working.dir}/classes" />
<property name="solr.working.dir" location="${working.dir}/solrHome" />
<property name="solr.libs.dir" location="${working.dir}/solrLibs" />
<path id="main.compile.classpath">
<fileset dir="${vitro.libs.dir}" includes="*.jar" />
<fileset dir="${solr.libs.dir}" includes="*.jar" />
</path>
<path id="test.run.classpath">
<pathelement location="${classes.dir}" />
<path refid="main.compile.classpath" />
</path>
<!-- =================================
target: describe
================================= -->
<target name="describe"
description="--> Describe the targets (this is the default).">
<echo>
all - Runs "clean", then "test".
clean - Delete all artifacts so the next build will be from scratch.
test - Set up the Solr configuration, compile, and run the JUnit tests.
</echo>
</target>
<!-- =================================
target: all
================================= -->
<target name="all"
depends="clean,test"
description="Build from scratch and run the tests.">
</target>
<!-- =================================
target: clean
================================= -->
<target name="clean"
description="Delete the Solr configuration and the compiled clases.">
<delete dir="${working.dir}" />
</target>
<!-- =================================
target: test
================================= -->
<target name="test" depends="compile" description="Run the tests.">
<java classname="org.junit.runner.JUnitCore"
fork="yes"
failonerror="true">
<arg value="edu.cornell.mannlib.vitro.utilities.solrtest.SolrConfigTester" />
<classpath refid="test.run.classpath" />
<sysproperty key="test.solr.home" value="${solr.working.dir}" />
</java>
</target>
<!-- - - - - - - - - - - - - - - - - -
target: setup
- - - - - - - - - - - - - - - - - -->
<target name="setup">
<mkdir dir="${working.dir}" />
<mkdir dir="${solr.working.dir}" />
<sync todir="${solr.working.dir}" includeemptydirs="true">
<fileset dir="${solr.home.template}" />
</sync>
<mkdir dir="${solr.libs.dir}" />
<unwar src="${solr.war.file}" dest="${solr.libs.dir}">
<patternset includes="WEB-INF/lib/*" />
<mapper type="flatten" />
</unwar>
<mkdir dir="${classes.dir}" />
<copy file="log4j.properties" todir="${classes.dir}" />
</target>
<!-- - - - - - - - - - - - - - - - - -
target: compile
- - - - - - - - - - - - - - - - - -->
<target name="compile" depends="setup">
<javac srcdir="${src.dir}"
destdir="${classes.dir}"
debug="true"
deprecation="true"
encoding="UTF8"
includeantruntime="false"
optimize="true"
source="1.7">
<classpath refid="main.compile.classpath" />
</javac>
</target>
</project>

View file

@ -0,0 +1,250 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.solrtest;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrCore;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* TODO
*/
@RunWith(JUnit4.class)
public class SolrConfigTester {
private static CoreContainer container;
private static EmbeddedSolrServer server;
@Before
public void setup() throws Exception {
String solrHomeString = System.getProperty("test.solr.home");
container = new CoreContainer(solrHomeString);
try (LogLeveler l = new LogLeveler(SolrCore.class, Level.ERROR)) {
container.load();
}
server = new EmbeddedSolrServer(container, "collection1");
server.deleteByQuery("*:*");
server.commit();
}
@After
public void tearDown() throws Exception {
server.shutdown();
}
// ----------------------------------------------------------------------
// The tests
// ----------------------------------------------------------------------
@Test
public void serverResponds() throws Exception {
server.ping();
}
@Test(expected = SolrException.class)
public void docIdIsRequred() throws Exception {
try (LogLeveler l = new LogLeveler(SolrCore.class, Level.OFF)) {
addDocuments(inputDoc(null, field("nameRaw", "No ID")));
}
}
@Test
public void simpleName() throws Exception {
addDocuments(inputDoc("idSimple", field("nameRaw", "SimpleName")));
assertQueryResults("simple name", "SimpleName", "idSimple");
}
@Test
public void upperLowerName() throws Exception {
addDocuments(inputDoc("idUpperLower", field("nameRaw", "Lower, Upper")));
assertQueryResults("upper lower name", "Lower", "idUpperLower");
assertQueryResults("upper lower name", "lower", "idUpperLower");
assertQueryResults("upper lower name", "UPPER", "idUpperLower");
}
/**
* Why does it behave as if name is stemmed? Is that the behavior that we
* want?
*/
@Ignore
@Test
public void nameIsNotStemmed() throws Exception {
addDocuments(inputDoc("nameStemming",
field("nameRaw", "Swimming, Bills"),
field("nameLowercaseSingleValued", "Lower, Upper")));
assertQueryResults("name not stemmed", "Swimming", "nameStemming");
assertQueryResults("name not stemmed", "Bills", "nameStemming");
assertQueryResults("name not stemmed", "Swim");
assertQueryResults("name not stemmed", "Bill");
}
/**
* A name with an umlaut over an o is found exactly, or with the umlaut
* missing, upper case or lower case.
*/
@Test
public void nameWithUmlaut() throws Exception {
addDocuments(inputDoc("idUmlaut",
field("nameRaw", "P\u00F6ysen B\u00F6ysen")));
assertQueryResults("name with umlaut", "Boysen", "idUmlaut");
assertQueryResults("name with umlaut", "B\u00F6ysen", "idUmlaut");
assertQueryResults("name with umlaut", "BOYSEN", "idUmlaut");
assertQueryResults("name with umlaut", "B\u00D6YSEN", "idUmlaut");
}
/**
* ALLTEXT is searched, but to make the 3rd case work, we need
* ALLTEXTUNSTEMMED also. Why is that?
*/
@Test
public void allTextIsSearched() throws Exception {
addDocuments(inputDoc("allTextSearch", field("ALLTEXT", "Wonderful"),
field("ALLTEXTUNSTEMMED", "Wonderful")));
assertQueryResults("all text", "Wonderful", "allTextSearch");
assertQueryResults("all text", "wonderful", "allTextSearch");
assertQueryResults("all text", "WoNdErFuL", "allTextSearch");
}
@Test
public void allTextIsStemmed() throws Exception {
addDocuments(inputDoc("allTextStem", field("ALLTEXT", "Swimming"),
field("ALLTEXTUNSTEMMED", "Swimming")));
assertQueryResults("all text stem", "Swim", "allTextStem");
assertQueryResults("all text stem", "swim", "allTextStem");
}
// ----------------------------------------------------------------------
// Helper methods
// ----------------------------------------------------------------------
public InputField field(String name, String... values) {
return new InputField(name, null, values);
}
public InputField field(String name, Float boost, String... values) {
return new InputField(name, boost, values);
}
public SolrInputDocument inputDoc(String docId, InputField... fields) {
SolrInputDocument doc = new SolrInputDocument();
if (docId != null) {
doc.addField("DocId", docId);
}
for (InputField f : fields) {
for (String value : f.values) {
if (f.boost == null) {
doc.addField(f.name, value);
} else {
doc.addField(f.name, value, f.boost);
}
}
}
return doc;
}
public void addDocuments(SolrInputDocument... documents)
throws SolrServerException, IOException {
for (SolrInputDocument doc : documents) {
server.add(doc);
}
server.commit();
}
private void assertQueryResults(String message, String query,
String... expectedDocIds) throws SolrServerException {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set("q", query);
QueryResponse qResp = server.query(params);
SolrDocumentList docList = qResp.getResults();
List<String> expected = Arrays.asList(expectedDocIds);
List<String> actual = new ArrayList<>();
for (int i = 0; i < docList.getNumFound(); i++) {
actual.add(String.valueOf(docList.get(i).getFirstValue("DocId")));
}
assertEquals(message + " : '" + query + "'", expected, actual);
}
// ----------------------------------------------------------------------
// Helper classes
// ----------------------------------------------------------------------
public static class InputField {
final String name;
final Float boost;
final String[] values;
public InputField(String name, Float boost, String[] values) {
this.name = name;
this.boost = boost;
this.values = values;
}
}
public static class LogLeveler implements AutoCloseable {
private final Logger logger;
private final Level initialLevel;
public LogLeveler(Class<?> clazz, Level desiredLevel) {
this.logger = Logger.getLogger(clazz);
this.initialLevel = this.logger.getLevel();
this.logger.setLevel(desiredLevel);
}
@Override
public void close() {
this.logger.setLevel(this.initialLevel);
}
}
}
/**
* TODO
*
* <pre>
* // ** Let's index a document into our embedded server
*
* SolrInputDocument newDoc = new SolrInputDocument();
* newDoc.addField(&quot;title&quot;, &quot;Test Document 1&quot;);
* newDoc.addField(&quot;id&quot;, &quot;doc-1&quot;);
* newDoc.addField(&quot;text&quot;, &quot;Hello world!&quot;);
* server.add(newDoc);
* server.commit();
*
* // ** And now let's query for it
*
* params.set(&quot;q&quot;, &quot;title:test&quot;);
* QueryResponse qResp = server.query(params);
*
* SolrDocumentList docList = qResp.getResults();
* System.out.println(&quot;Num docs: &quot; + docList.getNumFound());
* SolrDocument doc = docList.get(0);
* System.out.println(&quot;Title: &quot; + doc.getFirstValue(&quot;title&quot;).toString());
* </pre>
*/

View file

@ -0,0 +1,26 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner;
/**
* Indicates a problem with the attempt to run a command in a sub-process.
*/
public class CommandRunnerException extends Exception {
public CommandRunnerException() {
super();
}
public CommandRunnerException(String message) {
super(message);
}
public CommandRunnerException(Throwable cause) {
super(cause);
}
public CommandRunnerException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,26 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner;
/**
* Indicates a problem so severe that we might as well stop now.
*/
public class FatalException extends RuntimeException {
public FatalException() {
super();
}
public FatalException(String message) {
super(message);
}
public FatalException(Throwable cause) {
super(cause);
}
public FatalException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,434 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import edu.cornell.mannlib.vitro.utilities.testrunner.listener.Listener;
import edu.cornell.mannlib.vitro.utilities.testrunner.listener.LoggingListener;
import edu.cornell.mannlib.vitro.utilities.testrunner.listener.MulticastListener;
/**
* Holds the runtime parameters that are read from the properties file, perhaps
* with modifications from the GUI if we are running interactively.
*/
public class SeleniumRunnerParameters {
public static final String PROP_OUTPUT_DIRECTORY = "output_directory";
public static final String PROP_UPLOAD_DIRECTORY = "upload_directory";
public static final String PROP_SUITE_DIRECTORIES = "suite_parent_directories";
public static final String PROP_WEBSITE_URL = "website_url";
public static final String PROP_USER_EXTENSIONS_PATH = "user_extensions_path";
public static final String PROP_FIREFOX_PROFILE_PATH = "firefox_profile_template_path";
public static final String PROP_SUITE_TIMEOUT_LIMIT = "suite_timeout_limit";
public static final String PROP_SELENIUM_JAR_PATH = "selenium_jar_path";
public static final String PROP_IGNORED_TESTS = "ignored_tests_file";
public static final String PROP_SUMMARY_CSS = "summary_css_file";
public static final String LOGFILE_NAME = "log_file.txt";
private final String websiteUrl;
private final File userExtensionsFile;
private final File firefoxProfileDir;
private final int suiteTimeoutLimit;
private final File seleniumJarPath;
private final File uploadDirectory;
private final File outputDirectory;
private final File logFile;
private final Collection<File> suiteParentDirectories;
private final ModelCleanerProperties modelCleanerProperties;
private final IgnoredTests ignoredTests;
private boolean cleanModel = true;
private boolean cleanUploads = true;
// If we fail during the parameter parsing, we'll still write the log
// somewhere.
private Listener listener = new LoggingListener(System.out);
/**
* Read the required properties from the property file, and do some checks
* on them.
*/
public SeleniumRunnerParameters(String propertiesFilepath)
throws IOException {
Properties props = loadPropertiesFile(propertiesFilepath);
this.websiteUrl = getRequiredProperty(props, PROP_WEBSITE_URL);
this.userExtensionsFile = checkReadableFile(props,
PROP_USER_EXTENSIONS_PATH);
this.firefoxProfileDir = checkOptionalReadableDirectory(props,
PROP_FIREFOX_PROFILE_PATH);
this.suiteTimeoutLimit = getRequiredIntegerProperty(props,
PROP_SUITE_TIMEOUT_LIMIT);
this.seleniumJarPath = checkReadableFile(props, PROP_SELENIUM_JAR_PATH);
this.uploadDirectory = checkReadWriteDirectory(props,
PROP_UPLOAD_DIRECTORY);
this.outputDirectory = checkOutputDirectory(props);
this.logFile = new File(this.outputDirectory, LOGFILE_NAME);
this.listener = new MulticastListener();
addListener(new LoggingListener(this.logFile));
this.suiteParentDirectories = checkSuiteParentDirectories(props);
this.modelCleanerProperties = new ModelCleanerProperties(props);
// Get the list of ignored tests.
String ignoredFilesPath = getRequiredProperty(props, PROP_IGNORED_TESTS);
File ignoredFilesFile = new File(ignoredFilesPath);
FileHelper.checkReadableFile(ignoredFilesFile, "File '"
+ ignoredFilesPath + "'");
this.ignoredTests = new IgnoredTests(ignoredFilesFile);
}
/**
* Load the properties from the properties file.
*/
private Properties loadPropertiesFile(String propertiesFilepath)
throws FileNotFoundException, IOException {
File propsFile = new File(propertiesFilepath);
if (!propsFile.exists()) {
throw new FileNotFoundException("Property file does not exist: '"
+ propsFile + "'");
}
Reader propsReader = null;
try {
propsReader = new FileReader(propsFile);
Properties props = new Properties();
props.load(propsReader);
return props;
} finally {
if (propsReader != null) {
try {
propsReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* If there is a parameter for this key, it should point to a readable
* directory.
*/
private File checkOptionalReadableDirectory(Properties props, String key) {
String value = props.getProperty(key);
if (value == null) {
return null;
}
value = value.trim();
if (value.trim().length() == 0) {
return null;
}
File dir = new File(value);
if (!dir.exists()) {
throw new IllegalArgumentException("Directory " + key + " '"
+ value + "' does not exist.");
}
if (!dir.isDirectory()) {
throw new IllegalArgumentException("Directory " + key + " '"
+ value + "' is not a directory.");
}
if (!dir.canRead()) {
throw new IllegalArgumentException("Directory " + key + " '"
+ value + "' is not readable.");
}
return dir;
}
/**
* Check that there is a property for the required directory path, and that
* it points to a valid directory.
*/
private File checkReadWriteDirectory(Properties props, String key) {
String value = getRequiredProperty(props, key);
File dir = new File(value);
if (!dir.exists()) {
throw new IllegalArgumentException("Directory " + key + " '"
+ value + "' does not exist. (" + dir.getAbsolutePath()
+ ")");
}
if (!dir.isDirectory()) {
throw new IllegalArgumentException("Directory " + key + " '"
+ value + "' is not a directory. (" + dir.getAbsolutePath()
+ ")");
}
if (!dir.canRead()) {
throw new IllegalArgumentException("Directory " + key + " '"
+ value + "' is not readable. (" + dir.getAbsolutePath()
+ ")");
}
if (!dir.canWrite()) {
throw new IllegalArgumentException("Directory " + key + " '"
+ value + "' is not writeable. (" + dir.getAbsolutePath()
+ ")");
}
return dir;
}
private File checkReadableFile(Properties props, String key) {
String value = getRequiredProperty(props, key);
File file = new File(value);
if (!file.exists()) {
throw new IllegalArgumentException("File " + key + ": '" + value
+ "' does not exist. (" + file.getAbsolutePath() + ")");
}
if (!file.isFile()) {
throw new IllegalArgumentException("File " + key + ": '" + value
+ "' is not a file. (" + file.getAbsolutePath() + ")");
}
if (!file.canRead()) {
throw new IllegalArgumentException("File " + key + ": '" + value
+ "' is not readable. (" + file.getAbsolutePath() + ")");
}
return file;
}
/**
* Get the property for the output directory. If it does not exist, create
* it (the parent must exist). Ensure that it is writeable.
*/
private File checkOutputDirectory(Properties props) throws IOException {
String value = getRequiredProperty(props, PROP_OUTPUT_DIRECTORY);
File outputDirectory = new File(value);
File outputParent = outputDirectory.getParentFile();
if (!outputDirectory.exists()) {
if (!outputParent.exists()) {
throw new IllegalArgumentException(
"Output directory does not exist, nor does its parent. '"
+ outputDirectory + "' ("
+ outputDirectory.getAbsolutePath() + ")");
}
outputDirectory.mkdir();
if (!outputDirectory.exists()) {
throw new IOException("Failed to create output directory: '"
+ outputDirectory + "' ("
+ outputDirectory.getAbsolutePath() + ")");
}
}
if (!outputDirectory.isDirectory()) {
throw new IllegalArgumentException("Suite directory '"
+ outputDirectory.getPath() + "' is not a directory. ("
+ outputDirectory.getAbsolutePath() + ")");
}
if (!outputDirectory.canRead()) {
throw new IllegalArgumentException("Suite directory '"
+ outputDirectory.getPath() + "' is not readable. ("
+ outputDirectory.getAbsolutePath() + ")");
}
if (!outputDirectory.canWrite()) {
throw new IllegalArgumentException("Suite directory '"
+ outputDirectory.getPath() + "' is not writeable. ("
+ outputDirectory.getAbsolutePath() + ")");
}
return outputDirectory;
}
/**
* Get the property for the suite directories and ensure that each one is
* indeed a readable directory.
*/
private Collection<File> checkSuiteParentDirectories(Properties props) {
String value = getRequiredProperty(props, PROP_SUITE_DIRECTORIES);
List<File> dirs = new ArrayList<File>();
String[] paths = value.split("[:;]");
for (String path : paths) {
File dir = new File(path.trim());
if (!dir.exists()) {
throw new IllegalArgumentException("Suite directory '"
+ dir.getPath() + "' does not exist. ("
+ dir.getAbsolutePath() + ")");
}
if (!dir.isDirectory()) {
throw new IllegalArgumentException("Suite directory '"
+ dir.getPath() + "' is not a directory. ("
+ dir.getAbsolutePath() + ")");
}
if (!dir.canRead()) {
throw new IllegalArgumentException("Suite directory '"
+ dir.getPath() + "' is not readable. ("
+ dir.getAbsolutePath() + ")");
}
dirs.add(dir);
}
return dirs;
}
/**
* Get the value for this property. If there isn't one, or if it's empty,
* complain.
*/
private String getRequiredProperty(Properties props, String key) {
String value = props.getProperty(key);
if ((value == null) || (value.trim().length() == 0)) {
throw new IllegalArgumentException(
"Property file must provide a value for '" + key + "'");
}
return value;
}
/**
* This required property must be a valid integer.
*/
private int getRequiredIntegerProperty(Properties props, String key) {
String value = getRequiredProperty(props, key);
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Property value for '" + key
+ "' is not a valid integer: " + value);
}
}
public String getWebsiteUrl() {
return websiteUrl;
}
public File getUserExtensionsFile() {
return userExtensionsFile;
}
public boolean hasFirefoxProfileDir() {
return firefoxProfileDir != null;
}
public File getFirefoxProfileDir() {
return firefoxProfileDir;
}
public int getSuiteTimeoutLimit() {
return suiteTimeoutLimit;
}
public File getSeleniumJarPath() {
return seleniumJarPath;
}
public void addListener(Listener l) {
if (listener instanceof MulticastListener) {
((MulticastListener) listener).addListener(l);
} else {
throw new IllegalStateException("Listener is not a multi-cast -- "
+ "can't add new listeners.");
}
}
public Listener getListener() {
return listener;
}
public void setListener(Listener logger) {
this.listener = logger;
}
public File getUploadDirectory() {
return uploadDirectory;
}
public File getOutputDirectory() {
return outputDirectory;
}
public File getLogFile() {
return logFile;
}
public Collection<File> getSuiteParentDirectories() {
return suiteParentDirectories;
}
public ModelCleanerProperties getModelCleanerProperties() {
return modelCleanerProperties;
}
public IgnoredTests getIgnoredTests() {
return ignoredTests;
}
public boolean isCleanModel() {
return cleanModel;
}
public void setCleanModel(boolean cleanModel) {
this.cleanModel = cleanModel;
}
public boolean isCleanUploads() {
return cleanUploads;
}
public void setCleanUploads(boolean cleanUploads) {
this.cleanUploads = cleanUploads;
}
public String toString() {
return "Parameters:" + "\n websiteUrl: " + websiteUrl
+ "\n userExtensionsFile: " + userExtensionsFile
+ "\n firefoxProfileDir: " + firefoxProfileDir
+ "\n suiteTimeoutLimit: " + suiteTimeoutLimit
+ "\n seleniumJarPath: " + seleniumJarPath
+ "\n uploadDirectory: " + uploadDirectory
+ "\n outputDirectory: " + outputDirectory
+ "\n suiteParentDirectories: " + suiteParentDirectories
+ "\n modelCleanerProperties: " + modelCleanerProperties
+ "\n" + ignoredTests + "\n cleanModel: " + cleanModel
+ "\n cleanUploads: " + cleanUploads;
}
/**
* Look inside this parent directory and find any suite directories. You can
* recognize a suite directory because it contains a file named Suite.html.
*/
public Collection<File> findSuiteDirs(File parentDir) {
return Arrays.asList(parentDir.listFiles(new FileFilter() {
public boolean accept(File pathname) {
if (!pathname.isDirectory()) {
return false;
}
if (pathname.getName().charAt(0) == '.') {
return false;
}
File suiteFile = new File(pathname, "Suite.html");
if (suiteFile.exists()) {
return true;
} else {
listener.subProcessErrout("Warning: suite file '"
+ suiteFile.getPath() + "' does not exist.\n");
return false;
}
}
}));
}
}

View file

@ -0,0 +1,294 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner.datamodel;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import edu.cornell.mannlib.vitro.utilities.testrunner.IgnoredTests;
import edu.cornell.mannlib.vitro.utilities.testrunner.IgnoredTests.IgnoredTestInfo;
import edu.cornell.mannlib.vitro.utilities.testrunner.LogStats;
import edu.cornell.mannlib.vitro.utilities.testrunner.Status;
import edu.cornell.mannlib.vitro.utilities.testrunner.datamodel.SuiteData.TestData;
import edu.cornell.mannlib.vitro.utilities.testrunner.output.OutputDataListener;
import edu.cornell.mannlib.vitro.utilities.testrunner.output.OutputDataListener.ProcessOutput;
import edu.cornell.mannlib.vitro.utilities.testrunner.output.SuiteResults;
/**
* Collect all that we know about suites, tests, and their current status.
*/
public class DataModel {
/* base data */
private Collection<SuiteContents> suiteContents = Collections.emptyList();
private Collection<File> selectedSuites = Collections.emptyList();
private Collection<SuiteResults> suiteResults = Collections.emptyList();
private OutputDataListener.Info dataListenerInfo = OutputDataListener.Info.EMPTY_INFO;
private IgnoredTests ignoredTestList = IgnoredTests.EMPTY_LIST;
private LogStats logStats = LogStats.EMPTY_LOG_STATS; // TODO
/* derived data */
private Status runStatus = Status.PENDING;
private final SortedMap<String, SuiteData> suiteDataMap = new TreeMap<String, SuiteData>();
private final EnumMap<Status, List<SuiteData>> suiteMapByStatus = new EnumMap<Status, List<SuiteData>>(
Status.class);
private final List<TestData> allTests = new ArrayList<TestData>();
private final EnumMap<Status, List<TestData>> testMapByStatus = new EnumMap<Status, List<TestData>>(
Status.class);
// ----------------------------------------------------------------------
// Constructor
// ----------------------------------------------------------------------
public DataModel() {
calculate();
}
// ----------------------------------------------------------------------
// Update the base data.
// ----------------------------------------------------------------------
public void setSuiteContents(Collection<SuiteContents> suiteContents) {
this.suiteContents = new ArrayList<SuiteContents>(suiteContents);
calculate();
}
public void setSelectedSuites(Collection<File> selectedSuites) {
this.selectedSuites = new ArrayList<File>(selectedSuites);
calculate();
}
public Collection<File> getSelectedSuites() {
return new ArrayList<File>(selectedSuites);
}
public void setSuiteResults(Collection<SuiteResults> suiteResults) {
this.suiteResults = new ArrayList<SuiteResults>(suiteResults);
calculate();
}
public void captureDataListener(OutputDataListener dataListener) {
this.dataListenerInfo = dataListener.getInfo();
calculate();
}
public void setIgnoredTestList(IgnoredTests ignoredTestList) {
this.ignoredTestList = ignoredTestList;
calculate();
}
public void setLogStats(LogStats logStats) { // TODO
this.logStats = logStats;
calculate();
}
// ----------------------------------------------------------------------
// Keep the derived data current.
// ----------------------------------------------------------------------
/**
* Data in the model has been updated. Refresh all derived data.
*/
private void calculate() {
// Clear all derived data.
runStatus = Status.OK;
suiteDataMap.clear();
suiteMapByStatus.clear();
for (Status s : Status.values()) {
suiteMapByStatus.put(s, new ArrayList<SuiteData>());
}
allTests.clear();
testMapByStatus.clear();
for (Status s : Status.values()) {
testMapByStatus.put(s, new ArrayList<TestData>());
}
/*
* Populate the Suite map with all Suites.
*/
Map<String, SuiteResults> resultsMap = new HashMap<String, SuiteResults>();
for (SuiteResults result : suiteResults) {
resultsMap.put(result.getName(), result);
}
Map<String, SuiteContents> contentsMap = new HashMap<String, SuiteContents>();
for (SuiteContents contents : suiteContents) {
contentsMap.put(contents.getName(), contents);
}
for (SuiteContents contents : suiteContents) {
String name = contents.getName();
SuiteResults result = resultsMap.get(name);
boolean ignored = ignoredTestList.isIgnored(name);
ProcessOutput failureMessages = dataListenerInfo
.getFailureMessages().get(name);
suiteDataMap.put(name, new SuiteData(name, ignored, contents,
result, failureMessages));
}
/*
* Map the Suites by status.
*/
for (SuiteData s : suiteDataMap.values()) {
getSuites(s.getStatus()).add(s);
}
/**
* Populate the Test map with all Tests, and map by status.
*/
for (SuiteData s : suiteDataMap.values()) {
for (TestData t : s.getTestMap().values()) {
allTests.add(t);
getTests(t.getStatus()).add(t);
}
}
if (logStats.hasErrors() || !getSuites(Status.ERROR).isEmpty()) {
runStatus = Status.ERROR;
} else if (!getSuites(Status.PENDING).isEmpty()) {
runStatus = Status.PENDING;
} else {
runStatus = Status.OK;
}
}
// ----------------------------------------------------------------------
// Access the derived data.
// ----------------------------------------------------------------------
public Status getRunStatus() {
return runStatus;
}
public long getStartTime() {
return dataListenerInfo.getStartTime();
}
public long getEndTime() {
return dataListenerInfo.getEndTime();
}
public long getElapsedTime() {
return dataListenerInfo.getElapsedTime();
}
public boolean isAnyPasses() {
return !getTests(Status.OK).isEmpty();
}
public boolean isAnyFailures() {
return !getTests(Status.ERROR).isEmpty();
}
public boolean isAnyIgnores() {
return !getTests(Status.IGNORED).isEmpty();
}
public boolean isAnyPending() {
return !getTests(Status.PENDING).isEmpty();
}
public int getTotalSuiteCount() {
return suiteDataMap.size();
}
public int getPassingSuiteCount() {
return getSuites(Status.OK).size();
}
public int getFailingSuiteCount() {
return getSuites(Status.ERROR).size();
}
public int getIgnoredSuiteCount() {
return getSuites(Status.IGNORED).size();
}
public int getPendingSuiteCount() {
return getSuites(Status.PENDING).size();
}
public Collection<SuiteData> getAllSuites() {
return suiteDataMap.values();
}
public Map<String, SuiteData> getSuitesWithFailureMessages() {
Map<String, SuiteData> map = new TreeMap<String, SuiteData>();
for (SuiteData s : suiteDataMap.values()) {
if (s.getFailureMessages() != null) {
map.put(s.getName(), s);
}
}
return map;
}
public int getTotalTestCount() {
return allTests.size();
}
public int getPassingTestCount() {
return getTests(Status.OK).size();
}
public int getFailingTestCount() {
return getTests(Status.ERROR).size();
}
public int getIgnoredTestCount() {
return getTests(Status.IGNORED).size();
}
public int getPendingTestCount() {
return getTests(Status.PENDING).size();
}
public Collection<TestData> getAllTests() {
return Collections.unmodifiableCollection(allTests);
}
public Collection<TestData> getFailingTests() {
return Collections.unmodifiableCollection(getTests(Status.ERROR));
}
public Collection<TestData> getIgnoredTests() {
return Collections.unmodifiableCollection(getTests(Status.IGNORED));
}
public Collection<IgnoredTestInfo> getIgnoredTestInfo() {
return ignoredTestList.getList();
}
public String getReasonForIgnoring(String suiteName, String testName) {
return ignoredTestList.getReasonForIgnoring(suiteName, testName);
}
// ----------------------------------------------------------------------
// Helper methods
// ----------------------------------------------------------------------
/**
* Get the list of suites that have this status.
*/
private List<SuiteData> getSuites(Status st) {
return suiteMapByStatus.get(st);
}
/**
* Get the list of tests that have this status.
*/
private List<TestData> getTests(Status st) {
return testMapByStatus.get(st);
}
}

View file

@ -0,0 +1,375 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner.output;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import edu.cornell.mannlib.vitro.utilities.testrunner.FileHelper;
import edu.cornell.mannlib.vitro.utilities.testrunner.IgnoredTests.IgnoredTestInfo;
import edu.cornell.mannlib.vitro.utilities.testrunner.LogStats;
import edu.cornell.mannlib.vitro.utilities.testrunner.SeleniumRunnerParameters;
import edu.cornell.mannlib.vitro.utilities.testrunner.Status;
import edu.cornell.mannlib.vitro.utilities.testrunner.datamodel.DataModel;
import edu.cornell.mannlib.vitro.utilities.testrunner.datamodel.SuiteData;
import edu.cornell.mannlib.vitro.utilities.testrunner.datamodel.SuiteData.TestData;
import edu.cornell.mannlib.vitro.utilities.testrunner.output.OutputDataListener.ProcessOutput;
/**
* Creates the summary HTML file.
*/
public class OutputSummaryFormatter {
public static final String SUMMARY_HTML_FILENAME = "summary.html";
public static final String SUMMARY_CSS_FILENAME = "summary.css";
private final SimpleDateFormat dateFormat = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
private final SeleniumRunnerParameters parms;
private LogStats logStats;
private DataModel dataModel;
public OutputSummaryFormatter(SeleniumRunnerParameters parms) {
this.parms = parms;
}
/**
* Create a summary HTML file from the info contained in this log file and
* these suite outputs.
*/
public void format(LogStats logStats, DataModel dataModel) {
this.logStats = logStats;
this.dataModel = dataModel;
PrintWriter writer = null;
try {
copyCssFile();
File outputFile = new File(parms.getOutputDirectory(),
SUMMARY_HTML_FILENAME);
writer = new PrintWriter(outputFile);
writeHeader(writer);
writeStatsSection(writer);
writeErrorMessagesSection(writer);
writeCondensedTable(writer);
writeIgnoreSection(writer);
writeSuiteErrorMessagesSection(writer);
writeFooter(writer);
} catch (IOException e) {
// There is no appeal for any problems here. Just report them.
e.printStackTrace();
} finally {
if (writer != null) {
writer.close();
}
}
}
/**
* Copy the CSS file into the output directory.
*/
private void copyCssFile() {
InputStream cssStream = this.getClass().getResourceAsStream(
SUMMARY_CSS_FILENAME);
if (cssStream == null) {
System.out.println("Couldn't find the CSS file: '"
+ SUMMARY_CSS_FILENAME + "'");
} else {
File cssTarget = new File(parms.getOutputDirectory(),
SUMMARY_CSS_FILENAME);
try {
FileHelper.copy(cssStream, cssTarget);
cssStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void writeHeader(PrintWriter w) {
Status runStatus = dataModel.getRunStatus();
String statusString = (runStatus == Status.PENDING) ? "IN PROGRESS"
: runStatus.toString();
String startString = formatDateTime(dataModel.getStartTime());
w.println("<html>");
w.println("<head>");
w.println(" <title>Summary of Acceptance Tests " + startString
+ "</title>");
w.println(" <link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"summary.css\">");
w.println("</head>");
w.println("<body>");
w.println();
w.println(" <div class=\"heading\">");
w.println(" Acceptance test results: " + startString);
w.println(" <div class=\"" + runStatus.getHtmlClass()
+ " one-word\">" + statusString + "</div>");
w.println(" </div>");
}
private void writeStatsSection(PrintWriter w) {
String passClass = dataModel.isAnyPasses() ? Status.OK.getHtmlClass()
: "";
String failClass = dataModel.isAnyFailures() ? Status.ERROR
.getHtmlClass() : "";
String ignoreClass = dataModel.isAnyIgnores() ? Status.IGNORED
.getHtmlClass() : "";
String start = formatDateTime(dataModel.getStartTime());
String end = formatDateTime(dataModel.getEndTime());
String elapsed = formatElapsedTime(dataModel.getElapsedTime());
w.println(" <div class=\"section\">Summary</div>");
w.println();
w.println(" <table class=\"summary\" cellspacing=\"0\">");
w.println(" <tr>");
w.println(" <td>");
w.println(" <table cellspacing=\"0\">");
w.println(" <tr><td>Start time:</td><td>" + start
+ "</td></tr>");
w.println(" <tr><td>End time:</td><td>" + end + "</td></tr>");
w.println(" <tr><td>Elapsed time</td><td>" + elapsed
+ "</td></tr>");
w.println(" </table>");
w.println(" </td>");
w.println(" <td>");
w.println(" <table class=\"tallys\" cellspacing=\"0\">");
w.println(" <tr><th>&nbsp;</th><th>Suites</th><th>Tests</th>");
w.println(" <tr class=\"" + passClass
+ "\"><td>Passed</td><td>" + dataModel.getPassingSuiteCount()
+ "</td><td>" + dataModel.getPassingTestCount() + "</td>");
w.println(" <tr class=\"" + failClass
+ "\"><td>Failed</td><td>" + dataModel.getFailingSuiteCount()
+ "</td><td>" + dataModel.getFailingTestCount() + "</td>");
w.println(" <tr class=\"" + ignoreClass
+ "\"><td>Ignored</td><td>" + dataModel.getIgnoredSuiteCount()
+ "</td><td>" + dataModel.getIgnoredTestCount() + "</td>");
if (dataModel.isAnyPending()) {
w.println(" <tr class=\"" + Status.PENDING.getHtmlClass()
+ "\"><td>Pending</td><td>"
+ dataModel.getPendingSuiteCount() + "</td><td>"
+ dataModel.getPendingTestCount() + "</td>");
}
w.println(" <tr><td class=\"total\">Total</td><td>"
+ dataModel.getTotalSuiteCount() + "</td><td>"
+ dataModel.getTotalTestCount() + "</td>");
w.println(" </table>");
w.println(" </td>");
w.println(" </tr>");
w.println(" </table>");
w.println();
}
private void writeErrorMessagesSection(PrintWriter w) {
String errorClass = Status.ERROR.getHtmlClass();
w.println(" <div class=section>Errors and warnings</div>");
w.println();
w.println(" <table cellspacing=\"0\">");
if ((!logStats.hasErrors()) && (!logStats.hasWarnings())) {
w.println(" <tr><td colspan=\"2\">No errors or warnings</td></tr>");
} else {
for (String e : logStats.getErrors()) {
w.println(" <tr class=\"" + errorClass
+ "\"><td>ERROR</td><td>" + e + "</td></tr>");
}
}
w.println(" </table>");
w.println();
}
private void writeCondensedTable(PrintWriter w) {
w.println(" <div class=section>Condensed List</div>");
w.println();
w.println(" <table class=\"condensed\" cellspacing=\"0\">");
for (SuiteData s : dataModel.getAllSuites()) {
String sReason = "";
if (s.getStatus() == Status.IGNORED) {
sReason = dataModel.getReasonForIgnoring(s.getName(), "*");
} else if (s.getFailureMessages() != null) {
sReason = s.getFailureMessages().getErrout();
} else if (s.getStatus() == Status.PENDING) {
sReason = Status.PENDING.toString();
}
w.println(" <tr>");
w.println(" <td class=\"" + s.getStatus().getHtmlClass()
+ "\">");
w.println(" <div class=\"suite\">" + outputLink(s)
+ "</div>");
if (!sReason.isEmpty()) {
// The entire class is either failed or pending or ignored.
w.println(" <div class=\"reason\">" + sReason + "</div>");
} else {
// Show the individual tests.
for (TestData t : s.getTestMap().values()) {
String tClass = t.getStatus().getHtmlClass();
String tReason = dataModel.getReasonForIgnoring(
s.getName(), t.getTestName());
w.println(" <div class=\"test " + tClass + "\">");
w.println(" " + outputLink(t));
if (!tReason.isEmpty()) {
w.println(" <div class=\"tReason\">" + tReason
+ "</div>");
}
w.println(" </div>");
}
}
w.println(" </td>");
w.println(" </tr>");
}
w.println(" </table>");
w.println();
}
private void writeSuiteErrorMessagesSection(PrintWriter w) {
Map<String, SuiteData> failedSuiteMap = dataModel
.getSuitesWithFailureMessages();
if (failedSuiteMap.isEmpty()) {
return;
}
w.println(" <div class=section>All tests</div>");
w.println();
for (SuiteData s : failedSuiteMap.values()) {
ProcessOutput output = s.getFailureMessages();
w.println(" <a name=\"" + SuiteData.failureMessageAnchor(s)
+ "\">");
w.println(" <table cellspacing=\"0\">");
w.println(" <tr><th>Standard Output</th></tr>\n");
w.println(" <tr><td><pre>" + output.getStdout()
+ "</pre></td></tr>\n");
w.println(" </table>");
w.println("<br/>&nbsp;<br/>");
w.println(" <table cellspacing=\"0\">");
w.println(" <tr><th>Error Output</th></tr>\n");
w.println(" <tr><td><pre>" + output.getErrout()
+ "</pre></td></tr>\n");
w.println(" </table>");
w.println("<br/>&nbsp;<br/>");
w.println();
}
}
private void writeIgnoreSection(PrintWriter w) {
String warnClass = Status.IGNORED.getHtmlClass();
Collection<IgnoredTestInfo> ignoredTests = dataModel
.getIgnoredTestInfo();
w.println(" <div class=section>Ignored</div>");
w.println();
w.println(" <table class=\"ignored\" cellspacing=\"0\">");
w.println(" <tr><th>Suite name</th><th>Test name</th>"
+ "<th>Reason for ignoring</th></tr>\n");
if (ignoredTests.isEmpty()) {
w.println(" <tr><td colspan=\"3\">No tests ignored.</td>"
+ "</tr>");
} else {
for (IgnoredTestInfo info : ignoredTests) {
String suiteName = info.suiteName;
String testName = info.testName;
String reason = dataModel.getReasonForIgnoring(suiteName,
testName);
w.println(" <tr class=\"" + warnClass + "\">");
w.println(" <td>" + suiteName + "</td>");
w.println(" <td>" + testName + "</td>");
w.println(" <td>" + reason + "</td>");
w.println(" </tr>");
}
}
w.println(" </table>");
w.println();
}
private void writeFooter(PrintWriter w) {
w.println(" <div class=section>Log</div>");
w.println(" <pre class=\"log\">");
Reader reader = null;
try {
reader = new FileReader(parms.getLogFile());
char[] buffer = new char[4096];
int howMany;
while (-1 != (howMany = reader.read(buffer))) {
w.write(buffer, 0, howMany);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
w.println(" </pre>");
w.println("</body>");
w.println("</html>");
}
private String formatElapsedTime(long elapsed) {
if (elapsed == 0) {
return "---";
}
long elapsedSeconds = elapsed / 1000L;
long seconds = elapsedSeconds % 60L;
long elapsedMinutes = elapsedSeconds / 60L;
long minutes = elapsedMinutes % 60L;
long hours = elapsedMinutes / 60L;
String elapsedTime = "";
if (hours > 0) {
elapsedTime += hours + "h ";
}
if (minutes > 0 || hours > 0) {
elapsedTime += minutes + "m ";
}
elapsedTime += seconds + "s";
return elapsedTime;
}
private String formatDateTime(long dateTime) {
if (dateTime == 0) {
return "---";
}
return dateFormat.format(new Date(dateTime));
}
private String outputLink(SuiteData s) {
if (s.getOutputLink() == null) {
return s.getName();
} else {
return "<a href=\"" + s.getOutputLink() + "\">" + s.getName()
+ "</a>";
}
}
private String outputLink(TestData t) {
if (t.getOutputLink() == null) {
return t.getTestName();
} else {
return "<a href=\"" + t.getOutputLink() + "\">" + t.getTestName()
+ "</a>";
}
}
}

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:vitro="http://vitro.mannlib.cornell.edu/ns/vitro/0.7#"
xmlns:auth="http://vitro.mannlib.cornell.edu/ns/vitro/authorization#"
xmlns="http://vitro.mannlib.cornell.edu/ns/vitro/default#"
xml:base="http://vitro.mannlib.cornell.edu/ns/vitro/default">
<auth:UserAccount rdf:about="http://vivo.mydomain.edu/individual/TestAdmin">
<auth:emailAddress rdf:datatype="http://www.w3.org/2001/XMLSchema#string">testAdmin@cornell.edu</auth:emailAddress>
<auth:externalAuthId rdf:datatype="http://www.w3.org/2001/XMLSchema#string">testAdmin</auth:externalAuthId>
<auth:firstName rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Test</auth:firstName>
<auth:lastName rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Admin</auth:lastName>
<auth:md5password rdf:datatype="http://www.w3.org/2001/XMLSchema#string">DC647EB65E6711E155375218212B3964</auth:md5password>
<auth:status rdf:datatype="http://www.w3.org/2001/XMLSchema#string">ACTIVE</auth:status>
<auth:loginCount rdf:datatype="http://www.w3.org/2001/XMLSchema#int">1</auth:loginCount>
<auth:passwordLinkExpires rdf:datatype="http://www.w3.org/2001/XMLSchema#long">0</auth:passwordLinkExpires>
<auth:hasPermissionSet rdf:resource="http://vitro.mannlib.cornell.edu/ns/vitro/authorization#ADMIN"/>
</auth:UserAccount>
<auth:UserAccount rdf:about="http://vivo.mydomain.edu/individual/JohnCurator">
<auth:emailAddress rdf:datatype="http://www.w3.org/2001/XMLSchema#string">johnCurator@cornell.edu</auth:emailAddress>
<auth:externalAuthId rdf:datatype="http://www.w3.org/2001/XMLSchema#string">johnCurator</auth:externalAuthId>
<auth:firstName rdf:datatype="http://www.w3.org/2001/XMLSchema#string">John</auth:firstName>
<auth:lastName rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Curator</auth:lastName>
<auth:md5password rdf:datatype="http://www.w3.org/2001/XMLSchema#string">DC647EB65E6711E155375218212B3964</auth:md5password>
<auth:status rdf:datatype="http://www.w3.org/2001/XMLSchema#string">ACTIVE</auth:status>
<auth:loginCount rdf:datatype="http://www.w3.org/2001/XMLSchema#int">1</auth:loginCount>
<auth:passwordLinkExpires rdf:datatype="http://www.w3.org/2001/XMLSchema#long">0</auth:passwordLinkExpires>
<auth:hasPermissionSet rdf:resource="http://vitro.mannlib.cornell.edu/ns/vitro/authorization#CURATOR"/>
</auth:UserAccount>
<auth:UserAccount rdf:about="http://vivo.mydomain.edu/individual/SallyEditor">
<auth:emailAddress rdf:datatype="http://www.w3.org/2001/XMLSchema#string">sallyEditor@cornell.edu</auth:emailAddress>
<auth:externalAuthId rdf:datatype="http://www.w3.org/2001/XMLSchema#string">sallyEditor</auth:externalAuthId>
<auth:firstName rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Sally</auth:firstName>
<auth:lastName rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Editor</auth:lastName>
<auth:md5password rdf:datatype="http://www.w3.org/2001/XMLSchema#string">DC647EB65E6711E155375218212B3964</auth:md5password>
<auth:status rdf:datatype="http://www.w3.org/2001/XMLSchema#string">ACTIVE</auth:status>
<auth:loginCount rdf:datatype="http://www.w3.org/2001/XMLSchema#int">1</auth:loginCount>
<auth:passwordLinkExpires rdf:datatype="http://www.w3.org/2001/XMLSchema#long">0</auth:passwordLinkExpires>
<auth:hasPermissionSet rdf:resource="http://vitro.mannlib.cornell.edu/ns/vitro/authorization#EDITOR"/>
</auth:UserAccount>
<auth:UserAccount rdf:about="http://vivo.mydomain.edu/individual/JoeUser">
<auth:emailAddress rdf:datatype="http://www.w3.org/2001/XMLSchema#string">joeUser@cornell.edu</auth:emailAddress>
<auth:externalAuthId rdf:datatype="http://www.w3.org/2001/XMLSchema#string">joeUser</auth:externalAuthId>
<auth:firstName rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Joe</auth:firstName>
<auth:lastName rdf:datatype="http://www.w3.org/2001/XMLSchema#string">User</auth:lastName>
<auth:md5password rdf:datatype="http://www.w3.org/2001/XMLSchema#string">DC647EB65E6711E155375218212B3964</auth:md5password>
<auth:status rdf:datatype="http://www.w3.org/2001/XMLSchema#string">ACTIVE</auth:status>
<auth:loginCount rdf:datatype="http://www.w3.org/2001/XMLSchema#int">1</auth:loginCount>
<auth:passwordLinkExpires rdf:datatype="http://www.w3.org/2001/XMLSchema#long">0</auth:passwordLinkExpires>
<auth:hasPermissionSet rdf:resource="http://vitro.mannlib.cornell.edu/ns/vitro/authorization#SELF_EDITOR"/>
</auth:UserAccount>
</rdf:RDF>