NIHVIVO-1229 Create the RevisionInfoBean, with setup listener and unit tests.

This commit is contained in:
jeb228 2010-10-25 21:30:34 +00:00
parent bc86f50c5b
commit b438f7d36d
6 changed files with 860 additions and 11 deletions

View file

@ -0,0 +1,202 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.config;
import java.util.Date;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.ibm.icu.text.SimpleDateFormat;
/**
* Information about the provenance of this application: release, revision
* level, build date, etc.
*
* Except for the build date, the information is stored for all levels of the
* application. So an instance of NIHVIVO might read:
*
* date: 2010-11-09 12:15:44
*
* level: vitro-core, trunk, 1234:1236M
*
* level: vivo, branch rel_1.1_maint, 798
*
* Note that the levels should be listed from inner to outer.
*
* Instances of this class are immutable.
*/
public class RevisionInfoBean {
private static final Log log = LogFactory.getLog(RevisionInfoBean.class);
/** A dummy bean to use if there is no real one. */
static final RevisionInfoBean DUMMY_BEAN = new RevisionInfoBean(
new Date(0), Collections.singleton(LevelRevisionInfo.DUMMY_LEVEL));
/** The bean is attached to the session by this name. */
static final String ATTRIBUTE_NAME = RevisionInfoBean.class.getName();
// ----------------------------------------------------------------------
// static methods
// ----------------------------------------------------------------------
/** Package access: should only be used during setup. */
static void setBean(ServletContext context, RevisionInfoBean bean) {
if (bean == null) {
bean = DUMMY_BEAN;
}
context.setAttribute(ATTRIBUTE_NAME, bean);
log.info(bean);
}
public static RevisionInfoBean getBean(HttpSession session) {
if (session == null) {
log.warn("Tried to get revision info bean with a null session!");
return DUMMY_BEAN;
}
Object o = session.getServletContext().getAttribute(ATTRIBUTE_NAME);
if (o == null) {
log.warn("Tried to get revision info bean, but didn't find any.");
return DUMMY_BEAN;
}
if (!(o instanceof RevisionInfoBean)) {
log.error("Tried to get revision info bean, but found an instance of "
+ o.getClass().getName() + ": " + o);
return DUMMY_BEAN;
}
return (RevisionInfoBean) o;
}
public static void removeBean(ServletContext context) {
context.removeAttribute(ATTRIBUTE_NAME);
}
// ----------------------------------------------------------------------
// the bean
// ----------------------------------------------------------------------
private final long buildDate;
private final List<LevelRevisionInfo> levelInfos;
public RevisionInfoBean(Date buildDate,
Collection<LevelRevisionInfo> levelInfos) {
this.buildDate = buildDate.getTime();
this.levelInfos = Collections
.unmodifiableList(new ArrayList<LevelRevisionInfo>(levelInfos));
}
public Date getBuildDate() {
return new Date(buildDate);
}
public List<LevelRevisionInfo> getLevelInfos() {
return levelInfos;
}
public String getReleaseLabel() {
if (levelInfos.isEmpty()) {
return LevelRevisionInfo.DUMMY_LEVEL.getRelease();
}
int lastIndex = levelInfos.size() - 1;
LevelRevisionInfo outerLevel = levelInfos.get(lastIndex);
return outerLevel.getRelease();
}
@Override
public String toString() {
return "Revision info [build date: "
+ new SimpleDateFormat().format(new Date(buildDate))
+ ", level info: " + levelInfos + "]";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof RevisionInfoBean)) {
return false;
}
RevisionInfoBean that = (RevisionInfoBean) obj;
return (this.buildDate == that.buildDate)
&& this.levelInfos.equals(that.levelInfos);
}
@Override
public int hashCode() {
return new Long(buildDate).hashCode() ^ levelInfos.hashCode();
}
// ----------------------------------------------------------------------
// helper class
// ----------------------------------------------------------------------
/**
* Revision info about one level of the application -- e.g. vitro, vivo,
* vivoCornell, etc.
*/
public static class LevelRevisionInfo {
/** A level to use when no actual level can be found. */
static final LevelRevisionInfo DUMMY_LEVEL = new LevelRevisionInfo(
"no name", "unknown", "unknown");
private final String name;
private final String release;
private final String revision;
LevelRevisionInfo(String name, String release, String revision) {
this.name = name;
this.release = release;
this.revision = revision;
}
public String getName() {
return name;
}
public String getRelease() {
return release;
}
public String getRevision() {
return revision;
}
@Override
public String toString() {
return "[" + name + ", " + release + ", " + revision + "]";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof LevelRevisionInfo)) {
return false;
}
LevelRevisionInfo that = (LevelRevisionInfo) obj;
return this.name.equals(that.name)
&& this.release.equals(that.release)
&& this.revision.equals(that.revision);
}
@Override
public int hashCode() {
return name.hashCode() ^ release.hashCode() ^ revision.hashCode();
}
}
}

