/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.testing.tests.dbchangenotification; import java.util.*; import org.eclipse.persistence.sessions.Session; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.sessions.DatabaseRecord; /** * This class translates a database change notification message * into invalidation of a corresponding object in TopLink cache. * * The class expects the invalidation notification to be found in the following * JMSMessage properties: * String property "TABLE" should contain table name; * Properties of appropriate type should contain value(s) of PK field(s). * * Examples based on Employee demo: * 1. EMPLOYEE table: * getStringProperty("TABLE") == "EMPLOYEE"; * getObjectProperty("EMP_ID") == value of EMP_ID column for the modified row * * 2. SALARY table: * getStringProperty("TABLE") == "SALARY"; * getObjectProperty("EMP_ID") == value of EMP_ID column for the modified row * * 3. PHONE table: * getStringProperty("TABLE") == "PHONE"; * getObjectProperty("EMP_ID") == value of EMP_ID column for the modified row * getObjectProperty("TYPE") == value of TYPE column for the modified row */ public class CacheInvalidator { // maps a table name to Class mapped to this table Hashtable tableNameToClass; // maps table name to a vector of primary key fields' names Hashtable tableNameToPkFieldNames; // Create a CacheInvalidator object that invalidates cache if the changed table // is mapped by one of the descriptors of the passed session public CacheInvalidator(Session session) { // HashSet is used to avoid duplications HashSet tableNames = new HashSet(); // fill out tableNames collection with all tables' names mapped by all descriptors Iterator descriptors = session.getDescriptors().values().iterator(); while (descriptors.hasNext()) { ClassDescriptor desc = (ClassDescriptor)descriptors.next(); // Create a Vector containing names of all tables mapped to the descriptor Vector descTableNames = desc.getTableNames(); // Remove schema names (if any) converting "SCHEMA_NAME.TABLE_NAME" to "TABLE_NAME" removePrefixFromDatabaseObjectNames(descTableNames); // add descTableNames to the collection tableNames.addAll(descTableNames); } // initialize initializeWithTableNames(session, tableNames); } // Create a CacheInvalidator object that invalidates cache if the changed table's name // is in tableNames collection // and the table is mapped by one of the descriptors of the passed session. // Note that the Collection tableNames will be altered - only names // of tables not found in descriptors of the passed session will remain. public CacheInvalidator(Session session, Collection tableNames) { // initialize initializeWithTableNames(session, tableNames); } protected void initializeWithTableNames(Session session, Collection tableNames) { tableNameToClass = new Hashtable(tableNames.size()); tableNameToPkFieldNames = new Hashtable(tableNames.size()); // pkFieldVectors cached here to avoid calculating it more than once per class Hashtable classToPkFieldNames = new Hashtable(); // loop through the descriptors to fill out tableNameToClass and tableNameToPkFieldNames Iterator descriptors = session.getDescriptors().values().iterator(); while (descriptors.hasNext() && !tableNames.isEmpty()) { ClassDescriptor desc = (ClassDescriptor)descriptors.next(); // Create a Vector containing names of all tables mapped to the descriptor Vector descTableNames = desc.getTableNames(); // bypass descriptors with no tables if (descTableNames.isEmpty()) { continue; } // Remove schema names (if any) converting "SCHEMA_NAME.TABLE_NAME" to "TABLE_NAME" removePrefixFromDatabaseObjectNames(descTableNames); // handle inheritance: table name should be mapped to the base mapped class Class baseClass = desc.getJavaClass(); while (desc.isChildDescriptor()) { desc = session.getDescriptor(desc.getInheritancePolicy().getParentClass()); baseClass = desc.getJavaClass(); } Iterator it = tableNames.iterator(); while (it.hasNext()) { // for each tableName specified by the user String tableName = (String)it.next(); // verify whether the descriptor maps a table with the same name if (descTableNames.contains(tableName)) { // map the table name to the baseClass corresponding to the descriptor tableNameToClass.put(tableName, baseClass); // try to obtain cached pkFieldNames Vector corresponding to baseClass Vector pkFieldNames = (Vector)classToPkFieldNames.get(baseClass); if (pkFieldNames == null) { // Create a Vector containing names of all primary key fields pkFieldNames = desc.getPrimaryKeyFieldNames(); // Remove table name converting from "TABLE_NAME.FIELD_NAME" to "FIELD_NAME" removePrefixFromDatabaseObjectNames(pkFieldNames); // cache pkFieldNames Vector corresponding to baseClass classToPkFieldNames.put(baseClass, pkFieldNames); } // map the table name to the Vector of names of primary key fields. tableNameToPkFieldNames.put(tableName, pkFieldNames); // the table name is mapped - remove it from the list of table names to be mapped. it.remove(); } } } } // invalidates in tjhe cache the object corresponding to the massage public void invalidateObject(Session session, javax.jms.Message msg) throws javax.jms.JMSException { String tableName = msg.getStringProperty("TABLE"); if (tableName == null) { return; } Class baseClass = (Class)tableNameToClass.get(tableName); if (baseClass == null) { return; } Vector pkFieldNames = (Vector)tableNameToPkFieldNames.get(tableName); if (pkFieldNames == null) { return; } // create DatabaseRecord corresponding to the message DatabaseRecord row = new DatabaseRecord(pkFieldNames.size()); for (int i = 0; i < pkFieldNames.size(); i++) { String fieldName = (String)pkFieldNames.elementAt(i); Object value = msg.getObjectProperty(fieldName); row.put(fieldName, value); } // invalidate in TopLink cache the object corresponding to the row and the baseClass session.getIdentityMapAccessor().invalidateObject(row, baseClass); } // converts "SCHEMA_NAME.TABLE_NAME" to "TABLE_NAME" protected void removePrefixFromDatabaseObjectNames(Vector names) { for (int i = 0; i < names.size(); i++) { String qualifiedName = (String)names.elementAt(i); String name = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1); names.setElementAt(name, i); } } }