From a67c6703843853b50b5b8783bd1a8f7059ddd5ca Mon Sep 17 00:00:00 2001 From: Chris Mattmann Date: Sat, 7 Aug 2010 22:18:19 +0000 Subject: [PATCH 001/754] Branch and save current Nutch trunk as branch-1.3 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@983319 13f79535-47bb-0310-9956-ffa450edef68 From 08b38ce79c6384b6b087202a5d96851c3166487b Mon Sep 17 00:00:00 2001 From: Andrzej Bialecki Date: Fri, 17 Sep 2010 14:48:59 +0000 Subject: [PATCH 002/754] NUTCH-862 HttpClient null pointer exception. git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@998158 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ .../org/apache/nutch/protocol/httpclient/HttpResponse.java | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index bc97450e99..c1583507e5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-862 HttpClient null pointer exception (Sebastian Nagel via ab) + * NUTCH-870 Injector should add the metadata before calling injectedScore (jnioche via mattmann) * NUTCH-869 Add parse-html back (jnioche) diff --git a/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/HttpResponse.java b/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/HttpResponse.java index dadb00bd58..43baf371cb 100644 --- a/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/HttpResponse.java +++ b/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/HttpResponse.java @@ -134,7 +134,9 @@ public class HttpResponse implements Response { if (code == 200) throw new IOException(e.toString()); // for codes other than 200 OK, we are fine with empty content } finally { - in.close(); + if (in != null) { + in.close(); + } get.abort(); } From c4acc8e0651afca49eb52149f1ba86f8bd8506d4 Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Wed, 27 Oct 2010 10:25:49 +0000 Subject: [PATCH 003/754] Ported NUTCH-901 Making index-more plug-in configurable to 1.3 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1027889 13f79535-47bb-0310-9956-ffa450edef68 --- conf/nutch-default.xml | 11 ++++++++++ .../indexer/more/MoreIndexingFilter.java | 9 +++++--- .../indexer/more/TestMoreIndexingFilter.java | 22 +++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/conf/nutch-default.xml b/conf/nutch-default.xml index 0ff1d51d74..b7b8c9b9f8 100644 --- a/conf/nutch-default.xml +++ b/conf/nutch-default.xml @@ -751,6 +751,17 @@ + + + + moreIndexingFilter.indexMimeTypeParts + true + Determines whether the index-more plugin will split the mime-type + in sub parts, this requires the type field to be multi valued. Set to true for backward + compatibility. False will not split the mime-type. + + + diff --git a/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java b/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java index 78d9bbb85e..28db359217 100644 --- a/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java +++ b/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java @@ -224,10 +224,13 @@ private NutchDocument addType(NutchDocument doc, ParseData data, String url) { doc.add("type", contentType); - String[] parts = getParts(contentType); + // Check if we need to split the content type in sub parts + if (conf.getBoolean("moreIndexingFilter.indexMimeTypeParts", true)) { + String[] parts = getParts(contentType.toString()); - for(String part: parts) { - doc.add("type", part); + for(String part: parts) { + doc.add("type", part); + } } // leave this for future improvement diff --git a/src/plugin/index-more/src/test/org/apache/nutch/indexer/more/TestMoreIndexingFilter.java b/src/plugin/index-more/src/test/org/apache/nutch/indexer/more/TestMoreIndexingFilter.java index bf10d3de7e..1c3d3e1d41 100644 --- a/src/plugin/index-more/src/test/org/apache/nutch/indexer/more/TestMoreIndexingFilter.java +++ b/src/plugin/index-more/src/test/org/apache/nutch/indexer/more/TestMoreIndexingFilter.java @@ -43,7 +43,29 @@ public void testContentType() throws IndexingException { public void testGetParts() { String[] parts = MoreIndexingFilter.getParts("text/html"); assertParts(parts, 2, "text", "html"); + } + /** + * @since NUTCH-901 + */ + public void testNoParts(){ + Configuration conf = NutchConfiguration.create(); + conf.setBoolean("moreIndexingFilter.indexMimeTypeParts", false); + MoreIndexingFilter filter = new MoreIndexingFilter(); + filter.setConf(conf); + assertNotNull(filter); + NutchDocument doc = new NutchDocument(); + try{ + filter.filter(doc, "http://nutch.apache.org/index.html", new WebPage()); + } + catch(Exception e){ + e.printStackTrace(); + fail(e.getMessage()); + } + assertNotNull(doc); + assertTrue(doc.getFieldNames().contains("type")); + assertEquals(1, doc.getFieldValues("type").size()); + assertEquals("text/html", doc.getFieldValue("type")); } private void assertParts(String[] parts, int count, String... expected) { From 9adea98e98b903da5e1dadb4711e0840d9991616 Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Wed, 27 Oct 2010 10:39:55 +0000 Subject: [PATCH 004/754] Applied NUTCH-900 Confusion in nutch-default between http.content.limit and file.content.limit git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1027903 13f79535-47bb-0310-9956-ffa450edef68 --- conf/nutch-default.xml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/conf/nutch-default.xml b/conf/nutch-default.xml index b7b8c9b9f8..3c5129bc7e 100644 --- a/conf/nutch-default.xml +++ b/conf/nutch-default.xml @@ -27,9 +27,10 @@ file.content.limit 65536 - The length limit for downloaded content, in bytes. - If this value is nonnegative (>=0), content longer than it will be truncated; - otherwise, no truncation at all. + The length limit for downloaded content using the file:// + protocol, in bytes. If this value is nonnegative (>=0), content longer + than it will be truncated; otherwise, no truncation at all. Do not + confuse this setting with the http.content.limit setting. @@ -145,9 +146,10 @@ http.content.limit 65536 - The length limit for downloaded content, in bytes. - If this value is nonnegative (>=0), content longer than it will be truncated; - otherwise, no truncation at all. + The length limit for downloaded content using the http:// + protocol, in bytes. If this value is nonnegative (>=0), content longer + than it will be truncated; otherwise, no truncation at all. Do not + confuse this setting with the file.content.limit setting. From 14e43b4a71dd38365c343ac81f27608dda63dcf8 Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Wed, 27 Oct 2010 13:00:48 +0000 Subject: [PATCH 005/754] Fixed test for NUTCH-901 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1027947 13f79535-47bb-0310-9956-ffa450edef68 --- .../indexer/more/TestMoreIndexingFilter.java | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/plugin/index-more/src/test/org/apache/nutch/indexer/more/TestMoreIndexingFilter.java b/src/plugin/index-more/src/test/org/apache/nutch/indexer/more/TestMoreIndexingFilter.java index 1c3d3e1d41..ed214cd2bb 100644 --- a/src/plugin/index-more/src/test/org/apache/nutch/indexer/more/TestMoreIndexingFilter.java +++ b/src/plugin/index-more/src/test/org/apache/nutch/indexer/more/TestMoreIndexingFilter.java @@ -49,23 +49,25 @@ public void testGetParts() { * @since NUTCH-901 */ public void testNoParts(){ - Configuration conf = NutchConfiguration.create(); - conf.setBoolean("moreIndexingFilter.indexMimeTypeParts", false); - MoreIndexingFilter filter = new MoreIndexingFilter(); - filter.setConf(conf); - assertNotNull(filter); - NutchDocument doc = new NutchDocument(); - try{ - filter.filter(doc, "http://nutch.apache.org/index.html", new WebPage()); - } - catch(Exception e){ - e.printStackTrace(); - fail(e.getMessage()); - } - assertNotNull(doc); - assertTrue(doc.getFieldNames().contains("type")); - assertEquals(1, doc.getFieldValues("type").size()); - assertEquals("text/html", doc.getFieldValue("type")); + Configuration conf = NutchConfiguration.create(); + conf.setBoolean("moreIndexingFilter.indexMimeTypeParts", false); + MoreIndexingFilter filter = new MoreIndexingFilter(); + filter.setConf(conf); + assertNotNull(filter); + NutchDocument doc = new NutchDocument(); + ParseImpl parse = new ParseImpl("foo bar", new ParseData()); + + try{ + filter.filter(doc, parse, new Text("http://nutch.apache.org/index.html"), new CrawlDatum(), new Inlinks()); + } + catch(Exception e){ + e.printStackTrace(); + fail(e.getMessage()); + } + assertNotNull(doc); + assertTrue(doc.getFieldNames().contains("type")); + assertEquals(1, doc.getField("type").getValues().size()); + assertEquals("text/html", doc.getFieldValue("type")); } private void assertParts(String[] parts, int count, String... expected) { From 771df16604443afbbce351e9240a43dd064b841a Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Mon, 22 Nov 2010 14:28:29 +0000 Subject: [PATCH 006/754] NUTCH-936 - LanguageIdentifier should not set empty lang field on NutchDocument git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1037732 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/nutch/analysis/lang/LanguageIndexingFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/languageidentifier/src/java/org/apache/nutch/analysis/lang/LanguageIndexingFilter.java b/src/plugin/languageidentifier/src/java/org/apache/nutch/analysis/lang/LanguageIndexingFilter.java index bf7385bead..439d11d288 100644 --- a/src/plugin/languageidentifier/src/java/org/apache/nutch/analysis/lang/LanguageIndexingFilter.java +++ b/src/plugin/languageidentifier/src/java/org/apache/nutch/analysis/lang/LanguageIndexingFilter.java @@ -85,7 +85,7 @@ public NutchDocument filter(NutchDocument doc, Parse parse, Text url, CrawlDatum lang = this.languageIdentifier.identify(text); } - if (lang == null) { + if (lang == null || lang.length() == 0) { lang = "unknown"; } From 985ab8f59f1219503f91fff1ef132a00c29cadf9 Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Mon, 22 Nov 2010 14:31:09 +0000 Subject: [PATCH 007/754] NUTCH-912 - MoreIndexingFilter does not parse docx and xlsx date formats git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1037733 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/nutch/indexer/more/MoreIndexingFilter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java b/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java index 28db359217..cba7a6189a 100644 --- a/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java +++ b/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java @@ -150,7 +150,8 @@ private long getTime(String date, String url) { "dd MM yyyy HH:mm:ss zzz", "dd.MM.yyyy; HH:mm:ss", "dd.MM.yyyy HH:mm:ss", - "dd.MM.yyyy zzz" + "dd.MM.yyyy zzz", + "yyyy-MM-dd'T'HH:mm:ss'Z'" }); time = parsedDate.getTime(); // if (LOG.isWarnEnabled()) { From cb6e4d045e8d649530700d3c0e44448e735039cf Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Mon, 22 Nov 2010 14:56:40 +0000 Subject: [PATCH 008/754] NUTCH-935 - remove unnecessary /./ in basic urlnormalizer (via Stondubleyt) git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1037742 13f79535-47bb-0310-9956-ffa450edef68 --- .../net/urlnormalizer/basic/BasicURLNormalizer.java | 13 +++++++++++++ .../urlnormalizer/basic/TestBasicURLNormalizer.java | 6 +++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/plugin/urlnormalizer-basic/src/java/org/apache/nutch/net/urlnormalizer/basic/BasicURLNormalizer.java b/src/plugin/urlnormalizer-basic/src/java/org/apache/nutch/net/urlnormalizer/basic/BasicURLNormalizer.java index 29f55639d3..fb221d3301 100644 --- a/src/plugin/urlnormalizer-basic/src/java/org/apache/nutch/net/urlnormalizer/basic/BasicURLNormalizer.java +++ b/src/plugin/urlnormalizer-basic/src/java/org/apache/nutch/net/urlnormalizer/basic/BasicURLNormalizer.java @@ -43,6 +43,7 @@ protected synchronized Object initialValue() { }; private Rule relativePathRule = null; private Rule leadingRelativePathRule = null; + private Rule currentPathRule = null; private Rule adjacentSlashRule = null; private Configuration conf; @@ -65,6 +66,13 @@ public BasicURLNormalizer() { compiler.compile("^(/\\.\\./)+", Perl5Compiler.READ_ONLY_MASK); leadingRelativePathRule.substitution = new Perl5Substitution("/"); + // this pattern tries to find spots like "/./" in the url, + // which could be replaced by "/" + currentPathRule = new Rule(); + currentPathRule.pattern = (Perl5Pattern) + compiler.compile("(/\\./)", Perl5Compiler.READ_ONLY_MASK); + currentPathRule.substitution = new Perl5Substitution("/"); + // this pattern tries to find spots like "xx//yy" in the url, // which could be replaced by a "/" adjacentSlashRule = new Rule(); @@ -171,6 +179,11 @@ private String substituteUnnecessaryRelativePaths(String file) { fileWorkCopy = Util.substitute (matcher, leadingRelativePathRule.pattern, leadingRelativePathRule.substitution, fileWorkCopy, 1); + + // remove unnecessary "/./" + fileWorkCopy = Util.substitute + (matcher, currentPathRule.pattern, + currentPathRule.substitution, fileWorkCopy, 1); // collapse adjacent slashes with "/" diff --git a/src/plugin/urlnormalizer-basic/src/test/org/apache/nutch/net/urlnormalizer/basic/TestBasicURLNormalizer.java b/src/plugin/urlnormalizer-basic/src/test/org/apache/nutch/net/urlnormalizer/basic/TestBasicURLNormalizer.java index 3bf289a9c6..d930816264 100644 --- a/src/plugin/urlnormalizer-basic/src/test/org/apache/nutch/net/urlnormalizer/basic/TestBasicURLNormalizer.java +++ b/src/plugin/urlnormalizer-basic/src/test/org/apache/nutch/net/urlnormalizer/basic/TestBasicURLNormalizer.java @@ -60,6 +60,9 @@ public void testNormalizer() throws Exception { // normalizeTest("http://foo.com/%66oo.html", "http://foo.com/foo.html"); // check that unnecessary "../" are removed + + normalizeTest("http://foo.com/aa/./foo.html", + "http://foo.com/aa/foo.html" ); normalizeTest("http://foo.com/aa/../", "http://foo.com/" ); normalizeTest("http://foo.com/aa/bb/../", @@ -112,4 +115,5 @@ public static void main(String[] args) throws Exception { -} + +} \ No newline at end of file From 224c1cffab10227aac7d2f0abc94f245fef1a2fd Mon Sep 17 00:00:00 2001 From: Andrzej Bialecki Date: Tue, 21 Dec 2010 14:14:03 +0000 Subject: [PATCH 009/754] NUTCH-939 Added -dir command line option to SolrIndexer. git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1051505 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ .../apache/nutch/indexer/solr/SolrIndexer.java | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index c1583507e5..2dc98aa677 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -44,6 +44,8 @@ Release 1.3 - Current Development * NUTCH-832 Website menu has lots of broken links - in particular the API docs (Alex McLintock via mattmann) +* NUTCH-939 Added -dir command line option to SolrIndexer (Claudio Martella via ab) + Release 1.1 - 2010-06-06 diff --git a/src/java/org/apache/nutch/indexer/solr/SolrIndexer.java b/src/java/org/apache/nutch/indexer/solr/SolrIndexer.java index 4e01468e51..53989c951f 100644 --- a/src/java/org/apache/nutch/indexer/solr/SolrIndexer.java +++ b/src/java/org/apache/nutch/indexer/solr/SolrIndexer.java @@ -20,6 +20,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; +import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.mapred.FileOutputFormat; @@ -30,6 +31,7 @@ import org.apache.hadoop.util.ToolRunner; import org.apache.nutch.indexer.IndexerMapReduce; import org.apache.nutch.indexer.NutchIndexWriterFactory; +import org.apache.nutch.util.HadoopFSUtil; import org.apache.nutch.util.NutchConfiguration; import org.apache.nutch.util.NutchJob; import org.apache.nutch.util.TimingUtil; @@ -92,7 +94,7 @@ public void indexSolr(String solrUrl, Path crawlDb, Path linkDb, public int run(String[] args) throws Exception { if (args.length < 4) { - System.err.println("Usage: SolrIndexer ..."); + System.err.println("Usage: SolrIndexer ( ... | -dir )"); return -1; } @@ -101,7 +103,18 @@ public int run(String[] args) throws Exception { final List segments = new ArrayList(); for (int i = 3; i < args.length; i++) { - segments.add(new Path(args[i])); + if (args[i].equals("-dir")) { + Path dir = new Path(args[++i]); + FileSystem fs = dir.getFileSystem(getConf()); + FileStatus[] fstats = fs.listStatus(dir, + HadoopFSUtil.getPassDirectoriesFilter(fs)); + Path[] files = HadoopFSUtil.getPaths(fstats); + for (Path p : files) { + segments.add(p); + } + } else { + segments.add(new Path(args[i])); + } } try { From 107508477a6485161eed8ca2d5a3d67a51fa16d0 Mon Sep 17 00:00:00 2001 From: Andrzej Bialecki Date: Tue, 21 Dec 2010 14:38:54 +0000 Subject: [PATCH 010/754] NUTCH-948 Remove Lucene dependencies. git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1051509 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ ivy/ivy.xml | 2 -- .../nutch/indexer/basic/BasicIndexingFilter.java | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2dc98aa677..9afc492abd 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -46,6 +46,8 @@ Release 1.3 - Current Development * NUTCH-939 Added -dir command line option to SolrIndexer (Claudio Martella via ab) +* NUTCH-948 Remove Lucene dependencies (ab) + Release 1.1 - 2010-06-06 diff --git a/ivy/ivy.xml b/ivy/ivy.xml index 5d634384a0..f52bcb48f4 100644 --- a/ivy/ivy.xml +++ b/ivy/ivy.xml @@ -63,8 +63,6 @@ - - diff --git a/src/plugin/index-basic/src/java/org/apache/nutch/indexer/basic/BasicIndexingFilter.java b/src/plugin/index-basic/src/java/org/apache/nutch/indexer/basic/BasicIndexingFilter.java index 2df1825984..4029e16190 100644 --- a/src/plugin/index-basic/src/java/org/apache/nutch/indexer/basic/BasicIndexingFilter.java +++ b/src/plugin/index-basic/src/java/org/apache/nutch/indexer/basic/BasicIndexingFilter.java @@ -20,8 +20,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.lucene.document.DateTools; - import org.apache.nutch.metadata.Nutch; import org.apache.nutch.parse.Parse; @@ -32,9 +30,12 @@ import org.apache.nutch.crawl.CrawlDatum; import org.apache.nutch.crawl.Inlinks; +import org.apache.solr.common.util.DateUtil; import java.net.MalformedURLException; import java.net.URL; +import java.util.Date; + import org.apache.hadoop.conf.Configuration; /** Adds basic searchable fields to a document. */ @@ -86,9 +87,8 @@ public NutchDocument filter(NutchDocument doc, Parse parse, Text url, CrawlDatum } // add timestamp when fetched, for deduplication - doc.add("tstamp", - DateTools.timeToString(datum.getFetchTime(), - DateTools.Resolution.MILLISECOND)); + String tstamp = DateUtil.getThreadLocalDateFormat().format(new Date(datum.getFetchTime())); + doc.add("tstamp", tstamp); return doc; } From f2fd4152b35f091ff8fa008fbae3bb61bdf0a80c Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 22 Dec 2010 14:57:12 +0000 Subject: [PATCH 011/754] NUTCH-949 Conflicting ANT jars in classpath git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1051932 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ ivy/ivy.xml | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9afc492abd..052b820309 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-949 Conflicting ANT jars in classpath (jnioche) + * NUTCH-862 HttpClient null pointer exception (Sebastian Nagel via ab) * NUTCH-870 Injector should add the metadata before calling injectedScore (jnioche via mattmann) diff --git a/ivy/ivy.xml b/ivy/ivy.xml index f52bcb48f4..355474fe7e 100644 --- a/ivy/ivy.xml +++ b/ivy/ivy.xml @@ -34,11 +34,6 @@ conf="*->default" /> - - - - + From e27288664b9a8ce4c5f005512310e6b63e2b327b Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Tue, 4 Jan 2011 16:50:12 +0000 Subject: [PATCH 012/754] applied NUTCH-787 to 1.3 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055100 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ .../apache/nutch/scoring/link/LinkAnalysisScoringFilter.java | 3 --- .../java/org/apache/nutch/scoring/opic/OPICScoringFilter.java | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 052b820309..32d9cd8609 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-787 ScoringFilters should not override the injected score (jnioche) + * NUTCH-949 Conflicting ANT jars in classpath (jnioche) * NUTCH-862 HttpClient null pointer exception (Sebastian Nagel via ab) diff --git a/src/plugin/scoring-link/src/java/org/apache/nutch/scoring/link/LinkAnalysisScoringFilter.java b/src/plugin/scoring-link/src/java/org/apache/nutch/scoring/link/LinkAnalysisScoringFilter.java index cd5be07764..ae4360c249 100644 --- a/src/plugin/scoring-link/src/java/org/apache/nutch/scoring/link/LinkAnalysisScoringFilter.java +++ b/src/plugin/scoring-link/src/java/org/apache/nutch/scoring/link/LinkAnalysisScoringFilter.java @@ -36,7 +36,6 @@ public class LinkAnalysisScoringFilter implements ScoringFilter { private Configuration conf; - private float scoreInjected = 0.001f; private float normalizedScore = 1.00f; public LinkAnalysisScoringFilter() { @@ -50,7 +49,6 @@ public Configuration getConf() { public void setConf(Configuration conf) { this.conf = conf; normalizedScore = conf.getFloat("link.analyze.normalize.score", 1.00f); - scoreInjected = conf.getFloat("link.analyze.injected.score", 1.00f); } public CrawlDatum distributeScoreToOutlinks(Text fromUrl, @@ -78,7 +76,6 @@ public void initialScore(Text url, CrawlDatum datum) public void injectedScore(Text url, CrawlDatum datum) throws ScoringFilterException { - datum.setScore(scoreInjected); } public void passScoreAfterParsing(Text url, Content content, Parse parse) diff --git a/src/plugin/scoring-opic/src/java/org/apache/nutch/scoring/opic/OPICScoringFilter.java b/src/plugin/scoring-opic/src/java/org/apache/nutch/scoring/opic/OPICScoringFilter.java index 30d244d29d..b94510866b 100644 --- a/src/plugin/scoring-opic/src/java/org/apache/nutch/scoring/opic/OPICScoringFilter.java +++ b/src/plugin/scoring-opic/src/java/org/apache/nutch/scoring/opic/OPICScoringFilter.java @@ -67,16 +67,13 @@ public Configuration getConf() { public void setConf(Configuration conf) { this.conf = conf; - scoreInjected = conf.getFloat("db.score.injected", 1.0f); scorePower = conf.getFloat("indexer.score.power", 0.5f); internalScoreFactor = conf.getFloat("db.score.link.internal", 1.0f); externalScoreFactor = conf.getFloat("db.score.link.external", 1.0f); countFiltered = conf.getBoolean("db.score.count.filtered", false); } - /** Set to the value defined in config, 1.0f by default. */ public void injectedScore(Text url, CrawlDatum datum) throws ScoringFilterException { - datum.setScore(scoreInjected); } /** Set to 0.0f (unknown value) - inlink contributions will bring it to From dbdd0723ed794c069a39b45bf908128f2b5480ca Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Tue, 4 Jan 2011 19:26:16 +0000 Subject: [PATCH 013/754] applied NUTCH-905 to 1.3 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055145 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ conf/nutch-default.xml | 9 +++++++++ .../src/java/org/apache/nutch/protocol/file/File.java | 2 ++ .../org/apache/nutch/protocol/file/FileResponse.java | 5 ++++- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 32d9cd8609..a826d2cc74 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-905 Configurable file protocol parent directory crawling (Thorsten Scherler, mattmann, ab) + * NUTCH-787 ScoringFilters should not override the injected score (jnioche) * NUTCH-949 Conflicting ANT jars in classpath (jnioche) diff --git a/conf/nutch-default.xml b/conf/nutch-default.xml index 3c5129bc7e..8e72b9d1e3 100644 --- a/conf/nutch-default.xml +++ b/conf/nutch-default.xml @@ -33,6 +33,15 @@ confuse this setting with the http.content.limit setting. + + + file.crawl.parent + true + The crawler is not restricted to the directories that you specified in the + Urls file but it is jumping into the parent directories as well. For your own crawlings you can + change this bahavior (set to false) the way that only directories beneath the directories that you specify get + crawled. + file.content.ignored diff --git a/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/File.java b/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/File.java index f4390c85b4..510a37d145 100644 --- a/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/File.java +++ b/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/File.java @@ -53,6 +53,7 @@ public class File implements Protocol { static final int MAX_REDIRECTS = 5; int maxContentLength; + boolean crawlParents; private Configuration conf; @@ -156,6 +157,7 @@ public static void main(String[] args) throws Exception { public void setConf(Configuration conf) { this.conf = conf; this.maxContentLength = conf.getInt("file.content.limit", 64 * 1024); + this.crawlParents = conf.getBoolean("file.crawl.parent", true); } public Configuration getConf() { diff --git a/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/FileResponse.java b/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/FileResponse.java index d07ee8114f..c7ef9754f3 100644 --- a/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/FileResponse.java +++ b/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/FileResponse.java @@ -222,7 +222,10 @@ private void getDirAsHttpResponse(java.io.File f) throws IOException { String path = f.toString(); - this.content = list2html(f.listFiles(), path, "/".equals(path) ? false : true); + if (this.file.crawlParents) + this.content = list2html(f.listFiles(), path, "/".equals(path) ? false : true); + else + this.content = list2html(f.listFiles(), path, false); // set headers headers.set(Response.CONTENT_LENGTH, From f93d5647602163b922be6bd8c2ca7a5aa566188a Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Tue, 4 Jan 2011 19:30:38 +0000 Subject: [PATCH 014/754] applied NUTCH-716 to 1.3 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055148 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ conf/schema.xml | 2 +- .../nutch/collection/CollectionManager.java | 22 ++++++++++--------- .../SubcollectionIndexingFilter.java | 5 +++-- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index a826d2cc74..0be9e75411 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-716 Make subcollection index filed multivalued (Dmitry Lihachev via jnioche) + * NUTCH-905 Configurable file protocol parent directory crawling (Thorsten Scherler, mattmann, ab) * NUTCH-787 ScoringFilters should not override the injected score (jnioche) diff --git a/conf/schema.xml b/conf/schema.xml index 2fb636afa6..27159efe4f 100644 --- a/conf/schema.xml +++ b/conf/schema.xml @@ -91,7 +91,7 @@ + indexed="true" multiValued="true"/> diff --git a/src/plugin/subcollection/src/java/org/apache/nutch/collection/CollectionManager.java b/src/plugin/subcollection/src/java/org/apache/nutch/collection/CollectionManager.java index 52ba2733ad..3fc170a4df 100644 --- a/src/plugin/subcollection/src/java/org/apache/nutch/collection/CollectionManager.java +++ b/src/plugin/subcollection/src/java/org/apache/nutch/collection/CollectionManager.java @@ -22,9 +22,12 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; @@ -89,7 +92,7 @@ protected void parse(InputStream input) { .getElementsByTagName(Subcollection.TAG_COLLECTION); if (LOG.isInfoEnabled()) { - LOG.info("file has" + nodeList.getLength() + " elements"); + LOG.info("file has " + nodeList.getLength() + " elements"); } for (int i = 0; i < nodeList.getLength(); i++) { @@ -115,7 +118,7 @@ public static CollectionManager getCollectionManager(Configuration conf) { impl=new CollectionManager(conf); objectCache.setObject(key,impl); } catch (Exception e) { - throw new RuntimeException("Couldn't create CollectionManager",e); + throw new RuntimeException("Couldn't create CollectionManager", e); } } return impl; @@ -169,22 +172,21 @@ public Subcollection createSubCollection(final String id, final String name) { * The url to test against Collections * @return Space delimited string of collection names url is part of */ - public String getSubCollections(final String url) { - StringBuilder collections = new StringBuilder(); + public List getSubCollections(final String url) { + List collections = new ArrayList(); final Iterator iterator = collectionMap.values().iterator(); while (iterator.hasNext()) { final Subcollection subCol = (Subcollection) iterator.next(); if (subCol.filter(url) != null) { - if (collections.length() > 0) { - collections.append(' '); - } - collections.append(subCol.name); + collections.add(subCol.name); } } - if (LOG.isTraceEnabled()) { LOG.trace("subcollections:" + collections); } + if (LOG.isTraceEnabled()) { + LOG.trace("subcollections:" + Arrays.toString(collections.toArray())); + } - return collections.toString(); + return collections; } /** diff --git a/src/plugin/subcollection/src/java/org/apache/nutch/indexer/subcollection/SubcollectionIndexingFilter.java b/src/plugin/subcollection/src/java/org/apache/nutch/indexer/subcollection/SubcollectionIndexingFilter.java index db635c03ec..6651206a6f 100644 --- a/src/plugin/subcollection/src/java/org/apache/nutch/indexer/subcollection/SubcollectionIndexingFilter.java +++ b/src/plugin/subcollection/src/java/org/apache/nutch/indexer/subcollection/SubcollectionIndexingFilter.java @@ -62,8 +62,9 @@ public SubcollectionIndexingFilter(Configuration conf) { * @param url */ private void addSubCollectionField(NutchDocument doc, String url) { - String collname = CollectionManager.getCollectionManager(getConf()).getSubCollections(url); - doc.add(FIELD_NAME, collname); + for (String collname: CollectionManager.getCollectionManager(getConf()).getSubCollections(url)) { + doc.add(FIELD_NAME, collname); + } } public NutchDocument filter(NutchDocument doc, Parse parse, Text url, CrawlDatum datum, Inlinks inlinks) throws IndexingException { From d0f099b968668e0d1e00535e49ef19991118254b Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Tue, 4 Jan 2011 19:36:22 +0000 Subject: [PATCH 015/754] added NUTCH-901 to CHANGES.txt in 1.3 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055157 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 0be9e75411..3cdda9b714 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,10 @@ Nutch Change Log + Release 1.3 - Current Development +* NUTCH-901 Make index-more plug-in configurable (Markus Jelsma, mattmann) + * NUTCH-716 Make subcollection index filed multivalued (Dmitry Lihachev via jnioche) * NUTCH-905 Configurable file protocol parent directory crawling (Thorsten Scherler, mattmann, ab) From f73648c4efbbd29df93382f3fa76de2a6b45da11 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 5 Jan 2011 09:54:05 +0000 Subject: [PATCH 016/754] applied NUTCH-855 to branch 1.3 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055387 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 3 +- conf/nutch-default.xml | 13 ++ src/plugin/build.xml | 2 + src/plugin/urlmeta/build.xml | 22 +++ src/plugin/urlmeta/plugin.xml | 47 +++++ .../urlmeta/URLMetaIndexingFilter.java | 138 ++++++++++++++ .../apache/nutch/indexer/urlmeta/package.html | 12 ++ .../scoring/urlmeta/URLMetaScoringFilter.java | 174 ++++++++++++++++++ .../apache/nutch/scoring/urlmeta/package.html | 11 ++ 9 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 src/plugin/urlmeta/build.xml create mode 100644 src/plugin/urlmeta/plugin.xml create mode 100644 src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/URLMetaIndexingFilter.java create mode 100644 src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/package.html create mode 100644 src/plugin/urlmeta/src/java/org/apache/nutch/scoring/urlmeta/URLMetaScoringFilter.java create mode 100644 src/plugin/urlmeta/src/java/org/apache/nutch/scoring/urlmeta/package.html diff --git a/CHANGES.txt b/CHANGES.txt index 3cdda9b714..4e73c9cc74 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,8 +1,9 @@ Nutch Change Log - Release 1.3 - Current Development +* NUTCH-855 ScoringFilter and IndexingFilter: To allow for the propagation of URL Metatags and their subsequent indexing (Scott Gonyea via mattmann) + * NUTCH-901 Make index-more plug-in configurable (Markus Jelsma, mattmann) * NUTCH-716 Make subcollection index filed multivalued (Dmitry Lihachev via jnioche) diff --git a/conf/nutch-default.xml b/conf/nutch-default.xml index 8e72b9d1e3..125cd4a840 100644 --- a/conf/nutch-default.xml +++ b/conf/nutch-default.xml @@ -873,6 +873,19 @@ + + urlmeta.tags + + + To be used in conjunction with features introduced in NUTCH-655, which allows + for custom metatags to be injected alongside your crawl URLs. Specifying those + custom tags here will allow for their propagation into a pages outlinks, as + well as allow for them to be included as part of an index. + Values should be comma-delimited. ("tag1,tag2,tag3") Do not pad the tags with + white-space at their boundaries, if you are using anything earlier than Hadoop-0.21. + + + diff --git a/src/plugin/build.xml b/src/plugin/build.xml index fc444f6ce3..a56de605cd 100755 --- a/src/plugin/build.xml +++ b/src/plugin/build.xml @@ -59,6 +59,7 @@ + @@ -131,6 +132,7 @@ + diff --git a/src/plugin/urlmeta/build.xml b/src/plugin/urlmeta/build.xml new file mode 100644 index 0000000000..ed8d9c95ba --- /dev/null +++ b/src/plugin/urlmeta/build.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/src/plugin/urlmeta/plugin.xml b/src/plugin/urlmeta/plugin.xml new file mode 100644 index 0000000000..c31adf63a3 --- /dev/null +++ b/src/plugin/urlmeta/plugin.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/URLMetaIndexingFilter.java b/src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/URLMetaIndexingFilter.java new file mode 100644 index 0000000000..113de72404 --- /dev/null +++ b/src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/URLMetaIndexingFilter.java @@ -0,0 +1,138 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.nutch.indexer.urlmeta; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.io.Text; +import org.apache.nutch.crawl.CrawlDatum; +import org.apache.nutch.crawl.Inlinks; +import org.apache.nutch.indexer.IndexingException; +import org.apache.nutch.indexer.IndexingFilter; +import org.apache.nutch.indexer.NutchDocument; +import org.apache.nutch.indexer.lucene.LuceneWriter; +import org.apache.nutch.parse.Parse; + +/** + * This is part of the URL Meta plugin. It is designed to enhance the NUTCH-655 + * patch, by doing two things: 1. Meta Tags that are supplied with your Crawl + * URLs, during injection, will be propagated throughout the outlinks of those + * Crawl URLs. 2. When you index your URLs, the meta tags that you specified + * with your URLs will be indexed alongside those URLs--and can be directly + * queried, assuming you have done everything else correctly. + * + * The flat-file of URLs you are injecting should, per NUTCH-655, be tab-delimited + * in the form of: + * + * [www.url.com]\t[key1]=[value1]\t[key2]=[value2]...[keyN]=[valueN] + * + * Be aware that if you collide with keywords that are already in use (such + * as nutch.score/nutch.fetchInterval) then you are in for some unpredictable + * behavior. + * + * Furthermore, in your nutch-site.xml config, you must specify that this + * plugin is to be used (1), as well as what (2) Meta Tags it should + * actively look for. This does not mean that you must use these tags for + * every URL, but it does mean that you must list _all_ of meta tags that + * you have specified. If you want them to be propagated and indexed, that + * is. + * + * 1. As of Nutch 1.2, the property "plugin.includes" looks as follows: + * protocol-http|urlfilter-regex|parse-(text|html|js|tika|rss)|index + * -(basic|anchor)|query-(basic|site|url)|response-(json|xml)|summary-basic + * |scoring-opic|urlnormalizer-(pass|regex|basic) You must change + * "index-(basic|anchor)" to "index-(basic|anchor|urlmeta)", in order to + * call this plugin. + * + * 2. You must also specify the property "urlmeta.tags", who's values are + * comma-delimited key1, key2, key3 + * + * TODO: It may be ideal to offer two separate properties, to specify what + * gets indexed versus merely propagated. + * + */ +public class URLMetaIndexingFilter implements IndexingFilter { + + private static final Log LOG = LogFactory.getLog(URLMetaIndexingFilter.class); + private static final String CONF_PROPERTY = "urlmeta.tags"; + private static String[] urlMetaTags; + private Configuration conf; + + /** + * This will take the metatags that you have listed in your "urlmeta.tags" + * property, and looks for them inside the CrawlDatum object. If they exist, + * this will add it as an attribute inside the NutchDocument. + * + * @see IndexingFilter#filter + */ + public NutchDocument filter(NutchDocument doc, Parse parse, Text url, + CrawlDatum datum, Inlinks inlinks) throws IndexingException { + if (conf != null) + this.setConf(conf); + + if (urlMetaTags == null || doc == null) + return doc; + + for (String metatag : urlMetaTags) { + Text metadata = (Text) datum.getMetaData().get(new Text(metatag)); + + if (metadata != null) + doc.add(metatag, metadata.toString()); + } + + return doc; + } + + /** + * This tells the LuceneWriter that the above attributes should be part of its + * Indexing process. + * + * @see IndexingFilter#addIndexBackendOptions + */ + public void addIndexBackendOptions(Configuration conf) { + if (conf != null) + this.setConf(conf); + + if (urlMetaTags == null) + return; + + for (String metatag : urlMetaTags) { + LuceneWriter.addFieldOptions(metatag, LuceneWriter.STORE.YES, + LuceneWriter.INDEX.TOKENIZED, conf); + } + } + + /** Boilerplate */ + public Configuration getConf() { + return conf; + } + + /** + * handles conf assignment and pulls the value assignment from the + * "urlmeta.tags" property + */ + public void setConf(Configuration conf) { + this.conf = conf; + + if (conf == null) + return; + + urlMetaTags = conf.getStrings(CONF_PROPERTY); + } +} \ No newline at end of file diff --git a/src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/package.html b/src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/package.html new file mode 100644 index 0000000000..5da5d5684f --- /dev/null +++ b/src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/package.html @@ -0,0 +1,12 @@ + + +

+ URL Meta Tag Indexing Plugin +

+

+ Takes Meta Tags, injected alongside a URL (see NUTCH-655) and specified in the "urlmeta.tags" property, + and inserts them into the document--which is then sent to the Indexer. If you specify these fields in + the Nutch's schema (as well as the Indexer's), you can reasonably assume that they will be indexed. +

