Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: b8ec702c3260dfd92b2399b8a017f4e971e48220 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
/**
 * <copyright>
 *
 * Copyright (c) 2012 Springsite BV (The Netherlands) and others
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Martin Taal
 * </copyright>
 *
 * $Id: AnyEObjectType.java,v 1.7 2010/11/12 09:33:33 mtaal Exp $
 */
package org.eclipse.emf.teneo.hibernate.auditing;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.util.FeatureMapUtil;
import org.eclipse.emf.teneo.Constants;
import org.eclipse.emf.teneo.extension.ExtensionPoint;
import org.eclipse.emf.teneo.hibernate.HbUtil;
import org.eclipse.emf.teneo.hibernate.auditing.model.teneoauditing.TeneoAuditCommitInfo;
import org.eclipse.emf.teneo.hibernate.auditing.model.teneoauditing.TeneoAuditEntry;
import org.eclipse.emf.teneo.util.AssertUtil;
import org.eclipse.emf.teneo.util.StoreUtil;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;

/**
 * Main class for retrieving audit entries and revisions of specific entities.
 * 
 * @author <a href="mailto:mtaal@elver.org">Martin Taal</a>
 */
public class AuditVersionProvider implements ExtensionPoint {

	private static final String URI_STR = "http://www.eclipse.org/teneo/auditing";

	private Session session;
	private AuditResource auditResource;
	private ResourceSet resourceSet;
	private AuditDataStore dataStore;

	public AuditVersionProvider() {
		auditResource = new AuditResource(URI.createURI(URI_STR));

		resourceSet = new ResourceSetImpl() {
			@Override
			public void eNotify(Notification notification) {
			}

			@Override
			public boolean eDeliver() {
				return false;
			}

			@Override
			public Resource getResource(URI uri, boolean loadOnDemand) {
				return auditResource;
			}

			@Override
			public EObject getEObject(URI uri, boolean loadOnDemand) {
				return auditResource.getEObject(uri.fragment());
			}

			@Override
			public Resource createResource(URI uri, String contentType) {
				return auditResource;
			}

			@Override
			public Resource createResource(URI uri) {
				return auditResource;
			}
		};

		resourceSet.getResources().add(auditResource);
	}

	protected void checkState() {
		AssertUtil.assertTrue("data store must be set", dataStore != null);
	}

	/**
	 * Creates an id string which can be used to query for {@link TeneoAuditEntry} objects. The id
	 * string can be used as a filter value for references to other objects.
	 */
	public String getIdString(EObject entity) {
		final EClass eClass = entity.eClass();
		final String entityName = ((SessionImplementor) getSession()).bestGuessEntityName(entity);
		final Serializable id = ((SessionImplementor) getSession()).getEntityPersister(entityName,
				entity).getIdentifier(entity, (SessionImplementor) getSession());
		return getIdString(eClass, id);
	}

	/**
	 * @see #getIdString(EObject)
	 */
	public String getIdString(EClass eClass, Serializable id) {
		return dataStore.getAuditHandler().idToString(eClass, id);
	}

	/**
	 * Return all audit entries ({@link TeneoAuditEntry}) for a certain version. The audit entries are
	 * ordered by increasing commit time. The latest version is the last.
	 * 
	 * The {@link TeneoAuditEntry} gives version information and times, see also the associated
	 * {@link TeneoAuditCommitInfo} object which can be reached from the {@link TeneoAuditEntry}.
	 * 
	 * From an audit entry you can iterate to the next and previous entries. See:
	 * {@link TeneoAuditEntry#getTeneo_next_entry()} and
	 * {@link TeneoAuditEntry#getTeneo_previous_entry()}.
	 */
	public List<TeneoAuditEntry> getAllAuditEntries(EObject entity) {
		final EClass eClass = entity.eClass();
		final String entityName = ((SessionImplementor) getSession()).bestGuessEntityName(entity);
		final Serializable id = ((SessionImplementor) getSession()).getEntityPersister(entityName,
				entity).getIdentifier(entity, (SessionImplementor) getSession());
		return getAllAuditEntries(eClass, id);
	}

