Project

General

Profile

1
package eu.dnetlib.xml.database.exist; // NOPMD
2

    
3
import java.io.File;
4
import java.io.FileOutputStream;
5
import java.io.FileWriter;
6
import java.io.IOException;
7
import java.text.SimpleDateFormat;
8
import java.util.*;
9
import java.util.Map.Entry;
10
import java.util.concurrent.locks.Lock;
11
import java.util.concurrent.locks.ReentrantReadWriteLock;
12
import java.util.zip.ZipEntry;
13
import java.util.zip.ZipOutputStream;
14

    
15
import org.apache.commons.logging.Log;
16
import org.apache.commons.logging.LogFactory;
17
import org.exist.collections.CollectionConfiguration;
18
import org.exist.util.DatabaseConfigurationException;
19
import org.exist.xmldb.DatabaseImpl;
20
import org.exist.xmldb.DatabaseInstanceManager;
21
import org.exist.xmldb.EXistResource;
22
import org.exist.xmldb.XmldbURI;
23
import org.springframework.beans.factory.annotation.Required;
24
import org.springframework.context.Lifecycle;
25
import org.xmldb.api.DatabaseManager;
26
import org.xmldb.api.base.*;
27
import org.xmldb.api.base.Collection;
28
import org.xmldb.api.modules.CollectionManagementService;
29
import org.xmldb.api.modules.XPathQueryService;
30

    
31
import eu.dnetlib.miscutils.datetime.DateUtils;
32
import eu.dnetlib.xml.database.Trigger;
33

    
34
import eu.dnetlib.xml.database.XMLDatabase;
35

    
36
/**
37
 * eXist database wrapper.
38
 *
39
 * @author marko
40
 *
41
 */
