HER-1901 timestamped subdirectory for each launch

* HardLinker.java
    renamed FilesystemLinkMaker.java
* FilesystemLinkMaker.java
    add support for symbolic links
* CLibrary.java
    new method symlink()
* BdbModule.java
    use new class name FilesystemLinkMaker
* CrawlJob.java
    at crawl launch, create launch directory launch-{timestamp17}, copy cxml there, symlink "current" to launch dir, inform ConfigPaths
* ConfigPath.java
    interpolate ${launch-id} in configured paths
* ConfigFile.java
    obtainReader() - snapshot config files to launch dir when they are read
* ActionDirectory.java
    default doneDir now ${launch-id}/actions-done
    actOn() - symlink from old style done dir action/done to done files
* SurtPrefixedDecideRule.java
    default surtsDumpFile now ${launch-id}/surts.dump
    pathsFixedUp() - this gets called at build time, but we don't want anything written to disk until launch time, so remove call to dumpSurtPrefixSet() here
* CrawlerLoggerModule.java
    default logs dir now ${launch-id}/logs
* StatisticsTracker.java 
    default reports dir now ${launch-id}/reports
* WriterPoolProcessor.java
    default writer base path now ${launch-id} 
* profile-crawler-beans.cxml
    update to reflect new default paths under launch dirs
* PropertyUtils.java
    fix javadoc typo