	/**
	 * @see AuditVersionProvider#getAllVersions(EObject)
	 */
	public List<TeneoAuditEntry> getAllAuditEntries(EClass eClass, Object id) {
		checkState();

		final EClass auditingEClass = dataStore.getAuditHandler().getAuditingModelElement(eClass);
		final String entityName = auditingEClass.getEAnnotation(Constants.ANNOTATION_SOURCE_TENEO_JPA)
				.getDetails().get(Constants.ANNOTATION_KEY_ENTITY_NAME);
		final Query qry = getSession()
				.createQuery(
						"select e from " + entityName
								+ " e where teneo_object_id=:objectId order by e.teneo_start");
		final String idAsString = dataStore.getAuditHandler().idToString(eClass, id);
		qry.setParameter("objectId", idAsString);
		final List<TeneoAuditEntry> result = new ArrayList<TeneoAuditEntry>();
		for (Object o : qry.list()) {
			result.add((TeneoAuditEntry) o);
		}
		return result;
	}

	/**
	 * Return the latest audit entry ({@link TeneoAuditEntry}) for a certain {@link EClass} and id.
	 * 
	 * From this latest audit entry you can navigate to the previous audit entry using
	 * {@link TeneoAuditEntry#getTeneo_previous_entry()}.
	 * 
	 * @see #getAllVersions(EObject)
	 */
	public TeneoAuditEntry getLatestAuditEntry(EClass eClass, Object id) {
		checkState();

		final EClass auditingEClass = dataStore.getAuditHandler().getAuditingModelElement(eClass);
		final String entityName = auditingEClass.getEAnnotation(Constants.ANNOTATION_SOURCE_TENEO_JPA)
				.getDetails().get(Constants.ANNOTATION_KEY_ENTITY_NAME);
		final Query qry = getSession().createQuery(
				"select e from " + entityName + " e where teneo_object_id=:objectId and teneo_end="
						+ AuditProcessHandler.DEFAULT_END_TIMESTAMP);
		final String idAsString = dataStore.getAuditHandler().idToString(eClass, id);
		qry.setParameter("objectId", idAsString);
		qry.setMaxResults(1);
		return (TeneoAuditEntry) qry.uniqueResult();
	}

	/**
	 * Resolve an {@link EObject} against the internal resource used here. This method is usefull in
	 * case the EReference does not automatically resolve proxies.
	 */
	@SuppressWarnings("unchecked")
	public <T extends EObject> T resolve(T proxy) {
		return (T) EcoreUtil.resolve(proxy, auditResource);
	}

	/**
	 * Get the actual object representing a certain state defined by the auditEntry. Note that all
	 * references and values of the returned {@link EObject} represent the state at the
	 * {@link TeneoAuditEntry#getTeneo_start()}.
	 */
	public EObject getRevision(TeneoAuditEntry auditEntry) {
		final EClass eClass = dataStore.getAuditHandler().getModelElement(auditEntry.eClass());
		final long timeStamp = auditEntry.getTeneo_start();
		final AuditReference auditReference = dataStore.getAuditHandler().fromString(
				timeStamp + AuditHandler.ID_SEPARATOR + auditEntry.getTeneo_object_id());
		return getRevision(eClass, auditReference.getId(), timeStamp);
	}

	/**
	 * Returns the previous audit entry which reflects the state before the current entry.
	 * 
	 * @see AuditVersionProvider#getRevision(TeneoAuditEntry)
	 */
	public TeneoAuditEntry getPreviousEntry(TeneoAuditEntry entry) {
		// the first entry
		if (entry.getTeneo_previous_start() < 0) {
			return null;
		}
		final Query qry = getSession().createQuery(
				"select e from " + HbUtil.getEntityName(entry.eClass())
						+ " e where teneo_object_id=:objectId and teneo_start=:start");
		qry.setParameter("objectId", entry.getTeneo_object_id());
		qry.setParameter("start", entry.getTeneo_previous_start());
		qry.setMaxResults(1);
		return (TeneoAuditEntry) qry.uniqueResult();
	}

	/**
	 * Returns the previous audit entry which reflects the state before the current entry.
	 * 
	 * @see AuditVersionProvider#getRevision(TeneoAuditEntry)
	 */
	public TeneoAuditEntry getNextEntry(TeneoAuditEntry entry) {
		// the first entry
		if (entry.getTeneo_end() < 0) {
			return null;
		}
		final Query qry = getSession().createQuery(
				"select e from " + HbUtil.getEntityName(entry.eClass())
						+ " e where teneo_object_id=:objectId and teneo_previous_start=:start");
		qry.setParameter("objectId", entry.getTeneo_object_id());
		qry.setParameter("start", entry.getTeneo_start());
		qry.setMaxResults(1);
		return (TeneoAuditEntry) qry.uniqueResult();
	}

