/***************************************************
*
* cismet GmbH, Saarbruecken, Germany
*
* ... and it just works.
*
****************************************************/
package de.cismet.cids.trigger.builtin;
import Sirius.server.localserver.attribute.MemberAttributeInfo;
import Sirius.server.localserver.attribute.ObjectAttribute;
import Sirius.server.middleware.types.MetaObject;
import Sirius.server.newuser.User;
import Sirius.server.sql.DBConnection;
import org.openide.util.Exceptions;
import org.openide.util.lookup.ServiceProvider;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import de.cismet.cids.dynamics.CidsBean;
import de.cismet.cids.trigger.AbstractDBAwareCidsTrigger;
import de.cismet.cids.trigger.CidsTrigger;
import de.cismet.cids.trigger.CidsTriggerKey;
/**
* DOCUMENT ME!
*
* @author thorsten
* @version $Revision$, $Date$
*/
@ServiceProvider(service = CidsTrigger.class)
public class IndexTrigger extends AbstractDBAwareCidsTrigger {
//~ Static fields/initializers ---------------------------------------------
private static final transient org.apache.log4j.Logger severeIncidence = org.apache.log4j.Logger.getLogger(
"severe.incidence");
private static final transient org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(
IndexTrigger.class);
public static final String NULL = "NULL"; // NOI18N
private static final transient org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
IndexTrigger.class);
public static final String DEL_ATTR_STRING = "DELETE FROM cs_attr_string " // NOI18N
+ "WHERE class_id = ? AND object_id = ?"; // NOI18N
public static final String DEL_ATTR_MAPPING = "DELETE FROM cs_attr_object " // NOI18N
+ "WHERE class_id = ? AND object_id = ?"; // NOI18N
public static final String INS_ATTR_STRING = "INSERT INTO cs_attr_string " // NOI18N
+ "(class_id, object_id, attr_id, string_val) VALUES (?, ?, ?, ?)"; // NOI18N
public static final String INS_ATTR_MAPPING = "INSERT INTO cs_attr_object " // NOI18N
+ "(class_id, object_id, attr_class_id, attr_object_id) VALUES " // NOI18N
+ "(?, ?, ?, ?)"; // NOI18N
public static final String UP_ATTR_STRING = "UPDATE cs_attr_string " // NOI18N
+ "SET string_val = ? " // NOI18N
+ "WHERE class_id = ? AND object_id = ? AND attr_id = ?"; // NOI18N
public static final String UP_ATTR_MAPPING = "UPDATE cs_attr_object " // NOI18N
+ "SET attr_object_id = ? " // NOI18N
+ "WHERE class_id = ? AND object_id = ? AND attr_class_id = ?"; // NOI18N
public static final String DEL_ATTR_MAPPING_ARRAY = "DELETE from cs_attr_object " // NOI18N
+ "WHERE class_id = ? AND object_id = ? AND attr_class_id = ?"; // NOI18N
public static final String DEL_DERIVE_ATTR_MAPPING =
"delete from cs_attr_object_derived where class_id=? and object_id =?";
public static final String INS_DERIVE_ATTR_MAPPING = "insert into cs_attr_object_derived "
+ " WITH recursive derived_index(xocid,xoid,ocid,oid,acid,aid,depth) AS "
+ "( SELECT class_id, "
+ " object_id, "
+ " class_id , "
+ " object_id, "
+ " class_id , "
+ " object_id, "
+ " 0 "
+ "FROM cs_attr_object "
+ "WHERE class_id=? "
+ "AND object_id =? "
+ " "
+ "UNION ALL "
+ " "
+ "SELECT di.xocid , "
+ " di.xoid , "
+ " aam.class_id , "
+ " aam.object_id , "
+ " aam.attr_class_id , "
+ " aam.attr_object_id, "
+ " di.depth+1 "
+ "FROM cs_attr_object aam, "
+ " derived_index di "
+ "WHERE aam.class_id =di.acid "
+ "AND aam.object_id=di.aid "
+ ") "
+ "SELECT DISTINCT xocid, "
+ " xoid , "
+ " acid , "
+ " aid "
+ "FROM derived_index "
+ "ORDER BY 1,2,3,4 limit 1000000000;";
//~ Methods ----------------------------------------------------------------
@Override
public void afterDelete(final CidsBean cidsBean, final User user) {
de.cismet.tools.CismetThreadPool.executeSequentially(new Runnable() {
@Override
public void run() {
try {
final Connection connection = getDbServer().getConnectionPool().getConnection();
deleteIndex(connection, cidsBean.getMetaObject());
} catch (SQLException sQLException) {
log.error("Error during deleteIndex " + cidsBean.getMOString(), sQLException);
severeIncidence.error("Error during deleteIndex " + cidsBean.getMOString(), sQLException);
}
}
});
}
@Override
public void afterInsert(final CidsBean cidsBean, final User user) {
de.cismet.tools.CismetThreadPool.executeSequentially(new Runnable() {
@Override
public void run() {
try {
final Connection connection = getDbServer().getConnectionPool().getConnection();
insertIndex(connection, cidsBean.getMetaObject());
} catch (SQLException sQLException) {
log.error("Error during insertIndex " + cidsBean.getMOString(), sQLException);
severeIncidence.error("Error during insertIndex " + cidsBean.getMOString(), sQLException);
}
}
});
}
@Override
public void afterUpdate(final CidsBean cidsBean, final User user) {
// The triggers, which update the index should be executed sequentially, because
// during the execution of the deleteMetaObject method, the updateMetaObject method can
// be executed and this leads to a race condition between the
// delete trigger and the update trigger
de.cismet.tools.CismetThreadPool.executeSequentially(new Runnable() {
@Override
public void run() {
try {
final Connection connection = getDbServer().getConnectionPool().getConnection();
deleteIndex(connection, cidsBean.getMetaObject());
insertIndex(connection, cidsBean.getMetaObject());
// updateIndex(connection, cidsBean.getMetaObject());
} catch (SQLException sQLException) {
log.error("Error during updateIndex " + cidsBean.getMOString(), sQLException);
severeIncidence.error("Error during updateIndex " + cidsBean.getMOString(), sQLException);
}
}
});
}
@Override
public void beforeDelete(final CidsBean cidsBean, final User user) {
}
@Override
public void beforeInsert(final CidsBean cidsBean, final User user) {
}
@Override
public void beforeUpdate(final CidsBean cidsBean, final User user) {
}
@Override
public CidsTriggerKey getTriggerKey() {
return CidsTriggerKey.FORALL;
}
/**
* DOCUMENT ME!
*
* @param o DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
@Override
public int compareTo(final CidsTrigger o) {
return -1;
}
/**
* mscholl: Inserts the index in cs_attr_string and cs_all_attr_mapping for the given metaobject. If the metaobject
* does not contain a metaclass it is skipped.
*
* @param connection DOCUMENT ME!
* @param mo the metaobject which will be newly created
*
* @throws SQLException if an error occurs during index insertion
* @throws IllegalArgumentException NullPointerException DOCUMENT ME!
*/
private void insertIndex(final Connection connection, final MetaObject mo) throws SQLException {
if (mo == null) {
throw new IllegalArgumentException("MetaObject must not be null"); // NOI18N
} else if (mo.isDummy()) {
// don't do anything with a dummy object
if (LOG.isDebugEnabled()) {
LOG.debug("insert index for dummy won't be done"); // NOI18N
}
return;
} else if (LOG.isInfoEnabled()) {
LOG.info("insert index for MetaObject: " + mo); // NOI18N
}
try {
// we just want to make sure that there is no index present for the
// given object
deleteIndex(connection, mo);
} catch (final SQLException e) {
LOG.error("could not delete index before insert index", e); // NOI18N
throw e;
}
PreparedStatement psAttrString = null;
PreparedStatement psAttrMap = null;
try {
for (final ObjectAttribute attr : mo.getAttribs()) {
final MemberAttributeInfo mai = attr.getMai();
if (mai.isIndexed()) {
// set the appropriate param values according to the field
// value
if (mai.isForeignKey()) {
if (mai.isArray()) {
attr.getTypeId();
String query = "SELECT table_name FROM cs_class where id = "
+ attr.getMai().getForeignKeyClassId();
final ResultSet rs = connection.createStatement().executeQuery(query);
if (rs.next()) {
final String foreignTableName = rs.getString(1);
query = "SELECT id as id FROM " + foreignTableName + " WHERE "
+ mai.getArrayKeyFieldName()
+ " = " + String.valueOf(mo.getID());
final ResultSet arrayList = connection.createStatement().executeQuery(query);
while (arrayList.next()) {
// lazily prepare the statement
if (psAttrMap == null) {
psAttrMap = connection.prepareStatement(INS_ATTR_MAPPING);
}
psAttrMap.setInt(1, mo.getClassID());
psAttrMap.setInt(2, mo.getID());
psAttrMap.setInt(3, mai.getForeignKeyClassId());
psAttrMap.setInt(4, arrayList.getInt(1));
psAttrMap.addBatch();
}
arrayList.close();
}
rs.close();
} else {
// lazily prepare the statement
if (psAttrMap == null) {
psAttrMap = connection.prepareStatement(INS_ATTR_MAPPING);
}
psAttrMap.setInt(1, mo.getClassID());
psAttrMap.setInt(2, mo.getID());
psAttrMap.setInt(3, mai.getForeignKeyClassId());
// if field represents a foreign key the attribute value
// is assumed to be a MetaObject
final MetaObject value = (MetaObject)attr.getValue();
psAttrMap.setInt(4, (value == null) ? -1 : value.getID());
psAttrMap.addBatch();
}
} else {
// lazily prepare the statement
if (psAttrString == null) {
psAttrString = connection.prepareStatement(INS_ATTR_STRING);
}
psAttrString.setInt(1, mo.getClassID());
psAttrString.setInt(2, mo.getID());
psAttrString.setInt(3, mai.getId());
// interpret the fields value as a string
psAttrString.setString(4, (attr.getValue() == null) ? NULL : String.valueOf(attr.getValue()));
psAttrString.addBatch();
}
}
}
// execute the batches if there are indexed fields
if (psAttrString != null) {
final int[] strRows = psAttrString.executeBatch();
if (LOG.isDebugEnabled()) {
int insertCount = 0;
for (final int row : strRows) {
insertCount += row;
}
LOG.debug("cs_attr_string: inserted " + insertCount + " rows"); // NOI18N
}
}
if (psAttrMap != null) {
final int[] mapRows = psAttrMap.executeBatch();
if (LOG.isDebugEnabled()) {
int insertCount = 0;
for (final int row : mapRows) {
insertCount += row;
}
LOG.debug("cs_all_attr_mapping: inserted " + insertCount + " rows"); // NOI18N
}
if (mo.getMetaClass().isIndexed()) {
updateDerivedIndex(connection, mo);
}
}
} catch (final SQLException e) {
LOG.error(
"could not insert index for object '" // NOI18N
+ mo.getID()
+ "' of class '" // NOI18N
+ mo.getClass()
+ "'", // NOI18N
e);
throw e;
} finally {
DBConnection.closeStatements(psAttrString, psAttrMap);
}
}
/**
* mscholl: Updates the index of cs_attr_string and cs_all_attr_mapping for the given metaobject. Update for a
* certain attribute will only be done if the attribute is changed.
*
* @param connection DOCUMENT ME!
* @param mo the metaobject which will be updated
*
* @throws SQLException if an error occurs during index update
* @throws IllegalArgumentException NullPointerException DOCUMENT ME!
*
* @deprecated This method does not work properly. If you have a class with multiple subclasses of the same type
* which are in the search index, the update command will modify all cs_attr_object entries of the same
* type with the same update command, so that all entries have the value that was set by the last
* update command.
*/
private void updateIndex(final Connection connection, final MetaObject mo) throws SQLException {
if (mo == null) {
throw new IllegalArgumentException("MetaObject must not be null"); // NOI18N
} else if (mo.isDummy()) {
// don't do anything with a dummy object
if (LOG.isDebugEnabled()) {
LOG.debug("update index for dummy won't be done"); // NOI18N
}
return;
} else if (LOG.isInfoEnabled()) {
LOG.info("update index for MetaObject: " + mo); // NOI18N
}
PreparedStatement psAttrString = null;
PreparedStatement psAttrMap = null;
PreparedStatement psAttrMapArray = null;
try {
for (final ObjectAttribute attr : mo.getAttribs()) {
final MemberAttributeInfo mai = attr.getMai();
if (mai.isIndexed() && attr.isChanged()) {
// set the appropriate param values according to the field
// value
if (mai.isForeignKey()) {
if (mai.isArray()) {
attr.getTypeId();
String query = "SELECT table_name FROM cs_class where id = "
+ attr.getMai().getForeignKeyClassId();
final ResultSet rs = connection.createStatement().executeQuery(query);
if (rs.next()) {
final String foreignTableName = rs.getString(1);
query = "SELECT id as id FROM " + foreignTableName + " WHERE "
+ mai.getArrayKeyFieldName()
+ " = " + String.valueOf(mo.getID());
final ResultSet arrayList = connection.createStatement().executeQuery(query);
final PreparedStatement psAttrMapDelArray = connection.prepareStatement(
DEL_ATTR_MAPPING_ARRAY);
psAttrMapDelArray.setInt(1, mo.getClassID());
psAttrMapDelArray.setInt(2, mo.getID());
psAttrMapDelArray.setInt(3, mai.getForeignKeyClassId());
psAttrMapDelArray.executeUpdate();
while (arrayList.next()) {
// lazily prepare the statement
if (psAttrMapArray == null) {
psAttrMapArray = connection.prepareStatement(INS_ATTR_MAPPING);
}
psAttrMapArray.setInt(1, mo.getClassID());
psAttrMapArray.setInt(2, mo.getID());
psAttrMapArray.setInt(3, mai.getForeignKeyClassId());
psAttrMapArray.setInt(4, arrayList.getInt(1));
psAttrMapArray.addBatch();
}
arrayList.close();
}
rs.close();
} else {
// lazily prepare the statement
if (psAttrMap == null) {
psAttrMap = connection.prepareStatement(UP_ATTR_MAPPING);
}
// if field represents a foreign key the attribute value
// is assumed to be a MetaObject
final MetaObject value = (MetaObject)attr.getValue();
psAttrMap.setInt(1, (value == null) ? -1 : value.getID());
psAttrMap.setInt(2, mo.getClassID());
psAttrMap.setInt(3, mo.getID());
psAttrMap.setInt(4, mai.getForeignKeyClassId());
psAttrMap.addBatch();
if (LOG.isDebugEnabled()) {
final StringBuilder logMessage = new StringBuilder(
"Parameterized SQL added to batch: ");
logMessage.append(UP_ATTR_MAPPING);
logMessage.append('\n');
logMessage.append("attr_obj_id: ");
logMessage.append(String.valueOf((value == null) ? -1 : value.getID()));
logMessage.append("class_id: ");
logMessage.append(String.valueOf(mo.getClassID()));
logMessage.append("object_id: ");
logMessage.append(String.valueOf(mo.getID()));
logMessage.append("attr_class_id: ");
logMessage.append(String.valueOf(mai.getForeignKeyClassId()));
LOG.debug(logMessage.toString());
}
}
} else {
// lazily prepare the statement
if (psAttrString == null) {
psAttrString = connection.prepareStatement(UP_ATTR_STRING);
}
// interpret the fields value as a string
psAttrString.setString(1, (attr.getValue() == null) ? NULL : String.valueOf(attr.getValue()));
psAttrString.setInt(2, mo.getClassID());
psAttrString.setInt(3, mo.getID());
psAttrString.setInt(4, mai.getId());
psAttrString.addBatch();
if (LOG.isDebugEnabled()) {
final StringBuilder logMessage = new StringBuilder("Parameterized SQL added to batch: ");
logMessage.append(UP_ATTR_MAPPING);
logMessage.append('\n');
logMessage.append("attr_obj_id: ");
logMessage.append(String.valueOf(attr.getValue()));
logMessage.append("class_id: ");
logMessage.append(String.valueOf(mo.getClassID()));
logMessage.append("object_id: ");
logMessage.append(String.valueOf(mo.getID()));
logMessage.append("attr_class_id: ");
logMessage.append(String.valueOf(mai.getId()));
LOG.debug(logMessage.toString());
}
}
}
}
// execute the batches if there are indexed fields
if (psAttrString != null) {
final int[] strRows = psAttrString.executeBatch();
if (LOG.isDebugEnabled()) {
int updateCount = 0;
for (final int row : strRows) {
updateCount += row;
}
LOG.debug("cs_attr_string: updated " + updateCount + " rows"); // NOI18N
}
}
if (psAttrMap != null) {
final int[] mapRows = psAttrMap.executeBatch();
if (LOG.isDebugEnabled()) {
int updateCount = 0;
for (final int row : mapRows) {
updateCount += row;
}
LOG.debug("cs_attr_object(complex objects): updated " + updateCount + " rows"); // NOI18N
}
}
if (psAttrMapArray != null) {
final int[] mapRows = psAttrMapArray.executeBatch();
if (LOG.isDebugEnabled()) {
int updateCount = 0;
for (final int row : mapRows) {
updateCount += row;
}
LOG.debug("cs_attr_object(Array-Entries): updated " + updateCount + " rows"); // NOI18N
}
}
if (mo.getMetaClass().isIndexed()) {
updateDerivedIndex(connection, mo);
}
} catch (final SQLException e) {
LOG.error(
"could not insert index for object '" // NOI18N
+ mo.getID()
+ "' of class '" // NOI18N
+ mo.getClass()
+ "'", // NOI18N
e);
// TODO: consider to wrap exception
throw e;
} finally {
DBConnection.closeStatements(psAttrString, psAttrMap);
}
}
/**
* DOCUMENT ME!
*
* @param connection DOCUMENT ME!
* @param mo DOCUMENT ME!
*
* @throws SQLException DOCUMENT ME!
*/
private void updateDerivedIndex(final Connection connection, final MetaObject mo) throws SQLException {
final PreparedStatement psDeleteAttrMapDerive = connection.prepareStatement(DEL_DERIVE_ATTR_MAPPING);
final PreparedStatement psInsertAttrMapDerive = connection.prepareStatement(INS_DERIVE_ATTR_MAPPING);
psDeleteAttrMapDerive.setInt(1, mo.getClassID());
psDeleteAttrMapDerive.setInt(2, mo.getID());
psInsertAttrMapDerive.setInt(1, mo.getClassID());
psInsertAttrMapDerive.setInt(2, mo.getID());
final int del = psDeleteAttrMapDerive.executeUpdate();
final int ins = psInsertAttrMapDerive.executeUpdate();
if (LOG.isDebugEnabled()) {
LOG.debug("cs_attr_object_derived: updated. deleted:" + del + ", inserted:" + ins); // NOI18N
}
}
/**
* mscholl: Deletes the index from cs_attr_string and cs_all_attr_mapping for a given metaobject. If the metaobject
* does not contain a metaclass it is skipped.
*
* @param connection DOCUMENT ME!
* @param mo the metaobject which will be deleted
*
* @throws SQLException if an error occurs during index deletion
* @throws IllegalArgumentException NullPointerException DOCUMENT ME!
*/
private void deleteIndex(final Connection connection, final MetaObject mo) throws SQLException {
if (mo == null) {
throw new IllegalArgumentException("MetaObject must not be null"); // NOI18N
} else if (mo.isDummy()) {
// don't do anything with a dummy object
if (LOG.isDebugEnabled()) {
LOG.debug("delete index for dummy won't be done"); // NOI18N
}
return;
} else if (LOG.isInfoEnabled()) {
LOG.info("delete index for MetaObject: " + mo); // NOI18N
}
PreparedStatement psAttrString = null;
PreparedStatement psAttrMap = null;
PreparedStatement psAttrDerive = null;
try {
// prepare the update statements
psAttrString = connection.prepareStatement(DEL_ATTR_STRING);
psAttrMap = connection.prepareStatement(DEL_ATTR_MAPPING);
psAttrDerive = connection.prepareStatement(DEL_DERIVE_ATTR_MAPPING);
// set the appropriate param values
psAttrString.setInt(1, mo.getClassID());
psAttrString.setInt(2, mo.getID());
psAttrMap.setInt(1, mo.getClassID());
psAttrMap.setInt(2, mo.getID());
psAttrDerive.setInt(1, mo.getClassID());
psAttrDerive.setInt(2, mo.getID());
// execute the deletion
final int strRows = psAttrString.executeUpdate();
final int mapRows = psAttrMap.executeUpdate();
final int mapDeriveRows = psAttrDerive.executeUpdate();
if (LOG.isDebugEnabled()) {
LOG.debug("cs_attr_string: deleted " + strRows + " rows"); // NOI18N
LOG.debug("cs_all_attr_mapping: deleted " + mapRows + " rows"); // NOI18N
LOG.debug("cs_all_attr_mapping_derive: deleted " + mapDeriveRows + " rows"); // NOI18N
}
} catch (final SQLException e) {
LOG.error(
"could not delete index for object '" // NOI18N
+ mo.getID()
+ "' of class '" // NOI18N
+ mo.getClass()
+ "'", // NOI18N
e);
// TODO: consider to wrap exception
throw e;
} finally {
DBConnection.closeStatements(psAttrString, psAttrMap);
}
}
@Override
public void afterCommittedInsert(final CidsBean cidsBean, final User user) {
}
@Override
public void afterCommittedUpdate(final CidsBean cidsBean, final User user) {
}
@Override
public void afterCommittedDelete(final CidsBean cidsBean, final User user) {
}
}