This commit is contained in:
nlevitt
2011-07-13 19:18:55 +00:00
parent a8682ff1ff
commit 4250e07de9
13 changed files with 134 additions and 38 deletions
@@ -43,7 +43,7 @@ import org.archive.checkpointing.Checkpoint;
import org.archive.checkpointing.Checkpointable;
import org.archive.spring.ConfigPath;
import org.archive.util.CLibrary;
import org.archive.util.HardLinker;
import org.archive.util.FilesystemLinkMaker;
import org.archive.util.IdentityCacheable;
import org.archive.util.ObjectIdentityBdbManualCache;
import org.archive.util.ObjectIdentityCache;
@@ -487,7 +487,7 @@ public class BdbModule implements Lifecycle, Checkpointable, Closeable {
filedata[i] += ","+f.length();
if(getUseHardLinkCheckpoints()) {
File hardLink = new File(envCpDir,filedata[i]);
if (!HardLinker.makeHardLink(f.getAbsolutePath(), hardLink.getAbsolutePath())) {
if (!FilesystemLinkMaker.makeHardLink(f.getAbsolutePath(), hardLink.getAbsolutePath())) {
LOGGER.log(Level.SEVERE, "unable to create required checkpoint link "+hardLink);
}
}
@@ -26,6 +26,7 @@ import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import org.apache.commons.io.FileUtils;
import org.archive.io.ReadSource;
/**
@@ -48,6 +49,10 @@ public class ConfigFile extends ConfigPath implements ReadSource, WriteTarget {
if(!getFile().exists()) {
getFile().createNewFile();
}
// snapshot file to launch directory
FileUtils.copyFileToDirectory(getFile(), currentLaunchDir);
return new InputStreamReader(
new FileInputStream(getFile()),
"UTF-8");
@@ -56,9 +56,10 @@ public class ConfigPath implements Serializable {
String name;
String path;
ConfigPath base;
transient File resolved;
ConfigPath base;
transient String interpolatedPath;
transient File currentLaunchDir;
public ConfigPath() {
super();
@@ -68,6 +69,7 @@ public class ConfigPath implements Serializable {
super();
this.name = name;
this.path = path;
this.interpolatedPath = path;
}
public ConfigPath getBase() {
@@ -93,12 +95,13 @@ public class ConfigPath implements Serializable {
@Required
public void setPath(String path) {
this.path = path;
this.interpolatedPath = path;
}
public File getFile() {
return (base == null || path.startsWith("/"))
? new File(path)
: new File(base.getFile(), path);
return (base == null || interpolatedPath.startsWith("/"))
? new File(interpolatedPath)
: new File(base.getFile(), interpolatedPath);
}
/**
@@ -118,4 +121,10 @@ public class ConfigPath implements Serializable {
}
return this;
}
public void informOfLaunch(String currentLaunchId, File currentLaunchDir) {
this.currentLaunchDir = currentLaunchDir;
// could use PropertyUtils.interpolateWithProperties(String, Properties...), but no real need
interpolatedPath = path.replace("${launch-id}", currentLaunchId);
}
}
@@ -35,5 +35,6 @@ public interface CLibrary extends Library {
Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
CLibrary.class);
int link(String fromFile, String toFile);
int link(String existingPath, String newPath);
int symlink(String existingPath, String newPath);
}
@@ -32,7 +32,7 @@ import com.sun.jna.win32.StdCallLibrary;
*
* @see http://stackoverflow.com/questions/783075/creating-a-hard-link-in-java/3023349#3023349
*/
public class HardLinker {
public class FilesystemLinkMaker {
// see https://github.com/twall/jna/blob/master/www/GettingStarted.md
public interface Kernel32Library extends StdCallLibrary {
@@ -63,6 +63,9 @@ public class HardLinker {
*/
boolean CreateHardLinkA(String newPath, String existingPath, LPSECURITY_ATTRIBUTES lpSecurityAttributes);
// boolean CreateHardLinkW(String newPath, String existingPath, LPSECURITY_ATTRIBUTES lpSecurityAttributes);
// http://msdn.microsoft.com/en-us/library/aa363866%28v=VS.85%29.aspx
boolean CreateSymbolicLinkA(String newPath, String existingPath, LPSECURITY_ATTRIBUTES lpSecurityAttributes);
}
/**
@@ -70,6 +73,7 @@ public class HardLinker {
*
* @return true on success
*/
// XXX could handle errors better (examine errno, throw exception...)
public static boolean makeHardLink(String existingPath, String newPath) {
if (Platform.isWindows()) {
return Kernel32Library.INSTANCE.CreateHardLinkA(newPath, existingPath, null);
@@ -78,17 +82,42 @@ public class HardLinker {
return status == 0;
}
}
/**
* Wrapper over platform-dependent system calls to create a symboic link.
*
* @return true on success
*/
// XXX could handle errors better (examine errno, throw exception...)
public static boolean makeSymbolicLink(String existingPath, String newPath) {
if (Platform.isWindows()) {
return Kernel32Library.INSTANCE.CreateSymbolicLinkA(newPath, existingPath, null);
} else {
int status = CLibrary.INSTANCE.symlink(existingPath, newPath);
return status == 0;
}
}
public static void main(String[] args) throws IOException {
File existingPath = File.createTempFile("heritrixHardLinkTestExistingFile", ".tmp");
File newPath = File.createTempFile("heritrixHardLinkTestNewFile", ".tmp");
newPath.delete();
if (HardLinker.makeHardLink(existingPath.getAbsolutePath(), newPath.getAbsolutePath())) {
if (FilesystemLinkMaker.makeHardLink(existingPath.getAbsolutePath(), newPath.getAbsolutePath())) {
System.out.println("success - made hard link from " + newPath.getAbsolutePath() + " to " + existingPath.getAbsolutePath());
} else {
System.out.println("failed to make hard link from " + newPath.getAbsolutePath() + " to " + existingPath.getAbsolutePath());
}
existingPath = File.createTempFile("heritrixSymlinkTestExistingFile", ".tmp");
newPath = File.createTempFile("heritrixSymlinkTestNewFile", ".tmp");
newPath.delete();
if (FilesystemLinkMaker.makeSymbolicLink(existingPath.getPath(), newPath.getPath())) {
System.out.println("success - made symlink from " + newPath.getAbsolutePath() + " to " + existingPath.getAbsolutePath());
} else {
System.out.println("failed to make symlink from " + newPath.getAbsolutePath() + " to " + existingPath.getAbsolutePath());
}
}
}
@@ -83,7 +83,7 @@ public class PropertyUtils {
* the expression is replaced with the empty-string.
*
* @param original String
* @param properties Properties to try in order; first value found (if any) is used
* @param props Properties to try in order; first value found (if any) is used
* @return modified String
*/
public static String interpolateWithProperties(String original,
@@ -41,6 +41,7 @@ import org.apache.commons.lang.StringUtils;
import org.archive.modules.seeds.SeedModule;
import org.archive.spring.ConfigPath;
import org.archive.util.ArchiveUtils;
import org.archive.util.FilesystemLinkMaker;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@@ -100,9 +101,6 @@ public class ActionDirectory implements ApplicationContextAware, Lifecycle, Runn
this.delaySeconds = delay;
}
/**
* Scratch directory for temporary overflow-to-disk
*/
protected ConfigPath actionDir =
new ConfigPath("ActionDirectory source directory","action");
public ConfigPath getActionDir() {
@@ -112,16 +110,13 @@ public class ActionDirectory implements ApplicationContextAware, Lifecycle, Runn
this.actionDir = actionDir;
}
/**
* Scratch directory for temporary overflow-to-disk
*/
protected ConfigPath doneDir =
new ConfigPath("ActionDirectory done directory","action/done");
new ConfigPath("ActionDirectory done directory","${launch-id}/actions-done");
public ConfigPath getDoneDir() {
return doneDir;
}
public void setDoneDir(ConfigPath scratchDir) {
this.doneDir = scratchDir;
public void setDoneDir(ConfigPath doneDir) {
this.doneDir = doneDir;
}
ApplicationContext appCtx;
@@ -258,7 +253,19 @@ public class ActionDirectory implements ApplicationContextAware, Lifecycle, Runn
// move file to 'done' area with timestamp prefix
while(actionFile.exists()) {
try {
FileUtils.moveFile(actionFile, new File(doneDir.getFile(),timestamp+"."+actionFile.getName()));
File doneFile = new File(doneDir.getFile(),timestamp+"."+actionFile.getName());
FileUtils.moveFile(actionFile, doneFile);
// attempt to symlink from action/done/ to done file
File actionDoneDirFile = new File(actionDir.getFile(), "done");
if (!actionDoneDirFile.equals(doneDir.getFile())) {
actionDoneDirFile.mkdirs();
File doneSymlinkFile = new File(actionDoneDirFile, doneFile.getName());
boolean success = FilesystemLinkMaker.makeSymbolicLink(doneFile.getPath(), doneSymlinkFile.getPath());
if (!success) {
LOGGER.warning("failed to create symlink from " + doneSymlinkFile + " to " + doneFile);
}
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE,"unable to move "+actionFile,e);
}
@@ -57,6 +57,7 @@ import org.archive.spring.ConfigPath;
import org.archive.spring.ConfigPathConfigurer;
import org.archive.spring.PathSharingContext;
import org.archive.util.ArchiveUtils;
import org.archive.util.FilesystemLinkMaker;
import org.archive.util.TextUtils;
import org.joda.time.DateTime;
import org.springframework.beans.BeanWrapperImpl;
@@ -80,7 +81,7 @@ import org.xml.sax.SAXException;
*
* @contributor gojomo
*/
public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener {
public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener<ApplicationEvent> {
private final static Logger LOGGER =
Logger.getLogger(CrawlJob.class.getName());
@@ -97,7 +98,7 @@ public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener {
public CrawlJob(File cxml) {
primaryConfig = cxml;
isLaunchInfoPartial = false;
scanJobLog();
scanJobLog(); // XXX look at launch directories instead/first?
alertThreadGroup = new AlertThreadGroup(getShortName());
}
@@ -428,8 +429,9 @@ public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener {
alertThreadGroup.addLogger(getJobLogger());
Thread launcher = new Thread(alertThreadGroup, getShortName()+" launchthread") {
public void run() {
startContext();
CrawlController cc = getCrawlController();
initLaunchDir();
startContext();
if(cc!=null) {
cc.requestCrawlStart();
}
@@ -446,6 +448,50 @@ public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener {
}
}
protected transient String currentLaunchId;
public void initLaunchId() {
currentLaunchId = "launch-" + ArchiveUtils.get17DigitDate();
LOGGER.info("launch id " + currentLaunchId);
}
public String getCurrentLaunchId() {
return currentLaunchId;
}
protected transient File currentLaunchDir;
public File getCurrentLaunchDir() {
return currentLaunchDir;
}
protected void initLaunchDir() {
initLaunchId();
try {
currentLaunchDir = new File(getJobDir(), getCurrentLaunchId());
if (!currentLaunchDir.mkdir()) {
throw new IOException("failed to create directory " + currentLaunchDir);
}
// copy cxml to launch dir
FileUtils.copyFileToDirectory(getPrimaryConfig(), currentLaunchDir);
// attempt to symlink "current" to launch dir
File currentSymlink = new File(getJobDir(), "current");
currentSymlink.delete();
boolean success = FilesystemLinkMaker.makeSymbolicLink(currentLaunchDir.getName(), currentSymlink.getPath());
if (!success) {
LOGGER.warning("failed to create symlink from " + currentSymlink + " to " + currentLaunchDir);
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "failed to initialize launch directory: " + e);
currentLaunchDir = null;
}
// fill in ${launch-id} in all config paths, and let config files know
// where to snapshot themselves
for (ConfigPath configPath: getConfigPaths().values()) {
configPath.informOfLaunch(getCurrentLaunchId(), getCurrentLaunchDir());
}
}
/**
* Start the context, catching and reporting any BeansExceptions.
*/
@@ -515,7 +561,6 @@ public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener {
*
* @return Checkpointer
*/
@SuppressWarnings("unchecked")
public synchronized CheckpointService getCheckpointService() {
if(ac==null) {
return null;
@@ -62,7 +62,7 @@ public class CrawlerLoggerModule
Checkpointable, SimpleFileLoggerProvider {
private static final long serialVersionUID = 1L;
protected ConfigPath path = new ConfigPath(Engine.LOGS_DIR_NAME,"logs");
protected ConfigPath path = new ConfigPath(Engine.LOGS_DIR_NAME,"${launch-id}/logs");
public ConfigPath getPath() {
return path;
}
@@ -130,9 +130,9 @@ import com.sleepycat.je.DatabaseException;
public class StatisticsTracker
implements
ApplicationContextAware,
ApplicationListener,
ApplicationListener<ApplicationEvent>,
SeedListener,
Lifecycle,
Lifecycle,
Runnable,
Checkpointable,
BeanNameAware {
@@ -153,7 +153,7 @@ public class StatisticsTracker
this.bdb = bdb;
}
protected ConfigPath reportsDir = new ConfigPath(Engine.REPORTS_DIR_NAME,"reports");
protected ConfigPath reportsDir = new ConfigPath(Engine.REPORTS_DIR_NAME,"${launch-id}/reports");
public ConfigPath getReportsDir() {
return reportsDir;
}
@@ -123,7 +123,7 @@ http://example.example/example
<!-- <property name="seedsAsSurtPrefixes" value="true" /> -->
<!-- <property name="alsoCheckVia" value="false" /> -->
<!-- <property name="surtsSourceFile" value="" /> -->
<!-- <property name="surtsDumpFile" value="surts.dump" /> -->
<!-- <property name="surtsDumpFile" value="${launch-id}/surts.dump" /> -->
<!-- <property name="surtsSource">
<bean class="org.archive.spring.ConfigString">
<property name="value">
@@ -149,7 +149,7 @@ http://example.example/example
<bean class="org.archive.modules.deciderules.surt.SurtPrefixedDecideRule">
<property name="decision" value="REJECT"/>
<property name="seedsAsSurtPrefixes" value="false"/>
<property name="surtsDumpFile" value="negative-surts.dump" />
<property name="surtsDumpFile" value="${launch-id}/negative-surts.dump" />
<!-- <property name="surtsSource">
<bean class="org.archive.spring.ConfigFile">
<property name="path" value="negative-surts.txt" />
@@ -338,7 +338,7 @@ http://example.example/example
<!-- <property name="MaxWaitForIdleMs" value="500" /> -->
<!-- <property name="skipIdenticalDigests" value="false" /> -->
<!-- <property name="maxTotalBytesToWrite" value="0" /> -->
<!-- <property name="directory" value="." /> -->
<!-- <property name="directory" value="${launch-id}" /> -->
<!-- <property name="storePaths">
<list>
<value>warcs</value>
@@ -517,6 +517,7 @@ http://example.example/example
scripts, and other data to be processed during a crawl. -->
<bean id="actionDirectory" class="org.archive.crawler.framework.ActionDirectory">
<!-- <property name="actionDir" value="action" /> -->
<!-- <property name="doneDir" value="${launch-id}/actions-done" /> -->
<!-- <property name="initialDelaySeconds" value="10" /> -->
<!-- <property name="delaySeconds" value="30" /> -->
</bean>
@@ -612,7 +613,7 @@ http://example.example/example
<!-- STATISTICSTRACKER: standard stats/reporting collector -->
<bean id="statisticsTracker"
class="org.archive.crawler.reporting.StatisticsTracker" autowire="byName">
<!-- <property name="reportsDir" value="reports" /> -->
<!-- <property name="reportsDir" value="${launch-id}/reports" /> -->
<!-- <property name="liveHostReportSize" value="20" /> -->
<!-- <property name="intervalSeconds" value="20" /> -->
<!-- <property name="keepSnapshotsCount" value="5" /> -->
@@ -622,7 +623,7 @@ http://example.example/example
<!-- CRAWLERLOGGERMODULE: shared logging facility -->
<bean id="loggerModule"
class="org.archive.crawler.reporting.CrawlerLoggerModule">
<!-- <property name="path" value="logs" /> -->
<!-- <property name="path" value="${launch-id}/logs" /> -->
<!-- <property name="crawlLogPath" value="crawl.log" /> -->
<!-- <property name="alertsLogPath" value="alerts.log" /> -->
<!-- <property name="progressLogPath" value="progress-statistics.log" /> -->
@@ -111,7 +111,7 @@ implements
* Dump file to save SURT prefixes actually used: Useful debugging SURTs.
*/
protected ConfigFile surtsDumpFile =
new ConfigFile("surtsDumpFile","surts.dump");
new ConfigFile("surtsDumpFile","${launch-id}/surts.dump");
public ConfigFile getSurtsDumpFile() {
return surtsDumpFile;
}
@@ -156,7 +156,6 @@ implements
public void pathsFixedUp() {
readPrefixes();
dumpSurtPrefixSet();
}
public void concludedSeedBatch() {
@@ -225,7 +225,7 @@ implements Lifecycle, Checkpointable, WriterPoolSettings {
this.serverCache = serverCache;
}
protected ConfigPath directory = new ConfigPath("writer base path", ".");
protected ConfigPath directory = new ConfigPath("writer base path", "${launch-id}");
public ConfigPath getDirectory() {
return directory;
}