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

    
51
public class MongoPublisherStore implements PublisherStore<DNetOAIMongoCursor> {
52

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

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

    
61
	private MongoCollection<DBObject> collection;
62
	private MongoCollection<DBObject> discardedCollection;
63

    
64
	private CqlTranslator cqlTranslator;
65
	private RecordInfoGenerator recordInfoGenerator;
66
	private MetadataExtractor metadataExtractor;
67

    
68
	private RecordChangeDetector recordChangeDetector;
69

    
70
	private MongoSetCollection mongoSetCollection;
71

    
72

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

    
84
	private boolean alwaysNewRecord;
85

    
86
	public MongoPublisherStore() {
87
		super();
88
	}
89

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

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

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

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

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

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

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

    
172

    
173
	@Override
174
	public List<PublisherField> getIndices() {
175
		return this.mongoFields;
176
	}
177

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

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

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

    
243
	@Override
244
	public int feed(final Iterable<String> records, final String source) {
245
		final BlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(80);
246
		final Object sentinel = new Object();
247
		this.dropDiscarded(source);
248
		final Date feedDate = new Date();
249
		final Thread background = new Thread(new Runnable() {
250

    
251
			@Override
252
			public void run() {
253
				// For fast feeding we want to use a collection with unack write concern
254
				final MongoCollection<DBObject> unackCollection = MongoPublisherStore.this.collection.withWriteConcern(WriteConcern.UNACKNOWLEDGED);
255
				while (true) {
256
					try {
257
						final Object record = queue.take();
258
						if (record == sentinel) {
259
							break;
260
						}
261
						safeFeedRecord((String) record, source, feedDate, unackCollection);
262
					} catch (final InterruptedException e) {
263
						log.fatal("got exception in background thread", e);
264
						throw new IllegalStateException(e);
265
					}
266
				}
267
			}
268
		});
269
		background.start();
270
		final long startFeed = feedDate.getTime();
271
		try {
272
			log.info("feeding publisherStore " + this.id);
273
			for (final String record : records) {
274
				queue.put(record);
275
			}
276
			queue.put(sentinel);
277
			log.info("finished feeding publisherStore " + this.id);
278

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

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

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

    
326
		deletedSetter.start();
327
		try {
328
			deletedSetter.join();
329
		} catch (final InterruptedException e) {
330
			throw new IllegalStateException(e);
331
		}
332
	}
333

    
334
	@Override
335
	public void drop() {
336
		this.collection.drop();
337
	}
338

    
339
	@Override
340
	public void drop(final String queryString) {
341
		Bson query = parseQuery(queryString);
342
		final DeleteResult deleteResult = this.collection.deleteMany(query);
343
		log.debug("Deleted by query: " + queryString + " #deleted: " + deleteResult.getDeletedCount());
344
	}
345

    
346
	@Override
347
	public int count() {
348
		return (int) this.collection.count();
349
	}
350

    
351
	@Override
352
	public int count(final String queryString) {
353
		if (StringUtils.isBlank(queryString)) return (int) this.collection.count();
354
		Bson query = parseQuery(queryString);
355
		return (int) this.collection.count(query);
356
	}
357

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

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

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

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

    
418
					final Iterable<String> setSpecs =
419
							values.stream().map(s -> MongoPublisherStore.this.mongoSetCollection.normalizeSetSpec(s)).collect(Collectors.toList());
420

    
421
					obj.put(key, setSpecs);
422
				} else {
423
					// let's check if the key is the name of a repeatable field or not
424
					final PublisherField keyField = Iterables.find(this.mongoFields, new Predicate<PublisherField>() {
425

    
426
						@Override
427
						public boolean apply(final PublisherField field) {
428
							return field.getFieldName().equals(key);
429
						}
430
					}, null);
431
					if (keyField == null) {
432
						log.warn("Expected field to index: " + key + " could not be found, but we keep going...");
433
					}
434
					if ((keyField != null) && !keyField.isRepeatable()) {
435
						if ((values != null) && !values.isEmpty()) {
436
							obj.put(key, values.iterator().next());
437
						}
438
					} else {
439
						obj.put(key, values);
440
					}
441
				}
442
			}
443
		}
444
		try {
445
			obj.put(OAIConfigurationReader.BODY_FIELD, createCompressRecord(record));
446
			obj.put(OAIConfigurationReader.DELETED_FIELD, false);
447
			return obj;
448
		} catch (final IOException e) {
449
			throw new OaiPublisherRuntimeException(e);
450
		}
451
	}
452

    
453
	/**
454
	 * @param record
455
	 * @throws IOException
456
	 */
457
	public Binary createCompressRecord(final String record) throws IOException {
458
		final ByteArrayOutputStream os = new ByteArrayOutputStream();
459
		final ZipOutputStream zos = new ZipOutputStream(os);
460
		final ZipEntry entry = new ZipEntry(OAIConfigurationReader.BODY_FIELD);
461
		zos.putNextEntry(entry);
462
		zos.write(record.getBytes());
463
		zos.closeEntry();
464
		zos.flush();
465
		zos.close();
466
		return new Binary(os.toByteArray());
467
	}