42
public class ExistDatabase implements XMLDatabase, Lifecycle { // NOPMD by marko
43

    
44
	/**
45
	 * logger.
46
	 */
47
	private static final Log log = LogFactory.getLog(ExistDatabase.class); // NOPMD
48

    
49
	/**
50
	 * eXist collection configuration special file.
51
	 */
52
	public static final String COLLECTION_XCONF = "collection.xconf";
53

    
54
	/**
55
	 * exist xml resource type code.
56
	 */
57
	private static final String XMLRESOURCE = "XMLResource";
58

    
59
	/**
60
	 * collection name to trigger instance map.
61
	 *
62
	 * all triggers declared here will be registered at startup.
63
	 *
64
	 */
65
	private Map<String, Trigger> triggerConf = new HashMap<>();
66

    
67
	private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
68

    
69
	private final Lock readLock = rwl.readLock();
70

    
71
	private final Lock writeLock = rwl.writeLock();
72

    
73
	/**
74
	 * eXist database.
75
	 */
76
	private Database database;
77

    
78
	/**
79
	 * eXist collection.
80
	 */
81
	private Collection root;
82
	/**
83
	 * eXist database manager.
84
	 */
85
	private DatabaseInstanceManager manager;
86
	/**
87
	 * eXist xpath service.
88
	 */
89
	private XPathQueryService queryService;
90
	/**
91
	 * eXist collection manager.
92
	 */
93
	private CollectionManagementService colman;
94
	/**
95
	 * eXist configuration file.
96
	 */
97
	private String configFile;
98

    
99
	/**
100
	 * Directory in which backups are saved.
101
	 */
102
	private String backupDir;
103

    
104
	/**
105
	 * {@inheritDoc}
106
	 *
107
	 * @see org.springframework.context.Lifecycle#start()
108
	 */
109
	@Override
110
	public void start() {
111
		log.info("starting database");
112
		try {
113
			if (getDatabase() == null) {
114
				setDatabase(new DatabaseImpl());
115
				getDatabase().setProperty("configuration", getConfigFile());
116
				getDatabase().setProperty("create-database", "true");
117
			}
118

    
119
			DatabaseManager.registerDatabase(getDatabase());
120

    
121
			setRoot(DatabaseManager.getCollection("xmldb:exist://" + getRootCollection(), "admin", ""));
122
			setManager((DatabaseInstanceManager) getRoot().getService("DatabaseInstanceManager", "1.0"));
123
			setQueryService((XPathQueryService) getRoot().getService("XPathQueryService", "1.0"));
124
			setColman((CollectionManagementService) getRoot().getService("CollectionManagementService", "1.0"));
125

    
126
			for (final Entry<String, Trigger> entry : getTriggerConf().entrySet())
127
				registerTrigger(entry.getValue(), entry.getKey());
128

    
129
		} catch (final XMLDBException e) {
130
			throw new IllegalStateException("cannot open eXist database", e);
131
		}
132
	}
133

    
134
	/**
135
	 * helper method.
136
	 *
137
	 * @param collection
138
	 *            collection name
139
	 * @return an eXist collection
140
	 * @throws XMLDBException
141
	 *             happens
142
	 */
143
	protected Collection getCollection(final String collection) throws XMLDBException {
144
		readLock.lock();
145
		try {
146
			if (!collection.startsWith("/db"))
147
				throw new XMLDBException(0, "collection path should begin with /db");
148
			return database.getCollection("exist://" + collection, "admin", "");
149
		}finally {
150
			readLock.unlock();
151
		}
152
	}
153

    
154
	/**
155
	 * {@inheritDoc}
156
	 *
157
	 * @see eu.dnetlib.xml.database.XMLDatabase#create(java.lang.String, java.lang.String, java.lang.String)
158
	 */
159
	@Override
160
	public void create(final String name, final String collection, final String content) throws XMLDBException {
161
		writeLock.lock();
162
		try {
163
			if ("".equals(name))
164
				throw new XMLDBException(0, "cannot create a xml file with an empty file name");
165

    
166
			Collection col = getCollection(collection);
167

    
168
			if (col == null) {
169
				// create parent collections
170
				createCollection(collection, true);
171
				col = getCollection(collection);
172
			}
173

    
174
			final Resource res = col.createResource(name, XMLRESOURCE);
175
			res.setContent(content);
176
			col.storeResource(res);
177

    
178
			((EXistResource) res).freeResources();
179
			col.close();
180
		}finally {
181
			writeLock.unlock();
182
		}
183
	}
184

    
185
	/**
186
	 * {@inheritDoc}
187
	 *
188
	 * @see eu.dnetlib.xml.database.XMLDatabase#remove(java.lang.String, java.lang.String)
189
	 */
190
	@Override
191
	public boolean remove(final String name, final String collection) throws XMLDBException {
192
		writeLock.lock();
193
		try {
194
			final Collection col = getCollection(collection);
195

    
196
			final Resource res = col.getResource(name);
197
			if (res == null)
198
				return false;
199

    
200
			col.removeResource(res);
201
			col.close();
202
			return true;
203
		} finally {
204
			writeLock.unlock();
205
		}
206
	}
207

    
208
	/**
209
	 * {@inheritDoc}
210
	 *
211
	 * @see eu.dnetlib.xml.database.XMLDatabase#update(java.lang.String, java.lang.String, java.lang.String)
212
	 */
213
	@Override
214
	public void update(final String name, final String collection, final String content) throws XMLDBException {
215
		writeLock.lock();
216
		try{
217
			final Collection col = getCollection(collection);
218

    
219
			final Resource res = col.getResource(name);
220
			if (res == null) {
221
				throw new XMLDBException(0, "resource doesn't exist");
222
			}
223
			res.setContent(content);
224
			col.storeResource(res);
225
			((EXistResource) res).freeResources();
226
			col.close();
227
		} finally {
228
			writeLock.unlock();
229
		}
230

    
231
	}
232

    
233
	/**
234
	 * {@inheritDoc}
235
	 *
236
	 * @see eu.dnetlib.xml.database.XMLDatabase#read(java.lang.String, java.lang.String)
237
	 */
238
	@Override
239
	public String read(final String name, final String collection) throws XMLDBException {
240
		readLock.lock();
241
		try {
242
			Resource res = null;
243
			final Collection coll = getCollection(collection);
244
			try {
245
				if (coll == null)
246
					return null;
247
				res = coll.getResource(name);
248
				if (res != null)
249
					return (String) res.getContent();
250
				return null;
251
			} finally {
252
				if (res != null)
253
					((EXistResource) res).freeResources();
254
				coll.close();
255
			}
256
		}finally {
257
			readLock.unlock();
258
		}
259
	}
260

    
261
	/**
262
	 * {@inheritDoc}
263
	 *
264
	 * @see eu.dnetlib.xml.database.XMLDatabase#xquery(java.lang.String)
265
	 */
266
	@Override
267
	public Iterator<String> xquery(final String query) throws XMLDBException {
268
		readLock.lock();
269
		try {
270
			final ResourceSet result = getQueryService().query(query);
271
			if (result == null)
272
				return null;
273
			final ResourceIterator iterator = result.getIterator();
274
			return new Iterator<String>() {
275
				@Override
276
				public boolean hasNext() {
277
					try {
278
						return iterator.hasMoreResources();
279
					} catch (XMLDBException e) {
280
						throw new RuntimeException("Error while getting next element", e);
281
					}
282
				}
283

    
284
				@Override
285
				public String next() {
286
					Resource res = null;
287
					try {
288
						res = iterator.nextResource();
289
						return (String) res.getContent();
290
					} catch (XMLDBException e) {
291
						throw new RuntimeException("Error while getting next element", e);
292
					} finally {
293
						if (res != null)
294
							try {
295
								((EXistResource) res).freeResources();
296
							} catch (XMLDBException e) {
297
								log.error("error on free resource");
298
							}
299
					}
300
				}
301
			};
302
		} finally {
303
			readLock.unlock();
304
		}
305
	}
306

    
307

    
308
	@Override
309
	public void xupdate(final String query) throws XMLDBException {
310
		writeLock.lock();
311
		try{
312
			getQueryService().query(query);
313
		} finally {
314
			writeLock.unlock();
315
		}
316

    
317

    
318
	}
319

    
320

    
321
	/**
322
	 * {@inheritDoc}
323
	 *
324
	 * @see org.springframework.context.Lifecycle#stop()
325
	 */
326
	@Override
327
	public void stop() {
328
		// no operation
329
		try {
330
			getManager().shutdown();
331
			DatabaseManager.deregisterDatabase(database);
332
		} catch (final XMLDBException e) {
333
			log.fatal("cannot close database", e);
334
		}
335
	}
336

    
337
	/**
338
	 * {@inheritDoc}
339
	 *
340
	 * @see eu.dnetlib.xml.database.XMLDatabase#collectionExists(java.lang.String)
341
	 */
342
	@Override
343
	public boolean collectionExists(final String collection) throws XMLDBException {
344
		Collection col = null;
345
		try{
346
			col = getCollection(collection);
347
			return col != null;
348
		} finally {
349
			if (col!=null)
350
				col.close();
351
		}
352

    
353
	}
354

    
355
	/**
356
	 * {@inheritDoc}
357
	 *
358
	 * @see eu.dnetlib.xml.database.XMLDatabase#createCollection(java.lang.String)
359
	 */
360
	@Override
361
	public void createCollection(final String collection) throws XMLDBException {
362
		writeLock.lock();
363
		try {
364
			createCollection(collection, false);
365
		}finally {
366
			writeLock.unlock();
367
		}
368
	}
369

    
370

    
371

    
372
	private void createCollection(final String collection, final boolean recursive) throws XMLDBException {
373
		if (recursive) {
374
			final XmldbURI uri = XmldbURI.create(collection).removeLastSegment();
375
			if (!collectionExists(uri.toString()))
376
				createCollection(uri.toString(), true);
377
		}
378
		getColman().createCollection(collection);
379
	}
380

    
381
	/**
382
	 * {@inheritDoc}
383
	 *
384
	 * @see eu.dnetlib.xml.database.XMLDatabase#removeCollection(java.lang.String)
385
	 */
386
	@Override
387
	public void removeCollection(final String collection) throws XMLDBException {
388
		writeLock.lock();
389
		try {
390
			getColman().removeCollection(collection);
391
		}finally {
392
			writeLock.unlock();
393
		}
394
	}
395

    
396
	public String getConfigFile() {
397
		return configFile;
398
	}
399

    
400
	public void setConfigFile(final String configFile) {
401
		this.configFile = configFile;
402
	}
403

    
404

    
405
	@Override
406
	public String getBackupDir() {
407
		return backupDir;
408
	}
409

    
410
	@Required
411
	public void setBackupDir(final String backupDir) {
412
		this.backupDir = backupDir;
413
	}
414

    
415

    
416

    
417
	/**
418
	 * {@inheritDoc}
419
	 *
420
	 * @see org.springframework.context.Lifecycle#isRunning() useless contract with spring.
421
	 *
422
	 */
423
	@Override
424
	public boolean isRunning() {
425
		return false;
426
	}
427

    
428
	protected Database getDatabase() {
429
		return database;
430
	}
431

    
432
	protected void setDatabase(final Database database) {
433
		this.database = database;
434
	}
435

    
436
	protected Collection getRoot() {
437
		return root;
438
	}
439

    
440
	protected void setRoot(final Collection root) {
441
		this.root = root;
442
	}
443

    
444
	protected DatabaseInstanceManager getManager() {
445
		return manager;
446
	}
447

    
448
	protected void setManager(final DatabaseInstanceManager manager) {
449
		this.manager = manager;
450
	}
451

    
452
	protected XPathQueryService getQueryService() {
453
		return queryService;
454
	}
455

    
456
	protected void setQueryService(final XPathQueryService queryService) {
457
		this.queryService = queryService;
458
	}
459

    
460
	protected CollectionManagementService getColman() {
461
		return colman;
462
	}
463

    
464
	protected void setColman(final CollectionManagementService colman) {
465
		this.colman = colman;
466
	}
467

    
468
	@Override
469
	public String getRootCollection() {
470
		return "/db";
471
	}
472

    
473
	/**
474
	 * {@inheritDoc}
475
	 *
476
	 * @see eu.dnetlib.xml.database.XMLDatabase#listChildCollections(java.lang.String)
477
	 */
478
	@Override
479
	public List<String> listChildCollections(final String collection) throws XMLDBException {
480
		readLock.lock();
481
		try {
482
			final Collection col = getCollection(collection);
483
			if (col == null)
484
				return new ArrayList<>();
485
			return Arrays.asList(col.listChildCollections());
486
		} finally {
487
			readLock.unlock();
488
		}
489
	}
490

    
491
	/**
492
	 * {@inheritDoc}
493
	 *
494
	 * @see eu.dnetlib.xml.database.XMLDatabase#list(java.lang.String)
495
	 */
496
	@Override
497
	public List<String> list(final String collection) throws XMLDBException {
498
		readLock.lock();
499
		try {
500
			final Collection col = getCollection(collection);
501
			if(col == null)
502
				return new ArrayList();
503
			return Arrays.asList(col.listResources());
504
		} finally {
505
			readLock.unlock();
506
		}
507
	}
508

    
509
	/**
510
	 * sets an underlying eXist trigger class for a given collection.
511
	 *
512
	 * @param triggerClass
513
	 *            exist trigger class
514
	 * @param collection
515
	 *            collection name
516
	 * @param events
517
	 *            list of event names
518
	 * @param parameters
519
	 *            parameter map
520
	 * @throws XMLDBException
521
	 *             happens
522
	 */
523
	void setExistTrigger(final Class<?> triggerClass, final String collection, final List<String> events, final Map<String, String> parameters)
524
			throws XMLDBException {
525

    
526
		// Arrays.asList(new String[] { "store", "update", "delete" }
527

    
528
		final StringBuilder conf = new StringBuilder();
529
		conf.append("<exist:collection xmlns:exist=\"http://exist-db.org/collection-config/1.0\"><exist:triggers>");
530

    
531
		final String className = triggerClass.getCanonicalName(); // PMD
532

    
533
		conf.append("<exist:trigger event=\"store,update,remove\" class=\"" + className + "\">");
534
		if (parameters != null)
535
			for (final Entry<String, String> entry : parameters.entrySet())
536
				conf.append("<exist:parameter name=\"" + entry.getKey() + "\" value=\"" + entry.getValue() + "\"/>");
537
		conf.append("</exist:trigger>");
538

    
539
		conf.append("</exist:triggers></exist:collection>");
540

    
541
		log.info(conf.toString());
542
		createCollection("/db/system/config" + collection, true);
543
		create(CollectionConfiguration.DEFAULT_COLLECTION_CONFIG_FILE_URI.toString(), "/db/system/config" + collection, conf.toString());
544
	}
545

    
546
	/**
547
	 * {@inheritDoc}
548
	 *
549
	 * @see eu.dnetlib.xml.database.XMLDatabase#registerTrigger(eu.dnetlib.xml.database.Trigger, java.lang.String)
550
	 */
551
	@Override
552
	public void registerTrigger(final Trigger trigger, final String collection) throws XMLDBException {
553
		final Map<String, String> params = new HashMap<String, String>();
554
		params.put("triggerName", trigger.getName());
555

    
556
		ExistTriggerRegistry.defaultInstance().registerTrigger(trigger.getName(), trigger);
557

    
558
		setExistTrigger(DelegatingDiffTrigger.class, collection, Arrays.asList("store", "update", "delete" ), params);
559
	}
560

    
561
	public Map<String, Trigger> getTriggerConf() {
562
		return triggerConf;
563
	}
564

    
565
	public void setTriggerConf(final Map<String, Trigger> triggerConf) {
566
		this.triggerConf = triggerConf;
567
	}
568

    
569
	/**
570
	 * {@inheritDoc}
571
	 *
572
	 * @see eu.dnetlib.xml.database.XMLDatabase#backup()
573
	 */
574
	@Override
575
	public String backup() throws XMLDBException, DatabaseConfigurationException {
576
		log.info("Starting backup...");
577
		readLock.lock();
578
		try {
579
			verifyBackupDir();
580

    
581
			String seq = (new SimpleDateFormat("yyyyMMdd-HHmm")).format(new Date());
582

    
583
			ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(backupDir + "/data-" + seq + ".zip"));
584

    
585
			FileWriter logFile = new FileWriter(backupDir + "/report-" + seq + ".log");
586
			logFile.write("Backup started at: " + DateUtils.now_ISO8601() + "\n\n");
587

    
588
			backup(getRoot().getName(), zip, logFile);
589

    
590
			logFile.write("\nBackup finished at: " + DateUtils.now_ISO8601() + "\n");
591

    
592
			logFile.flush();
593
			logFile.close();
594

    
595
			zip.flush();
596
			zip.close();
597

    
598
			log.info("Backup finished");
599
			return backupDir;
600
		} catch (final Exception e) {
601
			log.error("Backup failed", e);
602
			throw new XMLDBException(0, "cannot backup", e);
603
		}
604
		finally {
605
			readLock.unlock();
606
		}
607
	}
608

    
609

    
610
	private void verifyBackupDir() {
611
		File d = new File(backupDir);
612
		if (!d.exists()) d.mkdirs();
613
	}
614

    
615
	private void backup(String coll, ZipOutputStream zip, FileWriter logFile) throws XMLDBException, IOException {
616
		readLock.lock();
617
		logFile.write("COLLECTION: " + coll + "\n");
618
		log.info("Backup of collection " + coll);
619
		try {
620
			for (String file : list(coll)) {
621
				zip.putNextEntry(new ZipEntry(coll + "/" + file + ".xml"));
622
				Resource resource = getCollection(coll).getResource(file);
623
				zip.write(resource.getContent().toString().getBytes());
624
				zip.closeEntry();
625
			}
626

    
627
			for (String c : listChildCollections(coll)) {
628
				backup(coll + "/" + c, zip, logFile);
629
			}
630
		}finally {
631
			readLock.unlock();
632
		}
633

    
634
	}
635
}
(4-4/8)