Part of [HER-1603] Restletify: xml representations of all resources that also

offer html
* XmlMarshaller.java
    new class to write xml based on nested map structure
* BeanBrowseResource.java ScriptResource.java EngineResource.java
  JobResource.java JobRelatedResource.java
    support xml representation
* WorkQueueFrontier.java PrecedenceProvider.java WorkQueue.java ToeThread.java
  ToePool.java CrawlJob.java Engine.java Frontier.java CrawlController.java
  StatisticsTracker.java CrawledBytesHistotable.java FetchStats.java
  ProcessorChain.java CrawlURI.java Reporter.java
    offer same data that is returned in the single line reports in a map
    suitable for use with XmlMarshaller
* FileUtils.java
    new method tryToCanonicalize(File)
This commit is contained in:
nlevitt committed 2009-11-25 01:43:57 +00:00
1 parent a1022c8246
commit 8ba641ed4b
24 files changed
+971 -58

No files matched your search

@@ -25,6 +25,7 @@ import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.LinkedList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -311,7 +312,6 @@ implements Serializable,
* @param newState State change we're to tell listeners' about.
* @param message Message on state change.
*/
@SuppressWarnings("unchecked")
protected void sendCrawlStateChangeEvent(State newState,
CrawlStatus status) {
if(this.state == newState) {
@@ -596,6 +596,10 @@ implements Serializable,
return (toePool == null) ? "" : ArchiveUtils.singleLineReport(toePool);
}
public Map<String,Object> getToeThreadReportShortData() {
return toePool == null ? null : toePool.singleLineReportData();
}
public String getFrontierReportShort() {
return ArchiveUtils.singleLineReport(getFrontier());
@@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -653,6 +654,21 @@ public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener {
return cc!=null ? cc.getStatisticsTracker() : null;
}
public Map<String,Number> rateReportData() {
StatisticsTracker stats = getStats();
if (stats == null) {
return null;
}
CrawlStatSnapshot snapshot = stats.getSnapshot();
Map<String,Number> map = new LinkedHashMap<String,Number>();
map.put("currentDocsPerSecond", snapshot.currentDocsPerSecond);
map.put("averageDocsPerSecond", snapshot.docsPerSecond);
map.put("currentKiBPerSec", snapshot.currentKiBPerSec);
map.put("averageKiBPerSec", snapshot.totalKiBPerSec);
return map;
}
public Object rateReport() {
StatisticsTracker stats = getStats();
if(stats==null) {
@@ -672,6 +688,23 @@ public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener {
return sb.toString();
}
public Map<String,Number> loadReportData() {
StatisticsTracker stats = getStats();
if (stats == null) {
return null;
}
CrawlStatSnapshot snapshot = stats.getSnapshot();
Map<String,Number> map = new LinkedHashMap<String,Number>();
map.put("busyThreads", snapshot.busyThreads);
map.put("totalThreads", stats.threadCount());
map.put("congestionRatio", snapshot.congestionRatio);
map.put("averageQueueDepth", snapshot.averageDepth);
map.put("deepestQueueDepth", snapshot.deepestUri);
return map;
}
public Object loadReport() {
StatisticsTracker stats = getStats();
if(stats==null) {
@@ -693,32 +726,54 @@ public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener {
return sb.toString();
}
public String uriTotalsReport() {
public Map<String,Long> uriTotalsReportData() {
StatisticsTracker stats = getStats();
if(stats==null) {
if (stats == null) {
return null;
}
CrawlStatSnapshot snapshot = stats.getSnapshot();
Map<String,Long> totals = new LinkedHashMap<String,Long>();
totals.put("downloadedUriCount", snapshot.downloadedUriCount);
totals.put("queuedUriCount", snapshot.queuedUriCount);
totals.put("totalUriCount", snapshot.totalCount());
totals.put("futureUriCount", snapshot.futureUriCount);
return totals;
}
public String uriTotalsReport() {
Map<String,Long> uriTotals = uriTotalsReportData();
if (uriTotals == null) {
return "<i>n/a</i>";
}
CrawlStatSnapshot snapshot = stats.getSnapshot();
long downloaded = snapshot.downloadedUriCount;
long total = snapshot.totalCount();
long queued = snapshot.queuedUriCount;
StringBuilder sb = new StringBuilder(64);
sb
.append(downloaded)
.append(uriTotals.get("downloadedUriCount"))
.append(" downloaded + ")
.append(queued)
.append(uriTotals.get("queuedUriCount"))
.append(" queued = ")
.append(total)
.append(uriTotals.get("totalUriCount"))
.append(" total");
if(snapshot.futureUriCount>0) {
if(uriTotals.get("futureUriCount") >0) {
sb
.append(" (")
.append(snapshot.futureUriCount)
.append(uriTotals.get("futureUriCount"))
.append(" future)");
}
return sb.toString();
}
public Map<String,Long> sizeTotalsReportData() {
StatisticsTracker stats = getStats();
if(stats==null) {
return null;
}
return stats.getCrawledBytes();
}
public String sizeTotalsReport() {
StatisticsTracker stats = getStats();
if(stats==null) {
@@ -727,6 +782,20 @@ public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener {
return stats.crawledBytesSummary();
}
public Map<String,Object> elapsedReportData() {
StatisticsTracker stats = getStats();
if(stats==null) {
return null;
}
Map<String,Object> map = new LinkedHashMap<String,Object>();
long timeElapsed = stats.getCrawlElapsedTime();
map.put("elapsedMilliseconds", timeElapsed);
map.put("elapsedPretty", ArchiveUtils.formatMillisecondsToConventional(timeElapsed));
return map;
}
public String elapsedReport() {
StatisticsTracker stats = getStats();
if(stats==null) {
@@ -736,6 +805,14 @@ public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener {
return ArchiveUtils.formatMillisecondsToConventional(timeElapsed,false);
}
public Map<String,Object> threadReportData() {
CrawlController cc = getCrawlController();
if (cc == null) {
return null;
}
return cc.getToeThreadReportShortData();
}
public String threadReport() {
CrawlController cc = getCrawlController();
if(cc==null) {
@@ -744,6 +821,14 @@ public class CrawlJob implements Comparable<CrawlJob>, ApplicationListener {
return cc.getToeThreadReportShort();
}
public Map<String,Object> frontierReportData() {
CrawlController cc = getCrawlController();
if (cc == null) {
return null;
}
return cc.getFrontier().singleLineReportData();
}
public String frontierReport() {
CrawlController cc = getCrawlController();
if(cc==null) {
@@ -24,6 +24,7 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -249,6 +250,14 @@ public class Engine {
return jobsDir;
}
public Map<String,Object> heapReportData() {
Map<String,Object> map = new LinkedHashMap<String, Object>();
map.put("usedBytes", Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
map.put("totalBytes", Runtime.getRuntime().totalMemory());
map.put("maxBytes", Runtime.getRuntime().maxMemory());
return map;
}
public String heapReport() {
long totalMemory = Runtime.getRuntime().totalMemory();
long freeMemory = Runtime.getRuntime().freeMemory();
@@ -348,5 +357,4 @@ public class Engine {
LOGGER.log(Level.SEVERE,"job: "+jobName+" not found in jobConfig!");
}
}
}
@@ -517,6 +517,7 @@ public interface Frontier extends Lifecycle, MultiReporter {
HOLD, // enter a consistent, stable, checkpointable state ASAP
PAUSE, // enter a stable state where no URIs are in-progress; unlike
// HOLD requires all in-process URIs to complete
FINISH }; // end and cleanup; may not return to any other state after
FINISH // end and cleanup; may not return to any other state after
// this state is requested/reached
}
}
@@ -21,9 +21,11 @@ package org.archive.crawler.framework;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeSet;
import org.apache.commons.lang.StringUtils;
import org.archive.crawler.reporting.AlertThreadGroup;
import org.archive.util.ArchiveUtils;
import org.archive.util.Histotable;
@@ -259,6 +261,34 @@ public class ToePool extends ThreadGroup implements MultiReporter {
}
}
public Map<String, Object> singleLineReportData() {
Histotable<Object> steps = new Histotable<Object>();
Histotable<Object> processors = new Histotable<Object>();
Thread[] toes = getToes();
for (int i = 0; i < toes.length; i++) {
if(!(toes[i] instanceof ToeThread)) {
continue;
}
ToeThread tt = (ToeThread)toes[i];
if(tt!=null) {
steps.tally(tt.getStep().toString());
String currentProcessorName = tt.getCurrentProcessorName();
if (StringUtils.isEmpty(currentProcessorName)) {
currentProcessorName = "noActiveProcessor";
}
processors.tally(currentProcessorName);
}
}
Map<String,Object> data = new LinkedHashMap<String, Object>();
data.put("toeCount", getToeCount());
data.put("steps", steps.getSortedByCounts());
data.put("processors", processors.getSortedByCounts());
return data;
}
public void singleLineReportTo(PrintWriter w) {
Histotable<Object> steps = new Histotable<Object>();
Histotable<Object> processors = new Histotable<Object>();
@@ -29,6 +29,8 @@ import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.net.InetAddress;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -479,6 +481,33 @@ implements RecorderMarker, MultiReporter, ProgressStatisticsReporter,
}
}
public Map<String, Object> singleLineReportData() {
Map<String,Object> data = new LinkedHashMap<String, Object>();
data.put("serialNumber", serialNumber);
CrawlURI c = currentCuri;
if (c != null) {
data.put("currentURI", c.toString());
data.put("currentProcessor", currentProcessorName);
data.put("fetchAttempts", c.getFetchAttempts());
} else {
data.put("currentURI", null);
}
long now = System.currentTimeMillis();
long time = 0;
if (lastFinishTime > lastStartTime) {
data.put("status", "WAITING");
time = now - lastFinishTime;
} else if (lastStartTime > 0) {
data.put("status", "ACTIVE");
time = now - lastStartTime;
}
data.put("currentStatusElapsedMilliseconds", time);
data.put("currentStatusElapsedPretty", ArchiveUtils.formatMillisecondsToConventional(time));
data.put("step", step);
return data;
}
/**
* @param w PrintWriter to write to.
*/
@@ -22,7 +22,10 @@ package org.archive.crawler.frontier;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
@@ -526,6 +529,35 @@ public abstract class WorkQueue implements Frontier.FrontierGroup,
reportTo(null,writer);
}
public Map<String, Object> singleLineReportData() {
Map<String,Object> map = new LinkedHashMap<String, Object>();
map.put("queueName", classKey);
map.put("precedence", getPrecedence());
map.put("itemCount", count);
map.put("enqueueCount", enqueueCount);
map.put("sessionBalance", sessionBalance);
map.put("lastCost", lastCost);
map.put("averageCost", (double) totalExpenditure / costCount);
if (lastDequeueTime != 0) {
map.put("lastDequeueTime", new Date(lastDequeueTime));
} else {
map.put("lastDequeueTime", null);
}
if (wakeTime != 0) {
map.put("lastDequeueTime", new Date(wakeTime));
} else {
map.put("lastDequeueTime", null);
}
map.put("totalExpenditure", totalExpenditure);
map.put("totalBudget", totalBudget);
map.put("errorCount", errorCount);
map.put("lastPeeked", lastPeeked);
map.put("lastQueued", lastQueued);
return map;
}
/* (non-Javadoc)
* @see org.archive.util.Reporter#singleLineReportTo(java.io.Writer)
*/
@@ -35,6 +35,7 @@ import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Queue;
import java.util.SortedMap;
@@ -1087,6 +1088,40 @@ implements Closeable,
return REPORTS;
}
public Map<String, Object> singleLineReportData() {
if (this.allQueues == null) {
return null;
}
int allCount = allQueues.size();
int inProcessCount = inProcessQueues.uniqueSet().size();
int readyCount = readyClassQueues.size();
int snoozedCount = snoozedClassQueues.size();
int activeCount = inProcessCount + readyCount + snoozedCount;
int inactiveCount = getTotalEligibleInactiveQueues();
int ineligibleCount = getTotalIneligibleInactiveQueues();
int retiredCount = getRetiredQueues().size();
int exhaustedCount = allCount - activeCount - inactiveCount - retiredCount;
int inCount = inbound.size();
int outCount = outbound.size();
Map<String,Object> map = new LinkedHashMap<String, Object>();
map.put("totalQueues", allCount);
map.put("inProcessQueues", inProcessCount);
map.put("readyQueues", readyCount);
map.put("snoozedQueues", snoozedCount);
map.put("activeQueues", activeCount);
map.put("inactiveQueues", inactiveCount);
map.put("ineligibleQueues", ineligibleCount);
map.put("retiredQueues", retiredCount);
map.put("exhaustedQueues", exhaustedCount);
map.put("lastReachedState", lastReachedState);
map.put("inboundCount", inCount);
map.put("outboundCount", outCount);
return map;
}
/**
* @param w Where to write to.
*/
@@ -20,6 +20,8 @@ package org.archive.crawler.frontier.precedence;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import org.archive.modules.CrawlURI;
import org.archive.modules.fetcher.FetchStats;
@@ -34,6 +36,8 @@ import org.archive.util.MultiReporter;
abstract public class PrecedenceProvider implements MultiReporter,
FetchStats.CollectsFetchStats, Serializable {
private static final long serialVersionUID = 1L;
abstract public int getPrecedence();
/* (non-Javadoc)
@@ -72,6 +76,12 @@ FetchStats.CollectsFetchStats, Serializable {
return ArchiveUtils.singleLineReport(this);
}
public Map<String, Object> singleLineReportData() {
Map<String,Object> data = new LinkedHashMap<String, Object>();
data.put("precedence", getPrecedence());
return data;
}
public void singleLineReportTo(PrintWriter writer) {
writer.print(getPrecedence());
}
@@ -306,6 +306,10 @@ public class StatisticsTracker
/** tally sizes novel, verified (same hash), vouched (not-modified) */
CrawledBytesHistotable crawledBytes = new CrawledBytesHistotable();
public CrawledBytesHistotable getCrawledBytes() {
return crawledBytes;
}
// TODO: fortify these against key explosion with bigmaps like other tallies
/** Keep track of the file types we see (mime type -> count) */
protected ObjectIdentityCache<String,AtomicLong> mimeTypeDistribution
@@ -26,6 +26,8 @@ import java.io.Writer;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
@@ -45,6 +47,7 @@ import org.restlet.resource.Variant;
import org.restlet.resource.WriterRepresentation;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.xml.sax.SAXException;
/**
* Restlet Resource which allows browsing the constructed beans in
@@ -59,6 +62,7 @@ public class BeanBrowseResource extends JobRelatedResource {
public BeanBrowseResource(Context ctx, Request req, Response res) throws ResourceException {
super(ctx, req, res);
getVariants().add(new Variant(MediaType.TEXT_HTML));
getVariants().add(new Variant(MediaType.APPLICATION_XML));
setModifiable(true); // accept POSTs
appCtx = cj.getJobContext();
beanPath = (String)req.getAttributes().get("beanPath");
@@ -71,9 +75,11 @@ public class BeanBrowseResource extends JobRelatedResource {
} else {
beanPath = "";
}
if(appCtx==null) {
if (appCtx == null) {
getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
getResponse().setEntity("No beans; crawl not yet built.",MediaType.TEXT_HTML);
// XXX xml?
getResponse().setEntity("No beans; crawl not yet built.", MediaType.TEXT_HTML);
//throw new ResourceException(404);
}
}
@@ -85,7 +91,6 @@ public class BeanBrowseResource extends JobRelatedResource {
String newVal = form.getFirstValue("newVal");
if(newVal!=null) {
int i = beanPath.indexOf(".");
String beanName = i<0?beanPath:beanPath.substring(0,i);
Object namedBean = appCtx.getBean(beanName);
@@ -93,13 +98,11 @@ public class BeanBrowseResource extends JobRelatedResource {
String propPath = beanPath.substring(i+1);
Object coercedVal = bwrap.convertIfNecessary(newVal, bwrap.getPropertyValue(propPath).getClass());
bwrap.setPropertyValue(propPath, coercedVal);
}
Reference ref = getRequest().getResourceRef();
ref.setPath(getBeansRefPath());
ref.addSegment(beanPath);
getResponse().redirectSeeOther(ref);
}
public String getBeansRefPath() {
@@ -114,20 +117,79 @@ public class BeanBrowseResource extends JobRelatedResource {
}
return path;
}
public Representation represent(Variant variant) throws ResourceException {
Representation representation = new WriterRepresentation(
MediaType.TEXT_HTML) {
public void write(Writer writer) throws IOException {
BeanBrowseResource.this.writeHtml(writer);
}
};
Representation representation;
if (variant.getMediaType() == MediaType.APPLICATION_XML) {
representation = new WriterRepresentation(MediaType.APPLICATION_XML) {
public void write(Writer writer) throws IOException {
try {
new XmlMarshaller(writer).marshalDocument("script", presentablify());
} catch (SAXException e) {
throw new IOException(e);
}
}
};
} else {
representation = new WriterRepresentation(
MediaType.TEXT_HTML) {
public void write(Writer writer) throws IOException {
BeanBrowseResource.this.writeHtml(writer);
}
};
}
// TODO: remove if not necessary in future?
representation.setCharacterSet(CharacterSet.UTF_8);
return representation;
}
protected LinkedHashMap<String,Object> presentablify() {
LinkedHashMap<String,Object> info = new LinkedHashMap<String,Object>();
info.put("crawlJobShortName", cj.getShortName());
info.put("crawlJobUrl", new Reference(getRequest().getResourceRef().getBaseRef(), "..").getTargetRef());
if (StringUtils.isNotBlank(beanPath)) {
info.put("beanPath", beanPath);
try {
int firstDot = beanPath.indexOf(".");
String beanName = firstDot<0?beanPath:beanPath.substring(0,firstDot);
Object namedBean = appCtx.getBean(beanName);
Object target;
if (firstDot < 0) {
target = namedBean;
info.put("bean", presentablifyObject(null, target, beanPath));
} else {
BeanWrapperImpl bwrap = new BeanWrapperImpl(namedBean);
String propPath = beanPath.substring(firstDot+1);
target = bwrap.getPropertyValue(propPath);
Class<?> type = bwrap.getPropertyType(propPath);
if(bwrap.isWritableProperty(propPath)
&& (bwrap.getDefaultEditor(type)!=null|| type == String.class)
&& !Collection.class.isAssignableFrom(type)) {
info.put("editable", true);
info.put("bean", presentablifyObject(null, target));
} else {
info.put("bean", presentablifyObject(null, target, beanPath));
}
}
} catch (BeansException e) {
info.put("problem", e.toString());
}
}
Collection<Object> nestedNames = new LinkedList<Object>();
Set<Object> alreadyWritten = new HashSet<Object>();
presentablifyNestedNames(appCtx.getBean("crawlController"), getBeansRefPath(), alreadyWritten, nestedNames);
for(String name: appCtx.getBeanDefinitionNames()) {
presentablifyNestedNames(appCtx.getBean(name), getBeansRefPath(), alreadyWritten, nestedNames);
}
info.put("allNamedCrawlBeans", nestedNames);
return info;
}
protected void writeHtml(Writer writer) {
PrintWriter pw = new PrintWriter(writer);
pw.println("<head><title>Crawl beans in "+cj.getShortName()+"</title></head>");
@@ -153,7 +215,7 @@ public class BeanBrowseResource extends JobRelatedResource {
String propPath = beanPath.substring(i+1);
target = bwrap.getPropertyValue(propPath);
Class type = bwrap.getPropertyType(propPath);
Class<?> type = bwrap.getPropertyType(propPath);
if(bwrap.isWritableProperty(propPath)
&& (bwrap.getDefaultEditor(type)!=null|| type == String.class)
&& !Collection.class.isAssignableFrom(type)) {
@@ -72,7 +72,7 @@ public class EditRepresentation extends CharacterRepresentation {
viewRef.setQuery(null);
pw.println("<a href='"+viewRef+"'>view</a>");
pw.println("<br/>");
pw.println("<textarea style='width:100%; height:100%' name='contents'>");
pw.println("<textarea style='width:100%;height:94%;background-color:#ffc;font-size:small;' name='contents'>");
StringEscapeUtils.escapeHtml(pw,fileRepresentation.getText());
pw.println("</textarea></form>");
pw.close();
@@ -24,12 +24,19 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.archive.crawler.framework.CrawlJob;
import org.archive.crawler.framework.Engine;
import org.archive.crawler.framework.CrawlController.State;
import org.archive.util.FileUtils;
import org.restlet.Context;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
@@ -41,6 +48,7 @@ import org.restlet.resource.Resource;
import org.restlet.resource.ResourceException;
import org.restlet.resource.Variant;
import org.restlet.resource.WriterRepresentation;
import org.xml.sax.SAXException;
/**
* Restlet Resource representing an Engine that may be used
@@ -54,15 +62,28 @@ public class EngineResource extends Resource {
super(ctx, req, res);
setModifiable(true);
getVariants().add(new Variant(MediaType.TEXT_HTML));
getVariants().add(new Variant(MediaType.APPLICATION_XML));
}
public Representation represent(Variant variant) throws ResourceException {
Representation representation = new WriterRepresentation(
MediaType.TEXT_HTML) {
public void write(Writer writer) throws IOException {
EngineResource.this.writeHtml(writer);
}
};
Representation representation;
if (variant.getMediaType() == MediaType.APPLICATION_XML) {
representation = new WriterRepresentation(MediaType.APPLICATION_XML) {
public void write(Writer writer) throws IOException {
try {
new XmlMarshaller(writer).marshalDocument("engine", presentablify());
} catch (SAXException e) {
throw new IOException(e);
}
}
};
} else {
representation = new WriterRepresentation(MediaType.TEXT_HTML) {
public void write(Writer writer) throws IOException {
EngineResource.this.writeHtml(writer);
}
};
}
// TODO: remove if not necessary in future?
representation.setCharacterSet(CharacterSet.UTF_8);
return representation;
@@ -126,6 +147,59 @@ public class EngineResource extends Resource {
getResponse().redirectSeeOther(getRequest().getOriginalRef());
}
protected List<String> getAvailableActions() {
List<String> actions = new LinkedList<String>();
actions.add("rescan");
actions.add("add");
actions.add("create");
return actions;
}
protected LinkedHashMap<String,Object> presentablify() {
String baseRef = getRequest().getResourceRef().getBaseRef().toString();
if(!baseRef.endsWith("/")) {
baseRef += "/";
}
LinkedHashMap<String,Object> info = new LinkedHashMap<String,Object>();
Engine engine = getEngine();
info.put("heritrixVersion", engine.getHeritrixVersion());
File jobsDir = FileUtils.tryToCanonicalize(engine.getJobsDir());
info.put("jobsDir", jobsDir.getAbsolutePath());
info.put("jobsDirUrl", baseRef + "jobsdir/");
info.put("availableActions", getAvailableActions());
info.put("heapReport", getEngine().heapReportData());
// re-scan job configs on each page load
ArrayList<CrawlJob> jobs = new ArrayList<CrawlJob>();
engine.findJobConfigs();
jobs.addAll(engine.getJobConfigs().values());
Collections.sort(jobs);
Collection<Map<String,Object>> jobsInfo = new LinkedList<Map<String,Object>>();
for (CrawlJob cj: jobs) {
// cj.writeHtmlTo(pw,"job/");
Map<String,Object> jobInfo = new LinkedHashMap<String, Object>();
jobInfo.put("shortName", cj.getShortName());
jobInfo.put("url", baseRef + "job/" + cj.getShortName());
jobInfo.put("isProfile", cj.isProfile());
jobInfo.put("launchCount", cj.getLaunchCount());
jobInfo.put("lastLaunch", cj.getLastLaunch());
File primaryConfig = FileUtils.tryToCanonicalize(cj.getPrimaryConfig());
jobInfo.put("primaryConfig", primaryConfig.getAbsolutePath());
jobInfo.put("primaryConfigUrl", baseRef + "job/" + cj.getShortName() + "/jobdir/" + primaryConfig.getName());
if (cj.getCrawlController() != null) {
jobInfo.put("crawlControllerState", cj.getCrawlController().getState());
if (cj.getCrawlController().getState() == State.FINISHED) {
jobInfo.put("crawlExitStatus", cj.getCrawlController().getCrawlExitStatus());
}
}
jobsInfo.add(jobInfo);
}
info.put("jobs", jobsInfo);
return info;
}
/**
* wraps a message in a styled div given messsage type
* @param msg message to be displayed
@@ -152,12 +226,7 @@ public class EngineResource extends Resource {
protected void writeHtml(Writer writer) {
Engine engine = getEngine();
String engineTitle = "Heritrix Engine "+engine.getHeritrixVersion();
File jobsDir = engine.getJobsDir();
try {
jobsDir = jobsDir.getCanonicalFile();
} catch (IOException ioe) {
// live with the noncanonical file
}
File jobsDir = FileUtils.tryToCanonicalize(engine.getJobsDir());
String baseRef = getRequest().getResourceRef().getBaseRef().toString();
if(!baseRef.endsWith("/")) {
baseRef += "/";
@@ -24,8 +24,11 @@ import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -34,6 +37,7 @@ import org.apache.commons.lang.StringUtils;
import org.archive.crawler.framework.CrawlJob;
import org.archive.crawler.framework.Engine;
import org.restlet.Context;
import org.restlet.data.Reference;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.resource.Resource;
@@ -64,6 +68,55 @@ public abstract class JobRelatedResource extends Resource {
return ((EngineApplication)getApplication()).getEngine();
}
protected void presentablifyNestedNames(Object obj, String prefix,
Set<Object> alreadyWritten, Collection<Object> namedBeans) {
if (obj == null || alreadyWritten.contains(obj)
|| obj.getClass().getName().startsWith("org.springframework.")) {
return;
}
Reference baseRef = getRequest().getResourceRef().getBaseRef();
if (getBeanToNameMap().containsKey(obj)) {
// this object is itself a named bean
Map<String, Object> bean = new LinkedHashMap<String, Object>();
bean.put("name", getBeanToNameMap().get(obj));
bean.put("url", new Reference(baseRef, "../beans/" + getBeanToNameMap().get(obj)).getTargetRef());
bean.put("class", obj.getClass().getName());
namedBeans.add(bean);
// nest children
namedBeans = new LinkedList<Object>();
bean.put("children", namedBeans);
}
if (!alreadyWritten.contains(obj)) {
alreadyWritten.add(obj);
BeanWrapperImpl bwrap = new BeanWrapperImpl(obj);
for (PropertyDescriptor pd : bwrap.getPropertyDescriptors()) {
if (pd.getReadMethod() != null) {
String propName = pd.getName();
Object propValue = bwrap.getPropertyValue(propName);
presentablifyNestedNames(propValue, prefix, alreadyWritten, namedBeans);
}
}
if (obj.getClass().isArray()) {
List<?> list = Arrays.asList(obj);
for (int i = 0; i < list.size(); i++) {
presentablifyNestedNames(list.get(i), prefix,
alreadyWritten, namedBeans);
}
}
if (obj instanceof Iterable<?>) {
for (Object next : (Iterable<?>) obj) {
presentablifyNestedNames(next, prefix, alreadyWritten, namedBeans);
}
}
}
}
/**
* Starting at (and including) the given object, write a hierarchical
* list of named beans in HTML to the PrintWriter.
@@ -99,7 +152,7 @@ public abstract class JobRelatedResource extends Resource {
}
}
if(obj.getClass().isArray()) {
List list = Arrays.asList(obj);
List<?> list = Arrays.asList(obj);
for(int i = 0; i < list.size(); i++) {
writeNestedNames(pw, list.get(i), prefix, alreadyWritten);
}
@@ -137,6 +190,127 @@ public abstract class JobRelatedResource extends Resource {
writeObject(pw, field, object, new HashSet<Object>(), beanPath);
}
protected Map<String, Object> presentablifyObject(String field, Object object) {
return presentablifyObject(field, object, new HashSet<Object>(), null);
}
protected Map<String, Object> presentablifyObject(String field, Object object, String beanPath) {
return presentablifyObject(field, object, new HashSet<Object>(), beanPath);
}
protected Map<String, Object> presentablifyObject(String field, Object object, HashSet<Object> alreadyWritten, String beanPathPrefix) {
Map<String,Object> info = new LinkedHashMap<String, Object>();
Reference baseRef = getRequest().getResourceRef().getBaseRef();
String beanPath = beanPathPrefix;
if(StringUtils.isNotBlank(field)) {
info.put("field", field);
if(StringUtils.isNotBlank(beanPathPrefix)) {
if(beanPathPrefix.endsWith(".")) {
beanPath += field;
} else if (beanPathPrefix.endsWith("[")) {
beanPath += field + "]";
}
info.put("url", new Reference(baseRef, "../beans/" + beanPath).getTargetRef());
}
}
String key = getBeanToNameMap().get(object);
if (object == null) {
info.put("propValue", null);
return info;
}
if (object instanceof String || BeanUtils.isSimpleValueType(object.getClass()) || object instanceof File) {
info.put("class", object.getClass().getName());
info.put("propValue", object);
return info;
}
if (alreadyWritten.contains(object)) {
info.put("propValuePreviouslyDescribed", null);
return info;
}
alreadyWritten.add(object); // guard against repeats and cycles
if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(field)) {
info.put("key", key);
info.put("url", new Reference(baseRef, "../beans/" + key).getTargetRef());
return info;
}
info.put("class", object.getClass().getName());
Collection<Object> properties = new LinkedList<Object>();
BeanWrapperImpl bwrap = new BeanWrapperImpl(object);
for (PropertyDescriptor pd : bwrap.getPropertyDescriptors()) {
if (object instanceof DescriptorUpdater) {
((DescriptorUpdater) object).updateDescriptor(pd);
} else {
defaultUpdateDescriptor(pd);
}
if (pd.getReadMethod() != null && !pd.isHidden()) {
String propName = pd.getName();
if (beanPath != null) {
beanPathPrefix = beanPath + ".";
}
Object propValue = presentablifyObject(propName,
bwrap.getPropertyValue(propName),
alreadyWritten, beanPathPrefix);
properties.add(propValue);
}
}
if (properties.size() > 0) {
info.put("properties", properties);
}
Collection<Object> propValues = new LinkedList<Object>();
if(object.getClass().isArray()) {
Object[] array = (Object[])object;
for(int i = 0; i < array.length; i++) {
if(beanPath!=null) {
beanPathPrefix = beanPath+"[";
}
// TODO: protect against overlong content?
propValues.add(presentablifyObject(i + "", array[i],
alreadyWritten, beanPathPrefix));
}
}
if (object instanceof List<?>) {
List<?> list = (List<?>) object;
for (int i = 0; i < list.size(); i++) {
if (beanPath != null) {
beanPathPrefix = beanPath + "[";
}
// TODO: protect against overlong content?
propValues.add(presentablifyObject(i + "", list.get(i),
alreadyWritten, beanPathPrefix));
}
} else if (object instanceof Iterable<?>) {
for (Object next : (Iterable<?>) object) {
propValues.add(presentablifyObject("#", next, alreadyWritten,
beanPathPrefix));
}
}
if (object instanceof Map<?,?>) {
for (Object next : ((Map<?,?>) object).entrySet()) {
// TODO: protect against giant maps?
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) next;
if (beanPath != null) {
beanPathPrefix = beanPath + "[";
}
propValues.add(presentablifyObject(entry.getKey().toString(),
entry.getValue(), alreadyWritten, beanPathPrefix));
}
}
if (propValues.size() > 0) {
info.put("propValue", propValues);
}
return info;
}
/**
* Get a map giving object beanNames.
*
@@ -146,7 +320,7 @@ public abstract class JobRelatedResource extends Resource {
if(beanToNameMap == null) {
beanToNameMap = new IdentityHashMap<Object, String>();
for(Object entryObj : cj.getJobContext().getBeansOfType(Object.class).entrySet()) {
Map.Entry entry = (Map.Entry)entryObj;
Map.Entry<?, ?> entry = (Map.Entry<?, ?>)entryObj;
beanToNameMap.put(entry.getValue(),(String)entry.getKey());
}
}
@@ -242,7 +416,7 @@ public abstract class JobRelatedResource extends Resource {
}
}
if(object instanceof List) {
List list = (List)object;
List<?> list = (List<?>)object;
for(int i = 0; i < list.size(); i++) {
if(beanPath!=null) {
beanPathPrefix = beanPath+"[";
@@ -258,7 +432,7 @@ public abstract class JobRelatedResource extends Resource {
if(object instanceof Map) {
for (Object next : ((Map)object).entrySet()) {
// TODO: protect against giant maps?
Map.Entry entry = (Map.Entry)next;
Map.Entry<?, ?> entry = (Map.Entry<?, ?>)next;
if(beanPath!=null) {
beanPathPrefix = beanPath+"[";
}
@@ -24,8 +24,14 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.lang.StringEscapeUtils;
@@ -33,6 +39,7 @@ import org.apache.commons.lang.StringUtils;
import org.archive.checkpointing.Checkpoint;
import org.archive.crawler.framework.CrawlJob;
import org.archive.crawler.framework.Engine;
import org.archive.crawler.framework.CrawlController.State;
import org.archive.crawler.reporting.AlertHandler;
import org.archive.crawler.reporting.AlertThreadGroup;
import org.archive.crawler.reporting.Report;
@@ -45,6 +52,7 @@ import org.restlet.Context;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Reference;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
@@ -53,6 +61,7 @@ import org.restlet.resource.Resource;
import org.restlet.resource.ResourceException;
import org.restlet.resource.Variant;
import org.restlet.resource.WriterRepresentation;
import org.xml.sax.SAXException;
/**
* Restlet Resource representing a single local CrawlJob inside an
@@ -64,12 +73,15 @@ public class JobResource extends Resource {
public static final IOFileFilter EDIT_FILTER =
FileUtils.getRegexFileFilter(".*\\.((c?xml)|(txt))$");
CrawlJob cj;
private static final Logger logger = Logger.getLogger(JobResource.class.getName());
protected CrawlJob cj;
public JobResource(Context ctx, Request req, Response res) throws ResourceException {
super(ctx, req, res);
setModifiable(true);
getVariants().add(new Variant(MediaType.TEXT_HTML));
getVariants().add(new Variant(MediaType.APPLICATION_XML));
cj = getEngine().getJob(TextUtils.urlUnescape((String)req.getAttributes().get("job")));
if(cj==null) {
throw new ResourceException(404);
@@ -77,17 +89,131 @@ public class JobResource extends Resource {
}
public Representation represent(Variant variant) throws ResourceException {
Representation representation = new WriterRepresentation(
MediaType.TEXT_HTML) {
Representation representation = null;
if (variant.getMediaType() == MediaType.APPLICATION_XML) {
representation = new WriterRepresentation(MediaType.APPLICATION_XML) {
public void write(Writer writer) throws IOException {
try {
new XmlMarshaller(writer).marshalDocument("job", presentablify());
} catch (SAXException e) {
throw new IOException(e);
}
}
};
} else {
representation = new WriterRepresentation(MediaType.TEXT_HTML) {
public void write(Writer writer) throws IOException {
JobResource.this.writeHtml(writer);
}
};
}
// TODO: remove if not necessary in future?
// honor requested charset?
representation.setCharacterSet(CharacterSet.UTF_8);
return representation;
}
protected LinkedHashMap<String,Object> presentablify() {
LinkedHashMap<String,Object> info = new LinkedHashMap<String,Object>();
String baseRef = getRequest().getResourceRef().getBaseRef().toString();
if (!baseRef.endsWith("/")) {
baseRef += "/";
}
Reference baseRefRef = new Reference(baseRef);
info.put("shortName", cj.getShortName());
if (cj.getCrawlController() != null) {
info.put("crawlControllerState", cj.getCrawlController().getState());
if (cj.getCrawlController().getState() == State.FINISHED) {
info.put("crawlExitStatus", cj.getCrawlController().getCrawlExitStatus());
}
}
info.put("statusDescription", getJobStatusDescription());
info.put("availableActions", getAvailableActions());
info.put("launchCount", cj.getLaunchCount());
info.put("lastLaunch", cj.getLastLaunch());
info.put("isProfile", cj.isProfile());
File primaryConfig = FileUtils.tryToCanonicalize(cj.getPrimaryConfig());
info.put("primaryConfig", primaryConfig.getAbsolutePath());
info.put("primaryConfigUrl", baseRef + "jobdir/" + primaryConfig.getName());
if (cj.getJobLog().exists()) try {
List<String> logLines = new LinkedList<String>();
FileUtils.pagedLines(cj.getJobLog(), -1, -5, logLines);
info.put("jobLogTail", logLines);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
if (cj.hasApplicationContext()) {
info.put("uriTotalsReport", cj.uriTotalsReportData());
info.put("sizeTotalsReport", cj.sizeTotalsReportData());
info.put("rateReport", cj.rateReportData());
info.put("loadReport", cj.loadReportData());
info.put("elapsedReport", cj.elapsedReportData());
info.put("threadReport", cj.threadReportData());
info.put("frontierReport", cj.frontierReportData());
info.put("heapReport", getEngine().heapReportData());
if(cj.isRunning() || (cj.hasApplicationContext() && !cj.isLaunchable())) {
try {
List<String> logLines = new LinkedList<String>();
FileUtils.pagedLines(
cj.getCrawlController().getLoggerModule().getCrawlLogPath().getFile(),
-1, -10, logLines);
info.put("crawlLogTail", logLines);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
}
List<Map<String,String>> configFiles = new LinkedList<Map<String,String>>();
for(String cppp: cj.getConfigPaths().keySet()) {
Map<String,String> configFileInfo = new LinkedHashMap<String,String>();
configFileInfo.put("key", cppp);
File path = FileUtils.tryToCanonicalize(cj.getConfigPaths().get(cppp).getFile());
configFileInfo.put("path", path.getAbsolutePath());
Reference urlRef = new Reference(baseRefRef, getHrefPath(path, cj)).getTargetRef();
configFileInfo.put("url", urlRef.toString());
configFiles.add(configFileInfo);
}
info.put("configFiles", configFiles);
return info;
}
protected Set<String> getAvailableActions() {
Set<String> actions = new LinkedHashSet<String>();
if (!cj.hasApplicationContext()) {
actions.add("build");
}
if (!cj.isProfile() && cj.isLaunchable()) {
actions.add("launch");
}
if (cj.isPausable()) {
actions.add("pause");
}
if (cj.isUnpausable()) {
actions.add("unpause");
}
if (cj.getCheckpointService() != null && cj.isRunning()) {
actions.add("checkpoint");
}
if (cj.isRunning()) {
actions.add("terminate");
}
if (cj.hasApplicationContext()) {
actions.add("teardown");
}
return actions;
}
protected void writeHtml(Writer writer) {
PrintWriter pw = new PrintWriter(writer);
String jobTitle = cj.getShortName() + " - "
@@ -420,7 +546,9 @@ public class JobResource extends Resource {
@Override
public void acceptRepresentation(Representation entity) throws ResourceException {
// copy op?
Form form = getRequest().getEntityAsForm();
Form form = null;
try {
form = getRequest().getEntityAsForm();
String copyTo = form.getFirstValue("copyTo");
if(copyTo!=null) {
copyJob(copyTo,"on".equals(form.getFirstValue("asProfile")));
@@ -460,8 +588,13 @@ public class JobResource extends Resource {
cj.terminate();
}
AlertThreadGroup.setThreadLogger(null);
// default: redirect to GET self
getResponse().redirectSeeOther(getRequest().getOriginalRef());
} catch (IllegalStateException e) {
logger.log(Level.WARNING, "problem accepting input (redirecting to GET self anyway): " + e, e);
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, e, "problem accepting input");
}
}
protected void copyJob(String copyTo, boolean asProfile) throws ResourceException {
@@ -23,7 +23,11 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
@@ -37,12 +41,14 @@ import org.restlet.Context;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Reference;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.resource.Representation;
import org.restlet.resource.ResourceException;
import org.restlet.resource.Variant;
import org.restlet.resource.WriterRepresentation;
import org.xml.sax.SAXException;
/**
* Restlet Resource which runs an arbitrary script, which is supplied
@@ -72,6 +78,7 @@ public class ScriptResource extends JobRelatedResource {
super(ctx, req, res);
setModifiable(true);
getVariants().add(new Variant(MediaType.TEXT_HTML));
getVariants().add(new Variant(MediaType.APPLICATION_XML));
}
@Override
@@ -113,17 +120,110 @@ public class ScriptResource extends JobRelatedResource {
}
public Representation represent(Variant variant) throws ResourceException {
Representation representation = new WriterRepresentation(
MediaType.TEXT_HTML) {
public void write(Writer writer) throws IOException {
ScriptResource.this.writeHtml(writer);
}
};
Representation representation;
if (variant.getMediaType() == MediaType.APPLICATION_XML) {
representation = new WriterRepresentation(MediaType.APPLICATION_XML) {
public void write(Writer writer) throws IOException {
try {
new XmlMarshaller(writer).marshalDocument("script", presentablify());
} catch (SAXException e) {
throw new IOException(e);
}
}
};
} else {
representation = new WriterRepresentation(MediaType.TEXT_HTML) {
public void write(Writer writer) throws IOException {
ScriptResource.this.writeHtml(writer);
}
};
}
// TODO: remove if not necessary in future?
representation.setCharacterSet(CharacterSet.UTF_8);
return representation;
}
protected List<String> getAvailableActions() {
List<String> actions = new LinkedList<String>();
actions.add("rescan");
actions.add("add");
actions.add("create");
return actions;
}
protected Collection<Map<String,String>> getAvailableScriptEngines() {
List<Map<String,String>> engines = new LinkedList<Map<String,String>>();
for (ScriptEngineFactory f: FACTORIES) {
Map<String,String> engine = new LinkedHashMap<String, String>();
engine.put("engine", f.getNames().get(0));
engine.put("language", f.getLanguageName());
engines.add(engine);
}
return engines;
}
protected Collection<Map<String,String>> getAvailableGlobalVariables() {
List<Map<String,String>> vars = new LinkedList<Map<String,String>>();
Map<String,String> var;
var = new LinkedHashMap<String,String>();
var.put("variable", "rawOut");
var.put("description", "a PrintWriter for arbitrary text output to this page");
vars.add(var);
var = new LinkedHashMap<String,String>();
var.put("variable", "htmlOut");
var.put("description", "a PrintWriter for HTML output to this page");
vars.add(var);
var = new LinkedHashMap<String,String>();
var.put("variable", "job");
var.put("description", "the current CrawlJob instance");
vars.add(var);
var = new LinkedHashMap<String,String>();
var.put("variable", "appCtx");
var.put("description", "current job ApplicationContext, if any");
vars.add(var);
var = new LinkedHashMap<String,String>();
var.put("variable", "scriptResource");
var.put("description", "the ScriptResource implementing this page, which offers utility methods");
vars.add(var);
return vars;
}
protected LinkedHashMap<String,Object> presentablify() {
LinkedHashMap<String,Object> info = new LinkedHashMap<String,Object>();
String baseRef = getRequest().getResourceRef().getBaseRef().toString();
if(!baseRef.endsWith("/")) {
baseRef += "/";
}
Reference baseRefRef = new Reference(baseRef);
info.put("crawlJobShortName", cj.getShortName());
info.put("crawlJobUrl", new Reference(baseRefRef, "..").getTargetRef());
info.put("availableScriptEngines", getAvailableScriptEngines());
info.put("availableGlobalVariables", getAvailableGlobalVariables());
if(linesExecuted>0) {
info.put("linesExecuted", linesExecuted);
}
if(ex!=null) {
info.put("exception", ex);
}
if(StringUtils.isNotBlank(rawOutput)) {
info.put("rawOutput", rawOutput);
}
if(StringUtils.isNotBlank(htmlOutput)) {
info.put("htmlOutput", htmlOutput);
}
return info;
}
protected void writeHtml(Writer writer) {
PrintWriter pw = new PrintWriter(writer);
pw.println("<head><title>Script in "+cj.getShortName()+"</title></head>");
@@ -0,0 +1,86 @@
/*
* This file is part of the Heritrix web crawler (crawler.archive.org).
*
* Licensed to the Internet Archive (IA) by one or more individual
* contributors.
*
* The IA licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.archive.crawler.restlet;
import java.io.Writer;
import java.util.Map;
import org.restlet.util.XmlWriter;
import org.xml.sax.SAXException;
/**
* @contributor nlevitt
*/
public class XmlMarshaller {
protected XmlWriter xmlWriter;
public XmlMarshaller(Writer writer) throws SAXException {
xmlWriter = new XmlWriter(writer);
// this looks ok, but makes the response much bigger - pipe to
// "xmllint --format -" instead
// xmlWriter.setDataFormat(true);
// xmlWriter.setIndentStep(2);
}
public void marshalDocument(String rootTag, Object content) throws SAXException {
xmlWriter.startDocument();
marshal(rootTag, content);
xmlWriter.endDocument(); // calls flush()
}
protected void marshal(String key, Object value) throws SAXException {
if (value == null) {
xmlWriter.emptyElement(key);
} else if (value instanceof Map<?,?>) {
marshal(key, (Map<?,?>) value);
} else if (value instanceof Iterable<?>) {
marshal(key, (Iterable<?>) value);
} else {
xmlWriter.dataElement(key, value.toString());
}
}
protected void marshal(String key, Map<?,?> map) throws SAXException {
xmlWriter.startElement(key);
for (Map.Entry<?,?> entry: map.entrySet()) {
marshal(entry.getKey().toString(), entry.getValue());
}
xmlWriter.endElement(key);
}
protected void marshal(String key, Iterable<?> iterable) throws SAXException {
xmlWriter.startElement(key);
for (Object item: iterable) {
marshal(item);
}
xmlWriter.endElement(key);
}
// something we have no name for goes in a <value/> tag
protected void marshal(Object item) throws SAXException {
if (item instanceof Map.Entry<?, ?>) {
// if it happens to have a name, use it
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) item;
marshal(entry.getKey().toString(), entry.getValue());
} else {
marshal("value", item);
}
}
}
@@ -33,9 +33,9 @@ implements CoreAttributeConstants {
public static final String NOTMODIFIED = "not-modified";
public static final String DUPLICATE = "dup-by-hash";
public static final String NOVEL = "novel";
public static final String NOTMODIFIEDCOUNT="not-modified-count";
public static final String DUPLICATECOUNT="dup-by-hash-count";
public static final String NOVELCOUNT="novel-count";
public static final String NOTMODIFIEDCOUNT = "not-modified-count";
public static final String DUPLICATECOUNT = "dup-by-hash-count";
public static final String NOVELCOUNT = "novel-count";
public CrawledBytesHistotable() {
super();