View file

@ -0,0 +1,177 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.config;
import static edu.cornell.mannlib.vitro.webapp.config.RevisionInfoBean.DUMMY_BEAN;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.config.RevisionInfoBean.LevelRevisionInfo;
/**
* <pre>
* Read the revision information, and store it in the servlet context.
*
* - The revision information is in a file in the classpath.
* - The name of the file is in RESOURCE_PATH, below.
* - The first line is the build date, with a format as in DATE_FORMAT, below.
* - Each additional non-blank line holds revision info for one application level:
* - level info is from inner (vitro) to outer (top-level product).
* - level info appears as product name, release name and revision level,
* delimited by " ~ ".
* - additional white space before and after info values is ignored.
*
* Example file:
* 2010-11-14 23:58:00
* vitroCore ~ Release 1.1 ~ 6604
* nihvivo ~ Release 1.1 ~ 1116
* </pre>
*/
public class RevisionInfoSetup implements ServletContextListener {
private static final Log log = LogFactory.getLog(RevisionInfoSetup.class);
private static final Pattern LEVEL_INFO_PATTERN = Pattern
.compile("(.+) ~ (.+) ~ (.+)");
static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
static final String RESOURCE_PATH = "/WEB-INF/resources/revisionInfo.txt";
/**
* On startup, read the revision info from the resource file in the
* classpath.
*
* If we can't find the file, or can't parse it, store an empty bean.
*
* Don't allow any Exceptions to percolate up past this point.
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
RevisionInfoBean bean;
try {
List<String> lines = readRevisionInfo(context);
bean = parseRevisionInformation(lines);
} catch (Exception e) {
log.error(e, e);
bean = DUMMY_BEAN;
}
RevisionInfoBean.setBean(sce.getServletContext(), bean);
}
private List<String> readRevisionInfo(ServletContext context)
throws IOException {
BufferedReader reader = null;
try {
reader = openRevisionInfoReader(context);
return readSignificantLines(reader);
} finally {
closeReader(reader);
}
}
private BufferedReader openRevisionInfoReader(ServletContext context)
throws FileNotFoundException {
InputStream stream = context.getResourceAsStream(RESOURCE_PATH);
if (stream == null) {
throw new FileNotFoundException(
"Can't find a resource in the webapp at '" + RESOURCE_PATH
+ "'.");
} else {
return new BufferedReader(new InputStreamReader(stream));
}
}
private List<String> readSignificantLines(BufferedReader reader)
throws IOException {
List<String> lines = new ArrayList<String>();
String line = null;
while (null != (line = reader.readLine())) {
line = line.trim();
if ((!line.isEmpty()) && (!line.startsWith("#"))) {
lines.add(line);
}
}
return lines;
}
private void closeReader(Reader reader) {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private RevisionInfoBean parseRevisionInformation(List<String> lines)
throws ParseException {
checkValidNumberOfLines(lines);
String dateLine = lines.get(0);
List<String> levelLines = lines.subList(1, lines.size());
Date buildDate = parseDateLine(dateLine);
List<LevelRevisionInfo> levelInfos = parseLevelLines(levelLines);
return new RevisionInfoBean(buildDate, levelInfos);
}
private void checkValidNumberOfLines(List<String> lines)
throws ParseException {
if (lines.isEmpty()) {
throw new ParseException(
"The revision info resource file contains no data.", 0);
}
}
private Date parseDateLine(String dateLine) throws ParseException {
return new SimpleDateFormat(DATE_FORMAT).parse(dateLine);
}
private List<LevelRevisionInfo> parseLevelLines(List<String> levelLines)
throws ParseException {
List<LevelRevisionInfo> infos = new ArrayList<LevelRevisionInfo>();
for (String line : levelLines) {
Matcher m = LEVEL_INFO_PATTERN.matcher(line);
if (m.matches()) {
String name = m.group(1).trim();
String release = m.group(2).trim();
String revision = m.group(3).trim();
infos.add(new LevelRevisionInfo(name, release, revision));
} else {
throw new ParseException(
"Failed to parse the revision info in '" + line + "'",
0);
}
}
return infos;
}
/** On shutdown, clean up. */
@Override
public void contextDestroyed(ServletContextEvent sce) {
RevisionInfoBean.removeBean(sce.getServletContext());
}
}