/[Apache-SVN]/lucene/nutch/trunk/src/java/org/apache/nutch/crawl/Generator.java
ViewVC logotype

Contents of /lucene/nutch/trunk/src/java/org/apache/nutch/crawl/Generator.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 823614 - (show annotations)
Fri Oct 9 17:02:32 2009 UTC (6 weeks, 6 days ago) by ab
File size: 22798 byte(s)
NUTCH-758 Set subversion eol-style to "native".
1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.nutch.crawl;
19
20 import java.io.*;
21 import java.net.*;
22 import java.util.*;
23 import java.text.*;
24
25 // Commons Logging imports
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28
29 import org.apache.hadoop.io.*;
30 import org.apache.hadoop.conf.*;
31 import org.apache.hadoop.mapred.*;
32 import org.apache.hadoop.util.*;
33 import org.apache.hadoop.fs.FileSystem;
34 import org.apache.hadoop.fs.Path;
35
36 import org.apache.nutch.metadata.Nutch;
37 import org.apache.nutch.net.URLFilterException;
38 import org.apache.nutch.net.URLFilters;
39 import org.apache.nutch.net.URLNormalizers;
40 import org.apache.nutch.scoring.ScoringFilterException;
41 import org.apache.nutch.scoring.ScoringFilters;
42 import org.apache.nutch.util.LockUtil;
43 import org.apache.nutch.util.NutchConfiguration;
44 import org.apache.nutch.util.NutchJob;
45
46 /** Generates a subset of a crawl db to fetch. */
47 public class Generator extends Configured implements Tool {
48
49 public static final String CRAWL_GENERATE_FILTER = "crawl.generate.filter";
50 public static final String GENERATE_MAX_PER_HOST_BY_IP = "generate.max.per.host.by.ip";
51 public static final String GENERATE_MAX_PER_HOST = "generate.max.per.host";
52 public static final String GENERATE_UPDATE_CRAWLDB = "generate.update.crawldb";
53 public static final String CRAWL_TOP_N = "crawl.topN";
54 public static final String CRAWL_GEN_CUR_TIME = "crawl.gen.curTime";
55 public static final String CRAWL_GEN_DELAY = "crawl.gen.delay";
56 public static final Log LOG = LogFactory.getLog(Generator.class);
57
58 public static class SelectorEntry implements Writable {
59 public Text url;
60 public CrawlDatum datum;
61
62 public SelectorEntry() {
63 url = new Text();
64 datum = new CrawlDatum();
65 }
66
67 public void readFields(DataInput in) throws IOException {
68 url.readFields(in);
69 datum.readFields(in);
70 }
71
72 public void write(DataOutput out) throws IOException {
73 url.write(out);
74 datum.write(out);
75 }
76
77 public String toString() {
78 return "url=" + url.toString() + ", datum=" + datum.toString();
79 }
80 }
81
82 /** Selects entries due for fetch. */
83 public static class Selector implements Mapper<Text, CrawlDatum, FloatWritable, SelectorEntry>, Partitioner<FloatWritable, Writable>, Reducer<FloatWritable, SelectorEntry, FloatWritable, SelectorEntry> {
84 private LongWritable genTime = new LongWritable(System.currentTimeMillis());
85 private long curTime;
86 private long limit;
87 private long count;
88 private HashMap<String, IntWritable> hostCounts =
89 new HashMap<String, IntWritable>();
90 private int maxPerHost;
91 private HashSet<String> maxedHosts = new HashSet<String>();
92 private HashSet<String> dnsFailureHosts = new HashSet<String>();
93 private Partitioner<Text, Writable> hostPartitioner = new PartitionUrlByHost();
94 private URLFilters filters;
95 private URLNormalizers normalizers;
96 private ScoringFilters scfilters;
97 private SelectorEntry entry = new SelectorEntry();
98 private FloatWritable sortValue = new FloatWritable();
99 private boolean byIP;
100 private long dnsFailure = 0L;
101 private boolean filter;
102 private long genDelay;
103 private FetchSchedule schedule;
104
105 public void configure(JobConf job) {
106 curTime = job.getLong(CRAWL_GEN_CUR_TIME, System.currentTimeMillis());
107 limit = job.getLong(CRAWL_TOP_N,Long.MAX_VALUE)/job.getNumReduceTasks();
108 maxPerHost = job.getInt(GENERATE_MAX_PER_HOST, -1);
109 byIP = job.getBoolean(GENERATE_MAX_PER_HOST_BY_IP, false);
110 filters = new URLFilters(job);
111 normalizers = new URLNormalizers(job, URLNormalizers.SCOPE_GENERATE_HOST_COUNT);
112 scfilters = new ScoringFilters(job);
113 hostPartitioner.configure(job);
114 filter = job.getBoolean(CRAWL_GENERATE_FILTER, true);
115 genDelay = job.getLong(CRAWL_GEN_DELAY, 7L) * 3600L * 24L * 1000L;
116 long time = job.getLong(Nutch.GENERATE_TIME_KEY, 0L);
117 if (time > 0) genTime.set(time);
118 schedule = FetchScheduleFactory.getFetchSchedule(job);
119 }
120
121 public void close() {}
122
123 /** Select & invert subset due for fetch. */
124 public void map(Text key, CrawlDatum value,
125 OutputCollector<FloatWritable, SelectorEntry> output, Reporter reporter)
126 throws IOException {
127 Text url = key;
128 if (filter) {
129 // If filtering is on don't generate URLs that don't pass URLFilters
130 try {
131 if (filters.filter(url.toString()) == null)
132 return;
133 } catch (URLFilterException e) {
134 if (LOG.isWarnEnabled()) {
135 LOG.warn("Couldn't filter url: " + url + " (" + e.getMessage()
136 + ")");
137 }
138 }
139 }
140 CrawlDatum crawlDatum = value;
141
142 // check fetch schedule
143 if (!schedule.shouldFetch(url, crawlDatum, curTime)) {
144 LOG.debug("-shouldFetch rejected '" + url+ "', fetchTime=" + crawlDatum.getFetchTime() + ", curTime=" + curTime);
145 return;
146 }
147
148 LongWritable oldGenTime = (LongWritable)crawlDatum.getMetaData().get(Nutch.WRITABLE_GENERATE_TIME_KEY);
149 if (oldGenTime != null) { // awaiting fetch & update
150 if (oldGenTime.get() + genDelay > curTime) // still wait for update
151 return;
152 }
153 float sort = 1.0f;
154 try {
155 sort = scfilters.generatorSortValue((Text)key, crawlDatum, sort);
156 } catch (ScoringFilterException sfe) {
157 if (LOG.isWarnEnabled()) {
158 LOG.warn("Couldn't filter generatorSortValue for " + key + ": " + sfe);
159 }
160 }
161 // sort by decreasing score, using DecreasingFloatComparator
162 sortValue.set(sort);
163 // record generation time
164 crawlDatum.getMetaData().put(Nutch.WRITABLE_GENERATE_TIME_KEY, genTime);
165 entry.datum = crawlDatum;
166 entry.url = (Text)key;
167 output.collect(sortValue, entry); // invert for sort by score
168 }
169
170 /** Partition by host. */
171 public int getPartition(FloatWritable key, Writable value,
172 int numReduceTasks) {
173 return hostPartitioner.getPartition(((SelectorEntry)value).url, key,
174 numReduceTasks);
175 }
176
177 /** Collect until limit is reached. */
178 public void reduce(FloatWritable key, Iterator<SelectorEntry> values,
179 OutputCollector<FloatWritable, SelectorEntry> output,
180 Reporter reporter)
181 throws IOException {
182
183 while (values.hasNext() && count < limit) {
184
185 SelectorEntry entry = values.next();
186 Text url = entry.url;
187 String urlString = url.toString();
188 URL u = null;
189
190 // skip bad urls, including empty and null urls
191 try {
192 u = new URL(url.toString());
193 } catch (MalformedURLException e) {
194 LOG.info("Bad protocol in url: " + url.toString());
195 continue;
196 }
197
198 String host = u.getHost();
199 host = host.toLowerCase();
200 String hostname = host;
201
202 // partitioning by ip will generate lots of DNS requests here, and will
203 // be up to double the overall dns load, do not run this way unless you
204 // are running a local caching DNS server or a two layer DNS cache
205 if (byIP) {
206 if (maxedHosts.contains(host)) {
207 if (LOG.isDebugEnabled()) { LOG.debug("Host already maxed out: " + host); }
208 continue;
209 }
210 if (dnsFailureHosts.contains(host)) {
211 if (LOG.isDebugEnabled()) { LOG.debug("Host name lookup already failed: " + host); }
212 continue;
213 }
214 try {
215 InetAddress ia = InetAddress.getByName(host);
216 host = ia.getHostAddress();
217 urlString = new URL(u.getProtocol(), host, u.getPort(), u.getFile()).toString();
218 }
219 catch (UnknownHostException uhe) {
220 // remember hostnames that could not be looked up
221 dnsFailureHosts.add(hostname);
222 if (LOG.isDebugEnabled()) {
223 LOG.debug("DNS lookup failed: " + host + ", skipping.");
224 }
225 dnsFailure++;
226 if ((dnsFailure % 1000 == 0) && (LOG.isWarnEnabled())) {
227 LOG.warn("DNS failures: " + dnsFailure);
228 }
229 continue;
230 }
231 }
232
233 try {
234 urlString = normalizers.normalize(urlString, URLNormalizers.SCOPE_GENERATE_HOST_COUNT);
235 host = new URL(urlString).getHost();
236 } catch (Exception e) {
237 LOG.warn("Malformed URL: '" + urlString + "', skipping (" +
238 StringUtils.stringifyException(e) + ")");
239 continue;
240 }
241
242 // only filter if we are counting hosts
243 if (maxPerHost > 0) {
244
245 IntWritable hostCount = hostCounts.get(host);
246 if (hostCount == null) {
247 hostCount = new IntWritable();
248 hostCounts.put(host, hostCount);
249 }
250
251 // increment hostCount
252 hostCount.set(hostCount.get() + 1);
253
254 // skip URL if above the limit per host.
255 if (hostCount.get() > maxPerHost) {
256 if (hostCount.get() == maxPerHost + 1) {
257 // remember the raw hostname that is maxed out
258 maxedHosts.add(hostname);
259 if (LOG.isInfoEnabled()) {
260 LOG.info("Host " + host + " has more than " + maxPerHost +
261 " URLs." + " Skipping additional.");
262 }
263 }
264 continue;
265 }
266 }
267
268 output.collect(key, entry);
269
270 // Count is incremented only when we keep the URL
271 // maxPerHost may cause us to skip it.
272 count++;
273 }
274 }
275 }
276
277 public static class DecreasingFloatComparator extends FloatWritable.Comparator {
278
279 /** Compares two FloatWritables decreasing. */
280 public int compare(byte[] b1, int s1, int l1,
281 byte[] b2, int s2, int l2) {
282 return super.compare(b2, s2, l2, b1, s1, l1);
283 }
284 }
285
286 public static class SelectorInverseMapper extends MapReduceBase implements Mapper<FloatWritable, SelectorEntry, Text, SelectorEntry> {
287
288 public void map(FloatWritable key, SelectorEntry value, OutputCollector<Text, SelectorEntry> output, Reporter reporter) throws IOException {
289 SelectorEntry entry = (SelectorEntry)value;
290 output.collect(entry.url, entry);
291 }
292 }
293
294 public static class PartitionReducer extends MapReduceBase
295 implements Reducer<Text, SelectorEntry, Text, CrawlDatum> {
296
297 public void reduce(Text key, Iterator<SelectorEntry> values,
298 OutputCollector<Text, CrawlDatum> output, Reporter reporter) throws IOException {
299 // if using HashComparator, we get only one input key in case of hash collision
300 // so use only URLs from values
301 while (values.hasNext()) {
302 SelectorEntry entry = values.next();
303 output.collect(entry.url, entry.datum);
304 }
305 }
306
307 }
308
309 /** Sort fetch lists by hash of URL. */
310 public static class HashComparator extends WritableComparator {
311 public HashComparator() {
312 super(Text.class);
313 }
314
315 public int compare(WritableComparable a, WritableComparable b) {
316 Text url1 = (Text) a;
317 Text url2 = (Text) b;
318 int hash1 = hash(url1.getBytes(), 0, url1.getLength());
319 int hash2 = hash(url2.getBytes(), 0, url2.getLength());
320 return (hash1 < hash2 ? -1 : (hash1 == hash2 ? 0 : 1));
321 }
322
323 public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
324 int hash1 = hash(b1, s1, l1);
325 int hash2 = hash(b2, s2, l2);
326 return (hash1 < hash2 ? -1 : (hash1 == hash2 ? 0 : 1));
327 }
328
329 private static int hash(byte[] bytes, int start, int length) {
330 int hash = 1;
331 // make later bytes more significant in hash code, so that sorting by
332 // hashcode correlates less with by-host ordering.
333 for (int i = length - 1; i >= 0; i--)
334 hash = (31 * hash) + (int) bytes[start + i];
335 return hash;
336 }
337 }
338
339 /**
340 * Update the CrawlDB so that the next generate won't include the same URLs.
341 */
342 public static class CrawlDbUpdater extends MapReduceBase implements Mapper<Text, CrawlDatum, Text, CrawlDatum>, Reducer<Text, CrawlDatum, Text, CrawlDatum> {
343 long generateTime;
344
345 public void configure(JobConf job) {
346 generateTime = job.getLong(Nutch.GENERATE_TIME_KEY, 0L);
347 }
348
349 public void map(Text key, CrawlDatum value, OutputCollector<Text, CrawlDatum> output, Reporter reporter) throws IOException {
350 output.collect(key, value);
351 }
352
353 private CrawlDatum orig = new CrawlDatum();
354 private LongWritable genTime = new LongWritable(0L);
355
356 public void reduce(Text key, Iterator<CrawlDatum> values, OutputCollector<Text, CrawlDatum> output, Reporter reporter) throws IOException {
357 genTime.set(0L);
358 while (values.hasNext()) {
359 CrawlDatum val = values.next();
360 if (val.getMetaData().containsKey(Nutch.WRITABLE_GENERATE_TIME_KEY)) {
361 LongWritable gt = (LongWritable)val.getMetaData().get(Nutch.WRITABLE_GENERATE_TIME_KEY);
362 genTime.set(gt.get());
363 if (genTime.get() != generateTime) {
364 orig.set(val);
365 genTime.set(0L);
366 continue;
367 }
368 } else {
369 orig.set(val);
370 }
371 }
372 if (genTime.get() != 0L) {
373 orig.getMetaData().put(Nutch.WRITABLE_GENERATE_TIME_KEY, genTime);
374 }
375 output.collect(key, orig);
376 }
377 }
378
379 public Generator() {}
380
381 public Generator(Configuration conf) {
382 setConf(conf);
383 }
384
385 /**
386 * Generate fetchlists in a segment. Whether to filter URLs or not is
387 * read from the crawl.generate.filter property in the configuration
388 * files. If the property is not found, the URLs are filtered.
389 *
390 * @param dbDir Crawl database directory
391 * @param segments Segments directory
392 * @param numLists Number of reduce tasks
393 * @param topN Number of top URLs to be selected
394 * @param curTime Current time in milliseconds
395 *
396 * @return Path to generated segment or null if no entries were
397 * selected
398 *
399 * @throws IOException When an I/O error occurs
400 */
401 public Path generate(Path dbDir, Path segments, int numLists,
402 long topN, long curTime) throws IOException {
403
404 JobConf job = new NutchJob(getConf());
405 boolean filter = job.getBoolean(CRAWL_GENERATE_FILTER, true);
406 return generate(dbDir, segments, numLists, topN, curTime, filter, false);
407 }
408
409 /**
410 * Generate fetchlists in a segment.
411 * @return Path to generated segment or null if no entries were selected.
412 * */
413 public Path generate(Path dbDir, Path segments,
414 int numLists, long topN, long curTime, boolean filter,
415 boolean force)
416 throws IOException {
417
418 Path tempDir =
419 new Path(getConf().get("mapred.temp.dir", ".") +
420 "/generate-temp-"+ System.currentTimeMillis());
421
422 Path segment = new Path(segments, generateSegmentName());
423 Path output = new Path(segment, CrawlDatum.GENERATE_DIR_NAME);
424
425 Path lock = new Path(dbDir, CrawlDb.LOCK_NAME);
426 FileSystem fs = FileSystem.get(getConf());
427 LockUtil.createLockFile(fs, lock, force);
428
429 LOG.info("Generator: Selecting best-scoring urls due for fetch.");
430 LOG.info("Generator: starting");
431 LOG.info("Generator: segment: " + segment);
432 LOG.info("Generator: filtering: " + filter);
433 if (topN != Long.MAX_VALUE) {
434 LOG.info("Generator: topN: " + topN);
435 }
436
437 // map to inverted subset due for fetch, sort by score
438 JobConf job = new NutchJob(getConf());
439 job.setJobName("generate: select " + segment);
440
441 if (numLists == -1) { // for politeness make
442 numLists = job.getNumMapTasks(); // a partition per fetch task
443 }
444 if ("local".equals(job.get("mapred.job.tracker")) && numLists != 1) {
445 // override
446 LOG.info("Generator: jobtracker is 'local', generating exactly one partition.");
447 numLists = 1;
448 }
449 job.setLong(CRAWL_GEN_CUR_TIME, curTime);
450 // record real generation time
451 long generateTime = System.currentTimeMillis();
452 job.setLong(Nutch.GENERATE_TIME_KEY, generateTime);
453 job.setLong(CRAWL_TOP_N, topN);
454 job.setBoolean(CRAWL_GENERATE_FILTER, filter);
455
456 FileInputFormat.addInputPath(job, new Path(dbDir, CrawlDb.CURRENT_NAME));
457 job.setInputFormat(SequenceFileInputFormat.class);
458
459 job.setMapperClass(Selector.class);
460 job.setPartitionerClass(Selector.class);
461 job.setReducerClass(Selector.class);
462
463 FileOutputFormat.setOutputPath(job, tempDir);
464 job.setOutputFormat(SequenceFileOutputFormat.class);
465 job.setOutputKeyClass(FloatWritable.class);
466 job.setOutputKeyComparatorClass(DecreasingFloatComparator.class);
467 job.setOutputValueClass(SelectorEntry.class);
468 try {
469 JobClient.runJob(job);
470 } catch (IOException e) {
471 LockUtil.removeLockFile(fs, lock);
472 throw e;
473 }
474
475 // check that we selected at least some entries ...
476 SequenceFile.Reader[] readers = SequenceFileOutputFormat.getReaders(job, tempDir);
477 boolean empty = true;
478 if (readers != null && readers.length > 0) {
479 for (int num = 0; num < readers.length; num++) {
480 if (readers[num].next(new FloatWritable())) {
481 empty = false;
482 break;
483 }
484 }
485 }
486
487 for (int i = 0; i < readers.length; i++) readers[i].close();
488
489 if (empty) {
490 LOG.warn("Generator: 0 records selected for fetching, exiting ...");
491 LockUtil.removeLockFile(fs, lock);
492 fs.delete(tempDir, true);
493 return null;
494 }
495
496 // invert again, paritition by host, sort by url hash
497 if (LOG.isInfoEnabled()) {
498 LOG.info("Generator: Partitioning selected urls by host, for politeness.");
499 }
500 job = new NutchJob(getConf());
501 job.setJobName("generate: partition " + segment);
502
503 job.setInt("partition.url.by.host.seed", new Random().nextInt());
504
505 FileInputFormat.addInputPath(job, tempDir);
506 job.setInputFormat(SequenceFileInputFormat.class);
507
508 job.setMapperClass(SelectorInverseMapper.class);
509 job.setMapOutputKeyClass(Text.class);
510 job.setMapOutputValueClass(SelectorEntry.class);
511 job.setPartitionerClass(PartitionUrlByHost.class);
512 job.setReducerClass(PartitionReducer.class);
513 job.setNumReduceTasks(numLists);
514
515 FileOutputFormat.setOutputPath(job, output);
516 job.setOutputFormat(SequenceFileOutputFormat.class);
517 job.setOutputKeyClass(Text.class);
518 job.setOutputValueClass(CrawlDatum.class);
519 job.setOutputKeyComparatorClass(HashComparator.class);
520 try {
521 JobClient.runJob(job);
522 } catch (IOException e) {
523 LockUtil.removeLockFile(fs, lock);
524 fs.delete(tempDir, true);
525 throw e;
526 }
527 if (getConf().getBoolean(GENERATE_UPDATE_CRAWLDB, false)) {
528 // update the db from tempDir
529 Path tempDir2 =
530 new Path(getConf().get("mapred.temp.dir", ".") +
531 "/generate-temp-"+ System.currentTimeMillis());
532
533 job = new NutchJob(getConf());
534 job.setJobName("generate: updatedb " + dbDir);
535 job.setLong(Nutch.GENERATE_TIME_KEY, generateTime);
536 FileInputFormat.addInputPath(job, output);
537 FileInputFormat.addInputPath(job, new Path(dbDir, CrawlDb.CURRENT_NAME));
538 job.setInputFormat(SequenceFileInputFormat.class);
539 job.setMapperClass(CrawlDbUpdater.class);
540 job.setReducerClass(CrawlDbUpdater.class);
541 job.setOutputFormat(MapFileOutputFormat.class);
542 job.setOutputKeyClass(Text.class);
543 job.setOutputValueClass(CrawlDatum.class);
544 FileOutputFormat.setOutputPath(job, tempDir2);
545 try {
546 JobClient.runJob(job);
547 CrawlDb.install(job, dbDir);
548 } catch (IOException e) {
549 LockUtil.removeLockFile(fs, lock);
550 fs.delete(tempDir, true);
551 fs.delete(tempDir2, true);
552 throw e;
553 }
554 fs.delete(tempDir2, true);
555 }
556 LockUtil.removeLockFile(fs, lock);
557 fs.delete(tempDir, true);
558
559 if (LOG.isInfoEnabled()) { LOG.info("Generator: done."); }
560
561 return segment;
562 }
563
564 private static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
565
566 public static synchronized String generateSegmentName() {
567 try {
568 Thread.sleep(1000);
569 } catch (Throwable t) {};
570 return sdf.format
571 (new Date(System.currentTimeMillis()));
572 }
573
574 /**
575 * Generate a fetchlist from the crawldb.
576 */
577 public static void main(String args[]) throws Exception {
578 int res = ToolRunner.run(NutchConfiguration.create(), new Generator(), args);
579 System.exit(res);
580 }
581
582 public int run(String[] args) throws Exception {
583 if (args.length < 2) {
584 System.out.println("Usage: Generator <crawldb> <segments_dir> [-force] [-topN N] [-numFetchers numFetchers] [-adddays numDays] [-noFilter]");
585 return -1;
586 }
587
588 Path dbDir = new Path(args[0]);
589 Path segmentsDir = new Path(args[1]);
590 long curTime = System.currentTimeMillis();
591 long topN = Long.MAX_VALUE;
592 int numFetchers = -1;
593 boolean filter = true;
594 boolean force = false;
595
596 for (int i = 2; i < args.length; i++) {
597 if ("-topN".equals(args[i])) {
598 topN = Long.parseLong(args[i+1]);
599 i++;
600 } else if ("-numFetchers".equals(args[i])) {
601 numFetchers = Integer.parseInt(args[i+1]);
602 i++;
603 } else if ("-adddays".equals(args[i])) {
604 long numDays = Integer.parseInt(args[i+1]);
605 curTime += numDays * 1000L * 60 * 60 * 24;
606 } else if ("-noFilter".equals(args[i])) {
607 filter = false;
608 } else if ("-force".equals(args[i])) {
609 force = true;
610 }
611
612 }
613
614 try {
615 Path seg = generate(dbDir, segmentsDir, numFetchers, topN, curTime, filter, force);
616 if (seg == null) return -2;
617 else return 0;
618 } catch (Exception e) {
619 LOG.fatal("Generator: " + StringUtils.stringifyException(e));
620 return -1;
621 }
622 }
623 }

Properties

Name Value
svn:eol-style native
svn:keywords Date Author Id Revision HeadURL

apache@apache.org
ViewVC Help
Powered by ViewVC 1.1.2