Revision: 16612
http://sourceforge.net/p/gate/code/16612
Author: valyt
Date: 2013-03-22 10:27:20 +0000 (Fri, 22 Mar 2013)
Log Message:
-----------
- more work on the ARC enumerator / naming strategy / input handler triad to
make it work with the new style document IDs.
- (untested) implementation for reading ARC records from a remote URL, using
byte ranges.
- removed some obsolete code.
Modified Paths:
--------------
gcp/trunk/src/gate/cloud/io/arc/ARCDocumentEnumerator.java
gcp/trunk/src/gate/cloud/io/arc/ARCDocumentNamingStrategy.java
gcp/trunk/src/gate/cloud/io/arc/ARCInputHandler.java
gcp/trunk/src/gate/cloud/io/file/SimpleNamingStrategy.java
Modified: gcp/trunk/src/gate/cloud/io/arc/ARCDocumentEnumerator.java
===================================================================
--- gcp/trunk/src/gate/cloud/io/arc/ARCDocumentEnumerator.java 2013-03-22
02:43:36 UTC (rev 16611)
+++ gcp/trunk/src/gate/cloud/io/arc/ARCDocumentEnumerator.java 2013-03-22
10:27:20 UTC (rev 16612)
@@ -218,12 +218,14 @@
if(mimeTypes == null || mimeTypes.contains(header.getMimetype())) {
// found a good document
Object2ObjectArrayMap<String, String> attrs =
- new Object2ObjectArrayMap<String, String>(new String[2],
- new String[2]);
+ new Object2ObjectArrayMap<String, String>(new String[3],
+ new String[3]);
attrs.put(ARCInputHandler.RECORD_OFFSET_ATTR,
Long.toString(header.getOffset()));
attrs.put(ARCInputHandler.RECORD_LENGTH_ATTR,
Long.toString(recordLength));
+ attrs.put(ARCInputHandler.RECORD_POSITION_ATTR,
+ Long.toString(inputSequence));
next = new DocumentID(header.getUrl(), attrs);
return;
}
Modified: gcp/trunk/src/gate/cloud/io/arc/ARCDocumentNamingStrategy.java
===================================================================
--- gcp/trunk/src/gate/cloud/io/arc/ARCDocumentNamingStrategy.java
2013-03-22 02:43:36 UTC (rev 16611)
+++ gcp/trunk/src/gate/cloud/io/arc/ARCDocumentNamingStrategy.java
2013-03-22 10:27:20 UTC (rev 16612)
@@ -18,6 +18,9 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.log4j.Logger;
+
+import gate.cloud.batch.DocumentID;
import gate.cloud.io.file.SimpleNamingStrategy;
import gate.util.GateException;
@@ -25,13 +28,16 @@
* A naming strategy to convert document IDs suitable for use with
* an {@link ARCInputHandler} to file paths suitable for saving the
* results of their processing. It assumes that the document IDs
- * start with one or more digits, and arranges the output files under
- * a single document root in numbered directories constructed by padding
- * the document number to the left with zeros and creating intermediate
- * directories according to a configurable pattern. The default
- * pattern is '3/3', which pads the numbers to a minimum of 6 digits
- * and then splits them up into groups of three. The remainder of the
- * ID after the number is cleaned up to remove any URL protocol like
+ * use the record URL as the id text (see {@link DocumentID#getIdText()}), and
+ * that the document ID attributes (see {@link DocumentID#getAttributes()})
+ * include a numeric value for {@link ARCInputHandler#RECORD_POSITION_ATTR}
that
+ * is used as a sequence number.
+ * It arranges the output files under a single document root in numbered
+ * directories constructed by padding the document sequence number to the left
+ * with zeros and creating intermediate directories according to a configurable
+ * pattern. The default pattern is '3/3', which pads the numbers to a minimum
+ * of 6 digits and then splits them up into groups of three. The remainder of
+ * the ID after the number is cleaned up to remove any URL protocol like
* http:// and any query string or fragment. Any sequences of non-ASCII
* characters are removed and any remaining slashes or colons are replaced
* with underscores. For example with the default pattern, the document
@@ -44,20 +50,14 @@
*
*/
public class ARCDocumentNamingStrategy extends SimpleNamingStrategy {
+
+ private static final Logger logger =
Logger.getLogger(ARCDocumentEnumerator.class);
+
protected String pattern;
protected long[] divisors;
/**
- * Regular expression used to parse the document IDs. It extracts
- * digits at the beginning, then ignores anything up to the first
- * ://, then extracts everything between that and the first question
- * mark or hash, if any, or the end of the string otherwise.
- */
- protected static final Pattern ID_PATTERN = Pattern.compile(
- "^(\\d+)_*(?:.*?://)?(.*?)(?:\\?.*)?(?:#.*)?$");
-
- /**
* A pattern matching any sequence of non-ascii characters.
*/
protected static final Pattern NON_ASCII_PATTERN = Pattern.compile(
@@ -112,14 +112,25 @@
pattern = patternBuilder.toString();
}
- protected String relativePathFor(String id) {
+ @Override
+ protected String relativePathFor(DocumentID id) {
StringBuffer pathBuilder = new StringBuffer();
- Matcher idMatcher = ID_PATTERN.matcher(id);
- if(!idMatcher.find()) {
- throw new IllegalArgumentException("Un-parsable document ID " + id);
+
+ // we use the record archive position as the id number, and we then
+ // distribute the output files into a directory structure.
+ String posStr =
id.getAttributes().get(ARCInputHandler.RECORD_POSITION_ATTR);
+ // default value is 0, which means that all input records with no explicit
+ // position will end up in files called: 000/.../000/URL_as_file_name
+ long idNum = 0;
+ if(posStr != null && posStr.length() > 0) {
+ try {
+ idNum = Long.parseLong(posStr);
+ } catch (NumberFormatException e) {
+ logger.warn("Invalid record position value (not an integer number): " +
+ posStr);
+ }
}
- long idNum = Long.parseLong(idMatcher.group(1));
-
+
// build up the arguments for the format string by dividing the
// id number by the relevant divisors
Object[] formatArgs = new Object[divisors.length];
@@ -129,9 +140,12 @@
}
// write the numeric bit of the relative path
- new Formatter(pathBuilder).format(pattern, formatArgs);
+ Formatter formatter = new Formatter(pathBuilder);
+ formatter.format(pattern, formatArgs);
+ formatter.close();
- String remaining = idMatcher.group(2);
+ // the rest of the output file path is constructed from the record URL
+ String remaining = id.getIdText();
if(remaining != null && remaining.length() > 0) {
// append an underscore and the cleaned-up remaining part of the name
pathBuilder.append("_");
Modified: gcp/trunk/src/gate/cloud/io/arc/ARCInputHandler.java
===================================================================
--- gcp/trunk/src/gate/cloud/io/arc/ARCInputHandler.java 2013-03-22
02:43:36 UTC (rev 16611)
+++ gcp/trunk/src/gate/cloud/io/arc/ARCInputHandler.java 2013-03-22
10:27:20 UTC (rev 16612)
@@ -11,24 +11,38 @@
*/
package gate.cloud.io.arc;
-import static gate.cloud.io.IOConstants.*;
+import static gate.cloud.io.IOConstants.PARAM_ARC_FILE_LOCATION;
+import static gate.cloud.io.IOConstants.PARAM_BATCH_FILE_LOCATION;
+import static gate.cloud.io.IOConstants.PARAM_DEFAULT_ENCODING;
+import static gate.cloud.io.IOConstants.PARAM_MIME_TYPE;
+import gate.Document;
+import gate.Factory;
+import gate.FeatureMap;
+import gate.cloud.batch.DocumentID;
+import gate.cloud.io.DocumentData;
+import gate.cloud.io.InputHandler;
+import gate.cloud.util.ByteArrayURLStreamHandler;
+import gate.util.GateException;
import it.unimi.dsi.fastutil.longs.LongArrayList;
+
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URL;
+import java.net.URLConnection;
import java.text.ParseException;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.log4j.Logger;
import org.archive.io.ArchiveRecord;
@@ -39,18 +53,26 @@
import org.archive.io.arc.ARCRecordMetaData;
import org.archive.util.ArchiveUtils;
-import gate.Document;
-import gate.Factory;
-import gate.FeatureMap;
-import gate.cloud.batch.DocumentID;
-import gate.cloud.batch.PooledDocumentProcessor;
-import gate.cloud.io.DocumentData;
-import gate.cloud.io.InputHandler;
-import gate.cloud.util.ByteArrayURLStreamHandler;
-import gate.util.GateException;
-
/**
* An input handler that reads from an ARC file.
+ * It assumes that the document IDs use the record URL as the id text
+ * (see {@link DocumentID#getIdText()}), and that the document ID attributes
+ * (see {@link DocumentID#getAttributes()}) include:
+ * <ul>
+ * <li>{@link #RECORD_OFFSET_ATTR}: the byte offset (inside the .arc file) for
+ * the required record.</li>
+ * <li>{@link #RECORD_LENGTH_ATTR}: the length (in bytes) for the required
+ * record.</li>
+ * <li>{@link #RECORD_POSITION_ATTR}: a numeric value for that
+ * is used as a sequence number. If the IDs are generated by an
+ * {@link ARCDocumentEnumerator}, the this attribute will contain the actual
+ * record position inside the .arc file.</li>
+ * <li>{@link #ARC_URL_ATTR} (optional): the full URL where the .arc file is
+ * available. This can be used to override the arc file configured on the
+ * actual input handler. For example, when a batch uses records from multiple
+ * .arc file as input, the arc file cannot be configured on the input handler,
+ * so each document ID must include one instead.
+ * </ul>
*/
public class ARCInputHandler implements InputHandler {
@@ -65,18 +87,27 @@
/**
* Name for an attribute used when generating {@link DocumentID} values for
* the {@link ARCInputHandler}. This attribute stores (a String
- * representation) of the Long byte offset for the input .record
+ * representation) of the Long byte offset for the input record.
+ *
+ * Value: {@value}
*/
static final String RECORD_OFFSET_ATTR = "recordOffset";
/**
* Name for an attribute used when generating {@link DocumentID} values for
* the {@link ARCInputHandler}. This attribute stores (a String
- * representation) of the Long byte length for the input .record
+ * representation) of the Long byte length for the input record.
*/
static final String RECORD_LENGTH_ATTR = "recordLength";
+
+ /**
+ * Name for an attribute used when generating {@link DocumentID} values for
+ * the {@link ARCInputHandler}. This attribute stores (a String
+ * representation) of the integer position inside the archive for the input
+ * record.
+ */
+ static final String RECORD_POSITION_ATTR = "recordPosition";
-
/**
* Name for an attribute used when generating {@link DocumentID} values for
* the {@link ARCInputHandler}. This attribute stores the URL where the input
@@ -115,22 +146,6 @@
protected String mimeType;
/**
- * Pool of archive readers.
- */
-// protected BlockingQueue<ARCReader> readers;
-
- /**
- * The index from archive record number to offset.
- */
- protected long[] index;
-
- /**
- * Regular expression pattern matching any sequence of digits
- * at the start of a string.
- */
- protected static final Pattern STARTING_DIGITS_PATTERN =
Pattern.compile("^(\\d+)");
-
- /**
* Regular expression pattern matching the "charset" from an HTTP
* Content-type header.
*/
@@ -140,29 +155,28 @@
GateException {
// ARC file
String arcFileStr = configData.get(PARAM_ARC_FILE_LOCATION);
- if(arcFileStr == null || arcFileStr.trim().length() == 0) {
- throw new IllegalArgumentException(
- "No value was provided for the required parameter \""
- + PARAM_ARC_FILE_LOCATION + "\"!");
+ if(arcFileStr != null) {
+ String batchFileStr = configData.get(PARAM_BATCH_FILE_LOCATION);
+ if(batchFileStr != null) {
+ batchDir = new File(batchFileStr).getParentFile();
+ }
+ arcFile = new File(arcFileStr);
+ if(!arcFile.isAbsolute()) {
+ arcFile = new File(batchDir, arcFileStr);
+ }
+ if(!arcFile.exists()) {
+ throw new IllegalArgumentException("File \"" + arcFile
+ + "\", provided as value for required parameter \""
+ + PARAM_ARC_FILE_LOCATION + "\", does not exist!");
+ }
+ if(!arcFile.isFile()) {
+ throw new IllegalArgumentException("File \"" + arcFile
+ + "\", provided as value for required parameter \""
+ + PARAM_ARC_FILE_LOCATION + "\", is not a file!");
+ }
+ } else {
+ arcFile = null;
}
- String batchFileStr = configData.get(PARAM_BATCH_FILE_LOCATION);
- if(batchFileStr != null) {
- batchDir = new File(batchFileStr).getParentFile();
- }
- arcFile = new File(arcFileStr);
- if(!arcFile.isAbsolute()) {
- arcFile = new File(batchDir, arcFileStr);
- }
- if(!arcFile.exists()) {
- throw new IllegalArgumentException("File \"" + arcFile
- + "\", provided as value for required parameter \""
- + PARAM_ARC_FILE_LOCATION + "\", does not exist!");
- }
- if(!arcFile.isFile()) {
- throw new IllegalArgumentException("File \"" + arcFile
- + "\", provided as value for required parameter \""
- + PARAM_ARC_FILE_LOCATION + "\", is not a file!");
- }
defaultEncoding = configData.get(PARAM_DEFAULT_ENCODING);
if(defaultEncoding == null) {
@@ -172,87 +186,68 @@
mimeType = configData.get(PARAM_MIME_TYPE);
}
- public void init() throws IOException, GateException {
- // build index
- logger.info("Building index for ARC file " + arcFile.getAbsolutePath()
- + " - this may take some time...");
- int recordNum = 0;
- ARCReader reader = ARCReaderFactory.get(arcFile);
- LongArrayList indexList = new LongArrayList();
- try {
- Iterator<ArchiveRecord> iter = reader.iterator();
- while(iter.hasNext()) {
- ARCRecord record = (ARCRecord)iter.next();
- indexList.add(record.getMetaData().getOffset());
- if(recordNum++ % 1000 == 0) {
- logger.debug("Processed " + (recordNum - 1) + " records");
- }
- }
+ public void init() throws IOException, GateException { }
+
+ public DocumentData getInputDocument(DocumentID id) throws IOException,
GateException {
+ if(id.getAttributes() == null) {
+ throw new IllegalArgumentException(
+ "Document IDs within an ARC must include \"" + RECORD_OFFSET_ATTR +
+ "\" and \"" + RECORD_LENGTH_ATTR + "\" attributes; \"" +
+ id.toString() + "\" does not.");
}
- finally {
- reader.close();
+ String arcUrlStr = id.getAttributes().get(ARC_URL_ATTR);
+ if(arcFile == null && (arcUrlStr == null || arcUrlStr.length() == 0)) {
+ throw new IllegalArgumentException(
+ "No .arc file was configured for this input handler, so the " +
+ "document IDs must provide a \"" + ARC_URL_ATTR +
+ "\" attribute; \"" + id.toString() + "\" does not.");
}
- // turn the list into an array
- indexList.trim();
- index = indexList.elements();
- logger.info("Finished indexing ARC file " + arcFile.getAbsolutePath()
- + ", " + index.length + " records total.");
-
-// readers = new LinkedBlockingQueue<ARCReader>();
- }
-
- public DocumentData getInputDocument(DocumentID id) throws IOException,
GateException {
- ARCReader reader = ARCReaderFactory.get(arcFile);
- reader.setParseHttpHeaders(true);
-
- try {
-// Matcher idMatcher = STARTING_DIGITS_PATTERN.matcher(id.getIdText());
-// if(!idMatcher.find()) {
-// throw new GateException("Document IDs within an ARC must start " +
-// "with one or more digits, \"" + id + "\" does not.");
-// }
-// int docIndex = Integer.parseInt(idMatcher.group(1));
-// if(docIndex >= index.length) {
-// throw new GateException("Input document at index " + docIndex + "
requested, " +
-// "but this ARC file only contains " + index.length + "
entries.");
-// }
-//
-//
-// // get the right ARC record
-// ARCRecord record = (ARCRecord)reader.get(index[docIndex]);
-
- if(id.getAttributes() == null) {
- throw new IllegalArgumentException(
+ String offsetStr = id.getAttributes().get(RECORD_OFFSET_ATTR);
+ String lengthStr = id.getAttributes().get(RECORD_LENGTH_ATTR);
+ if(offsetStr == null || offsetStr.length() == 0 ||
+ lengthStr == null || lengthStr.length() == 0) {
+ throw new IllegalArgumentException(
"Document IDs within an ARC must include \"" + RECORD_OFFSET_ATTR +
"\" and \"" + RECORD_LENGTH_ATTR + "\" attributes; \"" +
- id.toString() + "\" does not.");
+ id.toString() + "\" does not.");
+ }
+
+ long offset = -1;
+ try{
+ offset = Long.parseLong(offsetStr);
+ }catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException(
+ "Could not parse offset \"" + offsetStr + "\" as a long value.");
+ }
+ long length = -1;
+ try{
+ length = Long.parseLong(lengthStr);
+ }catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException(
+ "Could not parse length \"" + lengthStr + "\" as a long value.");
+ }
+ // read the arc record
+ ARCRecord record = null;
+ ARCReader reader = null;
+ try{
+ if(arcUrlStr != null) {
+ URL arcUrl = new URL(arcUrlStr);
+ URLConnection conn = arcUrl.openConnection();
+ conn.setRequestProperty("Range", "bytes=" + Long.toString(offset) +
+ "-" + Long.toString(offset + length - 1));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InputStream is = conn.getInputStream();
+ IOUtils.copy(is, baos);
+ is.close();
+ record = new ARCRecord(new ByteArrayInputStream(baos.toByteArray()),
+ arcUrlStr, 0, false, false, true);
+ } else {
+ // no custom URL, so we can use the default arc file
+ reader = ARCReaderFactory.get(arcFile);
+ reader.setParseHttpHeaders(true);
+ record = (ARCRecord)reader.get(offset);
}
- String offsetStr = id.getAttributes().get(RECORD_OFFSET_ATTR);
- String lengthStr = id.getAttributes().get(RECORD_LENGTH_ATTR);
- if(offsetStr == null || offsetStr.length() == 0 ||
- lengthStr == null || lengthStr.length() == 0) {
- throw new IllegalArgumentException(
- "Document IDs within an ARC must include \"" + RECORD_OFFSET_ATTR +
- "\" and \"" + RECORD_LENGTH_ATTR + "\" attributes; \"" +
- id.toString() + "\" does not.");
- }
-
- long offset = -1;
- try{
- offset = Long.parseLong(offsetStr);
- }catch (NumberFormatException nfe) {
- throw new IllegalArgumentException(
- "Could not parse offset \"" + offsetStr + "\" as a long value.");
- }
- long length = -1;
- try{
- length = Long.parseLong(lengthStr);
- }catch (NumberFormatException nfe) {
- throw new IllegalArgumentException(
- "Could not parse length \"" + lengthStr + "\" as a long value.");
- }
- ARCRecord record = (ARCRecord)reader.get(offset);
ARCRecordMetaData header = record.getMetaData();
// extract the content
@@ -298,7 +293,19 @@
docFeatures.put("redirect_to", redirect.toString());
}
docFeatures.put("archive_name", arcFile.getName());
-// docFeatures.put("archive_position", idMatcher.group(1));
+ // docFeatures.put("archive_position", idMatcher.group(1));
+ String posStr = id.getAttributes().get(RECORD_POSITION_ATTR);
+ if(posStr != null) {
+ try {
+ long pos = Long.parseLong(posStr);
+ docFeatures.put("archive_position", posStr);
+ } catch(NumberFormatException e) {
+ // log and ignore
+ logger.warn("Invalid record position value (not an integer number):
" +
+ posStr);
+ }
+ }
+
Date documentDate = getDate(header, record);
if(documentDate != null) {
docFeatures.put("retrievedAt", documentDate);
@@ -328,7 +335,7 @@
}
// Do the same for the HTTP headers
Header[] httpHeader = record.getHttpHeaders();
-
+
if(httpHeader != null) {
for(Header h : httpHeader) {
headerKey = h.getName();
@@ -344,11 +351,12 @@
return docData;
} finally {
try {
- reader.close();
+ if(reader != null) reader.close();
} catch(IOException e) {
// ignore
logger.error("Error while closing ARC reader.", e);
}
+ if(record != null) record.close();
}
}
Modified: gcp/trunk/src/gate/cloud/io/file/SimpleNamingStrategy.java
===================================================================
--- gcp/trunk/src/gate/cloud/io/file/SimpleNamingStrategy.java 2013-03-22
02:43:36 UTC (rev 16611)
+++ gcp/trunk/src/gate/cloud/io/file/SimpleNamingStrategy.java 2013-03-22
10:27:20 UTC (rev 16612)
@@ -100,7 +100,7 @@
public File toFile(DocumentID id) throws IOException {
try {
- String path = relativePathFor(id.getIdText());
+ String path = relativePathFor(id);
if(fileExtension != null) {
path += fileExtension;
}
@@ -125,8 +125,8 @@
* @param id the document ID
* @return a path suitable for use as a relative URI.
*/
- protected String relativePathFor(String id) {
- return id;
+ protected String relativePathFor(DocumentID id) {
+ return id.getIdText();
}
public String toString() {
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
------------------------------------------------------------------------------
Everyone hates slow websites. So do we.
Make your web apps faster with AppDynamics
Download AppDynamics Lite for free today:
http://p.sf.net/sfu/appdyn_d2d_mar
_______________________________________________
GATE-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/gate-cvs