Project

General

Profile

1
package eu.dnetlib.oai.mongo;
2

    
3
import java.io.ByteArrayOutputStream;
4
import java.io.IOException;
5
import java.util.Collection;
6
import java.util.Date;
7
import java.util.List;
8
import java.util.concurrent.ArrayBlockingQueue;
9
import java.util.concurrent.BlockingQueue;
10
import java.util.concurrent.TimeUnit;
11
import java.util.function.Function;
12
import java.util.stream.Collectors;
13
import java.util.zip.ZipEntry;
14
import java.util.zip.ZipOutputStream;
15

    
16
import com.google.common.base.Stopwatch;
17
import com.google.common.collect.Iterables;
18
import com.google.common.collect.Lists;
19
import com.google.common.collect.Multimap;
20
import com.mongodb.BasicDBObject;
21
import com.mongodb.BasicDBObjectBuilder;
22
import com.mongodb.DBObject;
23
import com.mongodb.WriteConcern;
24
import com.mongodb.client.FindIterable;
25
import com.mongodb.client.MongoCollection;
26
import com.mongodb.client.MongoDatabase;
27
import com.mongodb.client.model.Filters;
28
import com.mongodb.client.model.IndexOptions;
29
import com.mongodb.client.model.Sorts;
30
import com.mongodb.client.model.UpdateOptions;
31
import com.mongodb.client.result.DeleteResult;
32
import com.mongodb.client.result.UpdateResult;
33
import eu.dnetlib.cql.CqlTranslator;
34
import eu.dnetlib.oai.PublisherField;
35
import eu.dnetlib.oai.PublisherStore;
36
import eu.dnetlib.oai.RecordChangeDetector;
37
import eu.dnetlib.oai.conf.OAIConfigurationReader;
38
import eu.dnetlib.oai.info.RecordInfo;
39
import eu.dnetlib.oai.info.SetInfo;
40
import eu.dnetlib.oai.parser.PublisherRecordParser;
41
import eu.dnetlib.oai.sets.MongoSetCollection;
42
import eu.dnetlib.rmi.provision.OaiPublisherRuntimeException;
43
import org.apache.commons.lang3.StringUtils;
44
import org.apache.commons.logging.Log;
45
import org.apache.commons.logging.LogFactory;
46
import org.bson.conversions.Bson;
47
import org.bson.types.Binary;
48

    
49
public class MongoPublisherStore implements PublisherStore<DNetOAIMongoCursor> {
50

    
51
	private static final Log log = LogFactory.getLog(MongoPublisherStore.class); // NOPMD by marko on 11/24/08 5:02 PM
52

    
53
	private String id, metadataFormat, interpretation, layout;
54
	/**
55
	 * Keeps information about the fields to be created in mongo.
56
	 **/
57
	private List<PublisherField> mongoFields;
58

    
59
	private MongoCollection<DBObject> collection;
60
	private MongoCollection<DBObject> discardedCollection;
61

    
62
	private CqlTranslator cqlTranslator;
63
	private RecordInfoGenerator recordInfoGenerator;
64
	private MetadataExtractor metadataExtractor;
65

    
66
	private RecordChangeDetector recordChangeDetector;
67

    
68
	private MongoSetCollection mongoSetCollection;
69

    
70

    
71
	/**
72
	 * Used to generate the OAI identifiers compliant to the protocol. See
73
	 * http://www.openarchives.org/OAI/openarchivesprotocol.html#UniqueIdentifier.
74
	 */
75
	private String idScheme;
76
	/**
77
	 * Used to generate the OAI identifiers compliant to the protocol. See
78
	 * http://www.openarchives.org/OAI/openarchivesprotocol.html#UniqueIdentifier.
79
	 */
80
	private String idNamespace;
81

    
82
	private boolean alwaysNewRecord;
83

    
84
	public MongoPublisherStore() {
85
		super();
86
	}
87

    
88
	public MongoPublisherStore(final String id,
89
			final String metadataFormat,
90
			final String interpretation,
91
			final String layout,
92
			final MongoCollection<DBObject> collection,
93
			final List<PublisherField> mongoFields,
94
			final CqlTranslator cqlTranslator,
95
			final RecordInfoGenerator recordInfoGenerator,
96
			final String idScheme,
97
			final String idNamespace,
98
			final MetadataExtractor metadataExtractor,
99
			final RecordChangeDetector recordChangeDetector,
100
			final boolean alwaysNewRecord,
101
			final MongoDatabase mongodb) {
102
		super();
103
		this.id = id;
104
		this.metadataFormat = metadataFormat;
105
		this.interpretation = interpretation;
106
		this.layout = layout;
107
		this.collection = collection;
108
		this.discardedCollection = mongodb.getCollection("discarded-" + collection.getNamespace().getCollectionName(), DBObject.class);
109
		this.mongoFields = mongoFields;
110
		this.cqlTranslator = cqlTranslator;
111
		this.recordInfoGenerator = recordInfoGenerator;
112
		this.idScheme = idScheme;
113
		this.idNamespace = idNamespace;
114
		this.metadataExtractor = metadataExtractor;
115
		this.recordChangeDetector = recordChangeDetector;
116
		this.alwaysNewRecord = alwaysNewRecord;
117
	}
118

    
119
	@Override
120
	public RecordInfo getRecord(final String recordId) {
121
		final Bson query = Filters.eq(OAIConfigurationReader.ID_FIELD, recordId);
122
		final DBObject result = this.collection.find(query).first();
123
		return this.recordInfoGenerator.transformDBObject(result, true);
124
	}
125

    
126
	@Override
127
	public RecordInfo getRecord(final String recordId, final Function<String, String> unaryFunction) {
128
		final RecordInfo result = this.getRecord(recordId);
129
		if (result != null) {
130
			final String transformedBody = unaryFunction.apply(result.getMetadata());
131
			result.setMetadata(transformedBody);
132
		}
133
		return result;
134
	}
135

    
136
	@Override
137
	public DNetOAIMongoCursor getRecords(final String queryString, final boolean bodyIncluded, final int limit) {
138
		final FindIterable<DBObject> iter = loggedFindByQuery(queryString, limit);
139
		return new DNetOAIMongoCursor(iter.iterator(), bodyIncluded, this.recordInfoGenerator, this.metadataExtractor);
140
	}
141

    
142
	@Override
143
	public DNetOAIMongoCursor getRecords(final String queryString,
144
			final Function<String, String> unaryFunction,
145
			final boolean bodyIncluded,
146
			final int limit) {
147
		final FindIterable<DBObject> iter = loggedFindByQuery(queryString, limit);
148
		return new DNetOAIMongoCursor(iter.iterator(), unaryFunction, bodyIncluded, this.recordInfoGenerator, this.metadataExtractor);
149
	}
150

    
151
	private FindIterable<DBObject> loggedFindByQuery(final String queryString, final int limit) {
152
		final Bson query = parseQuery(queryString);
153
		long start = System.currentTimeMillis();
154
		Bson sortByIdAsc = Sorts.orderBy(Sorts.ascending("_id"));
155
		FindIterable<DBObject> iter = this.collection.find(query).sort(sortByIdAsc).limit(limit);
156
		long end = System.currentTimeMillis();
157
		log.debug("Query:" + query + "\ntime to get mongo iterable (ms): " + (end - start));
158
		return iter;
159
	}
160

    
161
	private Bson parseQuery(final String query) {
162
		try {
163
			return cqlTranslator.toMongo(query);
164
		} catch(Exception e ) {
165
			throw new OaiPublisherRuntimeException(e);
166
		}
167
	}
168

    
169

    
170
	@Override
171
	public List<PublisherField> getIndices() {
172
		return this.mongoFields;
173
	}
174

    
175
	/**
176
	 * <p>
177
	 * Ensure indices on the configuration-defined fields and on the system fields DATESTAMP_FIELD and LAST_COLLECTION_DATE_FIELD.
178
	 * <p>
179
	 * <p>
180
	 * Note that by default ID_FIELD, SET_FIELD, DELETED_FIELD, BODY_FIELD, UPDATED_FIELD are not indexed. If you want an index on those,
181
	 * then you have to specify it in the configuration file of the OAI Publisher: <br>
182
	 * <INDEX name="objIdentifier" repeatable="false">
183
	 * <SOURCE interpretation="transformed" layout="store" name="DMF" path="//*[local-name()='objIdentifier']"/>
184
	 * </INDEX>
185
	 * </p>
186
	 * <p>
187
	 * {@inheritDoc}
188
	 */
189
	@Override
190
	public void ensureIndices() {
191
		final IndexOptions indexOptions = new IndexOptions().background(true);
192
		final Stopwatch sw = Stopwatch.createUnstarted();
193
		sw.start();
194
		for (PublisherField field : this.mongoFields) {
195
			BasicDBObject mongoIdx = new BasicDBObject(field.getFieldName(), 1);
196
			log.debug("Creating index on store "+id+" : " + mongoIdx);
197
			this.collection.createIndex(mongoIdx, indexOptions);
198
		}
199
		log.debug("Creating index over : " + OAIConfigurationReader.DATESTAMP_FIELD);
200
		this.collection.createIndex(new BasicDBObject(OAIConfigurationReader.DATESTAMP_FIELD, 1), indexOptions);
201
		log.debug("Creating index over : " + OAIConfigurationReader.LAST_COLLECTION_DATE_FIELD);
202
		this.collection.createIndex(new BasicDBObject(OAIConfigurationReader.LAST_COLLECTION_DATE_FIELD, 1), indexOptions);
203
		sw.stop();
204
		log.info("All indexes have been updated in " + sw.elapsed(TimeUnit.MILLISECONDS) + " milliseconds");
205
	}
206

    
207
	/**
208
	 * Creates a compound index over the specified fields on the given store.
209
	 * <p>
210
	 * The creation is performed on the background
211
	 * </p>
212
	 *
213
	 * @param fieldNames
214
	 *            List of fields to be included in the compound index
215
	 * @theStore MongoPublisherStore where to create the index
216
	 */
217
	public void createCompoundIndex(final List<String> fieldNames) {
218
		if ((fieldNames == null) || fieldNames.isEmpty()) {
219
			log.fatal("No fields specified for the creation of the compound index");
220
		}
221
		BasicDBObjectBuilder theIndexBuilder = BasicDBObjectBuilder.start();
222
		for (String f : fieldNames) {
223
			theIndexBuilder.add(f, 1);
224
		}
225
		BasicDBObject theIndex = (BasicDBObject) theIndexBuilder.get();
226
		log.info("Creating index " + theIndex + " on " + this.getId());
227
		this.getCollection().createIndex(theIndex, new IndexOptions().background(true));
228
	}
229

    
230
	private void dropDiscarded(final String source) {
231
		if (StringUtils.isBlank(source)) {
232
			log.debug("Dropping discarded records from publisherStore " + this.id);
233
			this.discardedCollection.drop();
234
		} else {
235
			log.debug("Dropping discarded records for source " + source + " from publisherStore " + this.id);
236
			this.discardedCollection.deleteMany(Filters.eq(OAIConfigurationReader.SET_FIELD, source));
237
		}
238
	}
239

    
240
	@Override
241
	public int feed(final Iterable<String> records, final String source) {
242
		final BlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(80);
243
		final Object sentinel = new Object();
244
		this.dropDiscarded(source);
245
		final Date feedDate = new Date();
246
		final Thread background = new Thread(() -> {
247
			// For fast feeding we want to use a collection with unack write concern
248
			final MongoCollection<DBObject> unackCollection = MongoPublisherStore.this.collection.withWriteConcern(WriteConcern.UNACKNOWLEDGED);
249
			while (true) {
250
				try {
251
					final Object record = queue.take();
252
					if (record == sentinel) {
253
						break;
254
					}
255
					safeFeedRecord((String) record, source, feedDate, unackCollection);
256
				} catch (final InterruptedException e) {
257
					log.fatal("got exception in background thread", e);
258
					throw new IllegalStateException(e);
259
				}
260
			}
261
		});
262
		background.start();
263
		final long startFeed = feedDate.getTime();
264
		try {
265
			log.info("feeding publisherStore " + this.id);
266
			for (final String record : records) {
267
				queue.put(record);
268
			}
269
			queue.put(sentinel);
270
			log.info("finished feeding publisherStore " + this.id);
271

    
272
			background.join();
273
		} catch (final InterruptedException e) {
274
			throw new IllegalStateException(e);
275
		}
276
		final long endFeed = System.currentTimeMillis();
277
		log.fatal("OAI STORE " + this.id + " FEEDING COMPLETED IN " + (endFeed - startFeed) + "ms");
278
		this.setDeletedFlags(feedDate, source);
279
		//Let's add the set here so we can avoid to add in the workflow another job that upsert the sets.
280
		if(StringUtils.isNotBlank(source)) {
281
			this.upsertSets(Lists.newArrayList(source));
282
		}
283
		return this.count();
284
	}
285

    
286
	/**
287
	 * Launches the thread that flags the records to be considered as 'deleted'.
288
	 * <p>
289
	 * The datestamp of the deleted records must be updated as well, according to the OAI specs available at
290
	 * http://www.openarchives.org/OAI/openarchivesprotocol.html#DeletedRecords: if a repository does keep track of deletions then the
291
	 * datestamp of the deleted record must be the date and time that it was deleted.
292
	 * </p>
293
	 *
294
	 * @param feedDate
295
	 * @param source
296
	 */
297
	private void setDeletedFlags(final Date feedDate, final String source) {
298
		// get the collection with ACKNOWLEDGE Write concern
299
		final MongoCollection<DBObject> ackCollection = this.collection.withWriteConcern(WriteConcern.ACKNOWLEDGED);
300
		final Thread deletedSetter = new Thread(new Runnable() {
301

    
302
			@Override
303
			public void run() {
304
				Bson filter = Filters.and(Filters.eq(OAIConfigurationReader.DELETED_FIELD, false),
305
						Filters.lt(OAIConfigurationReader.LAST_COLLECTION_DATE_FIELD, feedDate));
306
				if (!StringUtils.isBlank(source)) {
307
					filter = Filters.and(filter, Filters.eq(OAIConfigurationReader.SET_FIELD, source));
308
				}
309
				log.debug("Delete flag query: " + filter);
310
				final BasicDBObject update = new BasicDBObject("$set",
311
						BasicDBObjectBuilder.start(OAIConfigurationReader.DELETED_FIELD, true).append(OAIConfigurationReader.DATESTAMP_FIELD, feedDate)
312
								.append(OAIConfigurationReader.UPDATED_FIELD, true).get());
313
				log.debug("Updating as: " + update.toString());
314
				final UpdateResult updateResult = ackCollection.updateMany(filter, update, new UpdateOptions().upsert(false));
315
				log.debug("Deleted flags set for source: " + source + " #records = " + updateResult.getModifiedCount());
316
			}
317
		});
318

    
319
		deletedSetter.start();
320
		try {
321
			deletedSetter.join();
322
		} catch (final InterruptedException e) {
323
			throw new IllegalStateException(e);
324
		}
325
	}
326

    
327
	@Override
328
	public void drop() {
329
		this.collection.drop();
330
	}
331

    
332
	@Override
333
	public void drop(final String queryString) {
334
		Bson query = parseQuery(queryString);
335
		final DeleteResult deleteResult = this.collection.deleteMany(query);
336
		log.debug("Deleted by query: " + queryString + " #deleted: " + deleteResult.getDeletedCount());
337
	}
338

    
339
	@Override
340
	public int count() {
341
		return (int) this.collection.count();
342
	}
343

    
344
	@Override
345
	public int count(final String queryString) {
346
		if (StringUtils.isBlank(queryString)) return (int) this.collection.count();
347
		Bson query = parseQuery(queryString);
348
		return (int) this.collection.count(query);
349
	}
350

    
351
	public List<String> getDistinctSetNamesFromRecords() {
352
		log.info("Going to ask for all distinct sets in the oaistore " + this.id + ": this may take a long time...");
353
		return Lists.newArrayList(this.collection.distinct(OAIConfigurationReader.SET_FIELD, String.class));
354
	}
355

    
356
	// ***********************************************************************************************//
357
	// Feed utilities
358
	// ***********************************************************************************************//
359
	private boolean safeFeedRecord(final String record, final String source, final Date feedDate, final MongoCollection<DBObject> unackCollection) {
360
		try {
361
			if (!record.isEmpty()) { return feedRecord(record, source, feedDate, unackCollection); }
362
		} catch (final Throwable e) {
363
			log.error("Got unhandled exception while parsing record", e);
364
			this.discardedCollection.insertOne(new BasicDBObject(OAIConfigurationReader.SET_FIELD, source).append(OAIConfigurationReader.BODY_FIELD, record));
365
		}
366
		return false;
367
	}
368

    
369
	/**
370
	 * Feed the record to the store.
371
	 *
372
	 * @return true if the record is new, false otherwise
373
	 */
374
	private boolean feedRecord(final String record, final String source, final Date feedDate, final MongoCollection<DBObject> unackCollection) {
375
		final PublisherRecordParser parser = new PublisherRecordParser(this.mongoFields);
376
		log.debug("configured parser for fields: "+this.mongoFields);
377
		final Multimap<String, String> recordProperties = parser.parseRecord(record, source);
378
		String id;
379
		String oaiID;
380
		if (recordProperties.containsKey(OAIConfigurationReader.ID_FIELD)) {
381
			id = recordProperties.get(OAIConfigurationReader.ID_FIELD).iterator().next();
382
			oaiID = getOAIIdentifier(id);
383
			if (isNewRecord(oaiID)) {
384
				feedNew(oaiID, record, recordProperties, feedDate, unackCollection);
385
				return true;
386
			} else {
387
				if (isChanged(oaiID, record)) {
388
					updateRecord(oaiID, record, recordProperties, feedDate, unackCollection);
389
				} else {
390
					// it is not changed, I only have to update the last collection date
391
					handleRecord(oaiID, feedDate, unackCollection);
392
				}
393
			}
394
		} else {
395
			log.error("parsed record seems invalid -- no identifier property with name: " + OAIConfigurationReader.ID_FIELD);
396
			log.error("Extracted property map: \n"+recordProperties);
397
			log.debug("from: \n"+record);
398
			this.discardedCollection
399
					.insertOne(new BasicDBObject(OAIConfigurationReader.SET_FIELD, source).append(OAIConfigurationReader.BODY_FIELD, record).append(
400
							OAIConfigurationReader.DATESTAMP_FIELD, feedDate));
401
		}
402
		return false;
403
	}
404

    
405
	private BasicDBObject createBasicObject(final String oaiID, final String record, final Multimap<String, String> recordProperties) {
406
		final BasicDBObject obj = new BasicDBObject();
407
		for (final String key : recordProperties.keySet()) {
408
			if (key.equals(OAIConfigurationReader.ID_FIELD)) {
409
				obj.put(key, oaiID);
410
			} else {
411
				final Collection<String> values = recordProperties.get(key);
412
				if (key.equals(OAIConfigurationReader.SET_FIELD)) {
413

    
414
					final Iterable<String> setSpecs =
415
							values.stream().map(s -> MongoPublisherStore.this.mongoSetCollection.normalizeSetSpec(s)).collect(Collectors.toList());
416
					obj.put(key, setSpecs);
417
				} else {
418
					// let's check if the key is the name of a repeatable field or not
419
					final PublisherField keyField = Iterables.find(this.mongoFields, field -> field.getFieldName().equals(key), null);
420
					if (keyField == null) {
421
						log.warn("Expected field to index: " + key + " could not be found, but we keep going...");
422
					}
423
					if ((keyField != null) && !keyField.isRepeatable()) {
424
						if ((values != null) && !values.isEmpty()) {
425
							obj.put(key, values.iterator().next());
426
						}
427
					} else {
428
						obj.put(key, values);
429
					}
430
				}
431
			}
432
		}
433
		try {
434
			obj.put(OAIConfigurationReader.BODY_FIELD, createCompressRecord(record));
435
			obj.put(OAIConfigurationReader.DELETED_FIELD, false);
436
			return obj;
437
		} catch (final IOException e) {
438
			throw new OaiPublisherRuntimeException(e);
439
		}
440
	}
441

    
442
	/**
443
	 * @param record
444
	 * @throws IOException
445
	 */
446
	public Binary createCompressRecord(final String record) throws IOException {
447
		final ByteArrayOutputStream os = new ByteArrayOutputStream();
448
		final ZipOutputStream zos = new ZipOutputStream(os);
449
		final ZipEntry entry = new ZipEntry(OAIConfigurationReader.BODY_FIELD);
450
		zos.putNextEntry(entry);
451
		zos.write(record.getBytes());
452
		zos.closeEntry();
453
		zos.flush();
454
		zos.close();
455
		return new Binary(os.toByteArray());
456
	}
457

    
458
	private void feedNew(final String oaiID,
459
			final String record,
460
			final Multimap<String, String> recordProperties,
461
			final Date feedDate,
462
			final MongoCollection<DBObject> unackCollection) {
463
		log.debug("New record received. Assigned oai id: " + oaiID);
464
		final DBObject obj = this.createBasicObject(oaiID, record, recordProperties);
465
		obj.put(OAIConfigurationReader.LAST_COLLECTION_DATE_FIELD, feedDate);
466
		obj.put(OAIConfigurationReader.DATESTAMP_FIELD, feedDate);
467
		obj.put(OAIConfigurationReader.UPDATED_FIELD, false);
468
		unackCollection.insertOne(obj);
469
		this.upsertSets(recordProperties.get(OAIConfigurationReader.SET_FIELD));
470
	}
471

    
472
	private void updateRecord(final String oaiID,
473
			final String record,
474
			final Multimap<String, String> recordProperties,
475
			final Date feedDate,
476
			final MongoCollection<DBObject> unackCollection) {
477
		log.debug("updating record " + oaiID);
478
		final BasicDBObject obj = this.createBasicObject(oaiID, record, recordProperties);
479
		obj.put(OAIConfigurationReader.LAST_COLLECTION_DATE_FIELD, feedDate);
480
		obj.put(OAIConfigurationReader.DATESTAMP_FIELD, feedDate);
481
		obj.put(OAIConfigurationReader.UPDATED_FIELD, true);
482
		final Bson oldObj = Filters.eq(OAIConfigurationReader.ID_FIELD, oaiID);
483
		unackCollection.replaceOne(oldObj, obj, new UpdateOptions().upsert(true));
484
		//		unackCollection.updateOne(oldObj, new Document("$set",obj), new UpdateOptions().upsert(true));
485
		this.upsertSets(recordProperties.get(OAIConfigurationReader.SET_FIELD));
486
	}
487

    
488
	public void upsertSets(final Iterable<String> setNames) {
489
		// feed the list of sets, if any
490
		if (setNames != null) {
491
			for (final String setName : setNames) {
492
				if (StringUtils.isNotBlank(setName)) {
493
					final SetInfo set = new SetInfo();
494
					final String setSpec = this.mongoSetCollection.normalizeSetSpec(setName);
495
					set.setSetSpec(setSpec);
496
					set.setSetName(setName);
497
					set.setSetDescription("This set contains metadata records whose provenance is " + setName);
498
					set.setEnabled(true);
499
					final String query = "(" + OAIConfigurationReader.SET_FIELD + " = \"" + setSpec + "\") ";
500
					set.setQuery(query);
501
					this.mongoSetCollection.upsertSet(set, false, getCollection().getNamespace().getDatabaseName());
502
				}
503
			}
504
		}
505
	}
506

    
507
	private void handleRecord(final String oaiID, final Date lastCollectionDate, final MongoCollection<DBObject> unackCollection) {
508
		log.debug("handling unchanged record " + oaiID);
509
		final Bson oldObj = Filters.eq(OAIConfigurationReader.ID_FIELD, oaiID);
510
		final BasicDBObject update = new BasicDBObject("$set", new BasicDBObject(OAIConfigurationReader.LAST_COLLECTION_DATE_FIELD, lastCollectionDate));
511
		unackCollection.updateOne(oldObj, update, new UpdateOptions().upsert(true));
512
	}
513

    
514
	private boolean isNewRecord(final String oaiIdentifier) {
515
		if (this.alwaysNewRecord || (this.collection.count() == 0)) { return true; }
516
		return this.collection.find(Filters.eq(OAIConfigurationReader.ID_FIELD, oaiIdentifier)).first() == null;
517
	}
518

    
519
	// ***********************************************************************************************//
520
	// Setters / Getters / Basic utilities
521
	// ***********************************************************************************************//
522

    
523
	private boolean isChanged(final String oaiID, final String record) {
524
		final RecordInfo oldRecord = getRecord(oaiID);
525
		if (oldRecord == null) { return StringUtils.isBlank(record); }
526
		return this.recordChangeDetector.differs(oldRecord.getMetadata(), record);
527
	}
528

    
529
	private String getOAIIdentifier(final String id) {
530
		return this.idScheme + ":" + this.idNamespace + ":" + id;
531
	}
532

    
533
	@Override
534
	public int hashCode() {
535
		final int prime = 31;
536
		int result = 1;
537
		result = (prime * result) + ((this.collection == null) ? 0 : this.collection.hashCode());
538
		result = (prime * result) + ((this.id == null) ? 0 : this.id.hashCode());
539
		result = (prime * result) + ((this.interpretation == null) ? 0 : this.interpretation.hashCode());
540
		result = (prime * result) + ((this.layout == null) ? 0 : this.layout.hashCode());
541
		result = (prime * result) + ((this.metadataFormat == null) ? 0 : this.metadataFormat.hashCode());
542
		return result;
543
	}
544

    
545
	@Override
546
	public boolean equals(final Object obj) {
547
		if (this == obj) { return true; }
548
		if (obj == null) { return false; }
549
		if (!(obj instanceof MongoPublisherStore)) { return false; }
550
		final MongoPublisherStore other = (MongoPublisherStore) obj;
551
		if (this.collection == null) {
552
			if (other.collection != null) { return false; }
553
		} else if (!this.collection.equals(other.collection)) { return false; }
554
		if (this.id == null) {
555
			if (other.id != null) { return false; }
556
		} else if (!this.id.equals(other.id)) { return false; }
557
		if (this.interpretation == null) {
558
			if (other.interpretation != null) { return false; }
559
		} else if (!this.interpretation.equals(other.interpretation)) { return false; }
560
		if (this.layout == null) {
561
			if (other.layout != null) { return false; }
562
		} else if (!this.layout.equals(other.layout)) { return false; }
563
		if (this.metadataFormat == null) {
564
			if (other.metadataFormat != null) { return false; }
565
		} else if (!this.metadataFormat.equals(other.metadataFormat)) { return false; }
566
		return true;
567
	}
568

    
569
	public MongoCollection<DBObject> getCollection() {
570
		return this.collection;
571
	}
572

    
573
	public void setCollection(final MongoCollection<DBObject> collection) {
574
		this.collection = collection;
575
	}
576

    
577
	public MongoCollection<DBObject> getDiscardedCollection() {
578
		return this.discardedCollection;
579
	}
580

    
581
	public void setDiscardedCollection(final MongoCollection<DBObject> discardedCollection) {
582
		this.discardedCollection = discardedCollection;
583
	}
584

    
585
	public String getIdScheme() {
586
		return this.idScheme;
587
	}
588

    
589
	public void setIdScheme(final String idScheme) {
590
		this.idScheme = idScheme;
591
	}
592

    
593
	public String getIdNamespace() {
594
		return this.idNamespace;
595
	}
596

    
597
	public void setIdNamespace(final String idNamespace) {
598
		this.idNamespace = idNamespace;
599
	}
600

    
601
	public RecordInfoGenerator getRecordInfoGenerator() {
602
		return this.recordInfoGenerator;
603
	}
604

    
605
	public void setRecordInfoGenerator(final RecordInfoGenerator recordInfoGenerator) {
606
		this.recordInfoGenerator = recordInfoGenerator;
607
	}
608

    
609
	public MetadataExtractor getMetadataExtractor() {
610
		return this.metadataExtractor;
611
	}
612

    
613
	public void setMetadataExtractor(final MetadataExtractor metadataExtractor) {
614
		this.metadataExtractor = metadataExtractor;
615
	}
616

    
617
	public RecordChangeDetector getRecordChangeDetector() {
618
		return this.recordChangeDetector;
619
	}
620

    
621
	public void setRecordChangeDetector(final RecordChangeDetector recordChangeDetector) {
622
		this.recordChangeDetector = recordChangeDetector;
623
	}
624

    
625
	@Override
626
	public String getId() {
627
		return this.id;
628
	}
629

    
630
	public void setId(final String id) {
631
		this.id = id;
632
	}
633

    
634
	@Override
635
	public String getMetadataFormat() {
636
		return this.metadataFormat;
637
	}
638

    
639
	public void setMetadataFormat(final String metadataFormat) {
640
		this.metadataFormat = metadataFormat;
641
	}
642

    
643
	@Override
644
	public String getInterpretation() {
645
		return this.interpretation;
646
	}
647

    
648
	public void setInterpretation(final String interpretation) {
649
		this.interpretation = interpretation;
650
	}
651

    
652
	@Override
653
	public String getLayout() {
654
		return this.layout;
655
	}
656

    
657
	public void setLayout(final String layout) {
658
		this.layout = layout;
659
	}
660

    
661
	public MongoSetCollection getMongoSetCollection() {
662
		return this.mongoSetCollection;
663
	}
664

    
665
	public void setMongoSetCollection(final MongoSetCollection mongoSetCollection) {
666
		this.mongoSetCollection = mongoSetCollection;
667
	}
668

    
669
	public List<PublisherField> getMongoFields() {
670
		return this.mongoFields;
671
	}
672

    
673
	public void setMongoFields(final List<PublisherField> mongoFields) {
674
		this.mongoFields = mongoFields;
675
	}
676

    
677
	public boolean isAlwaysNewRecord() {
678
		return this.alwaysNewRecord;
679
	}
680

    
681
	public void setAlwaysNewRecord(final boolean alwaysNewRecord) {
682
		this.alwaysNewRecord = alwaysNewRecord;
683
	}
684

    
685
}
(3-3/6)