Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » EclipseLink » eclipselink History Policy: custom field(how to add a custom field to history policy)
eclipselink History Policy: custom field [message #1724754] Thu, 25 February 2016 17:49 Go to next message
Iva Conte is currently offline Iva ConteFriend
Messages: 3
Registered: February 2016
Junior Member
Hi,
Taking a look at the history wiki I didn't find anything about adding a custom field to to HistoryPolicy. My intention is to add to my history table a field to keep who's made changes. Is there a way to do this using HistoryPolicy?


https://wiki.eclipse.org/EclipseLink/Examples/JPA/History#Tracking_Changes_Using_History_Policy


Thanks in advance,
Iva
Re: eclipselink History Policy: custom field [message #1754358 is a reply to message #1724754] Thu, 16 February 2017 17:56 Go to previous messageGo to next message
Kevin Jordan is currently offline Kevin JordanFriend
Messages: 4
Registered: February 2017
Junior Member
Did you figure out how to do this? I want to do something similar.
Re: eclipselink History Policy: custom field [message #1754361 is a reply to message #1724754] Thu, 16 February 2017 17:58 Go to previous messageGo to next message
Kevin Jordan is currently offline Kevin JordanFriend
Messages: 4
Registered: February 2017
Junior Member
Did you figure out how to do this?
Re: eclipselink History Policy: custom field [message #1754431 is a reply to message #1724754] Fri, 17 February 2017 17:28 Go to previous messageGo to next message
Kevin Jordan is currently offline Kevin JordanFriend
Messages: 4
Registered: February 2017
Junior Member
I was able to add user columns using the code below. Note that it needs a separate column for the user who modified it and the user who deleted it as it currently isn't possible to do a separate insert when someone deletes it and it uses the same row as the previous modification. Injecting the user is also difficult so I have an interface (CurrentUserProvider) that will get created in my main application that sets that interface implementation in a singleton object (CurrentUserSingleton) which it calls getCurrentUsername() whenever it needs the user below.

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.history.HistoryPolicy;
import org.eclipse.persistence.internal.expressions.SQLInsertStatement;
import org.eclipse.persistence.internal.expressions.SQLUpdateStatement;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.queries.StatementQueryMechanism;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DirectCollectionMapping;
import org.eclipse.persistence.mappings.ManyToManyMapping;
import org.eclipse.persistence.queries.DataModifyQuery;
import org.eclipse.persistence.queries.DeleteAllQuery;
import org.eclipse.persistence.queries.ModifyQuery;
import org.eclipse.persistence.queries.ObjectLevelModifyQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by kjord on 2017-02-16.
 */
public class UserHistoryPolicy extends HistoryPolicy {

	protected List<DatabaseField> deleteUserFields;

	protected List<DatabaseField> userFields;

	public void addDeleteUserFieldName(String deleteUserFieldName) {
		DatabaseField deleteUserField = new DatabaseField(deleteUserFieldName);
		deleteUserField.setType(ClassConstants.STRING);
		// #440278
		deleteUserField.setLength(6);

		if (deleteUserFields == null) {
			deleteUserFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance();
			deleteUserFields.add(deleteUserField);
			return;
		}

		for (DatabaseField existing : deleteUserFields) {
			if (deleteUserField.getTableName()
							   .equals(existing.getTableName())) {
				existing.setName(deleteUserField.getName());
				return;
			}
		}
		deleteUserFields.add(deleteUserField);
	}

	public void addUserFieldName(String userFieldName) {
		DatabaseField userField = new DatabaseField(userFieldName);
		userField.setType(ClassConstants.STRING);
		// #440278
		userField.setLength(6);

		if (userFields == null) {
			userFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance();
			userFields.add(userField);
			return;
		}

		for (DatabaseField existing : userFields) {
			if (userField.getTableName()
						 .equals(existing.getTableName())) {
				existing.setName(userField.getName());
				return;
			}
		}
		userFields.add(userField);
	}

	@Override
	public Object clone() {
		UserHistoryPolicy clone = (UserHistoryPolicy) super.clone();
		if (userFields != null) {
			clone.setUserFields(new ArrayList(userFields.size()));
			for (DatabaseField field : userFields) {
				clone.getUserFields()
					 .add(field.clone());
			}
		}
		if (deleteUserFields != null) {
			clone.setUserFields(new ArrayList(deleteUserFields.size()));
			for (DatabaseField field : deleteUserFields) {
				clone.getUserFields()
					 .add(field.clone());
			}
		}
		return clone;
	}

	protected DatabaseField getDeleteUser(int i) {
		return deleteUserFields.get(i);
	}

	protected DatabaseField getDeleteUser() {
		if (deleteUserFields != null) {
			return deleteUserFields.get(0);
		} else {
			return null;
		}
	}

	public List<DatabaseField> getDeleteUserFields() {
		return deleteUserFields;
	}

	public void setDeleteUserFields(List deleteUserFields) {
		this.deleteUserFields = deleteUserFields;
	}

	protected DatabaseField getUser(int i) {
		return userFields.get(i);
	}

	protected DatabaseField getUser() {
		if (userFields != null) {
			return userFields.get(0);
		} else {
			return null;
		}
	}

	public List<DatabaseField> getUserFields() {
		return userFields;
	}

	public void setUserFields(List userFields) {
		this.userFields = userFields;
	}

	@Override
	public void initialize(AbstractSession session) {
		if (getMapping() != null) {
			setDescriptor(getMapping().getDescriptor());
			if (getMapping().isDirectCollectionMapping()) {
				DatabaseTable refTable = ((DirectCollectionMapping) getMapping()).getReferenceTable();
				DatabaseTable histTable = getHistoricalTables().get(0);
				histTable.setName(refTable.getName());
				histTable.setTableQualifier(refTable.getTableQualifier());
				getStart().setTable(histTable);
				getEnd().setTable(histTable);
				getUser().setTable(histTable);
				getDeleteUser().setTable(histTable);
			} else if (getMapping().isManyToManyMapping()) {
				DatabaseTable relationTable = ((ManyToManyMapping) getMapping()).getRelationTable();
				DatabaseTable histTable = getHistoricalTables().get(0);
				histTable.setName(relationTable.getName());
				histTable.setTableQualifier(relationTable.getTableQualifier());
				getStart().setTable(histTable);
				getEnd().setTable(histTable);
				getUser().setTable(histTable);
				getDeleteUser().setTable(histTable);
			}
			verifyTableQualifiers(session.getPlatform());
			return;
		}

		// Some historicalTables will be inherited from a parent policy.
		int offset = getDescriptor().getTables()
									.size() - getHistoricalTables().size();

		// In this configuration descriptor tables, history tables, and start/end fields
		// are all in the same order.
		if (!getHistoricalTables().isEmpty() && getHistoricalTables().get(0)
																	 .getName()
																	 .equals("")) {
			for (int i = 0; i < getHistoricalTables().size(); i++) {
				DatabaseTable table = getHistoricalTables().get(i);
				if (table.getName()
						 .equals("")) {
					DatabaseTable mirrored = getDescriptor().getTables()
															.get(i + offset);
					table.setName(mirrored.getName());
					table.setTableQualifier(mirrored.getTableQualifier());
				}
				if (getStartFields().size() < (i + 1)) {
					DatabaseField startField = getStart(0).clone();
					startField.setTable(table);
					getStartFields().add(startField);
				} else {
					DatabaseField startField = getStart(i);
					startField.setTable(table);
				}
				if (getEndFields().size() < (i + 1)) {
					DatabaseField endField = getEnd(0).clone();
					endField.setTable(table);
					getEndFields().add(endField);
				} else {
					DatabaseField endField = getEnd(i);
					endField.setTable(table);
				}
				if (getUserFields().size() < (i + 1)) {
					DatabaseField userField = getUser(0).clone();
					userField.setTable(table);
					getUserFields().add(userField);
				} else {
					DatabaseField userField = getUser(i);
					userField.setTable(table);
				}
				if (getDeleteUserFields().size() < (i + 1)) {
					DatabaseField deleteUserField = getDeleteUser(0).clone();
					deleteUserField.setTable(table);
					getUserFields().add(deleteUserField);
				} else {
					DatabaseField deleteUserField = getDeleteUser(i);
					deleteUserField.setTable(table);
				}
			}
		} else {
			// The user did not specify history tables/fields in order, so
			// initialize will take a little longer.
			List<DatabaseTable> unsortedTables = getHistoricalTables();
			List<DatabaseTable> sortedTables = new ArrayList(unsortedTables.size());
			List<DatabaseField> sortedStartFields = new ArrayList(unsortedTables.size());
			List<DatabaseField> sortedEndFields = new ArrayList(unsortedTables.size());
			boolean universalStartField = ((getStartFields().size() == 1) && (!(getStartFields().get(0)).hasTableName()));
			boolean universalEndField = ((getEndFields().size() == 1) && (!(getEndFields().get(0)).hasTableName()));
			DatabaseTable descriptorTable = null;
			DatabaseTable historicalTable = null;
			DatabaseField historyField = null;

			List<DatabaseTable> descriptorTables = getDescriptor().getTables();
			for (int i = offset; i < descriptorTables.size(); i++) {
				descriptorTable = descriptorTables.get(i);

				int index = unsortedTables.indexOf(descriptorTable);
				if (index == -1) {
					// this is a configuration error!
				}
				historicalTable = unsortedTables.get(index);
				historicalTable.setTableQualifier(descriptorTable.getTableQualifier());
				sortedTables.add(historicalTable);

				if (universalStartField) {
					historyField = getStart(0).clone();
					historyField.setTable(historicalTable);
					sortedStartFields.add(historyField);
				} else {
					for (DatabaseField field : getStartFields()) {
						if (field.getTable()
								 .equals(historicalTable)) {
							sortedStartFields.add(field);
							break;
						}
					}
				}
				if (universalEndField) {
					historyField = getEnd(0).clone();
					historyField.setTable(historicalTable);
					sortedEndFields.add(historyField);
				} else {
					for (DatabaseField field : getEndFields()) {
						if (field.getTable()
								 .equals(historicalTable)) {
							sortedEndFields.add(field);
							break;
						}
					}
				}
			}
			setHistoricalTables(sortedTables);
			setStartFields(sortedStartFields);
			setEndFields(sortedEndFields);
		}
		verifyTableQualifiers(session.getPlatform());

		// A user need not set a policy on every level of an inheritance, but
		// historic tables can be inherited.
		if (getDescriptor().hasInheritance()) {
			ClassDescriptor parentDescriptor = getDescriptor().getInheritancePolicy()
															  .getParentDescriptor();
			while ((parentDescriptor != null) && (parentDescriptor.getHistoryPolicy() == null)) {
				parentDescriptor = parentDescriptor.getInheritancePolicy()
												   .getParentDescriptor();
			}
			if (parentDescriptor != null) {
				// Unique is required because the builder can add the same table many times.
				// This is done after init properties to make sure the default table is the first local one.
				setHistoricalTables(Helper.concatenateUniqueLists(parentDescriptor.getHistoryPolicy()
																				  .getHistoricalTables(),
																  getHistoricalTables()));
				setStartFields(Helper.concatenateUniqueLists(parentDescriptor.getHistoryPolicy()
																			 .getStartFields(),
															 getStartFields()));
				setEndFields(Helper.concatenateUniqueLists(parentDescriptor.getHistoryPolicy()
																		   .getEndFields(),
														   getEndFields()));
				if (parentDescriptor.getHistoryPolicy() instanceof UserHistoryPolicy) {
					setUserFields(Helper.concatenateUniqueLists(((UserHistoryPolicy) parentDescriptor.getHistoryPolicy()).getUserFields(),
																getUserFields()));
					setDeleteUserFields(Helper.concatenateUniqueLists(((UserHistoryPolicy) parentDescriptor.getHistoryPolicy()).getDeleteUserFields(),
																	  getUserFields()));
				}
			}
		}
	}

	@Override
	public void logicalDelete(ModifyQuery writeQuery,
							  boolean isUpdate,
							  boolean isShallow) {
		ClassDescriptor descriptor = writeQuery.getDescriptor();
		AbstractRecord originalModifyRow = writeQuery.getModifyRow();
		AbstractRecord modifyRow = new DatabaseRecord();
		StatementQueryMechanism updateMechanism = new StatementQueryMechanism(writeQuery);
		Object currentTime = getCurrentTime(writeQuery.getSession());

		for (int i = 0; i < getHistoricalTables().size(); i++) {
			DatabaseTable table = getHistoricalTables().get(i);

			if (isUpdate && !checkWastedVersioning(originalModifyRow,
												   table)) {
				continue;
			}
			SQLUpdateStatement updateStatement = new SQLUpdateStatement();
			updateStatement.setTable(table);
			Expression whereClause = null;
			if (writeQuery instanceof DeleteAllQuery) {
				if (writeQuery.getSelectionCriteria() != null) {
					whereClause = (Expression) writeQuery.getSelectionCriteria()
														 .clone();
				}
			} else {
				whereClause = descriptor.getObjectBuilder()
										.buildPrimaryKeyExpression(table);
			}
			ExpressionBuilder builder = ((whereClause == null)
										 ? new ExpressionBuilder()
										 : whereClause.getBuilder());
			whereClause = builder.getField(getEnd(i))
								 .isNull()
								 .and(whereClause);
			updateStatement.setWhereClause(whereClause);

			modifyRow.add(getEnd(i),
						  currentTime);

			// save a little time here and add the same timestamp value for
			// the start field in the logicalInsert.
			if (isUpdate) {
				if (isShallow) {
					// Bug 319276 - increment the timestamp by 1 to avoid unique constraint violation potential
					java.sql.Timestamp incrementedTime = (java.sql.Timestamp) currentTime;
					incrementedTime.setTime(incrementedTime.getTime() + getMinimumTimeIncrement(writeQuery.getSession()));
					originalModifyRow.add(getStart(i),
										  incrementedTime);
				} else {
					originalModifyRow.add(getStart(i),
										  currentTime);
				}
				originalModifyRow.add(getUser(i),
									  CurrentUserSingleton.getCurrentUserProvider()
														  .getCurrentUsername());
			} else {
				modifyRow.add(getDeleteUser(i),
							  CurrentUserSingleton.getCurrentUserProvider()
												  .getCurrentUsername());
			}
			updateMechanism.getSQLStatements()
						   .add(updateStatement);
		}
		if (updateMechanism.hasMultipleStatements()) {
			writeQuery.setModifyRow(modifyRow);
			updateMechanism.updateObject();
			writeQuery.setModifyRow(originalModifyRow);
		}
	}

	@Override
	public void logicalInsert(ObjectLevelModifyQuery writeQuery,
							  boolean isUpdate) {
		ClassDescriptor descriptor = getDescriptor();
		AbstractRecord modifyRow = null;
		AbstractRecord originalModifyRow = writeQuery.getModifyRow();
		Object currentTime = null;
		if (isUpdate) {
			modifyRow = descriptor.getObjectBuilder()
								  .buildRow(writeQuery.getObject(),
											writeQuery.getSession(),
											DatabaseMapping.WriteType.UPDATE); // Bug 319276
			// If anyone added items to the modify row, then they should also be added here.
			modifyRow.putAll(originalModifyRow);
		} else {
			modifyRow = originalModifyRow;
			// If update would have already discovered timestamp to use.
			currentTime = getCurrentTime(writeQuery.getSession());
		}
		StatementQueryMechanism insertMechanism = new StatementQueryMechanism(writeQuery);

		for (int i = 0; i < getHistoricalTables().size(); i++) {
			DatabaseTable table = getHistoricalTables().get(i);
			if (isUpdate && !checkWastedVersioning(originalModifyRow,
												   table)) {
				continue;
			}
			if (!isUpdate) {
				modifyRow.add(getStart(i),
							  currentTime);
				modifyRow.add(getUser(i),
							  CurrentUserSingleton.getCurrentUserProvider()
												  .getCurrentUsername());
			}
			SQLInsertStatement insertStatement = new SQLInsertStatement();
			insertStatement.setTable(table);
			insertMechanism.getSQLStatements()
						   .add(insertStatement);
		}
		if (insertMechanism.hasMultipleStatements()) {
			writeQuery.setTranslationRow(modifyRow);
			writeQuery.setModifyRow(modifyRow);
			insertMechanism.insertObject();
		}
	}

	@Override
	public void mappingLogicalInsert(DataModifyQuery originalQuery,
									 AbstractRecord arguments,
									 AbstractSession session) {
		DataModifyQuery historyQuery = new DataModifyQuery();
		SQLInsertStatement historyStatement = new SQLInsertStatement();
		DatabaseTable histTable = getHistoricalTables().get(0);

		historyStatement.setTable(histTable);
		AbstractRecord modifyRow = originalQuery.getModifyRow()
												.clone();
		AbstractRecord translationRow = arguments.clone();

		// Start could be the version field in timestamp locking.
		if (!modifyRow.containsKey(getStart())) {
			Object time = getCurrentTime(session);
			modifyRow.add(getStart(),
						  time);
			translationRow.add(getStart(),
							   time);
		}
		if (!modifyRow.containsKey(getUser())) {
			modifyRow.add(getUser(),
						  CurrentUserSingleton.getCurrentUserProvider()
											  .getCurrentUsername());
			translationRow.add(getUser(),
							   CurrentUserSingleton.getCurrentUserProvider()
												   .getCurrentUsername());
		}
		historyQuery.setSQLStatement(historyStatement);
		historyQuery.setModifyRow(modifyRow);
		historyStatement.setModifyRow(modifyRow);
		session.executeQuery(historyQuery,
							 translationRow);
	}
}
Re: eclipselink History Policy: custom field [message #1754786 is a reply to message #1754361] Wed, 22 February 2017 11:30 Go to previous messageGo to next message
Iva Conte is currently offline Iva ConteFriend
Messages: 3
Registered: February 2016
Junior Member
I didn't find anything helpful in this case Crying or Very Sad
Re: eclipselink History Policy: custom field [message #1755174 is a reply to message #1754786] Tue, 28 February 2017 17:18 Go to previous message
Kevin Jordan is currently offline Kevin JordanFriend
Messages: 4
Registered: February 2017
Junior Member
It's certainly not the easiest API to extend, but the code I posted above works. They should definitely make it easier. Hibernate Envers was easier in this respect, although I didn't exactly like the way that was set up either with it being another @Entity.
Previous Topic:Wildfly 10.0.0.Final - Sample War App - Using Eclipselink 2.6.4 - jar-file paths not properly handle
Next Topic:moxy is not able to convert a List<String> to JSON
Goto Forum:
  


Current Time: Fri Nov 16 09:37:08 GMT 2018

Powered by FUDForum. Page generated in 0.27081 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top