+ + diff --git a/src/plugin/urlmeta/src/java/org/apache/nutch/scoring/urlmeta/URLMetaScoringFilter.java b/src/plugin/urlmeta/src/java/org/apache/nutch/scoring/urlmeta/URLMetaScoringFilter.java new file mode 100644 index 0000000000..5da1fdbb36 --- /dev/null +++ b/src/plugin/urlmeta/src/java/org/apache/nutch/scoring/urlmeta/URLMetaScoringFilter.java @@ -0,0 +1,174 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.nutch.scoring.urlmeta; + +import java.util.Collection; +import java.util.Map.Entry; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.conf.Configured; +import org.apache.hadoop.io.Text; +import org.apache.nutch.crawl.CrawlDatum; +import org.apache.nutch.crawl.Inlinks; +import org.apache.nutch.indexer.NutchDocument; +import org.apache.nutch.parse.Parse; +import org.apache.nutch.parse.ParseData; +import org.apache.nutch.protocol.Content; +import org.apache.nutch.scoring.ScoringFilter; +import org.apache.nutch.scoring.ScoringFilterException; + +/** + * For documentation: + * + * @see URLMetaIndexingFilter + */ +public class URLMetaScoringFilter extends Configured implements ScoringFilter { + + private static final Log LOG = LogFactory.getLog(URLMetaScoringFilter.class); + private static final String CONF_PROPERTY = "urlmeta.tags"; + private static String[] urlMetaTags; + private Configuration conf; + + /** + * This will take the metatags that you have listed in your "urlmeta.tags" + * property, and looks for them inside the parseData object. If they exist, + * this will be propagated into your 'targets' Collection's ["outlinks"] + * attributes. + * + * @see ScoringFilter#distributeScoreToOutlinks + */ + public CrawlDatum distributeScoreToOutlinks(Text fromUrl, + ParseData parseData, Collection> targets, + CrawlDatum adjust, int allCount) throws ScoringFilterException { + if (urlMetaTags == null || targets == null || parseData == null) + return adjust; + + Iterator> targetIterator = targets.iterator(); + + while (targetIterator.hasNext()) { + Entry nextTarget = targetIterator.next(); + + for (String metatag : urlMetaTags) { + String metaFromParse = parseData.getMeta(metatag); + + if (metaFromParse == null) + continue; + + nextTarget.getValue().getMetaData().put(new Text(metatag), + new Text(metaFromParse)); + } + } + return adjust; + } + + /** + * Takes the metadata, specified in your "urlmeta.tags" property, from the + * datum object and injects it into the content. This is transfered to the + * parseData object. + * + * @see ScoringFilter#passScoreBeforeParsing + * @see URLMetaScoringFilter#passScoreAfterParsing + */ + public void passScoreBeforeParsing(Text url, CrawlDatum datum, Content content) { + if (urlMetaTags == null || content == null || datum == null) + return; + + for (String metatag : urlMetaTags) { + Text metaFromDatum = (Text) datum.getMetaData().get(new Text(metatag)); + + if (metaFromDatum == null) + continue; + + content.getMetadata().set(metatag, metaFromDatum.toString()); + } + } + + /** + * Takes the metadata, which was lumped inside the content, and replicates it + * within your parse data. + * + * @see URLMetaScoringFilter#passScoreBeforeParsing + * @see ScoringFilter#passScoreAfterParsing + */ + public void passScoreAfterParsing(Text url, Content content, Parse parse) { + if (urlMetaTags == null || content == null || parse == null) + return; + + for (String metatag : urlMetaTags) { + String metaFromContent = content.getMetadata().get(metatag); + + if (metaFromContent == null) + continue; + + parse.getData().getParseMeta().set(metatag, metaFromContent); + } + } + + /** Boilerplate */ + public float generatorSortValue(Text url, CrawlDatum datum, float initSort) + throws ScoringFilterException { + return initSort; + } + + /** Boilerplate */ + public float indexerScore(Text url, NutchDocument doc, CrawlDatum dbDatum, + CrawlDatum fetchDatum, Parse parse, Inlinks inlinks, float initScore) + throws ScoringFilterException { + return initScore; + } + + /** Boilerplate */ + public void initialScore(Text url, CrawlDatum datum) + throws ScoringFilterException { + return; + } + + /** Boilerplate */ + public void injectedScore(Text url, CrawlDatum datum) + throws ScoringFilterException { + return; + } + + /** Boilerplate */ + public void updateDbScore(Text url, CrawlDatum old, CrawlDatum datum, + List inlinked) throws ScoringFilterException { + return; + } + + /** + * handles conf assignment and pulls the value assignment from the + * "urlmeta.tags" property + */ + public void setConf(Configuration conf) { + super.setConf(conf); + + if (conf == null) + return; + + urlMetaTags = conf.getStrings(CONF_PROPERTY); + } + + /** Boilerplate */ + public Configuration getConf() { + return conf; + } +} diff --git a/src/plugin/urlmeta/src/java/org/apache/nutch/scoring/urlmeta/package.html b/src/plugin/urlmeta/src/java/org/apache/nutch/scoring/urlmeta/package.html new file mode 100644 index 0000000000..5bba7a8ecc --- /dev/null +++ b/src/plugin/urlmeta/src/java/org/apache/nutch/scoring/urlmeta/package.html @@ -0,0 +1,11 @@ + + +

+ URL Meta Tag Scoring Plugin +

+

+ Propagates Meta Tags, injected alongside a URL (see NUTCH-655) and specified in the "urlmeta.tags" property, + along to their outlinks. This does not actually perform scoring. +