	/**
	 * Returns the actual object instance representing the state at the specified timestamp.
	 * 
	 * If there is no revision at that timestamp then null is returned.
	 * 
	 * @see AuditVersionProvider#getRevision(TeneoAuditEntry)
	 */
	@SuppressWarnings("unchecked")
	public EObject getRevision(EClass eClass, Object id, long timeStamp) {
		checkState();

		final String idAsString = dataStore.getAuditHandler().idToString(eClass, id);
		final String fullId = timeStamp + AuditHandler.ID_SEPARATOR + idAsString;
		if (auditResource.getEObjectFromCache(fullId) != null) {
			return auditResource.getEObjectFromCache(fullId);
		}
		final EClass auditingEClass = dataStore.getAuditHandler().getAuditingModelElement(eClass);
		final String entityName = auditingEClass.getEAnnotation(Constants.ANNOTATION_SOURCE_TENEO_JPA)
				.getDetails().get(Constants.ANNOTATION_KEY_ENTITY_NAME);

		final Query qry = getSession().createQuery(
				"select e from " + entityName
						+ " e where teneo_object_id=:objectId and teneo_start<=:start and (teneo_end="
						+ AuditProcessHandler.DEFAULT_END_TIMESTAMP + " or teneo_end>:end)");
		qry.setParameter("objectId", idAsString);
		qry.setParameter("start", timeStamp);
		qry.setParameter("end", timeStamp);

		final TeneoAuditEntry auditEntry = (TeneoAuditEntry) qry.uniqueResult();
		if (auditEntry == null) {
			return null;
		}

		final EObject target = eClass.getEPackage().getEFactoryInstance().create(eClass);
		for (EStructuralFeature targetEFeature : target.eClass().getEAllStructuralFeatures()) {
			final EStructuralFeature sourceEFeature = auditingEClass.getEStructuralFeature(targetEFeature
					.getName());
			if (sourceEFeature == null) {
				continue;
			}
			if (!auditEntry.eIsSet(sourceEFeature) && !sourceEFeature.isMany()) {
				continue;
			}

			// embedded objects are stored as references
			if (sourceEFeature instanceof EAttribute && targetEFeature instanceof EReference) {
				if (sourceEFeature.isMany()) {
					for (Object value : ((Collection<?>) auditEntry.eGet(sourceEFeature))) {
						((Collection<Object>) target.eGet(targetEFeature)).add(createProxyEObject(
								(String) value, timeStamp));
					}
				} else {
					target.eSet(targetEFeature,
							createProxyEObject((String) auditEntry.eGet(sourceEFeature), timeStamp));
				}
			} else {
				if (StoreUtil.isMap(targetEFeature)) {
					for (Object value : ((Collection<?>) auditEntry.eGet(sourceEFeature))) {
						((Collection<Object>) target.eGet(targetEFeature)).add(createEMapEntry(
								(EReference) targetEFeature, (EObject) value, timeStamp));
					}
				} else if (FeatureMapUtil.isFeatureMap(targetEFeature)) {
					for (Object value : ((Collection<?>) auditEntry.eGet(sourceEFeature))) {
						final FeatureMap.Entry sourceEntry = (FeatureMap.Entry) value;
						final EStructuralFeature targetEntryFeature = dataStore.getAuditHandler()
								.getModelElement(sourceEntry.getEStructuralFeature());
						final FeatureMap.Entry targetEntry = createFeatureMapEntry(targetEntryFeature,
								sourceEntry, timeStamp);
						((Collection<Object>) target.eGet(targetEFeature)).add(targetEntry);
					}
				} else if (targetEFeature.isMany()) {
					for (Object value : ((Collection<?>) auditEntry.eGet(sourceEFeature))) {
						((Collection<Object>) target.eGet(targetEFeature)).add(dataStore.getAuditHandler()
								.convertValue(targetEFeature, value));
					}
				} else {
					target.eSet(
							targetEFeature,
							dataStore.getAuditHandler().convertValue(targetEFeature,
									auditEntry.eGet(sourceEFeature)));
				}
			}
		}

		if (auditEntry.getTeneo_container_id() != null) {
			final InternalEObject container = (InternalEObject) createProxyEObject(
					auditEntry.getTeneo_container_id(), timeStamp);
			((InternalEObject) target).eBasicSetContainer(container,
					auditEntry.getTeneo_container_feature_id(), null);
		}

		if (target.eResource() == null) {
			((InternalEObject) target).eSetResource(auditResource, null);
		}
		auditResource.putEObjectInCache(fullId, target);

		return target;
	}