468

    
469
	private void feedNew(final String oaiID,
470
			final String record,
471
			final Multimap<String, String> recordProperties,
472
			final Date feedDate,
473
			final MongoCollection<DBObject> unackCollection) {
474
		log.debug("New record received. Assigned oai id: " + oaiID);
475
		final DBObject obj = this.createBasicObject(oaiID, record, recordProperties);
476
		obj.put(OAIConfigurationReader.LAST_COLLECTION_DATE_FIELD, feedDate);
477
		obj.put(OAIConfigurationReader.DATESTAMP_FIELD, feedDate);
478
		obj.put(OAIConfigurationReader.UPDATED_FIELD, false);
479
		unackCollection.insertOne(obj);
480
		this.upsertSets(recordProperties.get(OAIConfigurationReader.SET_FIELD));
481
	}
482

    
483
	private void updateRecord(final String oaiID,
484
			final String record,
485
			final Multimap<String, String> recordProperties,
486
			final Date feedDate,
487
			final MongoCollection<DBObject> unackCollection) {
488
		log.debug("updating record " + oaiID);
489
		final BasicDBObject obj = this.createBasicObject(oaiID, record, recordProperties);
490
		obj.put(OAIConfigurationReader.LAST_COLLECTION_DATE_FIELD, feedDate);
491
		obj.put(OAIConfigurationReader.DATESTAMP_FIELD, feedDate);
492
		obj.put(OAIConfigurationReader.UPDATED_FIELD, true);
493
		final Bson oldObj = Filters.eq(OAIConfigurationReader.ID_FIELD, oaiID);
494
		unackCollection.replaceOne(oldObj, obj, new UpdateOptions().upsert(true));
495
		//		unackCollection.updateOne(oldObj, new Document("$set",obj), new UpdateOptions().upsert(true));
496
		this.upsertSets(recordProperties.get(OAIConfigurationReader.SET_FIELD));
497
	}
498

    
499
	public void upsertSets(final Iterable<String> setNames) {
500
		// feed the list of sets, if any
501
		if (setNames != null) {
502
			for (final String setName : setNames) {
503
				if (StringUtils.isNotBlank(setName)) {
504
					final SetInfo set = new SetInfo();
505
					final String setSpec = this.mongoSetCollection.normalizeSetSpec(setName);
506
					set.setSetSpec(setSpec);
507
					set.setSetName(setName);
508
					set.setSetDescription("This set contains metadata records whose provenance is " + setName);
509
					set.setEnabled(true);
510
					final String query = "(" + OAIConfigurationReader.SET_FIELD + " = \"" + setSpec + "\") ";
511
					set.setQuery(query);
512
					this.mongoSetCollection.upsertSet(set, false, getCollection().getNamespace().getDatabaseName());
513
				}
514
			}
515
		}
516
	}
517

    
518
	private void handleRecord(final String oaiID, final Date lastCollectionDate, final MongoCollection<DBObject> unackCollection) {
519
		log.debug("handling unchanged record " + oaiID);
520
		final Bson oldObj = Filters.eq(OAIConfigurationReader.ID_FIELD, oaiID);
521
		final BasicDBObject update = new BasicDBObject("$set", new BasicDBObject(OAIConfigurationReader.LAST_COLLECTION_DATE_FIELD, lastCollectionDate));
522
		unackCollection.updateOne(oldObj, update, new UpdateOptions().upsert(true));
523
	}
524

    
525
	private boolean isNewRecord(final String oaiIdentifier) {
526
		if (this.alwaysNewRecord || (this.collection.count() == 0)) { return true; }
527
		return this.collection.find(Filters.eq(OAIConfigurationReader.ID_FIELD, oaiIdentifier)).first() == null;
528
	}
529

    
530
	// ***********************************************************************************************//
531
	// Setters / Getters / Basic utilities
532
	// ***********************************************************************************************//
533

    
534
	private boolean isChanged(final String oaiID, final String record) {
535
		final RecordInfo oldRecord = getRecord(oaiID);
536
		if (oldRecord == null) { return StringUtils.isBlank(record); }
537
		return this.recordChangeDetector.differs(oldRecord.getMetadata(), record);
538
	}
539

    
540
	private String getOAIIdentifier(final String id) {
541
		return this.idScheme + ":" + this.idNamespace + ":" + id;
542
	}
543

    
544
	@Override
545
	public int hashCode() {
546
		final int prime = 31;
547
		int result = 1;
548
		result = (prime * result) + ((this.collection == null) ? 0 : this.collection.hashCode());
549
		result = (prime * result) + ((this.id == null) ? 0 : this.id.hashCode());
550
		result = (prime * result) + ((this.interpretation == null) ? 0 : this.interpretation.hashCode());
551
		result = (prime * result) + ((this.layout == null) ? 0 : this.layout.hashCode());
552
		result = (prime * result) + ((this.metadataFormat == null) ? 0 : this.metadataFormat.hashCode());
553
		return result;
554
	}