+ + From 8f7c7fffb88b769feef6872ac312926f60378090 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 5 Jan 2011 10:12:20 +0000 Subject: [PATCH 017/754] NUTCH-855 adapted code to 1.3 and removed reference to Lucene indexer + added Ivy file git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055392 13f79535-47bb-0310-9956-ffa450edef68 --- src/plugin/urlmeta/ivy.xml | 41 +++++ .../urlmeta/URLMetaIndexingFilter.java | 162 ++++++++---------- 2 files changed, 112 insertions(+), 91 deletions(-) create mode 100644 src/plugin/urlmeta/ivy.xml diff --git a/src/plugin/urlmeta/ivy.xml b/src/plugin/urlmeta/ivy.xml new file mode 100644 index 0000000000..0a363f7742 --- /dev/null +++ b/src/plugin/urlmeta/ivy.xml @@ -0,0 +1,41 @@ + + + + + + + + + + Apache Nutch + + + + + + + + + + + + + + + + diff --git a/src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/URLMetaIndexingFilter.java b/src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/URLMetaIndexingFilter.java index 113de72404..5e21037fb2 100644 --- a/src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/URLMetaIndexingFilter.java +++ b/src/plugin/urlmeta/src/java/org/apache/nutch/indexer/urlmeta/URLMetaIndexingFilter.java @@ -26,7 +26,6 @@ import org.apache.nutch.indexer.IndexingException; import org.apache.nutch.indexer.IndexingFilter; import org.apache.nutch.indexer.NutchDocument; -import org.apache.nutch.indexer.lucene.LuceneWriter; import org.apache.nutch.parse.Parse; /** @@ -37,102 +36,83 @@ * with your URLs will be indexed alongside those URLs--and can be directly * queried, assuming you have done everything else correctly. * - * The flat-file of URLs you are injecting should, per NUTCH-655, be tab-delimited - * in the form of: + * The flat-file of URLs you are injecting should, per NUTCH-655, be + * tab-delimited in the form of: * - * [www.url.com]\t[key1]=[value1]\t[key2]=[value2]...[keyN]=[valueN] + * [www.url.com]\t[key1]=[value1]\t[key2]=[value2]...[keyN]=[valueN] * - * Be aware that if you collide with keywords that are already in use (such - * as nutch.score/nutch.fetchInterval) then you are in for some unpredictable - * behavior. + * Be aware that if you collide with keywords that are already in use (such as + * nutch.score/nutch.fetchInterval) then you are in for some unpredictable + * behavior. * - * Furthermore, in your nutch-site.xml config, you must specify that this - * plugin is to be used (1), as well as what (2) Meta Tags it should - * actively look for. This does not mean that you must use these tags for - * every URL, but it does mean that you must list _all_ of meta tags that - * you have specified. If you want them to be propagated and indexed, that - * is. - * - * 1. As of Nutch 1.2, the property "plugin.includes" looks as follows: - * protocol-http|urlfilter-regex|parse-(text|html|js|tika|rss)|index - * -(basic|anchor)|query-(basic|site|url)|response-(json|xml)|summary-basic - * |scoring-opic|urlnormalizer-(pass|regex|basic) You must change - * "index-(basic|anchor)" to "index-(basic|anchor|urlmeta)", in order to - * call this plugin. - * - * 2. You must also specify the property "urlmeta.tags", who's values are - * comma-delimited key1, key2, key3 + * Furthermore, in your nutch-site.xml config, you must specify that this plugin + * is to be used (1), as well as what (2) Meta Tags it should actively look for. + * This does not mean that you must use these tags for every URL, but it does + * mean that you must list _all_ of meta tags that you have specified. If you + * want them to be propagated and indexed, that is. * - * TODO: It may be ideal to offer two separate properties, to specify what - * gets indexed versus merely propagated. + * 1. As of Nutch 1.2, the property "plugin.includes" looks as follows: + * protocol-http|urlfilter-regex|parse-(text|html|js|tika|rss)|index + * -(basic|anchor)|query-(basic|site|url)|response-(json|xml)|summary-basic + * |scoring-opic|urlnormalizer-(pass|regex|basic) You must change + * "index-(basic|anchor)" to "index-(basic|anchor|urlmeta)", in order to call + * this plugin. + * + * 2. You must also specify the property "urlmeta.tags", who's values are + * comma-delimited key1, key2, key3 + * + * TODO: It may be ideal to offer two separate properties, to specify what gets + * indexed versus merely propagated. * */ public class URLMetaIndexingFilter implements IndexingFilter { - private static final Log LOG = LogFactory.getLog(URLMetaIndexingFilter.class); - private static final String CONF_PROPERTY = "urlmeta.tags"; - private static String[] urlMetaTags; - private Configuration conf; - - /** - * This will take the metatags that you have listed in your "urlmeta.tags" - * property, and looks for them inside the CrawlDatum object. If they exist, - * this will add it as an attribute inside the NutchDocument. - * - * @see IndexingFilter#filter - */ - public NutchDocument filter(NutchDocument doc, Parse parse, Text url, - CrawlDatum datum, Inlinks inlinks) throws IndexingException { - if (conf != null) - this.setConf(conf); - - if (urlMetaTags == null || doc == null) - return doc; - - for (String metatag : urlMetaTags) { - Text metadata = (Text) datum.getMetaData().get(new Text(metatag)); - - if (metadata != null) - doc.add(metatag, metadata.toString()); - } - - return doc; - } - - /** - * This tells the LuceneWriter that the above attributes should be part of its - * Indexing process. - * - * @see IndexingFilter#addIndexBackendOptions - */ - public void addIndexBackendOptions(Configuration conf) { - if (conf != null) - this.setConf(conf); - - if (urlMetaTags == null) - return; - - for (String metatag : urlMetaTags) { - LuceneWriter.addFieldOptions(metatag, LuceneWriter.STORE.YES, - LuceneWriter.INDEX.TOKENIZED, conf); - } - } - - /** Boilerplate */ - public Configuration getConf() { - return conf; - } - - /** - * handles conf assignment and pulls the value assignment from the - * "urlmeta.tags" property - */ - public void setConf(Configuration conf) { - this.conf = conf; - - if (conf == null) - return; - - urlMetaTags = conf.getStrings(CONF_PROPERTY); - } + private static final Log LOG = LogFactory + .getLog(URLMetaIndexingFilter.class); + private static final String CONF_PROPERTY = "urlmeta.tags"; + private static String[] urlMetaTags; + private Configuration conf; + + /** + * This will take the metatags that you have listed in your "urlmeta.tags" + * property, and looks for them inside the CrawlDatum object. If they exist, + * this will add it as an attribute inside the NutchDocument. + * + * @see IndexingFilter#filter + */ + public NutchDocument filter(NutchDocument doc, Parse parse, Text url, + CrawlDatum datum, Inlinks inlinks) throws IndexingException { + if (conf != null) + this.setConf(conf); + + if (urlMetaTags == null || doc == null) + return doc; + + for (String metatag : urlMetaTags) { + Text metadata = (Text) datum.getMetaData().get(new Text(metatag)); + + if (metadata != null) + doc.add(metatag, metadata.toString()); + } + + return doc; + } + + /** Boilerplate */ + public Configuration getConf() { + return conf; + } + + /** + * handles conf assignment and pulls the value assignment from the + * "urlmeta.tags" property + */ + public void setConf(Configuration conf) { + this.conf = conf; + + if (conf == null) + return; + + urlMetaTags = conf.getStrings(CONF_PROPERTY); + } } \ No newline at end of file From 44f313da9a058d30c9e4e72bc72af00547bbb5aa Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 5 Jan 2011 10:30:09 +0000 Subject: [PATCH 018/754] NUTCH-936 : added to CHANGES.txt git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055398 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 4e73c9cc74..ff58e5509f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-936 LanguageIdentifier should not set empty lang field on NutchDocument (Markus Jelsma via jnioche) + * NUTCH-855 ScoringFilter and IndexingFilter: To allow for the propagation of URL Metatags and their subsequent indexing (Scott Gonyea via mattmann) * NUTCH-901 Make index-more plug-in configurable (Markus Jelsma, mattmann) From 4412ca47d7c780986c17cc4fcf50c923614f250a Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 5 Jan 2011 16:17:37 +0000 Subject: [PATCH 019/754] ported NUTCH-883 to 1.3 (seeNUTCH-951) git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055503 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 + conf/nutch-default.xml | 98 ++++++++---------------------------------- 2 files changed, 20 insertions(+), 80 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ff58e5509f..a0051b031a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-883 Remove unused parameters from nutch-default.xml (jnioche) + * NUTCH-936 LanguageIdentifier should not set empty lang field on NutchDocument (Markus Jelsma via jnioche) * NUTCH-855 ScoringFilter and IndexingFilter: To allow for the propagation of URL Metatags and their subsequent indexing (Scott Gonyea via mattmann) diff --git a/conf/nutch-default.xml b/conf/nutch-default.xml index 125cd4a840..1eeda4c4b6 100644 --- a/conf/nutch-default.xml +++ b/conf/nutch-default.xml @@ -682,86 +682,6 @@
- - - - indexer.score.power - 0.5 - Determines the power of link analyis scores. Each - pages's boost is set to scorescorePower where - score is its link analysis score and scorePower is the - value of this parameter. This is compiled into indexes, so, when - this is changed, pages must be re-indexed for it to take - effect. - - - - indexer.max.title.length - 100 - The maximum number of characters of a title that are indexed. - - - - - indexer.max.tokens - 10000 - - The maximum number of tokens that will be indexed for a single field - in a document. This limits the amount of memory required for - indexing, so that collections with very large files will not crash - the indexing process by running out of memory. - - Note that this effectively truncates large documents, excluding - from the index tokens that occur further in the document. If you - know your source documents are large, be sure to set this value - high enough to accomodate the expected size. If you set it to - -1, then the only limit is your memory, but you should anticipate - an OutOfMemoryError. - - - - - indexer.mergeFactor - 50 - The factor that determines the frequency of Lucene segment - merges. This must not be less than 2, higher values increase indexing - speed but lead to increased RAM usage, and increase the number of - open file handles (which may lead to "Too many open files" errors). - NOTE: the "segments" here have nothing to do with Nutch segments, they - are a low-level data unit used by Lucene. - - - - - indexer.minMergeDocs - 50 - This number determines the minimum number of Lucene - Documents buffered in memory between Lucene segment merges. Larger - values increase indexing speed and increase RAM usage. - - - - - indexer.maxMergeDocs - 2147483647 - This number determines the maximum number of Lucene - Documents to be merged into a new Lucene segment. Larger values - increase batch indexing speed and reduce the number of Lucene segments, - which reduces the number of open file handles; however, this also - decreases incremental indexing performance. - - - - - indexer.termIndexInterval - 128 - Determines the fraction of terms which Lucene keeps in - RAM when searching, to facilitate random-access. Smaller values use - more memory but make searches somewhat faster. Larger values use - less memory but make searches somewhat slower. - - - @@ -791,6 +711,24 @@ + + indexer.score.power + 0.5 + Determines the power of link analyis scores. Each + pages's boost is set to scorescorePower where + score is its link analysis score and scorePower is the + value of this parameter. This is compiled into indexes, so, when + this is changed, pages must be re-indexed for it to take + effect. + + + + indexer.max.title.length + 100 + The maximum number of characters of a title that are indexed. + + + From 366bea359572a56c4268f5ab8d6d56e8662753f0 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 5 Jan 2011 16:25:04 +0000 Subject: [PATCH 020/754] added NUTCH-930 to CHANGES (seeNUTCH-951) git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055509 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index a0051b031a..5195a012c9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-930 Remove remaining dependencies on Lucene API (ab) + * NUTCH-883 Remove unused parameters from nutch-default.xml (jnioche) * NUTCH-936 LanguageIdentifier should not set empty lang field on NutchDocument (Markus Jelsma via jnioche) From d6d1e2e0fa2ee0cdfa9984f0ea46ac9a0dcd795b Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 5 Jan 2011 16:27:37 +0000 Subject: [PATCH 021/754] ported NUTCH-886 to 1.3 (seeNUTCH-951) git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055512 13f79535-47bb-0310-9956-ffa450edef68 --- .gitignore | 7 +++++++ CHANGES.txt | 2 ++ 2 files changed, 9 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..1af3f31c5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +conf/*.txt +conf/*.xml +conf/hadoop-env.sh +conf/slaves +build/ +runtime/ +logs/ diff --git a/CHANGES.txt b/CHANGES.txt index 5195a012c9..bdd5f71af9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-886 A .gitignore file for Nutch (dogacan) + * NUTCH-930 Remove remaining dependencies on Lucene API (ab) * NUTCH-883 Remove unused parameters from nutch-default.xml (jnioche) From 7344ba9358eb58067e8164f50ea84a531884b9db Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 5 Jan 2011 16:42:12 +0000 Subject: [PATCH 022/754] NUTCH-912 added to CHANGES.txt git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055516 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index bdd5f71af9..7aa0237145 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-912 MoreIndexingFilter does not parse docx and xlsx date formats (Markus Jelsma, jnioche) + * NUTCH-886 A .gitignore file for Nutch (dogacan) * NUTCH-930 Remove remaining dependencies on Lucene API (ab) From 372dfb29da0b2c2d0ec54b71b1dfee191b8e62c7 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 5 Jan 2011 16:53:53 +0000 Subject: [PATCH 023/754] NUTCH-935 added to CHANGES git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055522 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 7aa0237145..4a23efa68b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-935 basicurlnormalizer removes unnecessary /./ in URLs + * NUTCH-912 MoreIndexingFilter does not parse docx and xlsx date formats (Markus Jelsma, jnioche) * NUTCH-886 A .gitignore file for Nutch (dogacan) From 8b232735884acedb3df5976a05d8b66c65430b16 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 5 Jan 2011 19:45:33 +0000 Subject: [PATCH 024/754] NUTCH-950 DomainURLFilter throws NPE on bogus urls git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1055604 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ .../apache/nutch/urlfilter/domain/DomainURLFilter.java | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4a23efa68b..fdd47f7e23 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-950 DomainURLFilter throws NPE on bogus urls (Alexis Detreglode via jnioche) + * NUTCH-935 basicurlnormalizer removes unnecessary /./ in URLs * NUTCH-912 MoreIndexingFilter does not parse docx and xlsx date formats (Markus Jelsma, jnioche) diff --git a/src/plugin/urlfilter-domain/src/java/org/apache/nutch/urlfilter/domain/DomainURLFilter.java b/src/plugin/urlfilter-domain/src/java/org/apache/nutch/urlfilter/domain/DomainURLFilter.java index 1ad5d8c765..70e6dd5e94 100644 --- a/src/plugin/urlfilter-domain/src/java/org/apache/nutch/urlfilter/domain/DomainURLFilter.java +++ b/src/plugin/urlfilter-domain/src/java/org/apache/nutch/urlfilter/domain/DomainURLFilter.java @@ -31,6 +31,7 @@ import org.apache.nutch.plugin.Extension; import org.apache.nutch.plugin.PluginRepository; import org.apache.nutch.util.URLUtil; +import org.apache.nutch.util.domain.DomainSuffix; /** *

Filters URLs based on a file containing domain suffixes, domain names, and @@ -170,9 +171,14 @@ public String filter(String url) { // match for suffix, domain, and host in that order. more general will // override more specific - String suffix = URLUtil.getDomainSuffix(url).getDomain(); String domain = URLUtil.getDomainName(url).toLowerCase().trim(); String host = URLUtil.getHost(url); + String suffix = null; + DomainSuffix domainSuffix = URLUtil.getDomainSuffix(url); + if (domainSuffix != null) { + suffix = domainSuffix.getDomain(); + } + if (domainSet.contains(suffix) || domainSet.contains(domain) || domainSet.contains(host)) { return url; From 3e960180336a2c8bf9c745186835735c86a1008c Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Fri, 7 Jan 2011 15:46:25 +0000 Subject: [PATCH 025/754] NUTCH-954 Strict application of Content-Length limit for http protocols (Alexis Detreglode via jnioche) git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1056359 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ .../src/java/org/apache/nutch/protocol/http/HttpResponse.java | 4 +--- .../org/apache/nutch/protocol/httpclient/HttpResponse.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index fdd47f7e23..69c3ce3d07 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-954 Strict application of Content-Length limit for http protocols (Alexis Detreglode via jnioche) + * NUTCH-950 DomainURLFilter throws NPE on bogus urls (Alexis Detreglode via jnioche) * NUTCH-935 basicurlnormalizer removes unnecessary /./ in URLs diff --git a/src/plugin/protocol-http/src/java/org/apache/nutch/protocol/http/HttpResponse.java b/src/plugin/protocol-http/src/java/org/apache/nutch/protocol/http/HttpResponse.java index 9ed77f5131..65b83f717a 100644 --- a/src/plugin/protocol-http/src/java/org/apache/nutch/protocol/http/HttpResponse.java +++ b/src/plugin/protocol-http/src/java/org/apache/nutch/protocol/http/HttpResponse.java @@ -225,12 +225,10 @@ private void readPlainContent(InputStream in) ByteArrayOutputStream out = new ByteArrayOutputStream(Http.BUFFER_SIZE); byte[] bytes = new byte[Http.BUFFER_SIZE]; int length = 0; // read content - for (int i = in.read(bytes); i != -1; i = in.read(bytes)) { + for (int i = in.read(bytes); i != -1 && length + i <= contentLength; i = in.read(bytes)) { out.write(bytes, 0, i); length += i; - if (length >= contentLength) - break; } content = out.toByteArray(); } diff --git a/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/HttpResponse.java b/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/HttpResponse.java index 43baf371cb..84a57c53b7 100644 --- a/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/HttpResponse.java +++ b/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/HttpResponse.java @@ -124,7 +124,7 @@ public class HttpResponse implements Response { int totalRead = 0; ByteArrayOutputStream out = new ByteArrayOutputStream(); while ((bufferFilled = in.read(buffer, 0, buffer.length)) != -1 - && totalRead < contentLength) { + && totalRead + bufferFilled < contentLength) { totalRead += bufferFilled; out.write(buffer, 0, bufferFilled); } From 17fdc291936dac80e4957732cdf7eeb46d0d4b91 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Fri, 7 Jan 2011 17:18:06 +0000 Subject: [PATCH 026/754] NUTCH-824 FileProtocol does not resolve encoded URLs git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1056401 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 + src/plugin/build.xml | 1 + src/plugin/protocol-file/build.xml | 9 +- .../file => sample}/testprotocolfile.txt | 0 .../sample/testprotocolfile_(encoded).txt | 1 + .../nutch/protocol/file/FileResponse.java | 123 +++++++++--------- .../nutch/protocol/file/TestProtocolFile.java | 48 ++++--- 7 files changed, 108 insertions(+), 76 deletions(-) rename src/plugin/protocol-file/{src/test/org/apache/nutch/protocol/file => sample}/testprotocolfile.txt (100%) create mode 100644 src/plugin/protocol-file/sample/testprotocolfile_(encoded).txt diff --git a/CHANGES.txt b/CHANGES.txt index 69c3ce3d07..572b4c773f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-824 Crawling - File Error 404 when fetching file with an hexadecimal character in the file name (Michela Becchi via jnioche) + * NUTCH-954 Strict application of Content-Length limit for http protocols (Alexis Detreglode via jnioche) * NUTCH-950 DomainURLFilter throws NPE on bogus urls (Alexis Detreglode via jnioche) diff --git a/src/plugin/build.xml b/src/plugin/build.xml index a56de605cd..7cb854cc5b 100755 --- a/src/plugin/build.xml +++ b/src/plugin/build.xml @@ -74,6 +74,7 @@ + diff --git a/src/plugin/protocol-file/build.xml b/src/plugin/protocol-file/build.xml index b44682603b..121b1fe50c 100644 --- a/src/plugin/protocol-file/build.xml +++ b/src/plugin/protocol-file/build.xml @@ -18,5 +18,12 @@ - + + + + + + + + diff --git a/src/plugin/protocol-file/src/test/org/apache/nutch/protocol/file/testprotocolfile.txt b/src/plugin/protocol-file/sample/testprotocolfile.txt similarity index 100% rename from src/plugin/protocol-file/src/test/org/apache/nutch/protocol/file/testprotocolfile.txt rename to src/plugin/protocol-file/sample/testprotocolfile.txt diff --git a/src/plugin/protocol-file/sample/testprotocolfile_(encoded).txt b/src/plugin/protocol-file/sample/testprotocolfile_(encoded).txt new file mode 100644 index 0000000000..fbe8a8acf2 --- /dev/null +++ b/src/plugin/protocol-file/sample/testprotocolfile_(encoded).txt @@ -0,0 +1 @@ +Protocol File Test diff --git a/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/FileResponse.java b/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/FileResponse.java index c7ef9754f3..ccba14fef1 100644 --- a/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/FileResponse.java +++ b/src/plugin/protocol-file/src/java/org/apache/nutch/protocol/file/FileResponse.java @@ -22,6 +22,7 @@ import java.util.Date; import java.util.TreeMap; import java.io.IOException; +import java.io.UnsupportedEncodingException; // Nutch imports import org.apache.nutch.crawl.CrawlDatum; @@ -37,31 +38,27 @@ // Hadoop imports import org.apache.hadoop.conf.Configuration; - /************************************ - * FileResponse.java mimics file replies as http response. - * It tries its best to follow http's way for headers, response codes - * as well as exceptions. - * - * Comments: - * (1) java.net.URL and java.net.URLConnection can handle file: scheme. - * However they are not flexible enough, so not used in this implementation. - * - * (2) java.io.File is used for its abstractness across platforms. - * Warning: - * java.io.File API (1.4.2) does not elaborate on how special files, - * such as /dev/* in unix and /proc/* on linux, are treated. Tests show - * (a) java.io.File.isFile() return false for /dev/* - * (b) java.io.File.isFile() return true for /proc/* - * (c) java.io.File.length() return 0 for /proc/* - * We are probably oaky for now. Could be buggy here. - * How about special files on windows? - * - * (3) java.io.File API (1.4.2) does not seem to know unix hard link files. - * They are just treated as individual files. - * + * FileResponse.java mimics file replies as http response. It tries its best to + * follow http's way for headers, response codes as well as exceptions. + * + * Comments: (1) java.net.URL and java.net.URLConnection can handle file: + * scheme. However they are not flexible enough, so not used in this + * implementation. + * + * (2) java.io.File is used for its abstractness across platforms. Warning: + * java.io.File API (1.4.2) does not elaborate on how special files, such as + * /dev/* in unix and /proc/* on linux, are treated. Tests show (a) + * java.io.File.isFile() return false for /dev/* (b) java.io.File.isFile() + * return true for /proc/* (c) java.io.File.length() return 0 for /proc/* We are + * probably oaky for now. Could be buggy here. How about special files on + * windows? + * + * (3) java.io.File API (1.4.2) does not seem to know unix hard link files. They + * are just treated as individual files. + * * (4) No funcy POSIX file attributes yet. May never need? - * + * * @author John Xing ***********************************/ public class FileResponse { @@ -75,25 +72,28 @@ public class FileResponse { private final File file; private Configuration conf; - + private MimeUtil MIME; /** Returns the response code. */ - public int getCode() { return code; } + public int getCode() { + return code; + } /** Returns the value of a named header. */ public String getHeader(String name) { return headers.get(name); } - public byte[] getContent() { return content; } + public byte[] getContent() { + return content; + } public Content toContent() { return new Content(orig, base, (content != null ? content : EMPTY_CONTENT), - getHeader(Response.CONTENT_TYPE), - headers, this.conf); + getHeader(Response.CONTENT_TYPE), headers, this.conf); } - + public FileResponse(URL url, CrawlDatum datum, File file, Configuration conf) throws FileException, IOException { @@ -119,6 +119,12 @@ public FileResponse(URL url, CrawlDatum datum, File file, Configuration conf) String path = "".equals(url.getPath()) ? "/" : url.getPath(); + try { + // specify the encoding via the config later? + path = java.net.URLDecoder.decode(path, "UTF-8"); + } catch (UnsupportedEncodingException ex) { + } + try { this.content = null; @@ -170,45 +176,46 @@ public FileResponse(URL url, CrawlDatum datum, File file, Configuration conf) } // get file as http response - private void getFileAsHttpResponse(java.io.File f) - throws FileException, IOException { + private void getFileAsHttpResponse(java.io.File f) throws FileException, + IOException { // ignore file of size larger than // Integer.MAX_VALUE = 2^31-1 = 2147483647 long size = f.length(); if (size > Integer.MAX_VALUE) { - throw new FileException("file is too large, size: "+size); + throw new FileException("file is too large, size: " + size); // or we can do this? - // this.code = 400; // http Bad request + // this.code = 400; // http Bad request // return; } // capture content int len = (int) size; - + if (this.file.maxContentLength >= 0 && len > this.file.maxContentLength) len = this.file.maxContentLength; this.content = new byte[len]; java.io.InputStream is = new java.io.FileInputStream(f); - int offset = 0; int n = 0; + int offset = 0; + int n = 0; while (offset < len - && (n = is.read(this.content, offset, len-offset)) >= 0) { + && (n = is.read(this.content, offset, len - offset)) >= 0) { offset += n; } if (offset < len) { // keep whatever already have, but issue a warning if (File.LOG.isWarnEnabled()) { - File.LOG.warn("not enough bytes read from file: "+f.getPath()); + File.LOG.warn("not enough bytes read from file: " + f.getPath()); } } - is.close(); + is.close(); // set headers headers.set(Response.CONTENT_LENGTH, new Long(size).toString()); - headers.set(Response.LAST_MODIFIED, HttpDateFormat.toString(f - .lastModified())); - + headers.set(Response.LAST_MODIFIED, + HttpDateFormat.toString(f.lastModified())); + MimeType mimeType = MIME.getMimeType(f); String mimeTypeString = mimeType != null ? mimeType.getName() : ""; headers.set(Response.CONTENT_TYPE, mimeTypeString); @@ -218,33 +225,33 @@ private void getFileAsHttpResponse(java.io.File f) } // get dir list as http response - private void getDirAsHttpResponse(java.io.File f) - throws IOException { + private void getDirAsHttpResponse(java.io.File f) throws IOException { String path = f.toString(); if (this.file.crawlParents) - this.content = list2html(f.listFiles(), path, "/".equals(path) ? false : true); + this.content = list2html(f.listFiles(), path, "/".equals(path) ? false + : true); else - this.content = list2html(f.listFiles(), path, false); + this.content = list2html(f.listFiles(), path, false); // set headers headers.set(Response.CONTENT_LENGTH, - new Integer(this.content.length).toString()); + new Integer(this.content.length).toString()); headers.set(Response.CONTENT_TYPE, "text/html"); headers.set(Response.LAST_MODIFIED, - HttpDateFormat.toString(f.lastModified())); + HttpDateFormat.toString(f.lastModified())); // response code this.code = 200; // http OK } // generate html page from dir list - private byte[] list2html(java.io.File[] list, - String path, boolean includeDotDot) { + private byte[] list2html(java.io.File[] list, String path, + boolean includeDotDot) { StringBuffer x = new StringBuffer(""); - x.append("Index of "+path+"\n"); - x.append("

Index of "+path+"

\n");
+    x.append("Index of " + path + "\n");
+    x.append("

Index of " + path + "

\n");
 
     if (includeDotDot) {
       x.append("../\t-\t-\t-\n");
@@ -253,20 +260,20 @@ private byte[] list2html(java.io.File[] list,
     // fix me: we might want to sort list here! but not now.
 
     java.io.File f;
-    for (int i=0; i"+name+"/\t");
-        x.append(time+"\t-\n");
+        // if (name.equals(".") || name.equals(".."))
+        // continue;
+        x.append("" + name + "/\t");
+        x.append(time + "\t-\n");
       } else if (f.isFile()) {
-        x.append(""+name+"\t");
-        x.append(time+"\t"+f.length()+"\n");
+        x.append("" + name + "\t");
+        x.append(time + "\t" + f.length() + "\n");
       } else {
         // ignore any other
       }
diff --git a/src/plugin/protocol-file/src/test/org/apache/nutch/protocol/file/TestProtocolFile.java b/src/plugin/protocol-file/src/test/org/apache/nutch/protocol/file/TestProtocolFile.java
index 8c3cf0aac4..3618fc6425 100644
--- a/src/plugin/protocol-file/src/test/org/apache/nutch/protocol/file/TestProtocolFile.java
+++ b/src/plugin/protocol-file/src/test/org/apache/nutch/protocol/file/TestProtocolFile.java
@@ -18,11 +18,15 @@
 package org.apache.nutch.protocol.file;
 
 // Hadoop imports
+import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.io.Text;
 
 // Nutch imports
 import org.apache.nutch.crawl.CrawlDatum;
 import org.apache.nutch.net.protocols.Response;
+import org.apache.nutch.protocol.Protocol;
+import org.apache.nutch.protocol.ProtocolException;
+import org.apache.nutch.protocol.ProtocolFactory;
 import org.apache.nutch.protocol.ProtocolOutput;
 import org.apache.nutch.protocol.ProtocolStatus;
 import org.apache.nutch.util.NutchConfiguration;
@@ -34,37 +38,47 @@
  * @author mattmann
  * @version $Revision$
  * 
- * 

- * Unit tests for the {@link File}Protocol. - *

. + *

+ * Unit tests for the {@link File}Protocol. + *

+ * . */ public class TestProtocolFile extends TestCase { - private static final org.apache.nutch.protocol.file.File fileProtocol = - new org.apache.nutch.protocol.file.File(); + private String fileSeparator = System.getProperty("file.separator"); + private String sampleDir = System.getProperty("test.data", "."); - private static final String testTextFile = "testprotocolfile.txt"; + private static final String[] testTextFiles = new String[] { + "testprotocolfile.txt", "testprotocolfile_(encoded).txt", "testprotocolfile_%28encoded%29.txt" }; private static final CrawlDatum datum = new CrawlDatum(); private static final String expectedMimeType = "text/plain"; - static { - fileProtocol.setConf(NutchConfiguration.create()); + private Configuration conf; + + protected void setUp() { + conf = NutchConfiguration.create(); + } + + public void testSetContentType() throws ProtocolException { + for (String testTextFile : testTextFiles) { + setContentType(testTextFile); + } } /** - * Tests the setting of the Response.CONTENT_TYPE metadata - * field. + * Tests the setting of the Response.CONTENT_TYPE metadata field. * * @since NUTCH-384 * */ - public void testSetContentType() { - Text fileUrl = new Text(this.getClass().getResource(testTextFile) - .toString()); - assertNotNull(fileUrl); - ProtocolOutput output = fileProtocol.getProtocolOutput(fileUrl, datum); + public void setContentType(String testTextFile) throws ProtocolException { + String urlString = "file:" + sampleDir + fileSeparator + testTextFile; + assertNotNull(urlString); + Protocol protocol = new ProtocolFactory(conf).getProtocol(urlString); + ProtocolOutput output = protocol.getProtocolOutput(new Text(urlString), + datum); assertNotNull(output); assertEquals("Status code: [" + output.getStatus().getCode() + "], not equal to: [" + ProtocolStatus.SUCCESS + "]: args: [" @@ -74,8 +88,8 @@ public void testSetContentType() { assertNotNull(output.getContent().getContentType()); assertEquals(expectedMimeType, output.getContent().getContentType()); assertNotNull(output.getContent().getMetadata()); - assertEquals(expectedMimeType, output.getContent().getMetadata().get( - Response.CONTENT_TYPE)); + assertEquals(expectedMimeType, + output.getContent().getMetadata().get(Response.CONTENT_TYPE)); } From 66454e4043713c7dded12530d387b579fc5ea84c Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Fri, 21 Jan 2011 14:18:52 +0000 Subject: [PATCH 027/754] NUTCH-957 fetcher.timelimit.mins is invalid when depth is greater than 1 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1061815 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ src/java/org/apache/nutch/fetcher/Fetcher.java | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 572b4c773f..cfb3a78d0a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-927 Fetcher.timelimit.mins is invalid when depth is greater than 1 (Wade Lau via jnioche) + * NUTCH-824 Crawling - File Error 404 when fetching file with an hexadecimal character in the file name (Michela Becchi via jnioche) * NUTCH-954 Strict application of Content-Length limit for http protocols (Alexis Detreglode via jnioche) diff --git a/src/java/org/apache/nutch/fetcher/Fetcher.java b/src/java/org/apache/nutch/fetcher/Fetcher.java index 840744cdec..12b0bcdf0f 100644 --- a/src/java/org/apache/nutch/fetcher/Fetcher.java +++ b/src/java/org/apache/nutch/fetcher/Fetcher.java @@ -323,7 +323,7 @@ public FetchItemQueues(Configuration conf) { this.byIP = conf.getBoolean("fetcher.threads.per.host.by.ip", false); this.crawlDelay = (long) (conf.getFloat("fetcher.server.delay", 1.0f) * 1000); this.minCrawlDelay = (long) (conf.getFloat("fetcher.server.min.delay", 0.0f) * 1000); - this.timelimit = conf.getLong("fetcher.timelimit.mins", -1); + this.timelimit = conf.getLong("fetcher.timelimit", -1); this.maxExceptionsPerQueue = conf.getInt("fetcher.max.exceptions.per.queue", -1); } @@ -1014,7 +1014,7 @@ public void run(RecordReader input, //feeder.setPriority((Thread.MAX_PRIORITY + Thread.NORM_PRIORITY) / 2); // the value of the time limit is either -1 or the time where it should finish - long timelimit = getConf().getLong("fetcher.timelimit.mins", -1); + long timelimit = getConf().getLong("fetcher.timelimit", -1); if (timelimit != -1) feeder.setTimeLimit(timelimit); feeder.start(); @@ -1081,7 +1081,7 @@ public void fetch(Path segment, int threads, boolean parsing) if (timelimit != -1) { timelimit = System.currentTimeMillis() + (timelimit * 60 * 1000); LOG.info("Fetcher Timelimit set for : " + timelimit); - getConf().setLong("fetcher.timelimit.mins", timelimit); + getConf().setLong("fetcher.timelimit", timelimit); } JobConf job = new NutchJob(getConf()); From a082800e2e5a8d5949f70b77f1573fbe73ccfd1a Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Mon, 24 Jan 2011 10:55:29 +0000 Subject: [PATCH 028/754] Removed unused index.html file and contrib directory git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1062728 13f79535-47bb-0310-9956-ffa450edef68 --- index.html | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 index.html diff --git a/index.html b/index.html deleted file mode 100644 index c79a89cfa7..0000000000 --- a/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - Redirecting... - - - -See docs. - - From e48d972429b8e898a34def6c509893db70087afc Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Thu, 27 Jan 2011 13:33:36 +0000 Subject: [PATCH 029/754] NUTCH-964 Upgraded Xerces to 2.91, ERROR conf.Configuration - Failed to set setXIncludeAware (markus) git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1064121 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 4 +++- ivy/ivy.xml | 2 +- src/plugin/lib-xml/ivy.xml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index cfb3a78d0a..9b50fb7f6b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-964 Upgraded Xerces to 2.91, ERROR conf.Configuration - Failed to set setXIncludeAware (markus) + * NUTCH-927 Fetcher.timelimit.mins is invalid when depth is greater than 1 (Wade Lau via jnioche) * NUTCH-824 Crawling - File Error 404 when fetching file with an hexadecimal character in the file name (Michela Becchi via jnioche) @@ -10,7 +12,7 @@ Release 1.3 - Current Development * NUTCH-950 DomainURLFilter throws NPE on bogus urls (Alexis Detreglode via jnioche) -* NUTCH-935 basicurlnormalizer removes unnecessary /./ in URLs +* NUTCH-935 basicurlnormalizer removes unnecessary /./ in URLs (Stondet via markus) * NUTCH-912 MoreIndexingFilter does not parse docx and xlsx date formats (Markus Jelsma, jnioche) diff --git a/ivy/ivy.xml b/ivy/ivy.xml index 355474fe7e..733b665306 100644 --- a/ivy/ivy.xml +++ b/ivy/ivy.xml @@ -64,7 +64,7 @@ - + diff --git a/src/plugin/lib-xml/ivy.xml b/src/plugin/lib-xml/ivy.xml index 6115bf7b5b..3fb860e45a 100644 --- a/src/plugin/lib-xml/ivy.xml +++ b/src/plugin/lib-xml/ivy.xml @@ -38,7 +38,7 @@ - + From c4c5b2914b855a040b459f3ac80b07703c370eb1 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Mon, 31 Jan 2011 10:51:55 +0000 Subject: [PATCH 030/754] NUTCH-951 : backport changes from 2.0 into 1.3 : NUTCH-564 done git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1065552 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ src/plugin/parse-ext/plugin.xml | 4 ++++ .../org/apache/nutch/parse/ext/ExtParser.java | 15 +++++++++++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9b50fb7f6b..4a865dae08 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-564 External parser supports encoding attribute (Antony Bowesman, mattmann) + * NUTCH-964 Upgraded Xerces to 2.91, ERROR conf.Configuration - Failed to set setXIncludeAware (markus) * NUTCH-927 Fetcher.timelimit.mins is invalid when depth is greater than 1 (Wade Lau via jnioche) diff --git a/src/plugin/parse-ext/plugin.xml b/src/plugin/parse-ext/plugin.xml index eceb398b09..6819b36237 100644 --- a/src/plugin/parse-ext/plugin.xml +++ b/src/plugin/parse-ext/plugin.xml @@ -41,6 +41,8 @@ + + + + diff --git a/src/plugin/parse-ext/src/java/org/apache/nutch/parse/ext/ExtParser.java b/src/plugin/parse-ext/src/java/org/apache/nutch/parse/ext/ExtParser.java index ac2924806d..aabcffdde3 100644 --- a/src/plugin/parse-ext/src/java/org/apache/nutch/parse/ext/ExtParser.java +++ b/src/plugin/parse-ext/src/java/org/apache/nutch/parse/ext/ExtParser.java @@ -42,6 +42,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; /** * A wrapper that invokes external command to do real parsing job. @@ -57,7 +58,7 @@ public class ExtParser implements Parser { static final int TIMEOUT_DEFAULT = 30; // in seconds - // handy map from String contentType to String[] {command, timeoutString} + // handy map from String contentType to String[] {command, timeoutString, encoding} Hashtable TYPE_PARAMS_MAP = new Hashtable(); private Configuration conf; @@ -77,6 +78,7 @@ public ParseResult getParse(Content content) { String command = params[0]; int timeout = Integer.parseInt(params[1]); + String encoding = params[2]; if (LOG.isTraceEnabled()) { LOG.trace("Use "+command+ " with timeout="+timeout+"secs"); @@ -117,7 +119,7 @@ public ParseResult getParse(Content content) { "External command " + command + " failed with error: " + es.toString()).getEmptyParseResult(content.getUrl(), getConf()); - text = os.toString(); + text = os.toString(encoding); } catch (Exception e) { // run time exception return new ParseStatus(e).getEmptyParseResult(content.getUrl(), getConf()); @@ -143,7 +145,7 @@ public void setConf(Configuration conf) { Extension[] extensions = PluginRepository.get(conf).getExtensionPoint( "org.apache.nutch.parse.Parser").getExtensions(); - String contentType, command, timeoutString; + String contentType, command, timeoutString, encoding; for (int i = 0; i < extensions.length; i++) { Extension extension = extensions[i]; @@ -160,11 +162,16 @@ public void setConf(Configuration conf) { if (command == null || command.equals("")) continue; + // null encoding means default + encoding = extension.getAttribute("encoding"); + if (encoding == null) + encoding = Charset.defaultCharset().name(); + timeoutString = extension.getAttribute("timeout"); if (timeoutString == null || timeoutString.equals("")) timeoutString = "" + TIMEOUT_DEFAULT; - TYPE_PARAMS_MAP.put(contentType, new String[] { command, timeoutString }); + TYPE_PARAMS_MAP.put(contentType, new String[] { command, timeoutString, encoding }); } } From 2ed2d7b81f50ecd718d11d0bebb42a3705f20ca8 Mon Sep 17 00:00:00 2001 From: Andrzej Bialecki Date: Wed, 9 Mar 2011 11:06:43 +0000 Subject: [PATCH 031/754] NUTCH-872 Change the default fetcher.parse to FALSE. git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1079746 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ conf/nutch-default.xml | 5 +++-- src/java/org/apache/nutch/fetcher/Fetcher.java | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4a865dae08..7600fe549c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-872 Change the default fetcher.parse to FALSE (ab) + * NUTCH-564 External parser supports encoding attribute (Antony Bowesman, mattmann) * NUTCH-964 Upgraded Xerces to 2.91, ERROR conf.Configuration - Failed to set setXIncludeAware (markus) diff --git a/conf/nutch-default.xml b/conf/nutch-default.xml index 1eeda4c4b6..7712454675 100644 --- a/conf/nutch-default.xml +++ b/conf/nutch-default.xml @@ -653,8 +653,9 @@ fetcher.parse - true - If true, fetcher will parse content. + false + If true, fetcher will parse content. Default is false, which means + that a separate parsing step is required after fetching is finished. diff --git a/src/java/org/apache/nutch/fetcher/Fetcher.java b/src/java/org/apache/nutch/fetcher/Fetcher.java index 12b0bcdf0f..f7f696c834 100644 --- a/src/java/org/apache/nutch/fetcher/Fetcher.java +++ b/src/java/org/apache/nutch/fetcher/Fetcher.java @@ -1129,7 +1129,7 @@ public int run(String[] args) throws Exception { Path segment = new Path(args[0]); int threads = getConf().getInt("fetcher.threads.fetch", 10); - boolean parsing = true; + boolean parsing = false; for (int i = 1; i < args.length; i++) { // parse command line if (args[i].equals("-threads")) { // found -threads option From c3b52bb27d76b96ba153b0e4e5643b1c3e9f28e2 Mon Sep 17 00:00:00 2001 From: Andrzej Bialecki Date: Wed, 9 Mar 2011 11:26:20 +0000 Subject: [PATCH 032/754] NUTCH-876 Remove remaining robots/IP blocking code in lib-http. git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1079753 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 + .../nutch/protocol/http/api/HttpBase.java | 197 +----------------- .../nutch/protocol/httpclient/Http.java | 5 - 3 files changed, 3 insertions(+), 201 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7600fe549c..9bb5fa4a9b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-876 Remove remaining robots/IP blocking code in lib-http (ab) + * NUTCH-872 Change the default fetcher.parse to FALSE (ab) * NUTCH-564 External parser supports encoding attribute (Antony Bowesman, mattmann) diff --git a/src/plugin/lib-http/src/java/org/apache/nutch/protocol/http/api/HttpBase.java b/src/plugin/lib-http/src/java/org/apache/nutch/protocol/http/api/HttpBase.java index 8ae0f612fe..e692100a09 100644 --- a/src/plugin/lib-http/src/java/org/apache/nutch/protocol/http/api/HttpBase.java +++ b/src/plugin/lib-http/src/java/org/apache/nutch/protocol/http/api/HttpBase.java @@ -18,11 +18,7 @@ // JDK imports import java.io.IOException; -import java.net.InetAddress; import java.net.URL; -import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.LinkedList; // Commons Logging imports import org.apache.commons.logging.Log; @@ -72,21 +68,6 @@ public abstract class HttpBase implements Protocol { /** The length limit for downloaded content, in bytes. */ protected int maxContent = 64 * 1024; - /** The number of times a thread will delay when trying to fetch a page. */ - protected int maxDelays = 3; - - /** - * The maximum number of threads that should be allowed - * to access a host at one time. - */ - protected int maxThreadsPerHost = 1; - - /** - * The number of seconds the fetcher will delay between - * successive requests to the same server. - */ - protected long serverDelay = 1000; - /** The Nutch 'User-Agent' request header */ protected String userAgent = getAgentString( "NutchCVS", null, "Nutch", @@ -96,25 +77,6 @@ public abstract class HttpBase implements Protocol { /** The "Accept-Language" request header value. */ protected String acceptLanguage = "en-us,en-gb,en;q=0.7,*;q=0.3"; - /** - * Maps from host to a Long naming the time it should be unblocked. - * The Long is zero while the host is in use, then set to now+wait when - * a request finishes. This way only one thread at a time accesses a - * host. - */ - private static HashMap BLOCKED_ADDR_TO_TIME = new HashMap(); - - /** - * Maps a host to the number of threads accessing that host. - */ - private static HashMap THREADS_PER_HOST_COUNT = new HashMap(); - - /** - * Queue of blocked hosts. This contains all of the non-zero entries - * from BLOCKED_ADDR_TO_TIME, ordered by increasing time. - */ - private static LinkedList BLOCKED_ADDR_QUEUE = new LinkedList(); - /** The default logger */ private final static Log LOGGER = LogFactory.getLog(HttpBase.class); @@ -124,21 +86,12 @@ public abstract class HttpBase implements Protocol { /** The nutch configuration */ private Configuration conf = null; - /** Do we block by IP addresses or by hostnames? */ - private boolean byIP = true; - /** Do we use HTTP/1.1? */ protected boolean useHttp11 = false; /** Skip page if Crawl-Delay longer than this value. */ protected long maxCrawlDelay = -1L; - /** Plugin should handle host blocking internally. */ - protected boolean checkBlocking = true; - - /** Plugin should handle robot rules checking internally. */ - protected boolean checkRobots = true; - /** Creates a new instance of HttpBase */ public HttpBase() { this(null); @@ -160,19 +113,12 @@ public void setConf(Configuration conf) { this.useProxy = (proxyHost != null && proxyHost.length() > 0); this.timeout = conf.getInt("http.timeout", 10000); this.maxContent = conf.getInt("http.content.limit", 64 * 1024); - this.maxDelays = conf.getInt("http.max.delays", 3); - this.maxThreadsPerHost = conf.getInt("fetcher.threads.per.host", 1); this.userAgent = getAgentString(conf.get("http.agent.name"), conf.get("http.agent.version"), conf .get("http.agent.description"), conf.get("http.agent.url"), conf.get("http.agent.email")); this.acceptLanguage = conf.get("http.accept.language", acceptLanguage); - this.serverDelay = (long) (conf.getFloat("fetcher.server.delay", 1.0f) * 1000); - this.maxCrawlDelay = (long)(conf.getInt("fetcher.max.crawl.delay", -1) * 1000); // backward-compatible default setting - this.byIP = conf.getBoolean("fetcher.threads.per.host.by.ip", true); this.useHttp11 = conf.getBoolean("http.useHttp11", false); this.robots.setConf(conf); - this.checkBlocking = conf.getBoolean(Protocol.CHECK_BLOCKING, true); - this.checkRobots = conf.getBoolean(Protocol.CHECK_ROBOTS, true); logConf(); } @@ -188,43 +134,8 @@ public ProtocolOutput getProtocolOutput(Text url, CrawlDatum datum) { String urlString = url.toString(); try { URL u = new URL(urlString); - long delay = serverDelay; - - if (checkRobots) { - try { - if (!robots.isAllowed(this, u)) { - return new ProtocolOutput(null, new ProtocolStatus(ProtocolStatus.ROBOTS_DENIED, url)); - } - } catch (Throwable e) { - // XXX Maybe bogus: assume this is allowed. - if (logger.isTraceEnabled()) { - logger.trace("Exception checking robot rules for " + url + ": " + e); - } - } - - long crawlDelay = robots.getCrawlDelay(this, u); - delay = crawlDelay > 0 ? crawlDelay : serverDelay; - } - if (checkBlocking && maxCrawlDelay >= 0 && delay > maxCrawlDelay) { - // skip this page, otherwise the thread would block for too long. - LOGGER.info("Skipping: " + u + " exceeds fetcher.max.crawl.delay, max=" - + (maxCrawlDelay / 1000) + ", Crawl-Delay=" + (delay / 1000)); - return new ProtocolOutput(null, ProtocolStatus.STATUS_WOULDBLOCK); - } String host = null; - if (checkBlocking) { - try { - host = blockAddr(u, delay); - } catch (BlockedException be) { - return new ProtocolOutput(null, ProtocolStatus.STATUS_BLOCKED); - } - } - Response response; - try { - response = getResponse(u, datum, false); // make a request - } finally { - if (checkBlocking) unblockAddr(host, delay); - } + Response response = getResponse(u, datum, false); // make a request int code = response.getCode(); byte[] content = response.getContent(); @@ -313,18 +224,6 @@ public int getMaxContent() { return maxContent; } - public int getMaxDelays() { - return maxDelays; - } - - public int getMaxThreadsPerHost() { - return maxThreadsPerHost; - } - - public long getServerDelay() { - return serverDelay; - } - public String getUserAgent() { return userAgent; } @@ -340,94 +239,6 @@ public boolean getUseHttp11() { return useHttp11; } - private String blockAddr(URL url, long crawlDelay) throws ProtocolException { - - String host; - if (byIP) { - try { - InetAddress addr = InetAddress.getByName(url.getHost()); - host = addr.getHostAddress(); - } catch (UnknownHostException e) { - // unable to resolve it, so don't fall back to host name - throw new HttpException(e); - } - } else { - host = url.getHost(); - if (host == null) - throw new HttpException("Unknown host for url: " + url); - host = host.toLowerCase(); - } - - int delays = 0; - while (true) { - cleanExpiredServerBlocks(); // free held addresses - - Long time; - synchronized (BLOCKED_ADDR_TO_TIME) { - time = (Long) BLOCKED_ADDR_TO_TIME.get(host); - if (time == null) { // address is free - - // get # of threads already accessing this addr - Integer counter = (Integer)THREADS_PER_HOST_COUNT.get(host); - int count = (counter == null) ? 0 : counter.intValue(); - - count++; // increment & store - THREADS_PER_HOST_COUNT.put(host, new Integer(count)); - - if (count >= maxThreadsPerHost) { - BLOCKED_ADDR_TO_TIME.put(host, new Long(0)); // block it - } - return host; - } - } - - if (delays == maxDelays) - throw new BlockedException("Exceeded http.max.delays: retry later."); - - long done = time.longValue(); - long now = System.currentTimeMillis(); - long sleep = 0; - if (done == 0) { // address is still in use - sleep = crawlDelay; // wait at least delay - - } else if (now < done) { // address is on hold - sleep = done - now; // wait until its free - } - - try { - Thread.sleep(sleep); - } catch (InterruptedException e) {} - delays++; - } - } - - private void unblockAddr(String host, long crawlDelay) { - synchronized (BLOCKED_ADDR_TO_TIME) { - int addrCount = ((Integer)THREADS_PER_HOST_COUNT.get(host)).intValue(); - if (addrCount == 1) { - THREADS_PER_HOST_COUNT.remove(host); - BLOCKED_ADDR_QUEUE.addFirst(host); - BLOCKED_ADDR_TO_TIME.put - (host, new Long(System.currentTimeMillis() + crawlDelay)); - } else { - THREADS_PER_HOST_COUNT.put(host, new Integer(addrCount - 1)); - } - } - } - - private static void cleanExpiredServerBlocks() { - synchronized (BLOCKED_ADDR_TO_TIME) { - for (int i = BLOCKED_ADDR_QUEUE.size() - 1; i >= 0; i--) { - String host = (String) BLOCKED_ADDR_QUEUE.get(i); - long time = ((Long) BLOCKED_ADDR_TO_TIME.get(host)).longValue(); - if (time <= System.currentTimeMillis()) { - BLOCKED_ADDR_TO_TIME.remove(host); - BLOCKED_ADDR_QUEUE.remove(i); - } - } - } - } - private static String getAgentString(String agentName, String agentVersion, String agentDesc, @@ -481,12 +292,6 @@ protected void logConf() { logger.info("http.content.limit = " + maxContent); logger.info("http.agent = " + userAgent); logger.info("http.accept.language = " + acceptLanguage); - logger.info(Protocol.CHECK_BLOCKING + " = " + checkBlocking); - logger.info(Protocol.CHECK_ROBOTS + " = " + checkRobots); - if (checkBlocking) { - logger.info("fetcher.server.delay = " + serverDelay); - logger.info("http.max.delays = " + maxDelays); - } } } diff --git a/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/Http.java b/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/Http.java index dd22ca93c1..5b96e9786f 100644 --- a/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/Http.java +++ b/src/plugin/protocol-httpclient/src/java/org/apache/nutch/protocol/httpclient/Http.java @@ -170,11 +170,6 @@ private void configureClient() { params.setSendBufferSize(BUFFER_SIZE); params.setReceiveBufferSize(BUFFER_SIZE); params.setMaxTotalConnections(maxThreadsTotal); - if (maxThreadsTotal > maxThreadsPerHost) { - params.setDefaultMaxConnectionsPerHost(maxThreadsPerHost); - } else { - params.setDefaultMaxConnectionsPerHost(maxThreadsTotal); - } // executeMethod(HttpMethod) seems to ignore the connection timeout on the connection manager. // set it explicitly on the HttpClient. From 488aba5cc4a38525483c6ef11c796b7cacc44a0e Mon Sep 17 00:00:00 2001 From: Andrzej Bialecki Date: Wed, 9 Mar 2011 11:48:22 +0000 Subject: [PATCH 033/754] NUTCH-921 Reduce dependency of Nutch on config files. git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1079760 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 + .../urlfilter/api/RegexURLFilterBase.java | 47 ++++++++++++------- .../automaton/AutomatonURLFilter.java | 17 +++++-- .../urlfilter/domain/DomainURLFilter.java | 16 ++++--- .../urlfilter/prefix/PrefixURLFilter.java | 17 +++++-- .../nutch/urlfilter/regex/RegexURLFilter.java | 18 +++++-- .../urlfilter/suffix/SuffixURLFilter.java | 15 ++++-- .../regex/RegexURLNormalizer.java | 45 +++++++++++------- .../regex/TestRegexURLNormalizer.java | 5 +- 9 files changed, 124 insertions(+), 58 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9bb5fa4a9b..f09a49ff83 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-921 Reduce dependency of Nutch on config files (ab) + * NUTCH-876 Remove remaining robots/IP blocking code in lib-http (ab) * NUTCH-872 Change the default fetcher.parse to FALSE (ab) diff --git a/src/plugin/lib-regex-filter/src/java/org/apache/nutch/urlfilter/api/RegexURLFilterBase.java b/src/plugin/lib-regex-filter/src/java/org/apache/nutch/urlfilter/api/RegexURLFilterBase.java index 173a768fc2..6347b635d8 100644 --- a/src/plugin/lib-regex-filter/src/java/org/apache/nutch/urlfilter/api/RegexURLFilterBase.java +++ b/src/plugin/lib-regex-filter/src/java/org/apache/nutch/urlfilter/api/RegexURLFilterBase.java @@ -17,11 +17,13 @@ package org.apache.nutch.urlfilter.api; // JDK imports +import java.io.File; import java.io.Reader; import java.io.FileReader; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; +import java.io.StringReader; import java.util.List; import java.util.ArrayList; @@ -74,10 +76,21 @@ public RegexURLFilterBase() { } * Constructs a new RegexURLFilter and init it with a file of rules. * @param filename is the name of rules file. */ - public RegexURLFilterBase(String filename) + public RegexURLFilterBase(File filename) throws IOException, IllegalArgumentException { this(new FileReader(filename)); } + + /** + * Constructs a new RegexURLFilter and inits it with a list of rules. + * @param rules string with a list of rules, one rule per line + * @throws IOException + * @throws IllegalArgumentException + */ + public RegexURLFilterBase(String rules) throws IOException, + IllegalArgumentException { + this(new StringReader(rules)); + } /** * Constructs a new RegexURLFilter and init it with a Reader of rules. @@ -85,7 +98,7 @@ public RegexURLFilterBase(String filename) */ protected RegexURLFilterBase(Reader reader) throws IOException, IllegalArgumentException { - rules = readRulesFile(reader); + rules = readRules(reader); } /** @@ -102,9 +115,9 @@ protected RegexURLFilterBase(Reader reader) * Returns the name of the file of rules to use for * a particular implementation. * @param conf is the current configuration. - * @return the name of the file of rules to use. + * @return the name of the resource containing the rules to use. */ - protected abstract String getRulesFile(Configuration conf); + protected abstract Reader getRulesReader(Configuration conf) throws IOException; /* -------------------------- * @@ -132,18 +145,18 @@ public synchronized String filter(String url) { public void setConf(Configuration conf) { this.conf = conf; - String file = getRulesFile(conf); - Reader reader = conf.getConfResourceAsReader(file); - if (reader == null) { - if (LOG.isFatalEnabled()) { LOG.fatal("Can't find resource: " + file); } - } else { - try { - rules = readRulesFile(reader); - } catch (IOException e) { - if (LOG.isFatalEnabled()) { LOG.fatal(e.getMessage()); } - //TODO mb@media-style.com: throw Exception? Because broken api. - throw new RuntimeException(e.getMessage(), e); - } + Reader reader = null; + try { + reader = getRulesReader(conf); + } catch (Exception e) { + if (LOG.isErrorEnabled()) { LOG.error(e.getMessage()); } + throw new RuntimeException(e.getMessage(), e); + } + try { + rules = readRules(reader); + } catch (IOException e) { + if (LOG.isErrorEnabled()) { LOG.error(e.getMessage()); } + throw new RuntimeException(e.getMessage(), e); } } @@ -161,7 +174,7 @@ public Configuration getConf() { * @param reader is a reader of regular expressions rules. * @return the corresponding {@RegexRule rules}. */ - private RegexRule[] readRulesFile(Reader reader) + private RegexRule[] readRules(Reader reader) throws IOException, IllegalArgumentException { BufferedReader in = new BufferedReader(reader); diff --git a/src/plugin/urlfilter-automaton/src/java/org/apache/nutch/urlfilter/automaton/AutomatonURLFilter.java b/src/plugin/urlfilter-automaton/src/java/org/apache/nutch/urlfilter/automaton/AutomatonURLFilter.java index a8d69462ed..850ddfb2bd 100644 --- a/src/plugin/urlfilter-automaton/src/java/org/apache/nutch/urlfilter/automaton/AutomatonURLFilter.java +++ b/src/plugin/urlfilter-automaton/src/java/org/apache/nutch/urlfilter/automaton/AutomatonURLFilter.java @@ -19,6 +19,7 @@ // JDK imports import java.io.Reader; import java.io.IOException; +import java.io.StringReader; import java.util.regex.PatternSyntaxException; // Hadoop imports @@ -41,6 +42,8 @@ * @see dk.brics.automaton */ public class AutomatonURLFilter extends RegexURLFilterBase { + public static final String URLFILTER_AUTOMATON_FILE = "urlfilter.automaton.file"; + public static final String URLFILTER_AUTOMATON_RULES = "urlfilter.automaton.rules"; public AutomatonURLFilter() { super(); @@ -61,9 +64,17 @@ public AutomatonURLFilter(String filename) * * * ----------------------------------- */ - // Inherited Javadoc - protected String getRulesFile(Configuration conf) { - return conf.get("urlfilter.automaton.file"); + /** + * Rules specified as a config property will override rules specified + * as a config file. + */ + protected Reader getRulesReader(Configuration conf) throws IOException { + String stringRules = conf.get(URLFILTER_AUTOMATON_RULES); + if (stringRules != null) { + return new StringReader(stringRules); + } + String fileRules = conf.get(URLFILTER_AUTOMATON_FILE); + return conf.getConfResourceAsReader(fileRules); } // Inherited Javadoc diff --git a/src/plugin/urlfilter-domain/src/java/org/apache/nutch/urlfilter/domain/DomainURLFilter.java b/src/plugin/urlfilter-domain/src/java/org/apache/nutch/urlfilter/domain/DomainURLFilter.java index 70e6dd5e94..0ade885b31 100644 --- a/src/plugin/urlfilter-domain/src/java/org/apache/nutch/urlfilter/domain/DomainURLFilter.java +++ b/src/plugin/urlfilter-domain/src/java/org/apache/nutch/urlfilter/domain/DomainURLFilter.java @@ -20,6 +20,7 @@ import java.io.FileReader; import java.io.IOException; import java.io.Reader; +import java.io.StringReader; import java.util.LinkedHashSet; import java.util.Set; @@ -70,7 +71,7 @@ public class DomainURLFilter private String domainFile = null; private Set domainSet = new LinkedHashSet(); - private void readConfigurationFile(Reader configReader) + private void readConfiguration(Reader configReader) throws IOException { // read the configuration file, line by line @@ -140,21 +141,24 @@ public void setConf(Configuration conf) { // domain file and attribute "file" take precedence if defined String file = conf.get("urlfilter.domain.file"); + String stringRules = conf.get("urlfilter.domain.rules"); if (domainFile != null) { file = domainFile; } else if (attributeFile != null) { file = attributeFile; } - - // get the file as a classpath resource and populate the domain set with - // the domains from the file + Reader reader = null; + if (stringRules != null) { // takes precedence over files + reader = new StringReader(stringRules); + } else { + reader = conf.getConfResourceAsReader(file); + } try { - Reader reader = conf.getConfResourceAsReader(file); if (reader == null) { reader = new FileReader(file); } - readConfigurationFile(reader); + readConfiguration(reader); } catch (IOException e) { LOG.error(org.apache.hadoop.util.StringUtils.stringifyException(e)); diff --git a/src/plugin/urlfilter-prefix/src/java/org/apache/nutch/urlfilter/prefix/PrefixURLFilter.java b/src/plugin/urlfilter-prefix/src/java/org/apache/nutch/urlfilter/prefix/PrefixURLFilter.java index dd759ef93e..9c483cb272 100644 --- a/src/plugin/urlfilter-prefix/src/java/org/apache/nutch/urlfilter/prefix/PrefixURLFilter.java +++ b/src/plugin/urlfilter-prefix/src/java/org/apache/nutch/urlfilter/prefix/PrefixURLFilter.java @@ -36,6 +36,7 @@ import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; +import java.io.StringReader; import java.util.List; import java.util.ArrayList; @@ -63,8 +64,8 @@ public PrefixURLFilter() throws IOException { } - public PrefixURLFilter(String filename) throws IOException { - trie = readConfigurationFile(new FileReader(filename)); + public PrefixURLFilter(String stringRules) throws IOException { + trie = readConfiguration(new StringReader(stringRules)); } public String filter(String url) { @@ -74,7 +75,7 @@ public String filter(String url) { return url; } - private TrieStringMatcher readConfigurationFile(Reader reader) + private TrieStringMatcher readConfiguration(Reader reader) throws IOException { BufferedReader in=new BufferedReader(reader); @@ -144,16 +145,22 @@ public void setConf(Configuration conf) { } String file = conf.get("urlfilter.prefix.file"); + String stringRules = conf.get("urlfilter.prefix.rules"); // attribute "file" takes precedence if defined if (attributeFile != null) file = attributeFile; - Reader reader = conf.getConfResourceAsReader(file); + Reader reader = null; + if (stringRules != null) { // takes precedence over files + reader = new StringReader(stringRules); + } else { + reader = conf.getConfResourceAsReader(file); + } if (reader == null) { trie = new PrefixStringMatcher(new String[0]); } else { try { - trie = readConfigurationFile(reader); + trie = readConfiguration(reader); } catch (IOException e) { if (LOG.isFatalEnabled()) { LOG.fatal(e.getMessage()); } // TODO mb@media-style.com: throw Exception? Because broken api. diff --git a/src/plugin/urlfilter-regex/src/java/org/apache/nutch/urlfilter/regex/RegexURLFilter.java b/src/plugin/urlfilter-regex/src/java/org/apache/nutch/urlfilter/regex/RegexURLFilter.java index dda8da2702..6778bcb3f3 100644 --- a/src/plugin/urlfilter-regex/src/java/org/apache/nutch/urlfilter/regex/RegexURLFilter.java +++ b/src/plugin/urlfilter-regex/src/java/org/apache/nutch/urlfilter/regex/RegexURLFilter.java @@ -19,6 +19,7 @@ // JDK imports import java.io.Reader; import java.io.IOException; +import java.io.StringReader; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -35,6 +36,9 @@ * {@link java.util.regex Java Regex implementation}. */ public class RegexURLFilter extends RegexURLFilterBase { + + public static final String URLFILTER_REGEX_FILE = "urlfilter.regex.file"; + public static final String URLFILTER_REGEX_RULES = "urlfilter.regex.rules"; public RegexURLFilter() { super(); @@ -55,9 +59,17 @@ public RegexURLFilter(String filename) * * * ----------------------------------- */ - // Inherited Javadoc - protected String getRulesFile(Configuration conf) { - return conf.get("urlfilter.regex.file"); + /** + * Rules specified as a config property will override rules specified + * as a config file. + */ + protected Reader getRulesReader(Configuration conf) throws IOException { + String stringRules = conf.get(URLFILTER_REGEX_RULES); + if (stringRules != null) { + return new StringReader(stringRules); + } + String fileRules = conf.get(URLFILTER_REGEX_FILE); + return conf.getConfResourceAsReader(fileRules); } // Inherited Javadoc diff --git a/src/plugin/urlfilter-suffix/src/java/org/apache/nutch/urlfilter/suffix/SuffixURLFilter.java b/src/plugin/urlfilter-suffix/src/java/org/apache/nutch/urlfilter/suffix/SuffixURLFilter.java index cf26dbca30..2ce921f99a 100644 --- a/src/plugin/urlfilter-suffix/src/java/org/apache/nutch/urlfilter/suffix/SuffixURLFilter.java +++ b/src/plugin/urlfilter-suffix/src/java/org/apache/nutch/urlfilter/suffix/SuffixURLFilter.java @@ -34,6 +34,7 @@ import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; +import java.io.StringReader; import java.util.List; import java.util.ArrayList; @@ -139,7 +140,7 @@ public SuffixURLFilter() throws IOException { } public SuffixURLFilter(Reader reader) throws IOException { - readConfigurationFile(reader); + readConfiguration(reader); } public String filter(String url) { @@ -167,7 +168,7 @@ public String filter(String url) { } } - public void readConfigurationFile(Reader reader) throws IOException { + public void readConfiguration(Reader reader) throws IOException { // handle missing config file if (reader == null) { @@ -269,12 +270,18 @@ public void setConf(Configuration conf) { } String file = conf.get("urlfilter.suffix.file"); + String stringRules = conf.get("urlfilter.suffix.rules"); // attribute "file" takes precedence if defined if (attributeFile != null) file = attributeFile; - Reader reader = conf.getConfResourceAsReader(file); + Reader reader = null; + if (stringRules != null) { // takes precedence over files + reader = new StringReader(stringRules); + } else { + reader = conf.getConfResourceAsReader(file); + } try { - readConfigurationFile(reader); + readConfiguration(reader); } catch (IOException e) { if (LOG.isFatalEnabled()) { LOG.fatal(e.getMessage()); } throw new RuntimeException(e.getMessage(), e); diff --git a/src/plugin/urlnormalizer-regex/src/java/org/apache/nutch/net/urlnormalizer/regex/RegexURLNormalizer.java b/src/plugin/urlnormalizer-regex/src/java/org/apache/nutch/net/urlnormalizer/regex/RegexURLNormalizer.java index 667196e725..5daa37878d 100644 --- a/src/plugin/urlnormalizer-regex/src/java/org/apache/nutch/net/urlnormalizer/regex/RegexURLNormalizer.java +++ b/src/plugin/urlnormalizer-regex/src/java/org/apache/nutch/net/urlnormalizer/regex/RegexURLNormalizer.java @@ -20,8 +20,11 @@ import java.net.URL; import java.net.MalformedURLException; import java.io.FileInputStream; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; import java.util.Collections; import java.util.HashMap; @@ -40,6 +43,7 @@ import javax.xml.parsers.*; import org.w3c.dom.*; +import org.xml.sax.InputSource; import org.apache.oro.text.regex.*; /** @@ -106,17 +110,23 @@ public void setConf(Configuration conf) { // the default constructor was called if (this.scopedRules == null) { String filename = getConf().get("urlnormalizer.regex.file"); + String stringRules = getConf().get("urlnormalizer.regex.rules"); scopedRules = new HashMap(); - URL url = getConf().getResource(filename); + Reader reader = null; + if (stringRules != null) { + reader = new StringReader(stringRules); + } else { + reader = getConf().getConfResourceAsReader(filename); + } List rules = null; - if (url == null) { - LOG.warn("Can't load the default config file! " + filename); + if (reader == null) { + LOG.warn("Can't load the default rules! "); rules = EMPTY_RULES; } else { try { - rules = readConfiguration(url.openStream()); + rules = readConfiguration(reader); } catch (Exception e) { - LOG.warn("Couldn't read default config from '" + url + "': " + e); + LOG.warn("Couldn't read default config: " + e); rules = EMPTY_RULES; } } @@ -125,8 +135,8 @@ public void setConf(Configuration conf) { } // used in JUnit test. - void setConfiguration(InputStream is, String scope) { - List rules = readConfiguration(is); + void setConfiguration(Reader reader, String scope) { + List rules = readConfiguration(reader); scopedRules.put(scope, rules); LOG.debug("Set config for scope '" + scope + "': " + rules.size() + " rules."); } @@ -141,17 +151,16 @@ public synchronized String regexNormalize(String urlString, String scope) { // try to populate String configFile = getConf().get("urlnormalizer.regex.file." + scope); if (configFile != null) { - URL resource = getConf().getResource(configFile); - LOG.debug("resource for scope '" + scope + "': " + resource); - if (resource == null) { + LOG.debug("resource for scope '" + scope + "': " + configFile); + if (configFile == null) { LOG.warn("Can't load resource for config file: " + configFile); } else { try { - InputStream is = resource.openStream(); - curRules = readConfiguration(resource.openStream()); + Reader reader = getConf().getConfResourceAsReader(configFile); + curRules = readConfiguration(reader); scopedRules.put(scope, curRules); } catch (Exception e) { - LOG.warn("Couldn't load resource '" + resource + "': " + e); + LOG.warn("Couldn't load resource '" + configFile + "': " + e); } } } @@ -185,22 +194,22 @@ private List readConfigurationFile(String filename) { LOG.info("loading " + filename); } try { - FileInputStream fis = new FileInputStream(filename); - return readConfiguration(fis); + FileReader reader = new FileReader(filename); + return readConfiguration(reader); } catch (Exception e) { - LOG.fatal("Error loading rules from '" + filename + "': " + e); + LOG.error("Error loading rules from '" + filename + "': " + e); return EMPTY_RULES; } } - private List readConfiguration(InputStream is) { + private List readConfiguration(Reader reader) { Perl5Compiler compiler = new Perl5Compiler(); List rules = new ArrayList(); try { // borrowed heavily from code in Configuration.java Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() - .parse(is); + .parse(new InputSource(reader)); Element root = doc.getDocumentElement(); if ((!"regex-normalize".equals(root.getTagName())) && (LOG.isFatalEnabled())) { diff --git a/src/plugin/urlnormalizer-regex/src/test/org/apache/nutch/net/urlnormalizer/regex/TestRegexURLNormalizer.java b/src/plugin/urlnormalizer-regex/src/test/org/apache/nutch/net/urlnormalizer/regex/TestRegexURLNormalizer.java index 4a0dafdf75..485943e0b4 100644 --- a/src/plugin/urlnormalizer-regex/src/test/org/apache/nutch/net/urlnormalizer/regex/TestRegexURLNormalizer.java +++ b/src/plugin/urlnormalizer-regex/src/test/org/apache/nutch/net/urlnormalizer/regex/TestRegexURLNormalizer.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; +import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; @@ -64,10 +65,10 @@ public boolean accept(File f) { }); for (int i = 0; i < configs.length; i++) { try { - FileInputStream fis = new FileInputStream(configs[i]); + FileReader reader = new FileReader(configs[i]); String cname = configs[i].getName(); cname = cname.substring(16, cname.indexOf(".xml")); - normalizer.setConfiguration(fis, cname); + normalizer.setConfiguration(reader, cname); NormalizedURL[] urls = readTestFile(cname); testData.put(cname, urls); } catch (Exception e) { From bb8625642df06c239ec853badf3c83929d567eae Mon Sep 17 00:00:00 2001 From: Andrzej Bialecki Date: Wed, 9 Mar 2011 12:01:55 +0000 Subject: [PATCH 034/754] NUTCH-962 max. redirects not handled correctly: fetcher stops at max-1 redirects. git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1079765 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ src/java/org/apache/nutch/fetcher/Fetcher.java | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f09a49ff83..96d56771b7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-962 max. redirects not handled correctly: fetcher stops at max-1 redirects (Sebastian Nagel via ab) + * NUTCH-921 Reduce dependency of Nutch on config files (ab) * NUTCH-876 Remove remaining robots/IP blocking code in lib-http (ab) diff --git a/src/java/org/apache/nutch/fetcher/Fetcher.java b/src/java/org/apache/nutch/fetcher/Fetcher.java index f7f696c834..074cd65426 100644 --- a/src/java/org/apache/nutch/fetcher/Fetcher.java +++ b/src/java/org/apache/nutch/fetcher/Fetcher.java @@ -751,7 +751,7 @@ public void run() { output(fit.url, fit.datum, null, status, CrawlDatum.STATUS_FETCH_RETRY); } - if (redirecting && redirectCount >= maxRedirect) { + if (redirecting && redirectCount > maxRedirect) { fetchQueues.finishFetchItem(fit); if (LOG.isInfoEnabled()) { LOG.info(" - redirect count exceeded " + fit.url); @@ -759,7 +759,7 @@ public void run() { output(fit.url, fit.datum, null, ProtocolStatus.STATUS_REDIR_EXCEEDED, CrawlDatum.STATUS_FETCH_GONE); } - } while (redirecting && (redirectCount < maxRedirect)); + } while (redirecting && (redirectCount <= maxRedirect)); } catch (Throwable t) { // unexpected exception // unblock From 9272fc6b4e5f9fb91b2bfa1978efb28d055de269 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Thu, 10 Mar 2011 21:54:05 +0000 Subject: [PATCH 035/754] NUTCH-951 : backport changes from 2.0 into 1.3 - NUTCH-825 done git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1080368 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 + build.xml | 135 +++++++++++++++++++++++- pom.xml | 294 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 pom.xml diff --git a/CHANGES.txt b/CHANGES.txt index 96d56771b7..2661649dcd 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-825 Publish nutch artifacts to central maven repository (mattmann, jnioche) + * NUTCH-962 max. redirects not handled correctly: fetcher stops at max-1 redirects (Sebastian Nagel via ab) * NUTCH-921 Reduce dependency of Nutch on config files (ab) diff --git a/build.xml b/build.xml index b0b1b44548..4d3dd0219a 100644 --- a/build.xml +++ b/build.xml @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - + @@ -23,7 +23,17 @@ - + + + + + + + + + + + @@ -53,6 +63,7 @@ + @@ -108,6 +119,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000..5f12134076 --- /dev/null +++ b/pom.xml @@ -0,0 +1,294 @@ + + + + 4.0.0 + org.apache.nutch + nutch + jar + Apache Nutch + 1.3-dev + Nutch is open source web-search software. It builds on + Lucene and Solr, adding web-specifics, such as a crawler, a link-graph + database, parsers for HTML and other document formats, etc. + + http://maven.apache.org + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + repo + + + + http://svn.apache.org/viewvc/nutch + + http://svn.apache.org/viewvc/nutch + + + + + ab + Andrzej Bialecki + ab@apache.org + + + mattmann + Chris A. Mattmann + mattmann@apache.org + + + kubes + Dennis Kubes + kubes@apache.org + + + dogacan + Dogacan Güney + dogacan@apache.org + + + jnioche + Julien Nioche + jnioche@apache.org + + + otis + Otis Gospodnetić + otis@apache.org + + + siren + Sami Siren + siren@apache.org + + + + + org.apache.solr + solr-solrj + 1.4.1 + + + org.slf4j + slf4j-log4j12 + 1.5.11 + + + commons-lang + commons-lang + 2.4 + + + commons-collections + commons-collections + 3.1 + + + commons-httpclient + commons-httpclient + 3.1 + + + commons-codec + commons-codec + 1.3 + + + org.apache.hadoop + hadoop-core + 0.20.2 + + + hsqldb + hsqldb + + + net.sf.kosmosfs + kfs + + + net.java.dev.jets3t + jets3t + + + org.mortbay.jetty + jsp + + + org.eclipse.jdt + core + + + ant + ant + + + + + com.ibm.icu + icu4j + 4.0.1 + + + org.apache.lucene + lucene-misc + 3.0.2 + + + org.apache.tika + tika-core + 0.7 + + + org.apache.tika + tika-parsers + 0.7 + + + org.sun.jdmk + + + com.sun.jmx + + + javax.jms + + + + + log4j + log4j + 1.2.15 + + + org.sun.jdmk + + + com.sun.jmx + + + javax.jms + + + + + xerces + xmlParserAPIs + 2.6.2 + + + xerces + xercesImpl + 2.9.1 + + + oro + oro + 2.0.8 + + + com.healthmarketscience.sqlbuilder + sqlbuilder + 2.0.6 + + + org.mortbay.jetty + jetty + 6.1.22 + test + + + junit + junit + 3.8.1 + test + + + org.apache.hadoop + hadoop-test + 0.20.2 + + + hsqldb + hsqldb + + + net.sf.kosmosfs + kfs + + + net.java.dev.jets3t + jets3t + + + org.mortbay.jetty + jsp + + + org.eclipse.jdt + core + + + test + + + org.mortbay.jetty + jetty + 6.1.22 + test + + + org.mortbay.jetty + jetty-util + 6.1.22 + test + + + org.mortbay.jetty + jetty-client + 6.1.22 + + + org.hsqldb + hsqldb + 2.0.0 + + + org.jdom + jdom + 1.1 + + + org.gora + gora-sql + 0.1 + + + com.sun.jdmk + + + com.sun.jmx + + + javax.jms + + + + + From eed2fc844c5814235bf82e1bdb2b71bdbfb3bc8a Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Fri, 18 Mar 2011 15:05:34 +0000 Subject: [PATCH 036/754] NUTCH-963 Add support for deleting Solr documents with STATUS_DB_GONE in CrawlDB (Claudio Martella, markus) git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1082943 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ conf/log4j.properties | 1 + src/bin/nutch | 3 +++ 3 files changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 2661649dcd..b1341a7708 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-963 Add support for deleting Solr documents with STATUS_DB_GONE in CrawlDB (Claudio Martella, markus) + * NUTCH-825 Publish nutch artifacts to central maven repository (mattmann, jnioche) * NUTCH-962 max. redirects not handled correctly: fetcher stops at max-1 redirects (Sebastian Nagel via ab) diff --git a/conf/log4j.properties b/conf/log4j.properties index 2aa8eab629..a0ee6e15a0 100644 --- a/conf/log4j.properties +++ b/conf/log4j.properties @@ -24,6 +24,7 @@ log4j.logger.org.apache.nutch.crawl.LinkDb=INFO,cmdstdout log4j.logger.org.apache.nutch.crawl.LinkDbMerger=INFO,cmdstdout log4j.logger.org.apache.nutch.indexer.solr.SolrIndexer=INFO,cmdstdout log4j.logger.org.apache.nutch.indexer.solr.SolrDeleteDuplicates=INFO,cmdstdout +log4j.logger.org.apache.nutch.indexer.solr.SolrClean=INFO,cmdstdout log4j.logger.org.apache.nutch=INFO log4j.logger.org.apache.hadoop=WARN diff --git a/src/bin/nutch b/src/bin/nutch index f395d00e79..dd01ae2da6 100755 --- a/src/bin/nutch +++ b/src/bin/nutch @@ -49,6 +49,7 @@ if [ $# = 0 ]; then echo " mergelinkdb merge linkdb-s, with optional filtering" echo " solrindex run the solr indexer on parsed segments and linkdb" echo " solrdedup remove duplicates from solr" + echo " solrclean remove HTTP 301 and 404 documents from solr" echo " plugin load a plugin and run one of its classes main()" echo " or" echo " CLASSNAME run the class named CLASSNAME" @@ -210,6 +211,8 @@ elif [ "$COMMAND" = "solrindex" ] ; then CLASS=org.apache.nutch.indexer.solr.SolrIndexer elif [ "$COMMAND" = "solrdedup" ] ; then CLASS=org.apache.nutch.indexer.solr.SolrDeleteDuplicates +elif [ "$COMMAND" = "solrclean" ] ; then + CLASS=org.apache.nutch.indexer.solr.SolrClean elif [ "$COMMAND" = "plugin" ] ; then CLASS=org.apache.nutch.plugin.PluginRepository else From 7acb6c8798618a198de9a1ccc40282ccacfb1115 Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Fri, 18 Mar 2011 15:07:14 +0000 Subject: [PATCH 037/754] NUTCH-963 SolrClean is now added git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1082944 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/nutch/indexer/solr/SolrClean.java | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 src/java/org/apache/nutch/indexer/solr/SolrClean.java diff --git a/src/java/org/apache/nutch/indexer/solr/SolrClean.java b/src/java/org/apache/nutch/indexer/solr/SolrClean.java new file mode 100644 index 0000000000..17deeb609b --- /dev/null +++ b/src/java/org/apache/nutch/indexer/solr/SolrClean.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.nutch.indexer.solr; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.text.SimpleDateFormat; +import java.util.Iterator; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.io.ByteWritable; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.mapred.FileInputFormat; +import org.apache.hadoop.mapred.JobClient; +import org.apache.hadoop.mapred.JobConf; +import org.apache.hadoop.mapred.Mapper; +import org.apache.hadoop.mapred.OutputCollector; +import org.apache.hadoop.mapred.Reducer; +import org.apache.hadoop.mapred.Reporter; +import org.apache.hadoop.mapred.SequenceFileInputFormat; +import org.apache.hadoop.mapred.lib.NullOutputFormat; +import org.apache.hadoop.util.Tool; +import org.apache.hadoop.util.ToolRunner; +import org.apache.nutch.crawl.CrawlDatum; +import org.apache.nutch.crawl.CrawlDb; +import org.apache.nutch.util.NutchConfiguration; +import org.apache.nutch.util.NutchJob; +import org.apache.nutch.util.TimingUtil; +import org.apache.solr.client.solrj.SolrServer; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer; +import org.apache.solr.client.solrj.request.UpdateRequest; + +/** +* The class scans CrawlDB looking for entries with status DB_GONE (404) and sends delete requests to Solr +* for those documents. +* +* +* @author Claudio Martella +* +*/ + +public class SolrClean implements Tool { + public static final Log LOG = LogFactory.getLog(SolrClean.class); + private Configuration conf; + + @Override + public Configuration getConf() { + return conf; + } + + @Override + public void setConf(Configuration conf) { + this.conf = conf; + } + + public static class DBFilter implements Mapper { + private ByteWritable OUT = new ByteWritable(CrawlDatum.STATUS_DB_GONE); + + @Override + public void configure(JobConf arg0) { } + + @Override + public void close() throws IOException { } + + @Override + public void map(Text key, CrawlDatum value, + OutputCollector output, Reporter reporter) + throws IOException { + + if (value.getStatus() == CrawlDatum.STATUS_DB_GONE) { + output.collect(OUT, key); + } + } + } + + public static class SolrDeleter implements Reducer { + private static final int NUM_MAX_DELETE_REQUEST = 1000; + private int numDeletes = 0; + private int totalDeleted = 0; + private SolrServer solr; + private UpdateRequest updateRequest = new UpdateRequest(); + + @Override + public void configure(JobConf job) { + try { + solr = new CommonsHttpSolrServer(job.get(SolrConstants.SERVER_URL)); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() throws IOException { + try { + if (numDeletes > 0) { + LOG.info("SolrClean: deleting " + numDeletes + " documents"); + updateRequest.process(solr); + totalDeleted += numDeletes; + } + + if (totalDeleted > 0) { + solr.commit(); + } + + LOG.info("SolrClean: deleted a total of " + totalDeleted + " documents"); + } catch (SolrServerException e) { + throw new IOException(e); + } + } + + @Override + public void reduce(ByteWritable key, Iterator values, + OutputCollector output, Reporter reporter) + throws IOException { + while (values.hasNext()) { + Text document = values.next(); + updateRequest.deleteById(document.toString()); + numDeletes++; + if (numDeletes >= NUM_MAX_DELETE_REQUEST) { + try { + LOG.info("SolrClean: deleting " + numDeletes + " documents"); + updateRequest.process(solr); + } catch (SolrServerException e) { + throw new IOException(e); + } + updateRequest = new UpdateRequest(); + totalDeleted += numDeletes; + numDeletes = 0; + } + } + } + } + + public void delete(String crawldb, String solrUrl) throws IOException { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + long start = System.currentTimeMillis(); + LOG.info("SolrClean: starting at " + sdf.format(start)); + + JobConf job = new NutchJob(getConf()); + + FileInputFormat.addInputPath(job, new Path(crawldb, CrawlDb.CURRENT_NAME)); + job.set(SolrConstants.SERVER_URL, solrUrl); + job.setInputFormat(SequenceFileInputFormat.class); + job.setOutputFormat(NullOutputFormat.class); + job.setMapOutputKeyClass(ByteWritable.class); + job.setMapOutputValueClass(Text.class); + job.setMapperClass(DBFilter.class); + job.setReducerClass(SolrDeleter.class); + + JobClient.runJob(job); + + long end = System.currentTimeMillis(); + LOG.info("SolrClean: finished at " + sdf.format(end) + ", elapsed: " + TimingUtil.elapsedTime(start, end)); + } + + public int run(String[] args) throws IOException { + if (args.length != 2) { + System.err.println("Usage: SolrClean "); + return 1; + } + + delete(args[0], args[1]); + + return 0; + } + + public static void main(String[] args) throws Exception { + int result = ToolRunner.run(NutchConfiguration.create(), + new SolrClean(), args); + System.exit(result); + } +} \ No newline at end of file From 107bf5a2a3f2ab60f38e8d83b678f75c3db8a910 Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Tue, 5 Apr 2011 15:22:12 +0000 Subject: [PATCH 038/754] NUTCH-975 Fix missing and wrong headers in source files git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1089077 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ .../org/apache/nutch/indexer/NutchField.java | 17 +++++++++++++++++ .../org/apache/nutch/parse/ParseCallable.java | 17 +++++++++++++++++ src/java/org/apache/nutch/tools/Benchmark.java | 17 +++++++++++++++++ .../regex/TestRegexURLNormalizer.java | 17 +++++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index b1341a7708..9bf43d6cfa 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-975 Fix missing/wrong headers in source files (markus) + * NUTCH-963 Add support for deleting Solr documents with STATUS_DB_GONE in CrawlDB (Claudio Martella, markus) * NUTCH-825 Publish nutch artifacts to central maven repository (mattmann, jnioche) diff --git a/src/java/org/apache/nutch/indexer/NutchField.java b/src/java/org/apache/nutch/indexer/NutchField.java index 246a5a0b28..6a94fadb9d 100644 --- a/src/java/org/apache/nutch/indexer/NutchField.java +++ b/src/java/org/apache/nutch/indexer/NutchField.java @@ -1,3 +1,20 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.nutch.indexer; import java.io.DataInput; diff --git a/src/java/org/apache/nutch/parse/ParseCallable.java b/src/java/org/apache/nutch/parse/ParseCallable.java index c296a0e33b..23e0948539 100644 --- a/src/java/org/apache/nutch/parse/ParseCallable.java +++ b/src/java/org/apache/nutch/parse/ParseCallable.java @@ -1,3 +1,20 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.nutch.parse; import java.util.concurrent.Callable; diff --git a/src/java/org/apache/nutch/tools/Benchmark.java b/src/java/org/apache/nutch/tools/Benchmark.java index 6e1ef381b0..99c7756b59 100755 --- a/src/java/org/apache/nutch/tools/Benchmark.java +++ b/src/java/org/apache/nutch/tools/Benchmark.java @@ -1,3 +1,20 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.nutch.tools; import java.io.OutputStream; diff --git a/src/plugin/urlnormalizer-regex/src/test/org/apache/nutch/net/urlnormalizer/regex/TestRegexURLNormalizer.java b/src/plugin/urlnormalizer-regex/src/test/org/apache/nutch/net/urlnormalizer/regex/TestRegexURLNormalizer.java index 485943e0b4..b3ba22bfc0 100644 --- a/src/plugin/urlnormalizer-regex/src/test/org/apache/nutch/net/urlnormalizer/regex/TestRegexURLNormalizer.java +++ b/src/plugin/urlnormalizer-regex/src/test/org/apache/nutch/net/urlnormalizer/regex/TestRegexURLNormalizer.java @@ -34,6 +34,23 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.nutch.net.URLNormalizers; +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ + import org.apache.nutch.util.NutchConfiguration; import junit.framework.TestCase; From 337843d6e363b39b2e563561e4eb28192542c74d Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Thu, 7 Apr 2011 13:08:05 +0000 Subject: [PATCH 039/754] NUTCH-975 Fix missing and wrong headers in source files (src/bin/nutch) git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1089866 13f79535-47bb-0310-9956-ffa450edef68 --- src/bin/nutch | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/bin/nutch b/src/bin/nutch index dd01ae2da6..4878b6bf59 100755 --- a/src/bin/nutch +++ b/src/bin/nutch @@ -1,4 +1,19 @@ #!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF 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. # # The Nutch command script # From 870d868f0ba7a572ce556e90f557b9c946442312 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Fri, 8 Apr 2011 10:09:54 +0000 Subject: [PATCH 040/754] NUTCH-967 Upgraded Tika to version 0.9 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1090182 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 + conf/tika-mimetypes.xml | 313 +++++++++++++++++++++---------- ivy/ivy.xml | 2 +- src/plugin/parse-tika/ivy.xml | 7 +- src/plugin/parse-tika/plugin.xml | 29 +-- 5 files changed, 234 insertions(+), 119 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9bf43d6cfa..226720c521 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-967 Upgrade to Tika 0.9 (jnioche) + * NUTCH-975 Fix missing/wrong headers in source files (markus) * NUTCH-963 Add support for deleting Solr documents with STATUS_DB_GONE in CrawlDB (Claudio Martella, markus) diff --git a/conf/tika-mimetypes.xml b/conf/tika-mimetypes.xml index 297ed90ffc..223ba38a6e 100644 --- a/conf/tika-mimetypes.xml +++ b/conf/tika-mimetypes.xml @@ -84,7 +84,7 @@ EPUB - Electronic Publication + <_comment>Electronic Publication @@ -209,8 +209,9 @@ + - Microsoft Word Document + <_comment>Microsoft Word Document @@ -222,7 +223,9 @@ - + + + @@ -295,7 +298,7 @@ PDF - Portable Document Format + <_comment>Portable Document Format @@ -343,7 +346,7 @@ - PostScript + <_comment>PostScript @@ -370,7 +373,7 @@ RDF/XML - XML syntax for RDF graphs + <_comment>XML syntax for RDF graphs @@ -556,6 +559,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -1075,7 +1098,7 @@ - FrameMaker MIF document + <_comment>FrameMaker MIF document @@ -1140,14 +1163,18 @@ + - Microsoft Excel Spreadsheet + <_comment>Microsoft Excel Spreadsheet + + + @@ -1161,21 +1188,21 @@ - Office Open XML Workbook Add-in (macro-enabled) + <_comment>Office Open XML Workbook Add-in (macro-enabled) - Office Open XML Workbook (macro-enabled) + <_comment>Office Open XML Workbook (macro-enabled) - Microsoft Excel 2007 Binary Spreadsheet + <_comment>Microsoft Excel 2007 Binary Spreadsheet - + @@ -1197,7 +1224,7 @@ - Microsoft Outlook Message + <_comment>Microsoft Outlook Message @@ -1212,8 +1239,14 @@ + - Microsoft Powerpoint Presentation + <_comment>Microsoft Powerpoint Presentation + + + + + @@ -1223,31 +1256,31 @@ - Office Open XML Presentation Add-in (macro-enabled) + <_comment>Office Open XML Presentation Add-in (macro-enabled) - + - Office Open XML Presentation (macro-enabled) + <_comment>Office Open XML Presentation (macro-enabled) - + - + - Office Open XML Presentation Slideshow (macro-enabled) + <_comment>Office Open XML Presentation Slideshow (macro-enabled) - + - + @@ -1256,6 +1289,7 @@ + @@ -1267,13 +1301,13 @@ - Office Open XML Document (macro-enabled) + <_comment>Office Open XML Document (macro-enabled) - Office Open XML Document Template (macro-enabled) + <_comment>Office Open XML Document Template (macro-enabled) @@ -1360,7 +1394,7 @@ - OpenDocument v1.0: Chart document + <_comment>OpenDocument v1.0: Chart document - OpenDocument v1.0: Chart document used as template + <_comment>OpenDocument v1.0: Chart document used as template - OpenDocument v1.0: Formula document + <_comment>OpenDocument v1.0: Formula document - OpenDocument v1.0: Formula document used as template + <_comment>OpenDocument v1.0: Formula document used as template - OpenDocument v1.0: Graphics document (Drawing) + <_comment>OpenDocument v1.0: Graphics document (Drawing) - OpenDocument v1.0: Graphics document used as template + <_comment>OpenDocument v1.0: Graphics document used as template - OpenDocument v1.0: Image document + <_comment>OpenDocument v1.0: Image document - OpenDocument v1.0: Image document used as template + <_comment>OpenDocument v1.0: Image document used as template - OpenDocument v1.0: Presentation document + <_comment>OpenDocument v1.0: Presentation document - OpenDocument v1.0: Presentation document used as template + <_comment>OpenDocument v1.0: Presentation document used as template - OpenDocument v1.0: Spreadsheet document + <_comment>OpenDocument v1.0: Spreadsheet document - OpenDocument v1.0: Spreadsheet document used as template + <_comment>OpenDocument v1.0: Spreadsheet document used as template - OpenDocument v1.0: Text document + <_comment>OpenDocument v1.0: Text document - OpenDocument v1.0: Global Text document + <_comment>OpenDocument v1.0: Global Text document - OpenDocument v1.0: Text document used as template + <_comment>OpenDocument v1.0: Text document used as template - OpenDocument v1.0: Text document used as template for HTML documents + <_comment>OpenDocument v1.0: Text document used as template for HTML documents - Office Open XML Presentation + <_comment>Office Open XML Presentation @@ -1614,43 +1648,43 @@ - Office Open XML Presentation Template + <_comment>Office Open XML Presentation Template - Office Open XML Presentation Slideshow + <_comment>Office Open XML Presentation Slideshow - Office Open XML Workbook + <_comment>Office Open XML Workbook - Office Open XML Workbook Template + <_comment>Office Open XML Workbook Template - Office Open XML Workbook Template (macro-enabled) + <_comment>Office Open XML Workbook Template (macro-enabled) - Office Open XML Document + <_comment>Office Open XML Document - Office Open XML Document Template + <_comment>Office Open XML Document Template @@ -1815,7 +1849,8 @@ - + + @@ -1846,7 +1881,7 @@ - OpenOffice v1.0: Writer Document + <_comment>OpenOffice v1.0: Writer Document - Microsoft Visio Diagram + <_comment>Microsoft Visio Diagram @@ -2074,13 +2109,13 @@ INDD - Adobe InDesign document + <_comment>Adobe InDesign document INX - Adobe InDesign Interchange format + <_comment>Adobe InDesign Interchange format @@ -2196,6 +2231,11 @@ + + + + + @@ -2251,7 +2291,7 @@ - Emacs Lisp bytecode + <_comment>Emacs Lisp bytecode @@ -2273,9 +2313,13 @@ + + OTF + <_comment>OpenType Font + @@ -2284,10 +2328,17 @@ + + TTF + <_comment>TrueType Font + + + + @@ -2296,6 +2347,13 @@ + + <_comment>Foxmail Email File + + + + + <_comment>Macromedia FutureSplash File @@ -2338,6 +2396,7 @@ + @@ -2418,6 +2477,9 @@ + + + @@ -2441,8 +2503,10 @@ + + WMF - Windows Metafile + <_comment>Windows Metafile @@ -2477,6 +2541,14 @@ + + + + + + + + @@ -2511,7 +2583,7 @@ Flash - Adobe Flash + <_comment>Adobe Flash @@ -2644,7 +2716,7 @@ - + @@ -2681,7 +2753,7 @@ XSLT - XSL Transformations + <_comment>XSL Transformations @@ -2780,7 +2852,7 @@ MIDI - Musical Instrument Digital Interface + <_comment>Musical Instrument Digital Interface @@ -2801,7 +2873,7 @@ MP3 - MPEG-1 Audio Layer 3 + <_comment>MPEG-1 Audio Layer 3 @@ -2818,7 +2890,8 @@ - + + @@ -2933,7 +3006,7 @@ AIFF - Audio Interchange File Format + <_comment>Audio Interchange File Format @@ -2972,7 +3045,7 @@ FLAC - Free Lossless Audio Codec + <_comment>Free Lossless Audio Codec @@ -3011,7 +3084,7 @@ - Real Audio + <_comment>Real Audio @@ -3053,12 +3126,22 @@ - - + + BMP - Windows bitmap + <_comment>Windows bitmap - + + + + + + + + + + + @@ -3066,7 +3149,7 @@ CGM - Computer Graphics Metafile + <_comment>Computer Graphics Metafile @@ -3082,7 +3165,7 @@ GIF - Graphics Interchange Format + <_comment>Graphics Interchange Format @@ -3097,7 +3180,7 @@ JPEG - Joint Photographic Experts Group + <_comment>Joint Photographic Experts Group @@ -3117,7 +3200,7 @@ PNG - Portable Network Graphics + <_comment>Portable Network Graphics @@ -3132,7 +3215,7 @@ SVG - Scalable Vector Graphics + <_comment>Scalable Vector Graphics @@ -3142,7 +3225,7 @@ TIFF - Tagged Image File Format + <_comment>Tagged Image File Format @@ -3165,9 +3248,25 @@ + + DWG + <_comment>AutoCad Drawing + + + + + + + + + + + + @@ -3233,7 +3332,7 @@ - Navy Interchange File Format + <_comment>Navy Interchange File Format @@ -3249,14 +3348,14 @@ PNM - Portable Any Map + <_comment>Portable Any Map PBM - Portable Bit Map + <_comment>Portable Bit Map @@ -3267,7 +3366,7 @@ PGM - Portable Gray Map + <_comment>Portable Gray Map @@ -3278,7 +3377,7 @@ PXM - Portable Pixel Map + <_comment>Portable Pixel Map @@ -3289,28 +3388,28 @@ DNG - Adobe Digital Negative + <_comment>Adobe Digital Negative - Hasselblad raw image + <_comment>Hasselblad raw image - Fuji raw image + <_comment>Fuji raw image - Canon raw image + <_comment>Canon raw image - Kodak raw image + <_comment>Kodak raw image @@ -3318,88 +3417,88 @@ - Minolta raw image + <_comment>Minolta raw image - Nikon raw image + <_comment>Nikon raw image - Olympus raw image + <_comment>Olympus raw image - Pentax raw image + <_comment>Pentax raw image - Sony raw image + <_comment>Sony raw image - Sigma raw image + <_comment>Sigma raw image - Epson raw image + <_comment>Epson raw image - Mamiya raw image + <_comment>Mamiya raw image - Leaf raw image + <_comment>Leaf raw image - Panasonic raw image + <_comment>Panasonic raw image - Phase One raw image + <_comment>Phase One raw image - Red raw image + <_comment>Red raw image - Imacon raw image + <_comment>Imacon raw image - Logitech raw image + <_comment>Logitech raw image - Casio raw image + <_comment>Casio raw image - Rawzor raw image + <_comment>Rawzor raw image @@ -3466,6 +3565,8 @@ + + @@ -3535,7 +3636,7 @@ - Cascading Style Sheet + <_comment>Cascading Style Sheet @@ -3562,7 +3663,13 @@ - + + + + + + + @@ -3571,7 +3678,7 @@ diff --git a/ivy/ivy.xml b/ivy/ivy.xml index 733b665306..5d40a682c4 100644 --- a/ivy/ivy.xml +++ b/ivy/ivy.xml @@ -59,7 +59,7 @@ - + diff --git a/src/plugin/parse-tika/ivy.xml b/src/plugin/parse-tika/ivy.xml index 9aad5be88b..f862aa4dd9 100644 --- a/src/plugin/parse-tika/ivy.xml +++ b/src/plugin/parse-tika/ivy.xml @@ -27,7 +27,7 @@ - + @@ -36,8 +36,9 @@ - - + + + diff --git a/src/plugin/parse-tika/plugin.xml b/src/plugin/parse-tika/plugin.xml index 64fff3c842..0ed7db196c 100644 --- a/src/plugin/parse-tika/plugin.xml +++ b/src/plugin/parse-tika/plugin.xml @@ -26,26 +26,31 @@ + - - - + + + + - + - + + - - - - - + + + + + + + + - - + From 6e81e6f6f4fc78ccb4c91068da42108b2a927e19 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Fri, 8 Apr 2011 11:05:36 +0000 Subject: [PATCH 041/754] NUTCH-972 : CrawlDbMerger doesn't break on non-existent input (Gabriele Kahlout via jnioche) git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1090199 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ src/java/org/apache/nutch/crawl/CrawlDbMerger.java | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 226720c521..e8002fd69c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-972 CrawlDbMerger doesn't break on non-existent input (Gabriele Kahlout via jnioche) + * NUTCH-967 Upgrade to Tika 0.9 (jnioche) * NUTCH-975 Fix missing/wrong headers in source files (markus) diff --git a/src/java/org/apache/nutch/crawl/CrawlDbMerger.java b/src/java/org/apache/nutch/crawl/CrawlDbMerger.java index cf9a8a9f6f..559d7798b4 100644 --- a/src/java/org/apache/nutch/crawl/CrawlDbMerger.java +++ b/src/java/org/apache/nutch/crawl/CrawlDbMerger.java @@ -120,6 +120,7 @@ public void merge(Path output, Path[] dbs, boolean normalize, boolean filter) th JobConf job = createMergeJob(getConf(), output, normalize, filter); for (int i = 0; i < dbs.length; i++) { + if (LOG.isInfoEnabled()) { LOG.info("Adding " + dbs[i]); } FileInputFormat.addInputPath(job, new Path(dbs[i], CrawlDb.CURRENT_NAME)); } JobClient.runJob(job); @@ -172,6 +173,7 @@ public int run(String[] args) throws Exception { ArrayList dbs = new ArrayList(); boolean filter = false; boolean normalize = false; + FileSystem fs = FileSystem.get(getConf()); for (int i = 1; i < args.length; i++) { if (args[i].equals("-filter")) { filter = true; @@ -180,7 +182,9 @@ public int run(String[] args) throws Exception { normalize = true; continue; } - dbs.add(new Path(args[i])); + final Path dbPath = new Path(args[i]); + if(fs.exists(dbPath)) + dbs.add(dbPath); } try { merge(output, dbs.toArray(new Path[dbs.size()]), normalize, filter); From 9b10e6818055b5a4f89f3a7f4cb1aa2aa088c1a5 Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Tue, 12 Apr 2011 12:51:16 +0000 Subject: [PATCH 042/754] NUTCH-891 Subcollection plugin won't require blacklist any more (markus) git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1091390 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ .../org/apache/nutch/collection/Subcollection.java | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e8002fd69c..3b21632a95 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-891 Subcollection plugin won't require blacklist any more (markus) + * NUTCH-972 CrawlDbMerger doesn't break on non-existent input (Gabriele Kahlout via jnioche) * NUTCH-967 Upgrade to Tika 0.9 (jnioche) diff --git a/src/plugin/subcollection/src/java/org/apache/nutch/collection/Subcollection.java b/src/plugin/subcollection/src/java/org/apache/nutch/collection/Subcollection.java index d12e181727..f3ebd41363 100644 --- a/src/plugin/subcollection/src/java/org/apache/nutch/collection/Subcollection.java +++ b/src/plugin/subcollection/src/java/org/apache/nutch/collection/Subcollection.java @@ -25,6 +25,7 @@ import org.apache.nutch.net.URLFilter; import org.apache.xerces.util.DOMUtil; import org.w3c.dom.Element; +import org.w3c.dom.NodeList; /** * SubCollection represents a subset of index, you can define url patterns that @@ -170,11 +171,15 @@ public void initialize(Element collection) { collection.getElementsByTagName(TAG_NAME).item(0)).trim(); this.wlString = DOMUtil.getChildText( collection.getElementsByTagName(TAG_WHITELIST).item(0)).trim(); - this.blString = DOMUtil.getChildText( - collection.getElementsByTagName(TAG_BLACKLIST).item(0)).trim(); parseList(this.whiteList, wlString); - parseList(this.blackList, blString); + + // Check if there's a blacklist we need to parse + NodeList nodeList = collection.getElementsByTagName(TAG_BLACKLIST); + if (nodeList.getLength() > 0) { + this.blString = DOMUtil.getChildText(nodeList.item(0)).trim(); + parseList(this.blackList, blString); + } } /** From a657ab07d8d1b899d2b49b5a9d492cda13f6d58b Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Tue, 12 Apr 2011 13:34:31 +0000 Subject: [PATCH 043/754] NUTCH-890 Fix IllegalAccessError with slf4j used in Solrj git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1091407 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ ivy/ivy.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3b21632a95..4932c1883d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-890 Fix IllegalAccessError with slf4j used in Solrj (markus) + * NUTCH-891 Subcollection plugin won't require blacklist any more (markus) * NUTCH-972 CrawlDbMerger doesn't break on non-existent input (Gabriele Kahlout via jnioche) diff --git a/ivy/ivy.xml b/ivy/ivy.xml index 5d40a682c4..a271ee9144 100644 --- a/ivy/ivy.xml +++ b/ivy/ivy.xml @@ -32,7 +32,7 @@ - + From 17315263fb3a38ffcf3f8966dec90a04d23ce9bc Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Thu, 14 Apr 2011 09:59:11 +0000 Subject: [PATCH 044/754] NUTCH-976 Rename properties solrindex.* to solr.* git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1092084 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ conf/nutch-default.xml | 13 ++++++++++++- .../apache/nutch/indexer/solr/SolrConstants.java | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4932c1883d..d9cd56d2f1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-976 Rename properties solrindex.* to solr.* (markus) + * NUTCH-890 Fix IllegalAccessError with slf4j used in Solrj (markus) * NUTCH-891 Subcollection plugin won't require blacklist any more (markus) diff --git a/conf/nutch-default.xml b/conf/nutch-default.xml index 7712454675..7d8d779c43 100644 --- a/conf/nutch-default.xml +++ b/conf/nutch-default.xml @@ -1024,8 +1024,9 @@ + - solrindex.mapping.file + solr.mapping.file solrindex-mapping.xml Defines the name of the file that will be used in the mapping of internal @@ -1033,4 +1034,14 @@ + + solr.commit.size + 1000 + + Defines the number of documents to send to Solr in a single update batch. + Decrease when handling very large documents to prevent Nutch from running + out of memory. + + + diff --git a/src/java/org/apache/nutch/indexer/solr/SolrConstants.java b/src/java/org/apache/nutch/indexer/solr/SolrConstants.java index bd5ce35232..56559dca97 100644 --- a/src/java/org/apache/nutch/indexer/solr/SolrConstants.java +++ b/src/java/org/apache/nutch/indexer/solr/SolrConstants.java @@ -22,6 +22,8 @@ public interface SolrConstants { public static final String SERVER_URL = SOLR_PREFIX + "server.url"; public static final String COMMIT_SIZE = SOLR_PREFIX + "commit.size"; + + public static final String MAPPING_FILE = SOLR_PREFIX + "mapping.file"; public static final String ID_FIELD = "id"; From ae0608d14e9f7cb56f4b4064ededd8cb1c8c9bea Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Thu, 14 Apr 2011 10:06:06 +0000 Subject: [PATCH 045/754] NUTCH-977 SolrMappingReader uses hardcoded configuration parameter name for mapping file git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1092091 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ .../org/apache/nutch/indexer/solr/SolrMappingReader.java | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d9cd56d2f1..68895f17f6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - Current Development +* NUTCH-977 SolrMappingReader uses hardcoded configuration parameter name for mapping file (markus) + * NUTCH-976 Rename properties solrindex.* to solr.* (markus) * NUTCH-890 Fix IllegalAccessError with slf4j used in Solrj (markus) diff --git a/src/java/org/apache/nutch/indexer/solr/SolrMappingReader.java b/src/java/org/apache/nutch/indexer/solr/SolrMappingReader.java index 1f37f91076..0eb6ed4c12 100644 --- a/src/java/org/apache/nutch/indexer/solr/SolrMappingReader.java +++ b/src/java/org/apache/nutch/indexer/solr/SolrMappingReader.java @@ -38,9 +38,6 @@ public class SolrMappingReader { public static Log LOG = LogFactory.getLog(SolrMappingReader.class); - - /** The property name of the parse solr index mapping location */ - private static final String SS_FILE_MAPPING = "solrindex.mapping.file"; private Configuration conf; @@ -65,7 +62,8 @@ protected SolrMappingReader(Configuration conf) { private void parseMapping() { InputStream ssInputStream = null; - ssInputStream = conf.getConfResourceAsInputStream(conf.get(SS_FILE_MAPPING, "solrindex-mapping.xml")); + ssInputStream = conf.getConfResourceAsInputStream(conf.get(SolrConstants.MAPPING_FILE, "solrindex-mapping.xml")); + InputSource inputSource = new InputSource(ssInputStream); try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); From 6a7965ebc6c53fb82f0c096a3e87d1382b1cf67b Mon Sep 17 00:00:00 2001 From: Chris Mattmann Date: Fri, 22 Apr 2011 06:17:30 +0000 Subject: [PATCH 046/754] Prep for 1.3 release candidate git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1095872 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 +- conf/nutch-default.xml | 2 +- default.properties | 4 ++-- pom.xml | 20 +++++++++++++++----- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 68895f17f6..f0b08246d1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,6 @@ Nutch Change Log -Release 1.3 - Current Development +Release 1.3 - 4/21/2011 * NUTCH-977 SolrMappingReader uses hardcoded configuration parameter name for mapping file (markus) diff --git a/conf/nutch-default.xml b/conf/nutch-default.xml index 7d8d779c43..e6ace21456 100644 --- a/conf/nutch-default.xml +++ b/conf/nutch-default.xml @@ -123,7 +123,7 @@ http.agent.version - Nutch-1.3-dev + Nutch-1.3 A version string to advertise in the User-Agent header. diff --git a/default.properties b/default.properties index 10055caa1b..7d9fbf120c 100644 --- a/default.properties +++ b/default.properties @@ -1,8 +1,8 @@ Name=Nutch name=nutch -version=1.3-dev +version=1.3 final.name=${name}-${version} -year=2010 +year=2011 basedir = ./ src.dir = ./src/java diff --git a/pom.xml b/pom.xml index 5f12134076..6a22b8a79a 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ nutch jar Apache Nutch - 1.3-dev + 1.3 Nutch is open source web-search software. It builds on Lucene and Solr, adding web-specifics, such as a crawler, a link-graph database, parsers for HTML and other document formats, etc. @@ -77,6 +77,16 @@ Sami Siren siren@apache.org + + markus + Markus Jelsma + markus@apache.org + + + alexis + Alexis Detlegrode + alexis@apache.org + @@ -153,12 +163,12 @@ org.apache.tika tika-core - 0.7 + 0.9 org.apache.tika tika-parsers - 0.7 + 0.9 org.sun.jdmk @@ -275,9 +285,9 @@ 1.1 - org.gora + org.apache.gora gora-sql - 0.1 + 0.1-incubating com.sun.jdmk From 40b69ee8ef5fded681dde92cae084fce1785ecb2 Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Thu, 28 Apr 2011 09:57:12 +0000 Subject: [PATCH 047/754] NUTCH-986 Dedup fails due to incorrect date format git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1097390 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ .../nutch/indexer/solr/SolrDeleteDuplicates.java | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index f0b08246d1..8d68deaf79 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - 4/21/2011 +* NUTCH 986 SolrDedup fails due to date incorrect format (markus) + * NUTCH-977 SolrMappingReader uses hardcoded configuration parameter name for mapping file (markus) * NUTCH-976 Rename properties solrindex.* to solr.* (markus) diff --git a/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java b/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java index 68dfdaf035..99828a87df 100644 --- a/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java +++ b/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java @@ -21,7 +21,9 @@ import java.io.IOException; import java.net.MalformedURLException; import java.text.SimpleDateFormat; +import java.text.DateFormat; import java.util.Iterator; +import java.util.Date; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -126,7 +128,15 @@ public long getTstamp() { public void readSolrDocument(SolrDocument doc) { id = (String)doc.getFieldValue(SolrConstants.ID_FIELD); boost = (Float)doc.getFieldValue(SolrConstants.BOOST_FIELD); - tstamp = (Long)doc.getFieldValue(SolrConstants.TIMESTAMP_FIELD); + + // Attempt to convert Solr formatted date to internally used long + try { + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + Date date = (Date)formatter.parse((String)doc.getFieldValue(SolrConstants.TIMESTAMP_FIELD)); + tstamp = (Long)date.getTime(); + } catch (Exception e) { + LOG.error("Could not convert date to long: " + e); + } } public void readFields(DataInput in) throws IOException { From e47f7ada80f478eeb77fdd2f1e7b470afd29bed9 Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Thu, 28 Apr 2011 10:54:51 +0000 Subject: [PATCH 048/754] NUTCH-986 fix for previous incorrect patch git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1097410 13f79535-47bb-0310-9956-ffa450edef68 --- .../nutch/indexer/solr/SolrDeleteDuplicates.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java b/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java index 99828a87df..a5852b1477 100644 --- a/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java +++ b/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java @@ -21,7 +21,6 @@ import java.io.IOException; import java.net.MalformedURLException; import java.text.SimpleDateFormat; -import java.text.DateFormat; import java.util.Iterator; import java.util.Date; @@ -129,14 +128,8 @@ public void readSolrDocument(SolrDocument doc) { id = (String)doc.getFieldValue(SolrConstants.ID_FIELD); boost = (Float)doc.getFieldValue(SolrConstants.BOOST_FIELD); - // Attempt to convert Solr formatted date to internally used long - try { - DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - Date date = (Date)formatter.parse((String)doc.getFieldValue(SolrConstants.TIMESTAMP_FIELD)); - tstamp = (Long)date.getTime(); - } catch (Exception e) { - LOG.error("Could not convert date to long: " + e); - } + Date buffer = (Date)doc.getFieldValue(SolrConstants.TIMESTAMP_FIELD); + tstamp = buffer.getTime(); } public void readFields(DataInput in) throws IOException { From 2150e90ab0b1ee474cd8016e04f5973a05db88cb Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Thu, 28 Apr 2011 11:18:29 +0000 Subject: [PATCH 049/754] NUTCH-991 SolrDedup must issue a commit git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1097415 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ .../org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 8d68deaf79..b8385c5656 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - 4/21/2011 +* NUTCH-991 SolrDedup must issue a commit (markus) + * NUTCH 986 SolrDedup fails due to date incorrect format (markus) * NUTCH-977 SolrMappingReader uses hardcoded configuration parameter name for mapping file (markus) diff --git a/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java b/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java index a5852b1477..9defa1ad1a 100644 --- a/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java +++ b/src/java/org/apache/nutch/indexer/solr/SolrDeleteDuplicates.java @@ -308,6 +308,7 @@ public void close() throws IOException { if (numDeletes > 0) { LOG.info("SolrDeleteDuplicates: deleting " + numDeletes + " duplicates"); updateRequest.process(solr); + solr.commit(); } } catch (SolrServerException e) { throw new IOException(e); From 341fba280bf308d704a2c67adf00ecd2a4209f0f Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 4 May 2011 15:20:00 +0000 Subject: [PATCH 050/754] NUTCH-888 : Remove parse-rss git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1099483 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 + conf/parse-plugins.xml | 7 +- src/plugin/build.xml | 3 - src/plugin/parse-rss/build.xml | 46 ---- src/plugin/parse-rss/ivy.xml | 42 ---- .../lib/commons-feedparser-0.6-fork.jar | Bin 160824 -> 0 bytes src/plugin/parse-rss/plugin.xml | 49 ---- src/plugin/parse-rss/sample/rsstest.rss | 21 -- .../parse/rss/FeedParserListenerImpl.java | 128 ---------- .../org/apache/nutch/parse/rss/RSSParser.java | 227 ------------------ .../nutch/parse/rss/structs/RSSChannel.java | 189 --------------- .../nutch/parse/rss/structs/RSSItem.java | 151 ------------ .../apache/nutch/parse/rss/TestRSSParser.java | 130 ---------- src/plugin/parse-tika/build.xml | 1 + src/plugin/parse-tika/sample/rsstest.rss | 37 +++ .../org/apache/nutch/tika/TestFeedParser.java | 130 ++++++++++ 16 files changed, 172 insertions(+), 991 deletions(-) delete mode 100644 src/plugin/parse-rss/build.xml delete mode 100644 src/plugin/parse-rss/ivy.xml delete mode 100644 src/plugin/parse-rss/lib/commons-feedparser-0.6-fork.jar delete mode 100644 src/plugin/parse-rss/plugin.xml delete mode 100644 src/plugin/parse-rss/sample/rsstest.rss delete mode 100644 src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/FeedParserListenerImpl.java delete mode 100644 src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/RSSParser.java delete mode 100644 src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/structs/RSSChannel.java delete mode 100644 src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/structs/RSSItem.java delete mode 100644 src/plugin/parse-rss/src/test/org/apache/nutch/parse/rss/TestRSSParser.java create mode 100644 src/plugin/parse-tika/sample/rsstest.rss create mode 100644 src/plugin/parse-tika/src/test/org/apache/nutch/tika/TestFeedParser.java diff --git a/CHANGES.txt b/CHANGES.txt index b8385c5656..cd2c81a1e1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - 4/21/2011 +* NUTCH-888 Remove parse-rss and add tests for rss to parse-tika (jnioche) + * NUTCH-991 SolrDedup must issue a commit (markus) * NUTCH 986 SolrDedup fails due to date incorrect format (markus) diff --git a/conf/parse-plugins.xml b/conf/parse-plugins.xml index f3dfd52a67..20c8724a9f 100644 --- a/conf/parse-plugins.xml +++ b/conf/parse-plugins.xml @@ -27,9 +27,9 @@ - + - + @@ -65,7 +65,6 @@ - @@ -88,8 +87,6 @@ - - @@ -77,7 +76,6 @@ - @@ -119,7 +117,6 @@ - diff --git a/src/plugin/parse-rss/build.xml b/src/plugin/parse-rss/build.xml deleted file mode 100644 index 71c40d3e74..0000000000 --- a/src/plugin/parse-rss/build.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/plugin/parse-rss/ivy.xml b/src/plugin/parse-rss/ivy.xml deleted file mode 100644 index 37a6be13af..0000000000 --- a/src/plugin/parse-rss/ivy.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - Apache Nutch - - - - - - - - - - - - - - - - - diff --git a/src/plugin/parse-rss/lib/commons-feedparser-0.6-fork.jar b/src/plugin/parse-rss/lib/commons-feedparser-0.6-fork.jar deleted file mode 100644 index a8eebe55561ad3ff488ad718632931dece6d0387..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160824 zcmbq*Wpo_dj;>>7W@ct)#|&*|X2v$#F~!WxF*7qWvtwq8DW;g2{c`TTv*yfMGxy9} z)7`7Oen=|SSCY1*t!_mC7&t7*A20X%Al84J{ObVo`&&+2RhU6qUV=&SHyJtz^zXOZ zU&zq^OeQBRFD)UiqRJ>Ikrlsb4a$NXd;#Ve+WEQvUXrO(rA)DplzMACPS$qiyXjcW zo9BzUhL$Z2`WLSQZ|5myyxi?N6QACVU@I~pNal`_O=l$N*4V2HYDz8OqKIC8V?MF-{0!K zNnPFcN%^3>f!3Y220UYKG+72BVYWV;fpHDBhTyUWYU_hE(e~bJY=7NXCS+Qrn7)Wp zs+vpRFpS^jX-1GBe^0p@5f;kz+mBKxom72EvY{z_1XTn;LFZ-V2yOqq|E5nMAb%kA zPaqLJy#EW3;J+yxoGku<;eSs7_b&<~ppl8C*}ow~`>%v14t919_RfD|i22L43IBTr zb2BqjpplcanbV(m75~CZ`M11g9!7RR+dnJ(R~ck~E5quygg+^v^cN*4{#JmkgNc#L zzte%`e=EY+*1_U8ByLtFe-`&wjs6}Mdovez2Pd2V(_J~Zx&U2W{*8#gM;7Sd{AU4w zH4W0gg~i#(<4+3wmH+P*a54M03XuP;0xk{?w*R#5f5x7_de$Fq_J?Qv%WwX}9sXq9 z!Y&SW62FaI@gK&{Xku&R>^!6i=b<{{`X1Z3=H>nXI}-pN>1py=Tbf>-84+&yD>x(x zOrWVgr(^13a^t<=;5h~+XW9BLr?gIq{oDcx5+74Wb2whcAZYM#6CU2-Y!JKa9RIgs zBr3T?%Ch+d=}h$N>5a`rSi7Lyw}i!q?^%3ohm$-Xeg~6n&$q#`0tkjhrw@CGnXH#6 zLbR7kUW}5OeN#*dEtG1S6-*4!l8fMt(qCLcDfa>$6{qqZzPXAyJ;I(Dj^- zhq)9keS0K%=le?SYr5-~)K`=C zSQnmCI+la$u^4;ynC_pG@n|6AwjJOfiH~|IJ{SipB7kKuPYz;%&R=m`@o@kk2 zBqbr>JlqWJL_p}s5fYWBZ#+h}SqK#`Y6$97ia$}tY_3fz$L;}fxSod(Z*|{PS?B3X zq^}-T=^R>s-Cb%te&UJLK0cqE6@#ON10{yl#9*kh+nQDhJXhiqL_i9ihRlN(n`7T3 z>zbAQR5%Yi7pvLOrp(s_C$SlBh%x~;&eYLDnw>Fo)|ikE!_kz&td@ZuGj4k%Ufnb{ z(~#cf3@d`LqIoxE;KM?mOZ1DMX!p1@w|&qC4j*bHxykXnR7;{<;Y%{#nAkI;|IXm^ z>K<(YR|CbFX>9p65AUdd`QvBORXXU~wKfmVyv@D(4MkcEm5K5YD3htLxx<@+oVF!o zFZMfeq?9`=G?3fy?!QQ=;-jdYF|J|yDie0jVzQ!naKxKhxaRY!c&{r4D?b+qnt^F` z#ImHSO?oD}wHtU+pWX5aCVh8G7pZoLYp`K#D7h}Q{j5Qb&n3cYB}vD=8=Os5gw)o; zlqfg!Xuo#sQJfltGD}vsk9_DIuIujqiKqABapg(d3n%fKFQrLF)##h#8dn?LJ#kedyGqUiuJ?D2RAjBc3H7U)*OW@var8&2yeCcQRCgwgt zFyrJ|cybw66;{Qt*g2M$ASy~b-Q}jdnTSfx;(8ik6Dgy1e=-Gcws*f2B!kH|@&kr0 znHH|+Qv{d6YoIp-gl#ZZ%~xK90iq3KC9~+jvKx(+R=A zEW5spvwtH#f;QS-;gBD2u;Ao&Oc#fwrm;fKcex~bq*&I!~v)S5jBqjlw zsp>S;H!6_~P46vLmM&m~#ATsf{~1%eUTT%af*#7+dwt!_?Zi}bSWKr#3u4+>G)1#s zwVJ~;onpc)nBs2EP--PX!iu$UH6a>n6@j)0+s8a&hSpnlX_V?lAe-F~Lio%A%dIu@K$uz67<f(sgSh*1^k*gR4b~FmiFOJRt=}cibYuKO0kWv!rTfEMQB@1u(O^& zqn+ilo_%G|0Ab|hPLmxpIf0FWrvt$Y#Ry9|jYHHn7)-WJ)hp-xlA2rXEuvC4LzBj( zMQEa~JD8)rHe}eU8c)IZ6BFQ8rdNz{*Vt@#vZ{*9TP3DMRu6E4E05e0aQTejrZY!2 zJ29sz$iw>O%igT_m&SedRg*D&4D`NmtC0jB>zrb`G6jgcM5Z736RW{RY$M3Dm=*!Q zYVxbqT>ZJ?RhpsI^~zdBc=Qr}Y9=WRg@|mHMWWu3^zz#DQ;3v%np~LcY0JVioJ{D` zQ_|7hr5jc_G5*+0Fn6@LSMs#)HDA8a=M{L?zc-s3f_2-DsHh81T|k#es!(^-vD33l zf01X*-49PHZsUsv~awDKc?pp0hjsi#xFxjjYxRRJs1JNXpA3oDB7s>r}-}}TxJf5tX z@x{$8rUE)*suXCX+0?%1=Q5XIB3o*^vZ$zVHG1I)w^Bw&uQKFuHXvGOdY&*!y)EO8 zaDF6l=`k|GZaN1ZN%fR%Ag7v&v^}xkb@ik+kF@25Yal!p=HK*B>v#y z7LA9ENgc(H-Ws^Va55-#TQ->f#{i~S54+Iv2>q%#upRg zr7XAphMxaLp`g5C^#;9j=RgS?13I?flos2c>BI^)l;2|80hTLU_;X7NpXztDd2kcV zMxcQq*u#1luB`|Gr-|TNExOL^S3AvMx5_@mN=Yj1I<)mVfKJC+6Ew_5*{0oyp41!| zk!xXG@jY&(IAmaBmwzq31Sa8r>)e1E;_FET^Tm(QvWe}Rc1qkI7@1q%kZk$ch6p)* zmTJIa-v`b)e?_%Xz6dQfOMG#=GCa7(fG$-RkVVtnl!6-=(=_7h{P6L8yK~Al{-bPG zeHR5B1L0#zJ4*^z-N+6WSrZ!;B+1o^{BEt*P=O0>u5*S~5+ zB8-Gw__N5?c7lo~Z-7m~d)x123AuO#h);F(@<=QsNHjub`pS~_K9@%@J8(AivRAFGdx=o&o^|*^JvUq+xL|*oUtf1=ey?@lMO3eix@7!$Q1Y9 z8`C{D%i}rSo)BF+YdG6be$RG}i3t0e2pzcx^~H&KyP&A;6BJz{j#aG!UO~5%vKWJW zf34OX$O<0W-Pb}UMT=&cMVeae#Pxw!ikvxy+DcfvOKc{!UcoTB% zzy+O&gWikd52i}(rNWGAKu;|nJ#(>dJg7K9A>Anjl;qkC;&C)Wl^x6BYKwQ14k)bh z$_anPwU#;|Iw}>?4|?%HIo~_^Kx%CKX0iVXQi8&+0|0iB=jz3Asn%Er=O?y3l2ZW% z*Q>#Q0?Gt{@n%QzUV!l3!~pqNQr^bdf*jDvBhY0Dxclah#{rgKms*OkfqZ>DuS&qW zU{xAEi-UM(Ig81{Wt%(fkFyigF{XLP1~F z5@~JRv$iN7needn=Ta$7r`Edo+b4=H25ZmjaUX3T;U`VQSDHPXN6N8R%$*lbnn%RG z560tH!n}`9!XG4Hdy?dD7^lMuQnw1ySJP6P)1D%X#AE>l%Km%G;ZTE-p@Y3%L!pvG zu}A<8`7sxMCJt_guY?F zI;!AWmnPs*yvVkQi5OE{mCfjcmh z*0#z8aFW1CzS(L@x2#^-t}n~bZXJ6J)}0;HGXepG?xcm3_5%L6?z>AUr?Y5S7e-HY z58+p<()r|u0w<v8Cc5Nu50w9V+;M!&b}HRowG55&9SClbK7B6iC0UZtHf zV&c(xuJZQ-h6;KOkMNf{mb@=gv#i_mVO(oW*nGv$ch zyFxs09V?UV^Mn$|qFo$-B@>6VBrjnCq~Q16u$|&CmI&aZMd<~0pvc-Lk=jUn6S$S+p~Hv+ANG6SU1Yp~~UH+V_*% zwb%_bVvH6`DhonehC2LwgqQRK3w_EmTH#BhxRHj#Z2lL7$a+hot^B9LTP7_6)sz)V zpu5L4)*t@N< z%8WGxl$K*^(SsPnf{hA(!qXKVy!okOq3D}Qs=bzMaZ#mj8mKe9L&>j%@u+OF(=^xq zG?Gik4`ZQeoXT_Ktx^30!Nl1rX@9ktEJ2gPMinyjG1OY7J1w$Q1!i?Rv|kzQXjl0< z;kge^VhYN~`}J*2&`Ng^g|-ibP@;p~3^!g*wzd&XM>JO4YFA?OPqvfT>=eh}#vk%T zam!cWa{>w@(C29H^Hqc;hQv)V_Ej}UE7C#~hA1YfTPt^At7WQLboFzX(REcE35ZHt z{gKqXgDS3%P5fPdUat-jod|K-E-OKHmhEB+DgkwdN*-m04j)Bw+v=5IyH&4};ai&P zF}t3w9HnpN9LccQlpO`kT-~9j*0|`%BljI{2yX2(t~k0;N>_dLhgwkA+-JaFr}>z} z%IcdzD<<+$uOLJwPPo`)a8vYCQfllE+0xiLd+oRGHd`9AlI5wK)~@=xH~Dz^vfy}9 zO|zAkY-(0NiAMblsAkvXUW9Ei=V(&|pJjnpRi}LDk@{3x#dh`^! z?Rbl5o`TJf75b7t=6J;N3-$ee1i(-pipF5x_hx4hgKh#4tLv3?n#L+;Vx8wJ+-oDo zzF9}mm!bkn=+_sQJa3q+DcN8Og7T+l^D>GfaK_f-*(znk{lC9^Zec#f`)6da5K-WF12l(2vC%-U5zB9R}ivE5urBho1egkRUaFL?8|A+?*Y4pi?lKZ8gI zsum>_`_MIL#)52Bv?^K?LTJsBtOp#-vL*3^cquNrA@BqD&8w^pj4vFBRm7NmR{SAA zaoR#1FX*jSSE0-t`Gp1POue-YarT7T5q@4L$Wjvfn0r=2Yax+y{Oevz(07gg13Due zEeJfn&Iq}-fkf?dFEITg5ck$MmW?wA_6-CNfrnYmue|t$)SA7pG_;0hPkI|w)E79^ z+@TVgl58%Ol2QC&Xh-DJCD2EpfmC+ef>fmlsEsGRf}El==3w{hy=t7rC7QiL5#KMq z1QpMlD%=1s1=YXz>jWCU(5o+R6E%v7C@4~$hlEWU1(YnR_2NG14!0p7=$5qU8(US! zl`UiM@UmsecN`6tbWRYwy&Iw(#zP&vUZR|oAgC{gPJBIQY>!|*!zSO?hkq!8ScRDy46742~GJBSA3(e9(;p{Ye4QBI9 zW(5qT_9KnR(dse?JJ#q7|8ek>O_g*4xvV=;ioz$;dri917oIr>QTaw(S( z*dHqcaiGwrZU_(%0)#)W41eu@bNubfAZlr3Z(-*A*A``#x{WiM8M^OQ{aEps$a<1` zfQM*rqa3b%M4|4PcrUxE4rmp`CZ$7SRmNs*hd(IqJEGsGADKIWOXE@3-*Nmxgv5Nd z1b*rg>U^!()xhC3@ zEFl?=gDi=?hmWNGT9Xt&IEto?B}b|lE|wZXgPE(Mm?3URAXkW&H0Q6-}l;V@!MwOI*cEHb3b=91qDpqz=~R9DPIF$hV}*EDH05D%QWAx2?PtFrt|HhJBB{{1 zyg`|vszb^Rrv}C>Ls_HPB%2gHm^}MSDm$F|v0E^<+%Dab1M_4MiXkxnc*H`Pxf_P% zyr5=Xs})ca0%$qSY|xunFQ z7-(iFuEqDU_6d`_QOnI}+g^4f6Y*E9T9QrhFZBYEINz6I{#S`myIe%cP@7!;7hZla zK~ONc+So5Ghkd=+jMyHfGxrzD8TgD@5~pVAG{hB^W5ts6v6pFOPLu8Im*78~Ku#Ui zQEn-#GXQi^OLU3`-!P7vUz@o*?1Z=X_4%X?dw$uDmGxRu$Mg5pZDg%DwzaG9===G@G?t!|LzS=C7HTl^}|!~v$odPX^MG-&^6wQ9AXTGYkW ze&A)SsQ7a zdh2j2+Jt+mgqogFMT^TyA-llF7vZHiM|6*n#=I#9nP93~^rHWg*BEqwX(nqqn@liA zwz6+jQaL`}_4GnS@LNRn-q+9i3dg^WP#!;pE|zI*Lp)#CC8bCY`3hej3;8Lj_4KNo zmgMV7lvkHY^mhFHGZsYC|%bJ8edpTkaVCSt{$(YCe99h6QAvD7$TlkXD zqXNH${!>1&U{4Ut1(QfQ#fUWD*$6QkdES$B`liy6C|4k+N?r4cjN@p7EqxzPXit$P zQPy$7hK7L%<;n6MiP?N;Ctj62V@~xJQL-+*y?1@ zoKNRcy|C;mvI^y`Qcy|c;1BV4M+p^*w$KC(kGWEY;0k{6Qs+3OPT5L4{iqv1(m|G| z-y23y&{ycyEgiqNJ%fL5cmC(j@n54O+uuh=2YVMYdlxY?7c&zV2d95--(;!ks3eYTBSl+2b~*oHfV_y>wcKbflQx)oHNo zeHe9@)rwe1p2~?=@xv~uU748sj-Y6lHRF)+FP~pKcVj|KT&d+2$$2aj2sKD(Leq>9 z_gMKnoA1V(q~=PE>Kr2s0&3pbdfBlD(^ELd-)rgyIg;__-J@I6 zlztXwAaYQU`)vH5kpZ1pYPlCh-;{qrF#g_Vy5QUaiCg|vz|Ubi*{ z-*ug;?xe;+^vc1@15=}N-KgKTF9~gC)csj>6YCM*!P&u=dm$)1pYqXZZ7AYhIpS`F zhA~O$bcznMShR|dzTtV6eEdd7mZ5i|uKP7Ff@xKfJMbU{Qbp3fd=I&;PGCDw{dG z+PeJ9C*y<_`adHF{}9OoRYS&r;DFo{=9vk+Dr8~7-p#WXfq`-WXc$)+t!L=#C;u%GK#uV=tSz*Ks zaBG%wTH2l8`w1#d2KsPi5XT`gz@R8XZv4pE7NRQfu02i!V-8({IIE_LziS&(hVq){ z*K2F3S*WEyA=b$x#^SqvZn!~Q8tWmkh~uh-hpll*6OCT^?7xy9M#gHLIKUI0Xg-`fB4#&u+WJr5u%Tf^P2DSHW)CtAEo-X|GKg9+G+gO zxUi5EFqpr)KL_xe8qlbho#60$|G&E%% zxe(4=!@&VZw!c*vmyv@ENhPVvgLr_#BLgD?Lj!|mt^@Ju?FHR$0QDsPhCtmv)$30W zI+#h^1h4yhoq>Yh_FjyqHt-^D2~o2=1oMiNw?zktlg2G>#;#7zpp~$ z9v01?$%d+~Ya1-iH=NJg+}(ZOpWnfHajh}X9r*^ckljT$;OZt7eJPV0GvJ5T-Uvdr zlh2_;+KN?;y=H9SZem){A||<9Cld+m3)hhX{WlY71}2I*H^Ux0JE`(DAHHZ8%~m64 z338P&E9sqH3mfC1mjTF(E509cy_eFc&?PbBnTk~`tKVXGFF5UI%1bzC*$+wFfc-dB z>R02k)*@4T5F)GoP(C5Y$@jbcEK6s{S!&Y1upv1s?~seUbR7>o8Lh} zla9(m4DGEOhYhtrUxB&>#6L` zLq+pNow7##U+M+`Z2|DjihQ z#p8U-%--b=S7WNU!I=s4w# z>GMA4@P`GKFb)eNf`Wj|{jLrF=gjBtz4%WS_}>}cP>PlVo*MedbaZNXR*{wEgueFp zE_a+2NuHH5?zcg$hz8`6JOuQ#mF4@whO~6Y1y!jqszIn9u>o! zFQP`)*{K{MsJ}hhANu(|Y&cI(8u)!&QGv*Q%?jbZL?6tp{4DNY4%!a-0=uEntJrIr zi*7fuhr|zA@Z@v$Sq0&Hc`2UPOXtydRw?~uhRu#X;uM^9BV>bGKxSWAr~TDkd)r8s zo!#{BeNaa0;h2M?d?NVauJUu%)|8g4i1Niou-Bbl6w1QRrVeL z?s^)@>-Jxck$gu&j2moHhlJ>!-{)tQKf%2)K49W*Td-~JgL3LwX}M6cVK((7*)gbc zOzc7iT8DtxpU@QW6}t}**0%1G~XGXnxn{xvmz zukE|VNN}HE6aRUDAwPN>dV=7*wLV`Ce?*~nt!D)aVg3qD^SPy43AOvZhjy2YQGA21 zGTgnA=Eo?_Cc}c9iZkLH=-eoC{18O?2B5vQ`;h3VJ`33%(g-;Ok^}(^zWV-gX;|#o zdzQ7;dt-$|jWU)~YcCtqj8imiwTPHoX;?)PZ=x}(p3K-aXoPMALG}>>y(cWk*->iS z02Tcx6rK49%_1bn8^?AJW%tAARBn%Z;D^h-yP?)S-tAjJmmoLA@FmtZDs6NhbTi~z zG7wkd3hNho`K@%aRb~>{9zzH{Gk>50zL&)n50nBVD%Yh(E5Lk&jF=KgSH8e| zSOeQA2C$86DiefIfGVfG54sS$*Eo7<13Qn#Y!w6FV7s&>R*9n{z2{<}pnMyP3-5Ej z^sYU;xeOpT2Y%9FS#1xi^2n&bI#!%Q&g&GLpSlx!QN}u(U_X-!xX19|h-;;?BPCBM z8bt8B4ywbbf^SB(lOM4>R}0_+05(X;hj*Gj8zbguCJnYHwIbWnSpTY-sA?IPpE1P) zPlrvVsV7J{h25%ftLL(+e}Ji%<~4Ws3R^{&YX-!1i6>eK#OW1{-9y(~wMxV%OV#6~ zoe_7)Cnm$xx0p3yp;kDtix?Zomzq{A24X)u&&PPG^wSLa2SR80$oHOMV zPAP!vlEi#2V*WB=bgX+!dt1SN_DR?)GGl(L0s$C5m`r-ZbaobF)OEk8oVo zys1Hgsk5Fb9{Z{W_$p&dWG3x}5 zouY|83UQl54-rf(6bxkKzLrlLb`&olf9UZ%W2OGIb1otWFt3p`rB{ zgfb+rDbHfQ0FJrw57PPCQ(8M>FBd~!Qc#toUx1H)WDFzUobAMa$Lbu+|69ENk4ac5 zGh17S|IEF}S^k+Hcd6O^P85)RQ@T@Wp#wzJD^{qRfFrCN%t` zq#?8H!*^CEcBGNgvPD{u*MsrqsLmc zuAO9d*_f+HwZl<`*z}=b&_PF#xh|%yDz6&>8;A&F)}?}N?q_zMx)NM;RNkv{Oi+b=xdG8h3sBowC&D4As5~#8YhxBjuS78GCe=uhh>`o1OEP(>2j_ zP8taVwII~cK61X?At~5xKGCyuM?i&XQI^0LW;7Q>U-1$5!7W14twiq6 zPEkvrl%b@E#3$!qWDP-7j24bZ8&K2$V2zs8KOGqw2?t#hjPqOq3%Nu3L9vqufM_ka z+=7X62~)2u_&EK{agTIyq`{d;{`6wia>P%Rao%WB-7w@oK=O;$$tm8yvPbbDNL`Hb zE{Vu-<`*{zs&HE+G+0F0${T=KxY9y|FzN9GAvnn}< z*v$Ob_2{D!0t|PKQYQi8LocV+8Y5&R_Cn@`AfqUDBXlISs4{GeQX)<}Hf*!7!453! zB$yInZ0-7D2B+lfgiHNPib(Rh%EZGZ&jBtwBS4e{|2&!c z^Y1Z@0Daf#IBw#1Ard_Ld88nQvu7l`)Xn<%`|QiCh*Tt=GJl!7!Iy~3z(A`y@!AuQ zi>Nk+P-ivKYb7czyP7Xt1Y{;1$zMUC^^4`s#ENj?ZF0w>+vzbhrfU4l@dJ$!I0Ox8Dl9d75c=hs zGgq)RIFK=}gds}={PO@hQUw~Db(A-=^geFe^g>TTU}r2mm2F)+pQ=OW3pGbO-0G`j zc_(XAi3*I{@*q4)s|woPhHJ21oWC{uoS;&&ht{OOO0we5!G~W6c5K%UpJ|;ms_A!& zyVO6QZ0wjUz8ip=P@tTynxvI`w(fDReloiu5)CmAXs#f`k#}HgsZrYt{mvuGp2X4_ zP4uQ*j?<;CJrk_{g?GOu=1aOg1g^sDcy9U{-t3@8?evZtU-VsGEgS*+i5f)aFwQr; z+3z!+fNn8|ZzJpzqg&_dX?nWeAchjQ|vm?Ra}>4%R+()vZh zoEsw`4uRR`!HrDY`JJuIA%Ip{eH~^Ii*}Le1LB>Y2%AEV7)sp|YNZYMDr)z_Rf2q} zZOreuW27Xm2!8)q0~3^TA^Cszhr<7@lM4NAl`KU_$p{%d*Dq>KFv zdX3#@XllsLnqy6sxy^bl@i!~N*H7=%jtTjB0h+hWlU@yM4qJ>b_wU!RhGBsqe4pIF zPEouIrU|BDrUO@2tV%IyyY`qlZrWzWJtp|IsC8-)$ox|=WHX(-F{c*-VV#RgjCO5{ zOZ+PF_0V|tMv|}?6O%vP!<-dW)MX=o3oIWvqMK4%6~TV{VNFIb8{yI}rrN1#*7z*q>H?v7j zbd~HT+@eCpMU{IZ>z2H%C7HxJkVY(=6M4}o3am!%FV0MAqw;Ptv2m3!)&YXOA^j01 z=dFbDFn;$eVt$9p|G8@X`#F<8o`8gvt;>Hjd!jX9ymi$Ug$}#NCdXWkoH{P%Ig{9> z8)WY(_2ww;L>rRm8SMP;%?>EiS<|I0r18|Xz{OC+QMA=pBXTvhrNNSYB+*yFQG}7f zQIM6;#kct$zHg*69#cKNT_5o(*GYUi3p^Rl5ZCs@XFG zoA4TH<1an9c!AMy85?oY-(@Vv4kRl?Y%q9*^Ef%yaf?pAUM@fO$Q zf~eZAO!Y&)1u^W>+>H@;Yq8{6Ulf%ZiyB%>PC0P90_uIvaBu6rX*0n(G%8p`H2 zBN_=9lOV_bAY3Bi4f)nUpkf)1oEDE>9$WpDEI**kv}fhGW`{>+pe^XIjN_2b+`xSY zLWZoSRWZFY>=G{st0K<<)i?4Iwci0vU*?gZ{{mHC;!&Xg0!?535v-pMx`up1x;65W zw!Z?VhNLs)lDGdCjGvST*iLj17s{5b2i%T*zYdI_mgT zq=51saB06U+l z>gR&~LHhP`+K`vfsvA4IewAWj%Z7a*G?yfUiJZQ43lB=pf29gErA3uj+RNWzD;A!*X^5NpG zp$xMxy0SMX;j^=LTUGEFVp8n(Db8B6Ev?($H=NI^a}!1(*`IUmInv>(i?L4RtH(AK zSlT&7U|~Irn$xEY$T}y;I={wucS~`n%-d=C`?BFSS=blM1M_M zDvp&2OLlT$i;nVfJJAS=0dk#Q>bzYM+W^BU zQ+`#wm;U71Q~%n~tAprNB0z``;$>}1VH*4v4-y%1i~>N`eV5{%@|PI>F5vV|EkgxK zYNu{s+G|}eTjXWOZmc1*sG-7w8e7R&K{d)VJRG>d6;vsBP`o%zB>WaO4o^3!DEdJhyG|5`Auil{KY$sV9lzB_4&M292P&)#sd`|+tbgWfC1 zhY0k9i){`!GPkW)U}wUiykeJPdH1v^hGfO?a_aa2P2Ds4olq=Nc)AyBDAASy}4`* z9|_gm>rylGLbydFIPDPSEI)9sP&G-_L##wIH9^J)zh666lS%$I48gcS(iWAC>S{ zcpT~!3!$`FcdnDoDt@M*ET7|)yE+6goTI?>QP{!LU$+ms#jfPR8p6l?tO&1}?ce9n zPHUHdV_UW~D#jEz1uJn;%pAsgMY%u^GC4T3XT{1^ZvpO&J;7P?@47fb5;X!fq7I`H zB8+3XRWgUH;I#Q(NxZ@oA_p9P#L_=5FjSo^q{u8@0mKT_S1>0YdD8lS@-Jc#9I+NO zYxc<)rwEl5Cgg;77v9qr3Csyu!Y$H^<@?)Hs9vv5z^1RGqUac7N^l!r)R>}5R4#8R zkDfy_ zYQI#kT$+npQkV%OZN`IOebo6^N@7?6<<>eT-b4>;`Q;f?Q7FkZQ&I7>u1qD-rE6!h zR(CYKcyn|<5%MD^OU;-%$`;ga+N`63Ij}M5%{wZ6ENpxeX92RSmo$RrYbZJ8CMp@K zwv(!)K7t)_RY}px;v+R^$8>5`*m~)oz z!W46rJ7X;1Rj5g#wghSyj|R=lC(*FRH%_sNQzk5F6_1MjKE=qCGOzGyRHht9PyY-g zPWz_P7E#EiRa{OLUzh*;(5zgI_(epAMgvM9URkI55#C&#R`6;o>|{twxesT7ZX&Ve zR>Rs2$uqpg&92E|_$oawP&=3<)D>-`)=e^>!JG)q=|S_#+}Gio0KM!eK^YDB$GB^BJUDhcua`1MkCJ~?}z z1-|NSy1Vsb`)|AP_epTS8S;kXj{$B|&_z%-2D3I2>pbNf_L^(MVQ2R!-!hs6CEaIy zSq{s=O^0^PlPYK{sBookD@LR6l>-}mRzibg*5gITpjZ86d~?f~np{Jh3j^y~i?-b2 z!GC3?Euci9SDAS^He09raI`NW5gg3i($NeGkDIIWHieRttPU|f`U#Fcc~#f^u%E>u z6!0SR6~=pua9V`Udz9((=Q--72LT4?aLh*4s|neEw5#-NI9}K2a1_4+$~(XY9dq9| zqFOf(55ZF+zGf)Be;;2*n&QKO&a|Y{2yAdf6%rVO;KnIg8E;DlAK20b?ay}pDZR`* zlReO!O3=6&JnskzGG%&dxZVJ4{w`5=bIn*(9c=;sPT&Pn&$a*a0?OT4_?EOceq*VKb-lhxLH#SR6lZ^bc}NgU_&qO-zWebv7i+=ob#zJOR*PoWG>k=5Nr9 z)WNjD@jG>b^6`A~y50{WNo(~OZenI}kk~?&#SIvnw2sK+1)sm(zm2D|C>=9OYolZm zU4f$gK%s0$p!^J4Jm8jwz-Rag-f3Lz>eN^VZFKQl>H5pW>YC#)8h0TN?flt#Mt{Kj+lnu}>B9?xC3WeFpjNZQl2y z+*@jqqaO@5v$omXV=fSUKQzXitafo*h-W-3D&GwURQO2w9WW4B>^Z5;hIaCxyt_(Hh+PsgrL|r=G+l zWiIRD{G?{ujcA1~_6^ShX@FR=fp5%cAv~53{f@eUKQD<(K8!4bBzfLqg{k-vsrt~i zLraLEr$vPLM<8zaGa;Cq@;c$M1}cCQosi4DzkqC=pIZ7dYO%KJ12>0BDuOvQ2m9RW zhbc5CrSt$VDlph^9cQ&<&TWW*nGu*Y$mK#*W;8b=8Ur0fb9aaomhs&J?HG)*0^T@Y z7KnNm=<$%ePU%fVZyr_li(r}LvNuJF3LgH<$Ts{C63wKxkwQJ#3rCdB=|@q<^jJjN zThPiu+77V;BMt+SQJdnO`~gNpWugumPKHi}EcY=g#0O>!6R^;&I->gXmKq+Y2CPus z0Oz@uAT<6~?9`P4)YS5o!fMlgvu6=EUOV&cl~yck!cdlaH*8FY6)27le?Wyfb_!62 z;H7uqqX4N_`QCR`a5U9*pw^pc^6(^)&h!nLy;1{Mq+7IOy;{0+IEsHRnU}5vf;TaQhIDW&< zZ{Vvp0Jk5Et54pOw=;B|f$Iy<&);eJS%m8=>8mj?vH!sS+TE!;Kr~9n-l=@P08H(a z8L0TlfvZnfGk^KzNff~$j?3C3u~Tb6XWt&dAz5hdlGC#zL?}gQ_7d?)1>s?ok7I-G z!RnFCGc`oisV8LM+8*H|9*X_7ty5#*W&aIVkeF!UlH{p2Bz9Di!&_FcdI#-V8X+g4 zXXeuC34q{7ku!U_e659$lQ?bj7~rim@MAQU-COHjeL!RXg56u~U4LMC{}=ANaChEL z#&s-iH^q;oONDC!gdfR%OP2yqSO`B-i7X%UUMoU~#(JzC>s~uTKH_pL9~GXM5QLIH zmM(>^*$_VHgqAPip3Gol4iT|g7{abBsf9OVO6{pPh8$qK^B(U6W7h-meWyZ$Pk_=WIxRG#&ysDF|g-B zZF-E1T3Hv9wv`Z!UHD3LEaT)>_=+KpR;i5$4r8LFh!2NAT|7CJwQht>w4O(R zBU>|$SM4VESAt=a6)XP8Ur`#4T~~X}N|jNQJOJRy?!d7qBCk<(iE*XCzTL zDQ&(2@uIkv#mvdo`?{CW>v|z`^LG6Vp$CY=6XS@4PIhKRb_qKM)Ay=nSR*^t9%Dep z=g{^{bR!;8SE#tFRf%w^Z}HZumQY=l{|(PcThK|_QxwZ!KMdP_`CEyeyAd9B;&ANe zjEa?xbZ>q{D1NZ|qMzNdP`Oj-cx=M;Y5khy_nt;hVT6-O3jG-Zxu(a?>Gg(JkGE)k ztSn7+vd#eMJDb8iys$XKImci07aT8qN;QaiU$(AL|In)JuoXjgKD0ovm|Du$MS!>&JHVHiJ-d zrgF_xZIQ{fFh2vA^CmWH-We3PC8y0K{b0d-B48gbcw&gD#fgPgTdrlGt6EwY=e3En zbtZvWssm*xHC?5ZCt(P0nRt~CLmWt52wJ=-D+Ys>`b)ec6>c)$(;x#egpx(V($1oV zHB*Rj|G`==tE~sZ)M6z=u6RDqW`Z z-a7uqxt`3{)#eISRd>rkUn3;M@1{}97J4~P-d|=}Xi#hX{=AI*N=bT%bq68jx`C%o zC8%^}v?rTiovzWL0egpEvxL?7W9=|0&u;g2$kOM`p;+yHs&vl>IzW_rB`$P+eWFAe z{%mCZjU69c3A1Nww~MxYp%fM){)e{&gP32>!QJMR*!<6= z(*JWLuUvjqwwE3$?VMUZYoBQ(ALCF*3Y-62aVr-oQh;_4 zM&EpY`FgCZo*>iq^3y?*3H!L6C>G~2E*bZj!>JTuy7DAVe7U71&UK#_i63Su)FsN8 zGwM>6?p}gdjG(5y;fKqpySB`CsGK=JB%F1BXD5Tc!{hsze`Gc`S>WnBPGV2u882Hpz{8ik_Hph4o9|z`!H1xTukQmmxFaNs9K>zq=^S9bv~6c(Cr)f2OCFX z(CpxgJIFv@h-!j?idM1E;hmSrZUu6g*K1qjGW$NBG_OBxp^W{GA{QUe6!9;j^S@U_ zw*S#X{>PUTaWgcwb27KJ`Nx}1P+GN_gNFX`d6^<96;62dMr&)CxDLn0B7J|qy`l2sBHlKIq|(@6 zFHI1`Q%9{X>7UCYxVpCwUc5kj!773=dca?VM*J)}e?k7FzJhujdDbh>bzxTNLKE$N z3?JX+v1mWPx?ERy7jq^vq7MYLw+fxKihJMJSH4bNuknBwmiY5W5}h$MLn#B9xN$5mVI4&!V8SR#J-|R{TEFG>@d9*3Ht_YiZ7B70?uNa7a{#8JtNG)4N>d{cc@?gZMylx+SSWD~ka7QFJ zRWJCnl9{DVrGJJ|e#7*Dg&n7oBj>}0y1*D7t>fYk)A_1to5t@&OdI4J`!uxDg9HB# zVZPr_sU<)tIGhtA)1z0Wh@xVFZkbs1B|YH#z&utdisAiHu;i@8yzkfF*PoLI@GKpT zU?D=uydC#K_Is#CQLT2(`wVDz^zE8^4rTWmPRAD$oGqy!bb4WrG{lGA5z+>^gS~s>(ot8O0#od|@Q1UZ&PEA9bjtc99^+?W$=LvqL9$ zSKF=3p2_ZaA}S!B7{HXaU8JR+6f~mV70uT28hXleQFre#=o+kuwqN(tRLRXxJlYMe2n1RY5TUO@bsVCQ#%>cUdQjkgbqw+(03=SbW(#Sm6B-4x5n)q_Mn$-khZW zW-VD6XDL9EPQVA5{;AU_k#c63>883Dc}X)#HZ~$y51O)OcoSAlAjJz5yKIJ5-7C~A zX*3eqJQB&f{R{B|Nx`FO$S80mP=S|UFEcuT($ZfAgClIgj5@H->`(|j=uaTk!v(0e z`S|be`%BsQcc1d#Cv*MlKqWpB;Qn!%<*F~9Xr|~soo8gV*nmJ9q!4*Yp{!J+xP26) zX3Eb70jcmPHhv8*wOk~)uC6o7%MQi$IvSSkmh~9c3g#>g8&GpdDZn|8Y!%D$`2~;8 z*R#&74pWz9t04$Qf=uq@%T%UAuEdA7!?81|cb@~4A1bd(gGd*1dX_^+6al%t0cDZ! zO;Up%c(Zm|!E!cU?1Zs#I2Q|7Aovg$F)xTGO_&5X+r7MA!h>S=nUOEo4ETs!yAJO1 zz2r_bJ>((}N}RkAlShv>J*NI5vIIf;Ls38W&kY(2NjIMm4IRm zC+U9Jj%YW@d?$L8TtA3&R!F^YuzK9g!cieMo@C~CDk*k3U8?wijHZ6*sVTipz92LQ z#>ujenb-wk?{yz75reOyX(9S(!B`#fWF!K^lyhyQvXwYKSyeCZ-))O4I%e?2g)iQK! z8^Ntc4R9RsEFn>sZC0mI&qlGkqVagVF=pGVv*g(q(w)PQZw7K@>JgH+E5Si3xvm`C6DZ zgz>-=Lrf56SG{^1paW$}glSaG3*DIujaGryTih9}Rp@;2qpV;bjpkF6GZC=dCOtiJ z8&$PiAXns5{1MAFx5*X)eeF+!7i>kR_l`(lL>Znc{4PXBza!2!?4+(+93K)5x}p6+ z7^%YV^+j(6FXTB*QoVBUuW*ShZDw1CZYlE(07VHfx@}AxpJMulq~$^P4&M+GdL|s5 z0zh8wwrbh?m)Qw5bNEWEM;0BHE8Mn%?M4Wn;6;|U?Cl2_Hmx`voDJqH$k*wv%qvqc zU*xLGE?sylPzfXwU^xek=B-+Ttidv80yqRj^wbp_nC~DToK+u}1B40B7W#qALUyZd zbbtsUy(X+u343|NjPJV0%t4e>PEa=Bxbb8@nDT_zhWXhc5Qc8?;wD?cEK zUNPIefKcs3yP93zVd-c4{i5tNJq29$np~r;+X^7VW;kkn`dD`$ z6TOf@FuGtZTpo|P=&TKt22G@eY_(d-Ut^MlWV0Dm1Fsw5p+!yE1tL!}^hQ`VF*tstw&jc+xJ$1xW?+S>*| z-9n^uFlJkmFgdNkZkItx^isOc29La8)S_HZPsr`0&q5C4SK(ecV=;&UAPs)jHM6Wh zDy2|IcTS^pVMdZl4V;n`s_aeaoxEco2NxaR2Kv68x|GWDj>baMXPV@d_M5OnYW7KN zQF)&+S@v+TU2oIE)wk+A((nfeEeG0^?c6jvM*}Bg0yt?;CANu2j;Pef-<=>ZwuhIv z+66SePme}gN)6z4m+@^Z?%w2=f?DW{HwCaoSlb0~mpk=6=o{hZZ8#j@XZ&2Mq?e=? z!`p0+FCR0zEd^WPX4<<~_d5LhWVY#S>@eO+?n;i@PZS4mhJHh2tnP#QcMI-xSr!I? zWIv3<7ws0mC#AZs_caOJqC2TZ$i8KGAZ8NenP949rKWfyb%B(LRN`*vm?)2TBDCS^ zSXed%6^<02cH3m!D{6`*6}F>T@WL;Pn8`G^^0-yvPI79TZZ~_cc|MJ0#S>sYIZfV5 za4eR~jOP|zz(nfn7EJkWMIsRv(-d-hHEWvWs5P%l%@UzIvhaKP!WrW-ep`$zHrT1uZ-YOx zRc|O$4v(=%0t>Bo&5VRto#(guO}&vPA#9nptuNNMcp^&lewt>xqIGY-zy7qcA)G6J zMUg+ZJ8dg^kZUQvLmM^H<=-vS-BD9=mK`TC^GGCfo&rN;YFfs0F4oO?>cNF)iPhiHy*&(MPPge>JNhYu%|zhyttVBu;Sy@(GGT$d%C^U8yc)6C{&uN zM$u16cmcj5+>r#0T}$T-G*5UzJ-C)mN{irgI{wWKUtZ;fFWnz^rIby?S4XCxK)L57 zY=)3QI%%~7;l@r}r@{zfAzkzfCbn?invBTh&X{$xthL8M)a^GxdncC}jq(_JeLaoEV;iwzMPrK$Q?5KD2+)gnvJYzn>WX7phcp`bY)-FL6o6_JfFepQbd) zz*7>!%NJz_38e&Ll_&=C<58SbdWj2suf#P?<-8HA+qlww#I)lV$3xP3QV3a&NMN`7 z;eb6EZn}}mR?Fw(^8}L>&}tmNRlWskWA3=x4Gge`bj3>3Bgz*?6RJ-g$>zh!tM&UKItw8e`fb~{X|D_EBR5QsJ=1vu%K#+Wl7uU&nq=yhj|$om6NKBaONPb{N=6Bd zhI@fXk8-yod;J=*%RKz5M1_|s_`WZw%XUc&oSCJEo@WB&4@@vzx4kc>ToL`62kIj50ZJ7bu@z3C1e_VI`~ggCcfAF?w4?j7J08fQN0*nc8(nT+bH zVnsXA^PtR)tDDE+nk|)9#(J79>yV^NAL7RgDmY*>#$Vsc@n^wA0BURXkpkM^IpBNS z9cdGo$2O-<-^4$|Xx?E--3eOL^B}mxQB%ZegG=vW5FMG{S}5}WViXCk8UmvlpVX82 z1r21CBA_(RmTZuO=T9vY+rsxZ+l4V+Gsm@1V^c<-a~wiFsXNlt476$(O5I&BciZv- zc>vkIz&U%a;~QMa01m>9&Zq4&n_gq6Yi#Ow4qDXS!~gP+gs&}W2i@e&=IbH*g2~>F zT^MZHWW&y8M|^0NnuN3*jBPV)l!57DFz(_oE1cT-Cl4*^C+Yt5L0KgJ!bAW4g3 zn<8z6%-Cta(}$PFM17u(rpOwFut)Dg2s>+64Jgt|JiF!1ht#y-avG)<+tx7$WP{o^ zcQjHq5Ma$!6^q&dGx$+_H&xVa@**)|sFwmEFJcv_**uve3yqYEt^u}+G8}p8l}-c3;5a4mvr}!vSIT7N=D@bME_Mf{)>!yx+$4rzk6H9 z#-*hJzk!p4!C|FaOAPDbI&SAoDZ*0c$119(*%Tg|r+(ugmy*q*UkBJUy#Q)pZ5Y#~ z5Adn#0#CllCQr2HOuSfc-18Bq_6{rLWv z8s=PumZz*dbSXonRpq5M<71>ZN?NKH<=?IYS1MmU8T`9xIqG4n-Jw~eyIjYqxCtJz zZHYAW@^0U& z__Z zq5{ekLc8D`ha1N(N3Oc(0`7P`*t1i?wu{muQ^AZ>7y;FkG1WuBYUfm|ZFNiR1W32w zCE_XvjKY>;`87uOp2bWGHHs=1**6*w01RTGlc)TLmm2j>zZXt-RRYaCL%G{6v6$RE z-gQb6htiuLpb&;pH0()iIf$z7UR+1~jlT19`#kGRi?xKhKRHJHW2Gg%^?8E1K#J=O zki{{I-m!Wz40;X-UH_xSnB64L%7Zp{tG3R$tBP$IPRDkEwdGzI2*E@D(l#PyW%5OO zLoV|o+`Y;94F~qFy(;Iij#ZkfGy8k zei2tm#;LD4PMXF^oLAyvK~$BfWhonk@GIcZrk$ccRh+)!@Y3mqh9_sp*@jhILB~}5 z2KtliXP^CL0}y{T-6DIo_qgM2Xacxk*O~tycH=Fy;v(5?4xfvqWG5%r@?C6q;C#L2R!y@CJOSz` z&9GR9Pc3D8sOV)|^-aI=ZCU0r1&RA{>E;5mabk#ifi>K1>^^BaFOFORifDB)^P&3^ z^?{5h{84hazMLsqQ&Y!Y1NMXXx&@=Ce?^3%s-%Nxkd)N;kf)C%EKSzqmyQ-~uH5Mv zJBa0lRLI@X4nZDgg`n{l&a^_y#rMsPQC4$RUOFoEhcdO%G%QRF9WZyN48BY5V^)_l z^!Ih!d^)>(Ww`q=%niK-FK?lzMh<$^(LC%qCVbg6j+1ks57W5NrG8YX+8>$Rrk3V^nW{Woz!RGfu`p0bV=|^TAercu48B`(tF+;v=3H z@*?|O#t&aOjXr#*s4S}B6PVBI)Q(B2-N#vEJJ8_YI0N{Fp}{T0==rMjKEqCKIV0On zIU{@*P^Bo^t13d75b4Io1n1#s>i|X8ol5M*Rjag+AJ?z$LQYGZ!VMzsC%A(IUOg*l z;Y|w)oHGM4_EdsEM@6gJC>lLlz7RJV;Zc8>kGEzN+9=h=jnVFN%_y>&CBquUV_IH{ zJrSsnljN(Yj?lYL@C3hzE2cA#rV1&MHgc3143hib*?qB3j^5Cs#WVSCumFYk?&@vt z+1zUf9#dpMzwj$?z;3>l-1t*Jp*@x=Mh};NG^!_^%I)AK4o%m-n)Hc-RuHF_j78uQ z{Oe{&BLj2`*7GUWug@!Xs0}+NS^)t|k&xA3QQ4@2RdK{-m=ig;hjz4uwd?GYsFhm~ zuVCIeDZ4s65W=Vs{Rvno#uZ#ix^+uQu@*rmA&>}{VUqn#O zktHdzC%4oKIO&E-#GIrO58*r`+RJx-&y%^-TOsJ(G*?GdzD9Cw zy_Ve=n=4GXj5QWHT5rn8=*X5gK6WzOiCNgzzN9b3fQ#vu2T}AFSJ_bAjJ;MA87WpfoOo$!+qe}@GtYg;BK9kbT(!!}; z^C%G}MvBp(L`lDbc)`~9%mMKUwExU>Q%f(cpAw&`P?&_9o*uD7f>)l9F_*}LZG725%slA9qWp^H6d!P>A4_kU*fXs#XR7D@%`BW zH3v}aNPeJ4xWACje?KwizdbQ=Wf|#zp~wH4`OR0+{s?)+@HoTRld96|pLnav-tn{kFc&-sQG@&F8j?YujeV6(+=Gc)61NR@5-L{7G?$ zLfPb3+DF&IJM-Zsu=DNh9{h*S5Ecqn_zu*!;(dqDm;F~99cKMV6~gpo2*e8EIY_6)OB^@C(5eTh{+Y|Mn$bB?4+GH3?SM| zj$!!u2^!7pn@pN2NDj{|rK84W(R5QTIR~Q+b4!hzcar1xx)V04tlb^vQN;^>K7_xB zR+y+sH5d1LhR;8Ir?!R4^pks7@cp+R5N%~Ct3Jo?;SidfE#0eg5- z{Mx2$ec3_Pq{3lYe75SWZD3^Obp*iHd2Ik5-<5w~BwuR)t~=F@G}_mhd~Qi4V~k~F z>dHb)-F?98?n_1D=amOw%05pW=BiSkSHbtj`b%1fLS-LaHs#_Jm!lrb@(u$RXdBJl z3@r>bz9G~q&&YROVm5PYQG5qg3$k0+mSH!e6jrle^z$fYsdUmC8Q8Q42Sb1+tprPv*e?c-Hj7vIS>JC~KXZ+lacuU+Z}W+4JwrJxs$p{@JRa|^oo1oO z#3l@SRNBsS;U#zipH?;ktL`vke1;-# zbXJOyse{OhOq~Mvnd;m1eY3(_ePrbwhV&JBHKBUMJMVm{?g(=Bu`he_Bu)W6h13$j z1-L8e%~vosi*U#-3^{)Qs{3Mm?xvyeJYs=jnq(3+|^Xnz-Q z|Jx^=>G#Q_3||(LqVd3V<(op};kRW`$Y9pz{f$kI2kt-8V_2VdCv|Y?N#-kvzp7`9;GdeP>m%t9bqllTuqi4fi4YwY%04 zoC_Su;$6Y_4sd6_C`11C%99zE;m3?Xc(Yec>C3<{dWU2x60!xtX0mEb!tY8YuE zhoKa>7u5eGNq51)>uYhzK0^x0N?mZv;*R*Df%*b*l%FY`rP4sFE^%l}sTTgr?_v|V zIp9e4!8T;<{xuv2+%u%&oQSnE_)$ad^4naunX1&?H!S=`s-iqjDMA4+Wtd?KCmx9C zKKHsEbI#ikLFmIOe0g^VKKy5ifKTwzkv;;$H{2OHA?54>K9gAh=KH1}0y*r;9ryIc zINfH+uQcb#E4aV1kY`(*lyY9aoly5h$|G&D5Z!Zbvw?w*zkGAUn4F#nx#RVdgaK4n zHAx!k!FzQE6UXOli{bqS5lhAK6`VzfjwNKr@ZTjgL+BPFWJmS~B5~vSFCeACbrTc1 zF@KH<%A);TD0t-tB`Z~h>hzPQ3ng-TMb_7QBL}lNBF-%}&$S4CBOj+zjMR;Q>G8)I zZyzVGAO=D14>>#3%pG_yQI*WuXYh+HDX)OaEBuZEe=Rr|?#R(P%XrE!@gcv#{{YB? z6oyp}9~BMwA1j*p-@>gwHwFsn+c`Nq7|Z>+ChuS1>#uM*Ur|$T?jsoj@NIQnt!ov3Bs_>}C*8XuKf-f3j3Ztfa4vt$zJ(oyAc9m(qF~PUNYf`U z;H-MYPo_@3w39qs@0OWL!^Wk&MjMuWh7$wxJ1pB|E@PT80-FqH{3^tETOhd5Ufu+H zTNoSv;f5}9m9&7cV=HvO5&ha!%DmWr+>n^H?qJYZ)jO@R&;dUb-<+=bDs@6rB|`r^ zGv#J`JCa4ti@V=HN`z*bdVM7;sujy{>GeIEsqEcvsKM;;T4wK9)HinV(CT4Gp;8T)EGUcc0x=>p z)Ouf)NP=3pUOaa#>e;au(Pea|)>GIBsalf+pPhw!iLFECDwzIC#;)7NkQYkM zp(6{*NPHw8Gw#}=9||q0_WFXP-J!qNv*0G=3L;TRwsNt?lPFd#!F!9C#WvUV@;Rp=MJhX7Qf2EYk2o7Lcier z2{!OA$dACTM(_^etD-Jf@Gq#38C?(HK3}6=<@MCJ#6Sx_f5CJU+;T$Ulie96$&lP3 zmw06kEsS#$6&kB(!eN#DvM6`NF*_l{QLr!D8kvphYA$}pN;|vWYY3(pWoAZDe+UnM zN-i_Xu0}cW5sv6x!7nkHm%a!q<}?^T*-^Ew%{YqVBDXSOAWL`rL`t04_)}z+>JS6Y zup^JlxOX_YUy+c_Wb8Y}AZ40jk3{Vm4saW(Cjl&G2@MZgR;b0QDYs!ChuoOt?b_dl z_TVCo%Vtm8nKijG<4E0`UudL3G|_345e5ecvB0m@%W-~EQB7?WeI#vp9ZQ1py^RLm zY^Yr@YR*Y!(a^y=40pfH5U4fV!0ZG;cu{*G5n_E#ck+`Nxs*Me^PI1A z{4;@8ZqfGJBH*W)iy~<+<%tdrC1Q*qIY!dsO)Ozz1B(sj0lZl6QfY8W*vQOJ64Lfd zqYe47gt$lS<}yx2D5K^fj~X?(Lmj#>6)BVNpDr9iK}ayHV#U0As&0O9H#UD){*B9* zjY-v3ICkTduQr!%48Kx_iJm%|(09?^Rzy`XYyzBw8EGrIxESF1uGhHP?knBfp*oon z*3wHOH(wHDY_Hj`U89a+pIek#X1k4L+`6*||NSJ_sqf65+j;&=9yPeNA~wgp2kEaL za1;ka>T?}?Sg?J{^kpKhq2`=f{z|(GO=l^p$yZiI+@vra?HThgC3o%hRNk?!Llj3F z((0<7@~QT#oMp9Bk*|D3N@YzmZCmxys{o>aFFZ!GRGqs8;d>`!w(;jPR^%ID=@My| zeF$(&VealOB-Fa(V487FQ=#ZuM(fWvQ?2?+#3k(sLEs$Tij*wTwZzM^+&?p2cR0A| z@@u|Owq+FW+Imwl$5Jjto8M&%9>e6lg>T5y$pn*^!?@AjS zK^3Uq52u0>FO&FWQvlnk9#~C&cgi=qBX{zf&KKYgk6?2Onl5k2IcGE5i^!;)i&I{d z)n|L1Ra{C^qUh+37!&m$ME^9B{1x07YbaMSGw*Q!qrJ#VwRFz4KNfqq<)+82cHSaZ zjyUnXI!E8sa-YFapb~A3VjP#QUfY7Iv7(&Og!8qnI(=Vs9lzZ9l3t5vWFN{~SFJ=ckAiK|SMQJaMx7OXWZZ2rg zS?qcX4J~Xna0c)&gTp;TG-l%pjU185O|wy{qRNQ^fIrnUV%1d)t@jOVyO7r^!SzSd`mUCD6lSF;|0;#v8_Sa)0h$ZKCcpD9Pj8z@)(k zrYPeehp6B^^aF*8iyf+oxZsqkxKO>r&kSpr!xK)Z)T+X2PxR$!k#!DOuD5}84v5#1 zeeL=H)&+%CvtKk~a~#I3r^OI?cOSo<5Tbo;tye&M^v0K&TYFNmp0 z{hnT^rtX++rpkAzuV}WibKCa=yFz)}`mzmT@z+8b8b3Ht^(Lxb+l5V(Uk2t*iZX}b zq+A5i#Q{NF*&~Et_>STW0%L|7!ER%P4Rq4yxZD<%RwEa=Oa6m`XTQM3UqJJ`>8qx? zE{K3$ou)sfU7y37-&$#V%zh0nE#F+{ySyOfKqU&Ye{QO>V zagWLu>| zsXZGV$i^bGR)2pCZF|4CUuXaN23&vUirn0E+&2w5CbNoDW=sa_A?-y^H)s_nfR=_>#>oXLA zFCn&XMADYN=R+-j-GKmH9Zhs6Lr7;OcHrU)jx3A&lT}33E27}L>E7re*khI)|8S=A zZ~d+S-mMI4nUlWSce;h4P9dRUKK>1Yua|?tuPaA~qjov^6zMjxA=yy2?;>H48ss~~ zl83qf*xAwvqQ8UjAFuzU3D#MRck(*=pEAsZ7lRWnVtZlVG_14u%)LS!5|GS>c-+NM15E; zH}x4$6U=x@PT?(gR@23A@QQ9yZ7pAHsV55C@@97tgtS%^DaD3HD#jAQKvwZyE64d`cV&`@CRWlM|@ZJ&(LTBW@Y!oq@8FiiCp= z+b-%1E2jdA=UXDr>J$mhpfw1eLfnMiS80&Ez)a}q$M=N$2aT+2T*tKjU?5k2F@gQN zruz3IZ~vD&{a?bWrt7RI(i>~XkizF?`(n>*nfZqN#Wr$gf(=?z(?*pDe}HcmVz^dAa~L>O@;<^)1kEH(?U`EcK?{ z!_0j+toS zR&EPX0PaEqJQ;$1@vE`0uLSZS-X^`k`l;*~ zz$z%9#)2x&Uv0hw`tI`UbIte4F%6cK0#Nk_T)%NG)NEAIzJAc-n?1Ku2O)m6r_zSf z*)nuSjeTE@1yF2}CdrLbU~kJ89IX|Z^pYI(cx+|gR7?0x#+B(H+Y*XDJ6ID*8LUlBmNWZLJ$(E8*1{(` zX9e(*GfH+eBu-UeS>i;$k*!ZFhWsVWn#us{K1+qg4%h`X6xME0Qv0GZB?Y#;e;Xj> z3IVe`b~6;sS+WWmUrDtEX{+a7mmP3xq zdDZu8K=8CMj5p`9DsbCKJU&4bt6r#KTo~Kp#UjIlJc{Xy=D~Jd9S835x8@QTQ1Xq7 z!}~rD*C#SkxM%nflXKkwv(Te>=c+XiVI5r9SkGU4a%>Y|o0d9A1?$W{kP>L1gEsXA z5W{M?@jDj?9{1Qi@u^$b3;xCxs$!Z#5rH#c0v`$ z?PRF7k$XfOMJe?@i9MZ#>zG@J41n`;5uI_ER7S z!JgpIF{ubIJVc}@C$k34_I0UGsSm)0oQb)yXN%lT$;U=SI{OxldQraXms8zDo*mHeSQR-MHrwDSH%Ljq_w;39M7|u32Kf zkpX=S zr0mDk=0Ilx-}lc7NW$F7SYTMk-Kj_@hIeyVs8A32W!5sQq^mg{KynmB-XDm@X|yCb zWtg6iCN7+twx%mllZ~z)>tfKOg=D0(NB#T&enJ4RP1ts~u4wc6LAYg04s}O1+mfUYL}YNJsmMW=X}^kIKpVghhp>@km zS*zkYd3`FT=9qyTfCr#4>ai!ccISSS)T`#6Jv&>AU8I~xDgl%t!HPA@J^7unM{qck^X)Oz!k09cIBTT+K>k!Iez=A+w%)BGm=ZPax~ zl9%e#k~KtezzJKWD#=!)h%4DIZ;rKfQ%*lS5O_Fnx+lj+}5V zQJdO-!bM*8+oT~|M1s0$I%??)q$OfP;CC7Bmp?7B=h`%Se<*DQ|50iC`!v`8%PIa< zTu+GNkp*M;*!e>u#R~09nY9J6OjbdNE+{VoiX7@ck)&TcJ)pCU8egkm_X#105gJ91 z9|Ty&w(I_U@O1urAH54I0PlqZ0qS?319IDyB-CK<@EWhClBRCTX)p&DHl}N!&alI% zxq9iLnc6Eea!YrnCmU>x-E#tCg4&bAYhvP#&aPGk9-AU~rURY@AN=UiV@XM}-A(oO zz(Ak9BB}nQeckra43d-~?x~5DmKqJ3UPcl@YWeb9x4Ql05I8l*r{~?}3X?5#FDn*k&&vO_inOZ*OC5ip;k5sVh6Vmszx?M{HL$WZ zbu@NxF*h`(6OfaZv~_l{(f{WVY`*e8m&#}_InxqgAa%-DQ%wn~(vuAkL5FcCK|qtr zAn6vYX2UICFt=*iHk@nTT=BQ1WB;FF}=*%srM zCAXul?(iUWTt`+I1Zgzuu~oGy`I;?8xNoEU1JrN>XIc^6Ud1^6`gufP)zjwlB2D9` zyoAkRH%+>`*>gTHDc2q0SQ)CyOHS^oo-aN3%BA`KL+hwH(+Bz_f{Wx9G;1xDDSKZe z<(f)usVcvd!!_xeBrP2(M7-2guY}I0gmXJn^dL$|*cJnM_FqnZ1I>oy-5B<9W2ae3 zD_rWaOiZXDU!z-j5mRhf5R9j2GDYfu_+cS49uh*PL|8O(;MGtDH54_wr&i%^6Q#s* zb%qiV>TZ4XD`?l8EJeYwS#Yg~)CLb;wKF@K26BN!SD(>Y2NE(7%IFT`e3czbhLINX zdHZ7GYAKFAYKII$G`Nop6hajf#kqcpNHfxCa}$rJ50-rAw0MHsZCn3k+z77PE8_1w zLCSMSaUvRVFNUlkJ$2)|#st`ht<{ZQZ#DkuTX-7DhFa^YiUqj$-eC+`Q2yEAs5_5S zknP*^%=D>1E^tBrMr$Qm6n0QAb9hKEaH0EiTabcA?fNGzfk>AW(gCF#FUg4{0y&0T z?=k5p4tt<^nhD1HP+OS94f8EN-1A~KlwrR7m~ls!S~BM}RzYKz(oKd!?`Wt>Hg2Yc z`6*5X`Lp`kS85?$NVZ~4Fr~nhy9-txS39=xVm(rvC%;R2*KpgqJqU+@!KxB2lbz*e zsg+f=$2sc$Z2YCXVcUmkBL*!u(zJG2wZ5*}b3HYcX;r}scgZKZ> zjTij5{igqwQT#yy|FJ_!yTzGO9v1wSUqm@8s3ME(bJaE2bv_{=7H8kQ1%D*zT(-7V ziBW&hr2oy3lCD!73)uk>!Oij<#dqY^M=uaHxBsh~5;Nu|q15haM82yrWM09T&IDKf*@#P`i>unV`f z|3%wdcE!0aYokdB-U;p&+}%CF-5mln?(Ul4?(VL^-KB92?he6S8Vj7xTyyQSKfGt| zcdW4w|DYN5)O}T5QnuU;R53o5r&eN)#_v$dY&HkhGTi-p91@A(1xcdUPdv0DQ6_xs z55nZgN@)?nI1cnE)=I7(B=$)_u$SmbmaRgXm-b2M=ljZ>UWdr_dO0|O)=!CF!zrt& zZ*Ps`wW*G!{?xj|CB3tX3dJ;C64RF*iJLl&`K_y0+L;UYg5^C>;~>)jFM<4>mc3J} zh&lc1H{f1gdSA5gx?j0s|(TGTNi?Gndv8n*T{@r{w*R>{`p&r?4S+w$or1?|F{8!P~(j zaHq}Y9w=O%-`@9uI4AXkgYF&!>~a}(4iK?Ww-h~!?4J@YWX9a~IseRu+4;22A&{E) z`^TCW`cG?K`ClFX?@>|R-3xt=;8m`6m=1(IZI>wh=@YzWub)1$gFc)%DP9ip7a70I z6FCm3^h{?Hho#XZLbVkORjb_KWe+tiZl|}UnA(5-qSwLRdavN?!{QCF%c4y0c5t8X zFK}m<`?+fu*yp_PmH!(58^(Q0kTq8UkVgP{0G8bgjtDb*rT2b_jKy}r4pyLS%kaMW zewF%V4P$`veTQ~GKki815z8vAKyYC76Pp)mwvxTbxBTsEbK*nR_)ASMIjUB@2toyB zszoFO$!zsY=nv|fG56OoVZrQly~G&NW*T${`M8znH_RVY{RAq3sX2PH$S61P9~%4! zXOJ;&q;b{YAB{hhLwhTM=KKg3s?~m?;m*T*C<%Au6tl4*JSGHv@Dr%W)%^J*2=j}) z|Fau?Z3vYncFH89n1KPuNMDC0u^DV7SE5e8&Y{XmA<#WwavoVJb4`}5lnMpIw zxHE_^L58wI-$g&pkYYTyK+uLP1@-D@M#E`4< zP()ehC{=MQEZvd3)xM8ji#>nA4vbArJGFYg1R?QlkWwb9>kq|nXAFy#?lu`%f!rpm z`MuOhD{|wVZ=hA@xRaH|cyKVH3gd6F90TaAuN79u+%V}?F6p4s*gELzRop0B5O^5m zH3s*%=>yJLJY@!D_I2n#eZ|Ifky$u%-Da7^8rMfQI}ii02l5rwsrO6^#UapVZjUGm z?4=#!MD0TrCIUHT;GyF^eAvOrWH$h?I`CpJ38S~L_gyuJiV|)n z&bn%_&378vIBgh?;gZSW)UOe!>GoZ(ZQ@jNRyGNnvEA zRA43~y+)iKR7C`ymQC6M3<7G*(+4};t`&k3&#!*0WXN45s8_qJvNRU_xc_2G!n|>m z0nXm?OARJHZ5^o9y(n*;Totmn{U>`<>6VY#I^5sb^7Si8kwm2mEAml((0t7OlL8% z3yz1T9MdIo>|k+YRyBWz@}4^SF6#GG<&oLGzU)4NPZAy4b9SuY9ffN3QRLSlXHZbVGs{ zcukPfBRR@7|2NrfeO$D>6-uAA@#S_r|WTR_tGhFTkc;$S-i z8IrzW12I|Phj7D?BL?2GRfMQigi>ju6X4}fC(@YyD%w>UQRLh<6rs?66nIW1k(NXf zqY0~nW1paHCDJNz)5RpOkq|x9n3pVxrR!a>tV?lv?`R>cX?83eyHpT=EJf1KD&>H9 zOIL*7|HF}L#T8e%r9h{8q|Qy6&VkSN&Z9H#`-<82X~Zl+%7FMS{%R<6GorLTC($nW zqSS*C*A6?~jwl{tM<5LB53aFm2rc5w!RL2k>cat&O^uq(WVS!SdV^JYtS@a*3OTml zJoN9@Lf=5yCuWb?$zoz?%J2$MG$X-W;oKF{=IvZBZ1gRWTsV?4<`vv)?7GVc(!io8 zqj4LO>9H2ZBF!9#Q@t?D^z8hopnUg`p6;s8z|yRVJ2+8WUBf!4)*BBUk@+6!_sb*0 z1mlQTJ1ZPV+rUq2(? z0!CQy;+*MK8tjd07I|X#exM}9;d-FwZr5OTed?zhlO2B&n3K=K{@|!0GS##zDkws? zt&xoQAUTFOYhp})tT}4%@`#KvX`4=fTOp|bh1~GH0KAY}>U_J|9ttr9!-il_ht2FPZLL_*IB>uPWgu!0*4bn(iyn(GwPe;ur z9VRBQsxLf*7RrtJGVV=hoSXR@brf~j3zqG)H&MupNSj*Q7;o{(b#c1OL#hxq=lU$) z-CJz8zR{KTl6_M}s!P;;+xOw^s>CXKH(p59hfS01$hWIP?bN~YLu(Xq4dKCT7l>z% zdbK(}fr z@r@Pbr>=sBTRmQ1{~B%mzk{U%A+(qZ`acmp|0&RSbhG`pyh_#DaYY2#XTpUr! zW3iz*=+28EhikEqj8?R!{{0Klozu8Pfb`2OBZl=xnA=ON|B>+0~+jDD^yX`!C zT5Z4ya@0HEZB+xyN;|rzo?A@K(+Tfqn5VzI6ZSH2@F%X>MduclpQwMZ=+N3cYt^lm zh#%t)vavp>q@Isu*;1E@22$)d8_QN5iUnE*DOd0`7AJBs&O0saRGP$=PO|%{7VSlu z+gURQNW6ieX>H2YHn}uebG1p$+B&fVOMWvA|CWG1>Ofd^nRn;_D%LNSvCYi;B`glu zK?AxZ*(5i#Lv+aQjjE(x!wlezR?GKG^0-gmKzi%B{K|rQ5$>qUU3=@yQr<{*TIRO#>=9e>Gu3mU3yh>bgV|gq_Q4O7NOse;5OaiiOLuIAQ@|3 zLSe@@7Bqz0%0WH(q4e|8=wJFx?{xyB-U&-JJwKdIUCx5iu2cN5lmjVC58l6PK!ED& zn~QgW1FcHRjE=uF-#c7iSoPOyHUorT?e!E)Rlw<+S6fONWd}A{f@QFQtPgQ_W|ym0 zWg^4q~X?{Ukxfk6~n>m=7~nscHitB{NGA2hgibn6s@5q zsN+!7{B8VE=SLd(z)(aA=y+jOQBY$58>Y=3Dd=iMqmV?|s;qu(134K3|SKk_GV>A`a z`+db#XlAG)r6aKBlZxPgIc9z_5?YE##)*-PA_@FE#5e%f!8o!ZF>+=Kx*`m!Z=NCR zi=Rj>EA{D@i7MUuLU_?!6e^`}4sU=oY!*V;W*yp&~ z$FPIfG<`2&)hl|)5O&vD-0s_r>H~D%X6Z|(e(MR1C1PtHHzJ*Bx=^X_Jh4?fo+&)%;**@kW-00C46SA zDU6Af*qCW7$Za4W@c`KN9p$1`6$bDMUjmO@yonbXX^@t3Ab&7YvnLt8`t1`H35n20VL+Xpw_X3Iqh>ppI*6a`V#VG~W%zCQArznLJGSO&5AjHeU+i$BKO_har~C3Loev8M%)f6@g?e z5!dxe1JFkML#XVSN*5sX3|2uo;suJB7C;MEI9pLH#C@#$Ih7>R3lXZQlIdtJ(%}p- z;&DVT%KZ_6FW)SDTdP-xNxsO*br%b;15W(#hig z$+|$)&CbEt@lVQ#sEw()&EG*bg>ioc*#vDwo;C+0B?;Zr>23eK%GI(Hu0SR)C@v9_ zO%(QAw&Ivji{jL>6?%}z-wX(NFZhipFvXd!T#95CL3%wC=lp5xB*ORg;S#Z%gK%zh zM>ph&m_vw9zvQe;WvL0UK416If8!_raP>0eCu1YEP2*qM2*uteVe%c?oLy*=~S8)+JTu~+g{ZczlkatVCV2fIw zFJW6xjpmw1Eu1$by|Nt7)Q1a{tB|1=u5hQ=9a2iw3<(BU*3>^aF)u{$jo3NV)Kg*| z$NCeEbh5EvR8lv%=pJ-BoK{Y{Sg-79ihZL29gkk)+`V|(0osyH+9})cSIT`yzxEJ0 z*5N~Eb?0|2^1n@K!o6&GvgsJ@5%E$i=w{m)7NRPUY5z)T? zl_lTHLr#-Aap70u`j4xAp*@=UA;%HdU3bJpTZg@!!v&$kAIBPuW}HK1n-og|qDMFD zy{CRFRL$SPXo*#<2mX!+IU=y+JR09R<5|yM{_;o!>nz_`Bb!Zn&a}qQ6iq88w;bkh9IvsFo;97rEq0XipPKd^cl5H?zP{u&+%e3r}Cm zSd)xAev30eH*hQLkfYH~K_dj7k!CI0yqZ0q|*qtBFD zX0!HtA$sMeKf2sbS(GspEsR>?aT*J>@x0TIVy3c>wk7s6-OlVjW}9LN@K%S-r8}6y z4r?yfEyKu@TU*t9E^i)Zy^y&HR~!F;K;ruL@MPmNmO%rvXzx1fgFeNLJ6 zUuw!JNedS4Hv02{B@Hb^a2rl=G=ZAV?J)FUYv~lx!N$(p1?*Z`!V~+!%C!TD^CSX| z_0Eefq)~SX=UM*2%gz-P_7$w80v*Z|{p2{cJT3s1;~u3~cKSzGJQLB!#uW|TBgGnk z-h17_CLtDaIi`c8eW@*}pxM((^5tm?TpkP<>MTgDGlaH<^P8Zg!c5@v5XDo+}Oo5^eKbDOwD$f!erD zKkYTi;EtDi1Uu!lh_YkTzD>E1H2j0Qj=DL=baU`i!MxSlwYH9p-C+ZvUTfUIM;ZWH z`&!XXu3ypeh^ux~UdL$lRT}?z^~=Z*eHs$a(jD#qDYSqR*)uCNX z$x)!bUs5+vQqko0DKDgUn2Gmfx4Mbt=;CvdE|~;RuQe4Pzg8G8s#DpQuCQU9QZ_cB zxB?_z&n*q^e6dd%#2r`wN(W(5qH8Q5(*6P1@!e7U8|4nUyAN8aY%F`3f^R&<;<4As z(gB&KOrjhF1l`(h>0)B*hXQW7NKxipghHKQR2GiUosQ@@f)Qg#qMhM&^-4q5xI1i7 z`>ZvD+ciYFmR+BDQ6WhsjvPEI(fKC(OlW7(%8{%AT=I^Qd{_s(;o*vyE-*ZR<->`6 z&z~a?g1|ms@ojT~_=1A;nVRg!D5C9QFR}9;jrm$!4A!{0BHsbHT(P8vXD78|VpR#g z!2^G&0#-^7F7Cun@i?S2AUEPt#RS;PeWRkdRAty970BxH#i_gKTfEU|-;sdQP26C$ zbTXLx^1*MBpA1tk&O{Fgt!D_ekrP70b#$;PS<)@nXPEQZHRp3vRW}*2Lkel(RkOc) znmM|0s2Z!&amI~Cj%FOmz@@-%=Hu6?F#v7H&0jg_cwq-2oe(!n(?1^V{ErApaR*~} zS)kK@U6CwQ`dg;QKu1E4QITEII3FD*%lT;ZKHaRr`%8Y|R?i~~9f-wbiMS4G`41my zW{~$M(XPD?dHEZ&^{SES8NcTwx9izS)y9VJyT!iDcMcHcL*%asz-JciO`5M#zo0+L;VA`ldL)gK>kQ7Sm69RhxYmN ztp|KOh`@Gm_k68gKsWW)FXCwwG*0CY4NHO)VWo8qTUY!(Uo|&;HU*jy{RF+XMSlG%6yG9zky)ktz?NM>u z>l+L=ktM?s%ThIC6~X!1DR&)-+i;Bns(1&+ZB%=J+8f>ehMIj2V>Qm>=WkE$ zWm}x3!y_g72634wMuQZCg~>{hdxC#(y1_F64gmxQh5X~A#rL1!AoD+5;oonT%D>{| zE!*sw%&DojK3Y@%{3%2i^EpQ?#4=k*m0gwWU}j*6EF9mq-bLtvl5n#W6;9w=w9pt& zlIk)-VZ-iPs$sUH$w2z^%k^)pZ@6WII%91bR0cI6+Jtm;EzPIfABBNm#aP@Y^wrPV zaWWRl=&>i}jLA5He<%IqZO1@Wsx1E&EL&MP%=%N(gT=&6OTG2@`Yn=2FOo`ERo>z` z+!Uc50pe+}h-|V%G`Q5BR%J1T-2A z0=>@2(rTNh_-=(n`bmTYV?wUuDaBXu*_-gx8MY~wMS((%;Gly{Dv3~Vn-tN(i6I?wds zH}8D*TPAOc`ly)wsR^fgw|94`5dblB0=6Dr?0ohG*>2Tc^J1U@AM|ALrp(LD@Z1ZhRx&@@r(0<&b6Sf{_JFMk3sF}%?07vhhrS?WQ!4(P0Suph22yH3B2&q!ef zUbFeVW3#AQP$z?paua3|Ra&8|>tRdqlesMPiXA#kCuy6dcRsmb3=!Y=uWRGDlfy{n zwvJoM@?p}v)uQnmhw+LB!&Em$=t?*`Kj^0K%>s#YJd4Eh8*6JcRQM_MF~J2S|Gp);H7gR*B_$&_anv2ELNh z?32z-saBQ?oQFo;1fs<)_yO*Q>v4{meyHJP->0lFfIVDTZwePyGQ{fDCa}KaKFH0E z&BDWsD(X_T#}L1s4WWP(l<+ViW)}~V)gqT0IFf`dJB}655=DMLSVaB4*YSO#628bz z>IH;mE%aiwaMwOsU?-CAs63|Ie0K$qpRAPP5r0;21~IZ^9-ATa#tLFiK#0~UyUL~Y=lmhm6bB}xR?dYK9FVHOH%Wf z$}h1#ozO1X6dtYKIgwLbQJ6|)D^v&ntn}u@0~*|zQrgKEKl_*mNoi_jmgrb(!W2ag zEYr5=uEK#wMuf%OL!Xc_%Nh|2UCD9UQ1{d<h<4$C1MqbV4D5ek0AVG zasEfmSk~4BXkcZm;%@iv)yV$~Qk9lTv;?)SD~K@CZEl7&g!1Md{lug}{

&FxYh` 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

7y^L($e^^2o$McL!io1u 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 u-K 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 - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/plugin/parse-rss/sample/rsstest.rss b/src/plugin/parse-rss/sample/rsstest.rss deleted file mode 100644 index 07d7ec0254..0000000000 --- a/src/plugin/parse-rss/sample/rsstest.rss +++ /dev/null @@ -1,21 +0,0 @@ - - - - TestChannel - http://test.channel.com/ - Sample RSS File for Junit test - en-us - - - Home Page of Chris Mattmann - http://www-scf.usc.edu/~mattmann/ - Chris Mattmann's home page - - - - Awesome Open Source Search Engine - http://www.nutch.org/ - Yup, that's what it is - - - diff --git a/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/FeedParserListenerImpl.java b/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/FeedParserListenerImpl.java deleted file mode 100644 index 67d1c6b4b4..0000000000 --- a/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/FeedParserListenerImpl.java +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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.apache.nutch.parse.rss; - -import org.apache.commons.feedparser.DefaultFeedParserListener; -import org.apache.commons.feedparser.FeedParserState; -import org.apache.commons.feedparser.FeedParserException; - -import java.util.List; -import java.util.Vector; - -import org.apache.nutch.parse.rss.structs.RSSChannel; -import org.apache.nutch.parse.rss.structs.RSSItem; - -/** - * - * @author mattmann - * @version 1.0 - * - *

- * Feed parser listener class which builds up an RSS Channel model that can be - * iterated through to retrieve the parsed information. - *

- */ -public class FeedParserListenerImpl extends DefaultFeedParserListener { - - private List fRssChannels = null; - - private RSSChannel fCurrentChannel = null; - - /** - *

- * Default Constructor - *

- */ - public FeedParserListenerImpl() { - fRssChannels = new Vector(); - } - - /** - *

- * Gets a {@link List}of {@link RSSChannel}s that the listener parsed from - * the RSS document. - *

- * - * @return A {@link List}of {@link RSSChannel}s. - */ - public List getChannels() { - if (fRssChannels.size() > 0) { - return fRssChannels; - } else { - //there was only one channel found - //add it here, then return it - fRssChannels.add(fCurrentChannel); - return fRssChannels; - } - } - - /** - *

- * Callback method when the parser encounters an RSS Channel. - *

- * - * @param state - * The current state of the FeedParser. - * @param title - * The title of the RSS Channel. - * @param link - * A hyperlink to the RSS Channel. - * @param description - * The description of the RSS Channel. - */ - public void onChannel(FeedParserState state, String title, String link, - String description) throws FeedParserException { - - //capture the old channel if it's not null - if (fCurrentChannel != null) { - fRssChannels.add(fCurrentChannel); - } - - //System.out.println("Found a new channel: " + title); - - fCurrentChannel = new RSSChannel(title, link, description); - - } - - /** - *

- * Callback method when the parser encounters an RSS Item. - *

- * - * @param state - * The current state of the FeedParser. - * @param title - * The title of the RSS Item. - * @param link - * A hyperlink to the RSS Item. - * @param description - * The description of the RSS Item. - * @param permalink - * A permanent link to the RSS Item. - */ - public void onItem(FeedParserState state, String title, String link, - String description, String permalink) throws FeedParserException { - - //System.out.println("Found a new published article: " + permalink); - if (fCurrentChannel != null) { //should never be null - fCurrentChannel.getItems().add( - new RSSItem(title, link, description, permalink)); - } - - } - -} diff --git a/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/RSSParser.java b/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/RSSParser.java deleted file mode 100644 index 39d35f7c62..0000000000 --- a/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/RSSParser.java +++ /dev/null @@ -1,227 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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.apache.nutch.parse.rss; - -// JDK imports -import java.io.ByteArrayInputStream; -import java.net.MalformedURLException; -import java.util.List; -import java.util.Vector; - -// Commons Logging imports -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -// Hadoop imports -import org.apache.hadoop.io.Text; -import org.apache.hadoop.conf.Configuration; - -// Nutch imports -import org.apache.nutch.crawl.CrawlDatum; -import org.apache.nutch.parse.ParseResult; -import org.apache.nutch.parse.Parser; -import org.apache.nutch.parse.Parse; -import org.apache.nutch.parse.ParseStatus; -import org.apache.nutch.parse.ParseData; -import org.apache.nutch.parse.ParseImpl; -import org.apache.nutch.parse.Outlink; -import org.apache.nutch.parse.rss.structs.RSSItem; -import org.apache.nutch.parse.rss.structs.RSSChannel; -import org.apache.nutch.protocol.Content; -import org.apache.nutch.protocol.Protocol; -import org.apache.nutch.protocol.ProtocolFactory; -import org.apache.nutch.util.LogUtil; -import org.apache.nutch.util.NutchConfiguration; - -// RSS parsing imports -import org.apache.commons.feedparser.FeedParserListener; -import org.apache.commons.feedparser.FeedParser; -import org.apache.commons.feedparser.FeedParserFactory; - - -/** - * - * @author mattmann - * @version 1.0 - * - *

- * RSS Parser class for nutch - *

- */ -public class RSSParser implements Parser { - public static final Log LOG = LogFactory.getLog("org.apache.nutch.parse.rss"); - private Configuration conf; - - /** - *

- * Implementation method, parses the RSS content, and then returns a - * {@link ParseImpl}. - *

- * - * @param content - * The content to parse (hopefully an RSS content stream) - * @return A {@link ParseImpl}which implements the {@link Parse}interface. - */ - public ParseResult getParse(Content content) { - - List theRSSChannels = null; - - try { - byte[] raw = content.getContent(); - - // create a new FeedParser... - FeedParser parser = FeedParserFactory.newFeedParser(); - - // create a listener for handling our callbacks - FeedParserListener listener = new FeedParserListenerImpl(); - - // start parsing our feed and have the onItem methods called - parser.parse(listener, new ByteArrayInputStream(raw), /* resource */ - null); - - theRSSChannels = ((FeedParserListenerImpl) listener).getChannels(); - - } catch (Exception e) { // run time exception - if (LOG.isWarnEnabled()) { - e.printStackTrace(LogUtil.getWarnStream(LOG)); - LOG.warn("nutch:parse-rss:RSSParser Exception: " + e.getMessage()); - } - return new ParseStatus(ParseStatus.FAILED, - "Can't be handled as rss document. " + e).getEmptyParseResult(content.getUrl(), getConf()); - } - - StringBuffer contentTitle = new StringBuffer(), indexText = new StringBuffer(); - List theOutlinks = new Vector(); - - // for us, the contentTitle will be a concatenation of the titles of the - // RSS Channels that we've parsed - // and the index text will be a concatenation of the RSS Channel - // descriptions, and descriptions of the RSS Items in the channel - - // also get the outlinks - - if (theRSSChannels != null) { - for (int i = 0; i < theRSSChannels.size(); i++) { - RSSChannel r = (RSSChannel) theRSSChannels.get(i); - contentTitle.append(r.getTitle()); - contentTitle.append(" "); - - // concat the description to the index text - indexText.append(r.getDescription()); - indexText.append(" "); - - if (r.getLink() != null) { - try { - // get the outlink - if (r.getDescription()!= null ) { - theOutlinks.add(new Outlink(r.getLink(), r.getDescription())); - } else { - theOutlinks.add(new Outlink(r.getLink(), "")); - } - } catch (MalformedURLException e) { - if (LOG.isWarnEnabled()) { - LOG.warn("MalformedURL: " + r.getLink()); - LOG.warn("Attempting to continue processing outlinks"); - e.printStackTrace(LogUtil.getWarnStream(LOG)); - } - continue; - } - } - - // now get the descriptions of all the underlying RSS Items and - // then index them too - for (int j = 0; j < r.getItems().size(); j++) { - RSSItem theRSSItem = (RSSItem) r.getItems().get(j); - indexText.append(theRSSItem.getDescription()); - indexText.append(" "); - - String whichLink = null; - - if (theRSSItem.getPermalink() != null) - whichLink = theRSSItem.getPermalink(); - else - whichLink = theRSSItem.getLink(); - - if (whichLink != null) { - try { - if (theRSSItem.getDescription()!=null) { - theOutlinks.add(new Outlink(whichLink, theRSSItem.getDescription())); - } else { - theOutlinks.add(new Outlink(whichLink, "")); - } - } catch (MalformedURLException e) { - if (LOG.isWarnEnabled()) { - LOG.warn("MalformedURL: " + whichLink); - LOG.warn("Attempting to continue processing outlinks"); - e.printStackTrace(LogUtil.getWarnStream(LOG)); - } - continue; - } - } - - } - - } - - if (LOG.isTraceEnabled()) { - LOG.trace("nutch:parse-rss:getParse:indexText=" + indexText); - LOG.trace("nutch:parse-rss:getParse:contentTitle=" + contentTitle); - } - - } else if (LOG.isTraceEnabled()) { - LOG.trace("nutch:parse-rss:Error:getParse: No RSS Channels recorded!"); - } - - // format the outlinks - Outlink[] outlinks = (Outlink[]) theOutlinks.toArray(new Outlink[theOutlinks.size()]); - - if (LOG.isTraceEnabled()) { - LOG.trace("nutch:parse-rss:getParse:found " + outlinks.length + " outlinks"); - } - // if (LOG.isInfoEnabled()) { - // LOG.info("Outlinks: "+outlinks); - // } - - ParseData parseData = new ParseData(ParseStatus.STATUS_SUCCESS, - contentTitle.toString(), outlinks, content.getMetadata()); - return ParseResult.createParseResult(content.getUrl(), new ParseImpl(indexText.toString(), parseData)); - } - - public void setConf(Configuration conf) { - this.conf = conf; - } - - public Configuration getConf() { - return this.conf; - } - - public static void main(String[] args) throws Exception { - //LOG.setLevel(Level.FINE); - String url = args[0]; - Configuration conf = NutchConfiguration.create(); - RSSParser parser = new RSSParser(); - parser.setConf(conf); - Protocol protocol = new ProtocolFactory(conf).getProtocol(url); - Content content = protocol.getProtocolOutput(new Text(url), new CrawlDatum()).getContent(); - Parse parse = parser.getParse(content).get(content.getUrl()); - System.out.println("data: "+ parse.getData()); - System.out.println("text: "+parse.getText()); - } - - -} diff --git a/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/structs/RSSChannel.java b/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/structs/RSSChannel.java deleted file mode 100644 index b496197939..0000000000 --- a/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/structs/RSSChannel.java +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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.apache.nutch.parse.rss.structs; - -import java.util.List; -import java.util.Vector; - -/** - * - *

- * Data class for holding RSS Channels to send to Nutch's indexer - *

- * - * @author mattmann - * @version 1.0 - */ -public class RSSChannel { - - //description of the channel - private String fDescription = null; - - // link to the channel's page - private String fLink = null; - - // title of the Channel - private String fTitle = null; - - // set of items in the Channel - private List fItems = null; - - /** - * - *

- * Default Constructor - *

- * - * @param desc - * The description of the channel. - * @param link - * A link to the channel's url. - * @param title - * The title of the channel. - * @param items - * A list of {@link RSSItem}s for this RSS Channel. - */ - public RSSChannel(String desc, String link, String title, List items) { - fDescription = desc; - fLink = link; - fTitle = title; - fItems = items; - - } - - /** - * - *

- * Constructor if you don't have the list of RSS Items ready yet. - *

- * - * @param desc - * The description of the channel. - * @param link - * A link to the channel's url. - * @param title - * The title of the channel. - */ - public RSSChannel(String desc, String link, String title) { - fDescription = desc; - fLink = link; - fTitle = title; - fItems = new Vector(); - - } - - /** - * - *

- * Get the list of items for this channel. - *

- * - * @return A list of {@link RSSItem}s. - */ - public List getItems() { - return fItems; - } - - /** - * - *

- * Returns the channel title - *

- * - * @return The title of the channel. - */ - - public String getTitle() { - return fTitle; - } - - /** - * - *

- * Returns a link to the RSS Channel. - *

- * - * @return A {@link String}link to the RSS Channel. - */ - public String getLink() { - return fLink; - } - - /** - * - *

- * Returns a {@link String}description of the RSS Channel. - *

- * - * @return The description of the RSS Channel. - */ - public String getDescription() { - return fDescription; - } - - /** - * - *

- * Sets the list of RSS items for this channel. - *

- * - * @param items - * A List of {@link RSSItem}s for this RSSChannel. - */ - public void setItems(List items) { - fItems = items; - } - - /** - * - *

- * Sets the Title for this RSS Channel. - *

- * - * @param title - * The title of this RSSChannel. - */ - public void setTitle(String title) { - fTitle = title; - } - - /** - * - *

- * Sets the link to this RSSChannel - *

- * - * @param link - * A {@link String}representation of a link to this RSS Channel. - */ - public void setLink(String link) { - fLink = link; - } - - /** - * - *

- * Sets the description of this RSSChannel - *

- * - * @param description - * A String description of this RSS Channel. - */ - public void setDescription(String description) { - fDescription = description; - } -} diff --git a/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/structs/RSSItem.java b/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/structs/RSSItem.java deleted file mode 100644 index 608b4367f0..0000000000 --- a/src/plugin/parse-rss/src/java/org/apache/nutch/parse/rss/structs/RSSItem.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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.apache.nutch.parse.rss.structs; - -/** - * - *

- * Data class for holding RSS Items to send to Nutch's indexer - *

- * - * @author mattmann - * @version 1.0 - */ -public class RSSItem { - - //The title of this RSS Item - private String fTitle = null; - - //The link that this RSS Item points to - private String fLink = null; - - //The description of this RSS Item - private String fDescription = null; - - //A permanent link that this RSS Item points to - private String fPermalink = null; - - public RSSItem(String title, String link, String description, - String permalink) { - fTitle = title; - fLink = link; - fDescription = description; - fPermalink = permalink; - } - - /** - * - *

- * Get the title for this RSS Item - *

- * - * @return The title of this RSS Item - */ - public String getTitle() { - return fTitle; - } - - /** - * - *

- * Gets the link that this RSS Item points to. - *

- * - * @return The link that this RSS Items points to. - */ - public String getLink() { - return fLink; - } - - /** - * - *

- * Gets the Description of this RSS Item - *

- * - * @return The description of this RSS Item. - */ - public String getDescription() { - return fDescription; - } - - /** - * - *

- * If this RSS Item points to a permanent link, then this method returns it. - *

- * - * @return The permanent link that this RSS Items points to. - */ - public String getPermalink() { - return fPermalink; - } - - /** - * - *

- * Sets the title for this RSS Item. - *

- * - * @param title - * The title of this RSS Item - */ - public void setTitle(String title) { - fTitle = title; - } - - /** - * - *

- * Sets the link that this RSS Item points to. - *

- * - * @param link - * The link that this RSS Item points to. - */ - public void setLink(String link) { - fTitle = link; - } - - /** - * - *

- * Sets the description of this RSS Item. - *

- * - * @param description - * The description of this RSS Item. - */ - public void setDescription(String description) { - fDescription = description; - } - - /** - * - *

- * Sets the permanent link that this RSS Item points to. - *

- * - * @param permalink - * The permanent link that this RSS Item points to - */ - public void setPermalink(String permalink) { - fPermalink = permalink; - } - -} diff --git a/src/plugin/parse-rss/src/test/org/apache/nutch/parse/rss/TestRSSParser.java b/src/plugin/parse-rss/src/test/org/apache/nutch/parse/rss/TestRSSParser.java deleted file mode 100644 index d94198b185..0000000000 --- a/src/plugin/parse-rss/src/test/org/apache/nutch/parse/rss/TestRSSParser.java +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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.apache.nutch.parse.rss; - -import org.apache.nutch.protocol.ProtocolFactory; -import org.apache.nutch.protocol.Protocol; -import org.apache.nutch.protocol.Content; -import org.apache.nutch.protocol.ProtocolException; - -import org.apache.nutch.parse.Parse; -import org.apache.nutch.parse.ParseUtil; -import org.apache.nutch.parse.ParseException; -import org.apache.nutch.parse.ParseData; -import org.apache.nutch.parse.Outlink; -import org.apache.hadoop.conf.Configuration; -import org.apache.nutch.util.NutchConfiguration; - -import org.apache.hadoop.io.Text; -import org.apache.nutch.crawl.CrawlDatum; - -import junit.framework.TestCase; - -/** - * Unit tests for the RSS Parser based on John Xing's TestPdfParser class. - * - * @author mattmann - * @version 1.0 - */ -public class TestRSSParser extends TestCase { - - private String fileSeparator = System.getProperty("file.separator"); - - // This system property is defined in ./src/plugin/build-plugin.xml - private String sampleDir = System.getProperty("test.data", "."); - - // Make sure sample files are copied to "test.data" as specified in - // ./src/plugin/parse-rss/build.xml during plugin compilation. - - private String[] sampleFiles = { "rsstest.rss" }; - - /** - *

- * Default constructor - *

- * - * @param name - * The name of the RSSParserTest - */ - public TestRSSParser(String name) { - super(name); - } - - /** - *

- * The test method: tests out the following 2 asserts: - *

- * - *
    - *
  • There are 3 outlinks read from the sample rss file
  • - *
  • The 3 outlinks read are in fact the correct outlinks from the sample - * file
  • - *
- */ - public void testIt() throws ProtocolException, ParseException { - String urlString; - Protocol protocol; - Content content; - Parse parse; - - Configuration conf = NutchConfiguration.create(); - for (int i = 0; i < sampleFiles.length; i++) { - urlString = "file:" + sampleDir + fileSeparator + sampleFiles[i]; - - protocol = new ProtocolFactory(conf).getProtocol(urlString); - content = protocol.getProtocolOutput(new Text(urlString), new CrawlDatum()).getContent(); - parse = new ParseUtil(conf).parseByExtensionId("parse-rss", content).get(content.getUrl()); - - //check that there are 3 outlinks: - //http://test.channel.com - //http://www-scf.usc.edu/~mattmann/ - //http://www.nutch.org - - ParseData theParseData = parse.getData(); - - Outlink[] theOutlinks = theParseData.getOutlinks(); - - assertTrue("There aren't 3 outlinks read!", theOutlinks.length == 3); - - //now check to make sure that those are the two outlinks - boolean hasLink1 = false, hasLink2 = false, hasLink3 = false; - - for (int j = 0; j < theOutlinks.length; j++) { - //System.out.println("reading "+theOutlinks[j].getToUrl()); - if (theOutlinks[j].getToUrl().equals( - "http://www-scf.usc.edu/~mattmann/")) { - hasLink1 = true; - } - - if (theOutlinks[j].getToUrl().equals("http://www.nutch.org/")) { - hasLink2 = true; - } - - if (theOutlinks[j].getToUrl() - .equals("http://test.channel.com/")) { - hasLink3 = true; - } - } - - if (!hasLink1 || !hasLink2 || !hasLink3) { - fail("Outlinks read from sample rss file are not correct!"); - } - } - } - -} diff --git a/src/plugin/parse-tika/build.xml b/src/plugin/parse-tika/build.xml index ad71bf4b5a..51a9a48c31 100644 --- a/src/plugin/parse-tika/build.xml +++ b/src/plugin/parse-tika/build.xml @@ -29,6 +29,7 @@ + diff --git a/src/plugin/parse-tika/sample/rsstest.rss b/src/plugin/parse-tika/sample/rsstest.rss new file mode 100644 index 0000000000..6c4ae488de --- /dev/null +++ b/src/plugin/parse-tika/sample/rsstest.rss @@ -0,0 +1,37 @@ + + + + + TestChannel + http://test.channel.com/ + Sample RSS File for Junit test + en-us + + + Home Page of Chris Mattmann + http://www-scf.usc.edu/~mattmann/ + Chris Mattmann's home page + + + + Awesome Open Source Search Engine + http://www.nutch.org/ + Yup, that's what it is + + + diff --git a/src/plugin/parse-tika/src/test/org/apache/nutch/tika/TestFeedParser.java b/src/plugin/parse-tika/src/test/org/apache/nutch/tika/TestFeedParser.java new file mode 100644 index 0000000000..0b69cbd5a5 --- /dev/null +++ b/src/plugin/parse-tika/src/test/org/apache/nutch/tika/TestFeedParser.java @@ -0,0 +1,130 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.nutch.tika; + +import junit.framework.TestCase; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.io.Text; +import org.apache.nutch.crawl.CrawlDatum; +import org.apache.nutch.parse.Outlink; +import org.apache.nutch.parse.Parse; +import org.apache.nutch.parse.ParseData; +import org.apache.nutch.parse.ParseException; +import org.apache.nutch.parse.ParseUtil; +import org.apache.nutch.parse.tika.TikaParser; +import org.apache.nutch.protocol.Content; +import org.apache.nutch.protocol.Protocol; +import org.apache.nutch.protocol.ProtocolException; +import org.apache.nutch.protocol.ProtocolFactory; +import org.apache.nutch.util.NutchConfiguration; + +/** + * + * @author mattmann / jnioche + * + * Test Suite for the RSS feeds with the {@link TikaParser}. + * + */ +public class TestFeedParser extends TestCase { + + private String fileSeparator = System.getProperty("file.separator"); + + // This system property is defined in ./src/plugin/build-plugin.xml + private String sampleDir = System.getProperty("test.data", "."); + + private String[] sampleFiles = { "rsstest.rss" }; + + public static final Log LOG = LogFactory.getLog(TestFeedParser.class + .getName()); + + /** + * Default Constructor. + * + * @param name + * The name of this {@link TestCase}. + */ + public TestFeedParser(String name) { + super(name); + } + + /** + *

+ * The test method: tests out the following 2 asserts: + *

+ * + *
    + *
  • There are 3 outlinks read from the sample rss file
  • + *
  • The 3 outlinks read are in fact the correct outlinks from the sample + * file
  • + *
+ */ + public void testIt() throws ProtocolException, ParseException { + String urlString; + Protocol protocol; + Content content; + Parse parse; + + Configuration conf = NutchConfiguration.create(); + for (int i = 0; i < sampleFiles.length; i++) { + urlString = "file:" + sampleDir + fileSeparator + sampleFiles[i]; + + protocol = new ProtocolFactory(conf).getProtocol(urlString); + content = protocol.getProtocolOutput(new Text(urlString), + new CrawlDatum()).getContent(); + parse = new ParseUtil(conf).parseByExtensionId("parse-tika", + content).get(content.getUrl()); + + // check that there are 2 outlinks: + // unlike the original parse-rss + // tika ignores the URL and description of the channel + + // http://test.channel.com + // http://www-scf.usc.edu/~mattmann/ + // http://www.nutch.org + + ParseData theParseData = parse.getData(); + + Outlink[] theOutlinks = theParseData.getOutlinks(); + + assertTrue("There aren't 2 outlinks read!", + theOutlinks.length == 2); + + // now check to make sure that those are the two outlinks + boolean hasLink1 = false, hasLink2 = false; + + for (int j = 0; j < theOutlinks.length; j++) { + if (theOutlinks[j].getToUrl().equals( + "http://www-scf.usc.edu/~mattmann/")) { + hasLink1 = true; + } + + if (theOutlinks[j].getToUrl().equals("http://www.nutch.org/")) { + hasLink2 = true; + } + } + + if (!hasLink1 || !hasLink2) { + fail("Outlinks read from sample rss file are not correct!"); + } + } + } + +} From 7156531aa1b76df05c9f720ac2706321f7e2abb9 Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Thu, 5 May 2011 13:46:16 +0000 Subject: [PATCH 051/754] NUTCH-989 Index-basic plugin and Solr schema now use date fieldType for tstamp field git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1099800 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ conf/schema.xml | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index cd2c81a1e1..7e6ee23ae1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - 4/21/2011 +* NUTCH-989 Index-basic plugin and Solr schema now use date fieldType for tstamp field (markus) + * NUTCH-888 Remove parse-rss and add tests for rss to parse-tika (jnioche) * NUTCH-991 SolrDedup must issue a commit (markus) diff --git a/conf/schema.xml b/conf/schema.xml index 27159efe4f..bdef021cdc 100644 --- a/conf/schema.xml +++ b/conf/schema.xml @@ -28,6 +28,8 @@ omitNorms="true"/> + @@ -71,7 +73,7 @@ - + Date: Thu, 5 May 2011 17:55:32 +0000 Subject: [PATCH 052/754] NUTCH-983 Upgrade SolrJ to 3.1 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1099895 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ ivy/ivy.xml | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7e6ee23ae1..e3f9536456 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - 4/21/2011 +* NUTCH-983 Upgrade SolrJ to 3.1 (markus, jnioche) + * NUTCH-989 Index-basic plugin and Solr schema now use date fieldType for tstamp field (markus) * NUTCH-888 Remove parse-rss and add tests for rss to parse-tika (jnioche) diff --git a/ivy/ivy.xml b/ivy/ivy.xml index a271ee9144..9f10da4a3c 100644 --- a/ivy/ivy.xml +++ b/ivy/ivy.xml @@ -30,8 +30,8 @@ - + + + + + + From 2d2322b633fff6105d62e4d9bf8da6b5e813c820 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Thu, 5 May 2011 18:10:33 +0000 Subject: [PATCH 053/754] Fixed TestParserFactory post Nutch-888 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1099902 13f79535-47bb-0310-9956-ffa450edef68 --- src/test/org/apache/nutch/parse/TestParserFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/org/apache/nutch/parse/TestParserFactory.java b/src/test/org/apache/nutch/parse/TestParserFactory.java index 08c96e5370..cf944e29b3 100644 --- a/src/test/org/apache/nutch/parse/TestParserFactory.java +++ b/src/test/org/apache/nutch/parse/TestParserFactory.java @@ -95,7 +95,7 @@ public void testGetParsers() throws Exception { parsers = parserFactory.getParsers("text/rss","http://foo.com"); assertNotNull(parsers); assertEquals(1,parsers.length); - assertEquals("org.apache.nutch.parse.rss.RSSParser", + assertEquals("org.apache.nutch.parse.tika.TikaParser", parsers[0].getClass().getName()); } From 764319aa844874cbb5255945025d646f7ad22a35 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Sat, 7 May 2011 07:04:29 +0000 Subject: [PATCH 054/754] Removed references to parse-rss post NUTCH-888 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1100468 13f79535-47bb-0310-9956-ffa450edef68 --- LICENSE.txt | 8 +------- build.xml | 2 -- default.properties | 1 - 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index b14cf495e2..1b7a967d22 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -4233,12 +4233,6 @@ ASF lib/log4j-1.2.15.jar ASF -src/plugin/parse-rss/lib/xmlrpc-1.2.jar -ASF - -src/plugin/parse-rss/lib/commons-feedparser-0.6-fork.jar -ASF - src/plugin/feed/lib/rome-0.9.jar Copyright 2004 Sun Microsystems, Inc. @@ -5796,4 +5790,4 @@ src/plugin/lib-xml/lib/xercesImpl.jar ASF - \ No newline at end of file + diff --git a/build.xml b/build.xml index 4d3dd0219a..4831a64af2 100644 --- a/build.xml +++ b/build.xml @@ -158,7 +158,6 @@ - @@ -534,7 +533,6 @@ - diff --git a/default.properties b/default.properties index 7d9fbf120c..f3be00938f 100644 --- a/default.properties +++ b/default.properties @@ -94,7 +94,6 @@ plugins.scoring=\ plugins.parse=\ org.apache.nutch.parse.ext*:\ org.apache.nutch.parse.js:\ - org.apache.nutch.parse.rss*:\ org.apache.nutch.parse.swf*:\ org.apache.nutch.parse.tika:\ org.apache.nutch.parse.zip From 85f295ffecf7bf6986f468b391303a6674334ccd Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Tue, 10 May 2011 00:46:04 +0000 Subject: [PATCH 055/754] NUTCH-996 Indexer adds solr.commit.size+1 docs git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1101280 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ src/java/org/apache/nutch/indexer/solr/SolrWriter.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index e3f9536456..4f09edcb12 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - 4/21/2011 +* NUTCH-996 Indexer adds solr.commit.size+1 docs (markus) + * NUTCH-983 Upgrade SolrJ to 3.1 (markus, jnioche) * NUTCH-989 Index-basic plugin and Solr schema now use date fieldType for tstamp field (markus) diff --git a/src/java/org/apache/nutch/indexer/solr/SolrWriter.java b/src/java/org/apache/nutch/indexer/solr/SolrWriter.java index 45b6165f21..8979d9ba54 100644 --- a/src/java/org/apache/nutch/indexer/solr/SolrWriter.java +++ b/src/java/org/apache/nutch/indexer/solr/SolrWriter.java @@ -59,7 +59,7 @@ public void write(NutchDocument doc) throws IOException { } inputDoc.setDocumentBoost(doc.getWeight()); inputDocs.add(inputDoc); - if (inputDocs.size() > commitSize) { + if (inputDocs.size() >= commitSize) { try { solr.add(inputDocs); } catch (final SolrServerException e) { From f5031bd832399723b17ce925250df934fd39c8e9 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Wed, 18 May 2011 11:50:02 +0000 Subject: [PATCH 056/754] NUTCH-997 IndexingFitlers to store Date objects instead of Strings git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1124202 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ conf/schema.xml | 8 ++++---- .../org/apache/nutch/indexer/solr/SolrWriter.java | 9 ++++++++- .../apache/nutch/indexer/feed/FeedIndexingFilter.java | 10 ++-------- .../nutch/indexer/basic/BasicIndexingFilter.java | 4 +--- .../apache/nutch/indexer/more/MoreIndexingFilter.java | 11 ++--------- 6 files changed, 19 insertions(+), 25 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4f09edcb12..0fa994ded0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - 4/21/2011 +* NUTCH-997 IndexingFitlers to store Date objects instead of Strings (jnioche) + * NUTCH-996 Indexer adds solr.commit.size+1 docs (markus) * NUTCH-983 Upgrade SolrJ to 3.1 (markus, jnioche) diff --git a/conf/schema.xml b/conf/schema.xml index bdef021cdc..fe373c8a95 100644 --- a/conf/schema.xml +++ b/conf/schema.xml @@ -84,9 +84,9 @@ multiValued="true"/> - - + @@ -99,9 +99,9 @@ - - id diff --git a/src/java/org/apache/nutch/indexer/solr/SolrWriter.java b/src/java/org/apache/nutch/indexer/solr/SolrWriter.java index 8979d9ba54..1fadd4d65c 100644 --- a/src/java/org/apache/nutch/indexer/solr/SolrWriter.java +++ b/src/java/org/apache/nutch/indexer/solr/SolrWriter.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Map.Entry; @@ -29,6 +30,7 @@ import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer; import org.apache.solr.common.SolrInputDocument; +import org.apache.solr.common.util.DateUtil; public class SolrWriter implements NutchIndexWriter { @@ -50,7 +52,12 @@ public void write(NutchDocument doc) throws IOException { final SolrInputDocument inputDoc = new SolrInputDocument(); for(final Entry e : doc) { for (final Object val : e.getValue().getValues()) { - inputDoc.addField(solrMapping.mapKey(e.getKey()), val, e.getValue().getWeight()); + // normalise the string representation for a Date + Object val2 = val; + if (val instanceof Date){ + val2 = DateUtil.getThreadLocalDateFormat().format(val); + } + inputDoc.addField(solrMapping.mapKey(e.getKey()), val2, e.getValue().getWeight()); String sCopy = solrMapping.mapCopyKey(e.getKey()); if (sCopy != e.getKey()) { inputDoc.addField(sCopy, val); diff --git a/src/plugin/feed/src/java/org/apache/nutch/indexer/feed/FeedIndexingFilter.java b/src/plugin/feed/src/java/org/apache/nutch/indexer/feed/FeedIndexingFilter.java index b098b36541..613562a47c 100644 --- a/src/plugin/feed/src/java/org/apache/nutch/indexer/feed/FeedIndexingFilter.java +++ b/src/plugin/feed/src/java/org/apache/nutch/indexer/feed/FeedIndexingFilter.java @@ -18,9 +18,7 @@ package org.apache.nutch.indexer.feed; //JDK imports -import java.text.SimpleDateFormat; import java.util.Date; -import java.util.TimeZone; //APACHE imports import org.apache.hadoop.conf.Configuration; @@ -96,18 +94,14 @@ public NutchDocument filter(NutchDocument doc, Parse parse, Text url, CrawlDatum if (feed != null) doc.add(Feed.FEED, feed); - SimpleDateFormat sdf = new SimpleDateFormat(dateFormatStr); - sdf.setTimeZone(TimeZone.getTimeZone("GMT")); if (published != null) { Date date = new Date(Long.parseLong(published)); - String dateString = sdf.format(date); - doc.add(PUBLISHED_DATE, dateString); + doc.add(PUBLISHED_DATE, date); } if (updated != null) { Date date = new Date(Long.parseLong(updated)); - String dateString = sdf.format(date); - doc.add(UPDATED_DATE, dateString); + doc.add(UPDATED_DATE, date); } return doc; diff --git a/src/plugin/index-basic/src/java/org/apache/nutch/indexer/basic/BasicIndexingFilter.java b/src/plugin/index-basic/src/java/org/apache/nutch/indexer/basic/BasicIndexingFilter.java index 4029e16190..914b7e5b9b 100644 --- a/src/plugin/index-basic/src/java/org/apache/nutch/indexer/basic/BasicIndexingFilter.java +++ b/src/plugin/index-basic/src/java/org/apache/nutch/indexer/basic/BasicIndexingFilter.java @@ -30,7 +30,6 @@ import org.apache.nutch.crawl.CrawlDatum; import org.apache.nutch.crawl.Inlinks; -import org.apache.solr.common.util.DateUtil; import java.net.MalformedURLException; import java.net.URL; @@ -87,8 +86,7 @@ public NutchDocument filter(NutchDocument doc, Parse parse, Text url, CrawlDatum } // add timestamp when fetched, for deduplication - String tstamp = DateUtil.getThreadLocalDateFormat().format(new Date(datum.getFetchTime())); - doc.add("tstamp", tstamp); + doc.add("tstamp", new Date(datum.getFetchTime())); return doc; } diff --git a/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java b/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java index cba7a6189a..7a7842a08f 100644 --- a/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java +++ b/src/plugin/index-more/src/java/org/apache/nutch/indexer/more/MoreIndexingFilter.java @@ -51,7 +51,6 @@ import java.text.SimpleDateFormat; import java.util.Date; -import java.util.TimeZone; import org.apache.commons.lang.time.DateUtils; @@ -101,21 +100,15 @@ private NutchDocument addTime(NutchDocument doc, ParseData data, if (lastModified != null) { // try parse last-modified time = getTime(lastModified,url); // use as time // store as string - doc.add("lastModified", Long.toString(time)); + doc.add("lastModified", new Date(time)); } if (time == -1) { // if no last-modified time = datum.getFetchTime(); // use fetch time } - // add support for query syntax date: - // query filter is implemented in DateQueryFilter.java - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); - sdf.setTimeZone(TimeZone.getTimeZone("GMT")); - String dateString = sdf.format(new Date(time)); - // un-stored, indexed and un-tokenized - doc.add("date", dateString); + doc.add("date", new Date(time)); return doc; } From 54cd07edfa6ba689e276da46e3564b3a4a5c2075 Mon Sep 17 00:00:00 2001 From: Markus Jelsma Date: Mon, 23 May 2011 10:17:14 +0000 Subject: [PATCH 057/754] NUTCH-994 Fine tune Solr schema git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1126417 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 ++ conf/schema.xml | 40 +++++++++++++++++++++++++--------------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 0fa994ded0..15c1b30779 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,8 @@ Nutch Change Log Release 1.3 - 4/21/2011 +* NUTCH-994 Fine tune Solr schema (markus) + * NUTCH-997 IndexingFitlers to store Date objects instead of Strings (jnioche) * NUTCH-996 Indexer adds solr.commit.size+1 docs (markus) diff --git a/conf/schema.xml b/conf/schema.xml index fe373c8a95..b4673598c3 100644 --- a/conf/schema.xml +++ b/conf/schema.xml @@ -15,21 +15,28 @@ and limitations under the License. --> - + - - - - + + + + + @@ -53,7 +60,6 @@ - @@ -95,14 +101,18 @@ - + - + + + + id content From a14c6ea415ca18a0bc72ad393585103d7f7d52d6 Mon Sep 17 00:00:00 2001 From: Julien Nioche Date: Tue, 31 May 2011 15:02:38 +0000 Subject: [PATCH 058/754] CHANGES.TXT : added missing change list for 1.2 git-svn-id: https://svn.apache.org/repos/asf/nutch/branches/branch-1.3@1129731 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 71 ++- build.xml | 1343 +++++++++++++++++++++----------------------- default.properties | 3 +- ivy/ivy-2.1.0.jar | Bin 910990 -> 0 bytes ivy/ivy.xml | 10 +- ivy/mvn.template | 88 +++ pom.xml | 304 ---------- 7 files changed, 787 insertions(+), 1032 deletions(-) delete mode 100644 ivy/ivy-2.1.0.jar create mode 100644 ivy/mvn.template delete mode 100644 pom.xml diff --git a/CHANGES.txt b/CHANGES.txt index 15c1b30779..a92d87c645 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,6 @@ Nutch Change Log -Release 1.3 - 4/21/2011 +Release 1.3 - 5/31/2011 * NUTCH-994 Fine tune Solr schema (markus) @@ -68,65 +68,82 @@ Release 1.3 - 4/21/2011 * NUTCH-936 LanguageIdentifier should not set empty lang field on NutchDocument (Markus Jelsma via jnioche) -* NUTCH-855 ScoringFilter and IndexingFilter: To allow for the propagation of URL Metatags and their subsequent indexing (Scott Gonyea via mattmann) +* NUTCH-787 ScoringFilters should not override the injected score (jnioche) -* NUTCH-901 Make index-more plug-in configurable (Markus Jelsma, mattmann) +* NUTCH-949 Conflicting ANT jars in classpath (jnioche) -* NUTCH-716 Make subcollection index filed multivalued (Dmitry Lihachev via jnioche) +* NUTCH-863 Benchmark and a testbed proxy server (ab) -* NUTCH-905 Configurable file protocol parent directory crawling (Thorsten Scherler, mattmann, ab) +* NUTCH-844 Improve NutchConfiguration (ab) -* NUTCH-787 ScoringFilters should not override the injected score (jnioche) +* NUTCH-845 Native hadoop libs not available through maven (ab) -* NUTCH-949 Conflicting ANT jars in classpath (jnioche) +* NUTCH-843 Separate the build and runtime environments (ab) + +* NUTCH-821 Use ivy in nutch builds (Enis Soztutar, jnioche) + +* NUTCH-837 Remove search servers and Lucene dependencies (ab) + +* NUTCH-836 Remove deprecated parse plugins (jnioche) + +* NUTCH-939 Added -dir command line option to SolrIndexer (Claudio Martella via ab) + +* NUTCH-948 Remove Lucene dependencies (ab) + +Release 1.2 - 09/18/2010 + +* NUTCH-901 Make index-more plug-in configurable (Markus Jelsma via mattmann) + +* NUTCH-908 Infinite Loop and Null Pointer Bugs in Searching (kubes via mattmann) + +* NUTCH-906 Nutch OpenSearch sometimes raises DOMExceptions (Asheesh Laroia via ab) * NUTCH-862 HttpClient null pointer exception (Sebastian Nagel via ab) +* NUTCH-905 Configurable file protocol parent directory crawling (Thorsten Scherler, mattmann, ab) + +* NUTCH-877 Allow setting of slop values for non-quote phrase queries on query-basic plugin (kubes via jnioche) + +* NUTCH-716 Make subcollection index filed multivalued (Dmitry Lihachev via jnioche) + +* NUTCH-878 ScoringFilters should not override the injected score + * NUTCH-870 Injector should add the metadata before calling injectedScore (jnioche via mattmann) +* NUTCH-858 No longer able to set per-field boosts on lucene documents (ab) + * NUTCH-869 Add parse-html back (jnioche) -* NUTCH-871 MoreIndexingFilter missing date format (Max Lynch via mattmann) +* NUTCH-871 MoreIndexingFilter missing date format (Max Lynch via mattmann) * NUTCH-696 Timeout for Parser (ab, jnioche) -* NUTCH-863 Benchmark and a testbed proxy server (ab) +* NUTCH-857 DistributedBeans should not close their RPC counterparts (kubes) + +* NUTCH-855 ScoringFilter and IndexingFilter: To allow for the propagation of URL Metatags + and their subsequent indexing (Scott Gonyea via mattmann) -* NUTCH-677 Segment merge filtering based on segment content (Marcin Okraszewski via mattmann) +* NUTCH-677 Segment merge filering based on segment content (Marcin Okraszewski via mattmann) * NUTCH-774 Retry interval in crawl date is set to 0 (Reinhard Schwab via mattmann) * NUTCH-697 Generate log output for solr indexer and dedup (Dmitry Lihachev, Jeroen van Vianen via mattmann) -* NUTCH-844 Improve NutchConfiguration (ab) - * NUTCH-850 SolrDeleteDuplicates needs to clone the SolrRecord objects (jnioche) -* NUTCH-845 Native hadoop libs not available through maven (ab) - -* NUTCH-843 Separate the build and runtime environments (ab) - -* NUTCH-821 Use ivy in nutch builds (Enis Soztutar, jnioche) - * NUTCH-838 Add timing information to all Tool classes (Jeroen van Vianen, mattmann) -* NUTCH-837 Remove search servers and Lucene dependencies (ab) - -* NUTCH-836 Remove deprecated parse plugins (jnioche) - * NUTCH-835 Document deduplication failed using MD5Signature (Sebastian Nagel via ab) +* NUTCH-831 Allow configuration of how fields crawled by Nutch are stored / indexed / + tokenized (Jeroen van Vianen via mattmann) + * NUTCH-278 Fetcher-status might need clarification: kbit/s instead of kb/s shown (Alex McLintock via mattmann) * NUTCH-833 Website is still Lucene branded (mattmann, Alex McLintock) * NUTCH-832 Website menu has lots of broken links - in particular the API docs (Alex McLintock via mattmann) -* NUTCH-939 Added -dir command line option to SolrIndexer (Claudio Martella via ab) - -* NUTCH-948 Remove Lucene dependencies (ab) - - Release 1.1 - 2010-06-06 * NUTCH-819 Included Solr schema.xml and solrindex-mapping.xml don't play together (ab) diff --git a/build.xml b/build.xml index 4831a64af2..e52250bd32 100644 --- a/build.xml +++ b/build.xml @@ -17,708 +17,663 @@ --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - unusedcode - - - - - - - - - - - - - - - - - - FAILURE: PMD shows ${pmd.failures} rule violations. See ${pmd.report} for details. - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + unusedcode + + + + + + + + + + + + + + + + + + FAILURE: PMD shows ${pmd.failures} rule violations. See ${pmd.report} for details. + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - Tests failed! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - You need Apache Ivy 2.0 or later from http://ant.apache.org/ + + + + + + + + + + + + + + + + + + + + + + Tests failed! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You need Apache Ivy 2.2.0 or later from http://ant.apache.org/ It could not be loaded from ${ivy.repo.url} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - " + " " + " bots " + + ""), + + new String(" " + " " + + " separate this " + " from this" + + "" + ""), + + // this one relies on certain neko fixup behavior, possibly + // distributing the anchors into the LI's-but not the other + // anchors (outside of them, instead)! So you get a tree that + // looks like: + // ...
  • home
  • + //
  • 1
  • + //
  • 2
  • + new String(" my title " + + " body " + "" + + ""), + + // test frameset link extraction. The invalid frame in the middle + // will be + // fixed to a third standalone frame. + new String(" my title " + + " " + "" + + "" + "" + + "" + "" + + "" + "" + "" + + "" + "" + ""), + + // test and