	private Object createEMapEntry(EReference targetEFeature, EObject sourceEntry, long timeStamp) {
		final EObject targetEntry = EcoreUtil.create(targetEFeature.getEReferenceType());
		final Object value = sourceEntry.eGet(sourceEntry.eClass().getEStructuralFeature("value"));
		final Object key = sourceEntry.eGet(sourceEntry.eClass().getEStructuralFeature("key"));
		final EStructuralFeature keyEFeature = targetEntry.eClass().getEStructuralFeature("key");
		final EStructuralFeature valueEFeature = targetEntry.eClass().getEStructuralFeature("value");

		final Object newKey;
		if (keyEFeature instanceof EAttribute) {
			newKey = dataStore.getAuditHandler().convertValue(keyEFeature, key);
		} else if (key instanceof EObject) {
			newKey = key;
		} else {
			newKey = createProxyEObject((String) key, timeStamp);
		}
		targetEntry.eSet(keyEFeature, newKey);

		final Object newValue;
		if (valueEFeature instanceof EAttribute) {
			newValue = dataStore.getAuditHandler().convertValue(valueEFeature, value);
		} else if (value instanceof EObject) {
			newValue = value;
		} else {
			newValue = createProxyEObject((String) value, timeStamp);
		}
		targetEntry.eSet(valueEFeature, newValue);

		return targetEntry;
	}

	private FeatureMap.Entry createFeatureMapEntry(EStructuralFeature eFeature,
			FeatureMap.Entry sourceEntry, long timeStamp) {
		Object value = sourceEntry.getValue();
		if (eFeature instanceof EReference) {
			value = createProxyEObject((String) value, timeStamp);
		}
		return FeatureMapUtil.createEntry(eFeature, value);
	}

	protected EObject createProxyEObject(String value, long timeStamp) {
		if (value == null) {
			return null;
		}
		final String idStr = timeStamp + AuditHandler.ID_SEPARATOR + value;
		EObject eObject = auditResource.getEObjectFromCache(idStr);
		if (eObject != null) {
			return eObject;
		}
		final AuditReference reference = dataStore.getAuditHandler().fromString(idStr);
		final EClass refEClass = dataStore.toEClass(reference.getEntityName());
		eObject = refEClass.getEPackage().getEFactoryInstance().create(refEClass);
		((InternalEObject) eObject).eSetProxyURI(URI.createURI(URI_STR + "#" + idStr));
		((InternalEObject) eObject).eSetResource(auditResource, null);
		return eObject;
	}

	protected Session getSession() {
		if (session == null) {
			session = ((SessionFactoryImplementor) dataStore.getSessionFactory()).openTemporarySession();
		}
		return session;
	}

	public AuditDataStore getDataStore() {
		return dataStore;
	}

	public void setDataStore(AuditDataStore dataStore) {
		this.dataStore = dataStore;
	}

	public void setSession(Session session) {
		this.session = session;
	}

	public void close() {
		if (session != null) {
			session.close();
		}
	}

	private class AuditResource extends ResourceImpl {

		private Map<String, EObject> idToEObjectMap = new HashMap<String, EObject>();

		public AuditResource(URI uri) {
			super(uri);
		}

		public EObject getEObjectFromCache(String idStr) {
			return idToEObjectMap.get(idStr);
		}

		public void putEObjectInCache(String idStr, EObject eObject) {
			idToEObjectMap.put(idStr, eObject);
		}

		@Override
		public EObject getEObject(String uriFragment) {

			if (idToEObjectMap.containsKey(uriFragment)) {
				return idToEObjectMap.get(uriFragment);
			}

			// read the auditEntry from the session
			final AuditReference reference = dataStore.getAuditHandler().fromString(uriFragment);
			final EClass eClass = dataStore.toEClass(reference.getEntityName());
			EObject version = AuditVersionProvider.this.getRevision(eClass, reference.getId(),
					reference.getTimeStamp());
			if (version == null) {
				version = (EObject) getSession().get(reference.getEntityName(), reference.getId());
			}

			if (version.eResource() == null) {
				((InternalEObject) version).eSetResource(this, null);
			}
			if (version != null) {
				idToEObjectMap.put(uriFragment, version);
			}
			return version;
		}
	}
}

Back to the top