555

    
556
	@Override
557
	public boolean equals(final Object obj) {
558
		if (this == obj) { return true; }
559
		if (obj == null) { return false; }
560
		if (!(obj instanceof MongoPublisherStore)) { return false; }
561
		final MongoPublisherStore other = (MongoPublisherStore) obj;
562
		if (this.collection == null) {
563
			if (other.collection != null) { return false; }
564
		} else if (!this.collection.equals(other.collection)) { return false; }
565
		if (this.id == null) {
566
			if (other.id != null) { return false; }
567
		} else if (!this.id.equals(other.id)) { return false; }
568
		if (this.interpretation == null) {
569
			if (other.interpretation != null) { return false; }
570
		} else if (!this.interpretation.equals(other.interpretation)) { return false; }
571
		if (this.layout == null) {
572
			if (other.layout != null) { return false; }
573
		} else if (!this.layout.equals(other.layout)) { return false; }
574
		if (this.metadataFormat == null) {
575
			if (other.metadataFormat != null) { return false; }
576
		} else if (!this.metadataFormat.equals(other.metadataFormat)) { return false; }
577
		return true;
578
	}
579

    
580
	public MongoCollection<DBObject> getCollection() {
581
		return this.collection;
582
	}
583

    
584
	public void setCollection(final MongoCollection<DBObject> collection) {
585
		this.collection = collection;
586
	}
587

    
588
	public MongoCollection<DBObject> getDiscardedCollection() {
589
		return this.discardedCollection;
590
	}
591

    
592
	public void setDiscardedCollection(final MongoCollection<DBObject> discardedCollection) {
593
		this.discardedCollection = discardedCollection;
594
	}
595

    
596
	public String getIdScheme() {
597
		return this.idScheme;
598
	}
599

    
600
	public void setIdScheme(final String idScheme) {
601
		this.idScheme = idScheme;
602
	}
603

    
604
	public String getIdNamespace() {
605
		return this.idNamespace;
606
	}
607

    
608
	public void setIdNamespace(final String idNamespace) {
609
		this.idNamespace = idNamespace;
610
	}
611

    
612
	public RecordInfoGenerator getRecordInfoGenerator() {
613
		return this.recordInfoGenerator;
614
	}
615

    
616
	public void setRecordInfoGenerator(final RecordInfoGenerator recordInfoGenerator) {
617
		this.recordInfoGenerator = recordInfoGenerator;
618
	}
619

    
620
	public MetadataExtractor getMetadataExtractor() {
621
		return this.metadataExtractor;
622
	}
623

    
624
	public void setMetadataExtractor(final MetadataExtractor metadataExtractor) {
625
		this.metadataExtractor = metadataExtractor;
626
	}
627

    
628
	public RecordChangeDetector getRecordChangeDetector() {
629
		return this.recordChangeDetector;
630
	}
631

    
632
	public void setRecordChangeDetector(final RecordChangeDetector recordChangeDetector) {
633
		this.recordChangeDetector = recordChangeDetector;
634
	}
635

    
636
	@Override
637
	public String getId() {
638
		return this.id;
639
	}
640

    
641
	public void setId(final String id) {
642
		this.id = id;
643
	}
644

    
645
	@Override
646
	public String getMetadataFormat() {
647
		return this.metadataFormat;
648
	}
649

    
650
	public void setMetadataFormat(final String metadataFormat) {
651
		this.metadataFormat = metadataFormat;
652
	}
653

    
654
	@Override
655
	public String getInterpretation() {
656
		return this.interpretation;
657
	}
658

    
659
	public void setInterpretation(final String interpretation) {
660
		this.interpretation = interpretation;
661
	}
662

    
663
	@Override
664
	public String getLayout() {
665
		return this.layout;
666
	}
667

    
668
	public void setLayout(final String layout) {
669
		this.layout = layout;
670
	}
671

    
672
	public MongoSetCollection getMongoSetCollection() {
673
		return this.mongoSetCollection;
674
	}
675

    
676
	public void setMongoSetCollection(final MongoSetCollection mongoSetCollection) {
677
		this.mongoSetCollection = mongoSetCollection;
678
	}
679

    
680
	public List<PublisherField> getMongoFields() {
681
		return this.mongoFields;
682
	}
683

    
684
	public void setMongoFields(final List<PublisherField> mongoFields) {
685
		this.mongoFields = mongoFields;
686
	}
687

    
688
	public boolean isAlwaysNewRecord() {
689
		return this.alwaysNewRecord;
690
	}
691

    
692
	public void setAlwaysNewRecord(final boolean alwaysNewRecord) {
693
		this.alwaysNewRecord = alwaysNewRecord;
694
	}
695

    
696
}
(3-3/6)