/* * Copyright (C) 2009 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.services.jcr.impl.storage.jdbc; import org.exoplatform.services.jcr.dataflow.ItemState; import org.exoplatform.services.jcr.datamodel.ItemData; import org.exoplatform.services.jcr.datamodel.ItemType; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.datamodel.QPathEntry; import org.exoplatform.services.jcr.impl.storage.JCRInvalidItemStateException; import org.exoplatform.services.jcr.impl.storage.JCRItemExistsException; import java.io.IOException; import java.sql.BatchUpdateException; import java.sql.SQLException; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemExistsException; import javax.jcr.RepositoryException; /** * Created by The eXo Platform SAS. * * The eXo JCR database has constraints can be violated. * * JCR_PK_XCONTAINER - Can not be exisits two containers with same version * * JCR_PK_XITEM - Item already exists with this ID JCR_FK_XITEM_PARENT - Parent not found by ID * JCR_IDX_XITEM_PARENT - Item already exists with the parent, name, index, type(N/P), persisted * version JCR_IDX_XITEM_PARENT_NAME - Item already exists with the type(N/P), parent, name, index, * version JCR_IDX_XITEM_PARENT_ID - Item already exists with the type(N/P), parent, ID, * persisted version * * JCR_PK_XVALUE - Value already exists with the ID (impossible, ID is autiincremented) * JCR_FK_XVALUE_PROPERTY - There is no property exists for the value. JCR_IDX_XVALUE_PROPERTY - * Value already exists with the property and order number * * JCR_PK_XREF - Reference already exists to the node from property with order number * JCR_IDX_XREF_PROPERTY - Reference already exists with the property and order number * * @author Peter Nedonosko * @version $Id: SQLExceptionHandler.java 34801 2009-07-31 15:44:50Z dkatayev $ */ public class SQLExceptionHandler { /** * Ccontainer name. */ private final String containerName; /** * Storage connection. */ private final JDBCStorageConnection conn; // ---------------- SQLException handler ------------------- /** * SQLExceptionHandler constructor. * * @param containerName * - workspace container name * @param conn * - storage connection */ SQLExceptionHandler(String containerName, JDBCStorageConnection conn) { this.containerName = containerName; this.conn = conn; } /** * Handle Add SQLException. * * @param e * - an SQLException * @param item * - context ItemData * @return String with error message * @throws RepositoryException * if <code>RepositoryException</code> should be thrown * @throws InvalidItemStateException * if <code>InvalidItemStateException</code> should be thrown */ public String handleAddException(SQLException e, ItemData item) throws RepositoryException, InvalidItemStateException { if (e instanceof BatchUpdateException) { // gets original exception if (e.getNextException() != null) { e = e.getNextException(); } else if (e.getCause() instanceof SQLException) { e = (SQLException)e.getCause(); } } StringBuilder message = new StringBuilder("["); message.append(containerName).append("] ADD ").append(item.isNode() ? "NODE. " : "PROPERTY. "); String errMessage = e.getMessage(); String itemInfo = item.getQPath().getAsString() + ", ID: " + item.getIdentifier() + ", ParentID: " + item.getParentIdentifier() + (errMessage != null ? ". Cause >>>> " + errMessage : ""); if (errMessage != null) { // try detect error by foreign key names String umsg = errMessage.toLowerCase().toUpperCase(); if (umsg.indexOf(conn.JCR_FK_ITEM_PARENT) >= 0) { message.append("Parent not found. Item ").append(itemInfo); throw new JCRInvalidItemStateException(message.toString(), item.getIdentifier(), ItemState.ADDED, e); } else if (umsg.indexOf(conn.JCR_PK_ITEM) >= 0) { message.append("Item already exists. Condition: ID. ").append(itemInfo); // InvalidItemStateException ! - because it's impossible add new item with existed UUID throw new JCRInvalidItemStateException(message.toString(), item.getIdentifier(), ItemState.ADDED, e); } else if (umsg.indexOf(conn.JCR_IDX_ITEM_PARENT_NAME) >= 0) { message.append("Item already exists. Condition: parent ID and ID. ").append(itemInfo); throw new ItemExistsException(message.toString(), e); } else if (umsg.indexOf(conn.JCR_IDX_ITEM_PARENT_ID) >= 0) { message.append("Item already exists. Condition: parent ID and ID. ").append(itemInfo); throw new ItemExistsException(message.toString(), e); } else if (umsg.indexOf(conn.JCR_IDX_ITEM_PARENT) >= 0) { message.append("Item already exists. Condition: parent ID, name, index. ").append(itemInfo); throw new ItemExistsException(message.toString(), e); } else if (umsg.indexOf(conn.JCR_FK_VALUE_PROPERTY) >= 0) { message.append("Property is not exist but the value is being created. Condition: property ID. ").append( itemInfo); throw new RepositoryException(message.toString(), e); } else if (umsg.indexOf(conn.JCR_IDX_VALUE_PROPERTY) >= 0) { message.append("Property already exists. Condition: property ID, order number. ").append(itemInfo); throw new RepositoryException(message.toString(), e); } else if (umsg.indexOf(conn.JCR_PK_VALUE) >= 0) { message .append( "[FATAL] Value already exists with the ValueID. Impossible state, check is ValueID is autoincremented. ") .append(itemInfo); throw new RepositoryException(message.toString(), e); } else if (umsg.indexOf(conn.JCR_PK_REF) >= 0) { message.append("Reference chain already exists. Condition: node ID, property ID, order number. ").append( itemInfo); throw new RepositoryException(message.toString(), e); } else if (umsg.indexOf(conn.JCR_IDX_REF_PROPERTY) >= 0) { message.append("Referenceable property value already exists. Condition: property ID, order number. ") .append(itemInfo); throw new RepositoryException(message.toString(), e); } } // try detect integrity violation RepositoryException ownException = null; try { NodeData parent = (NodeData)conn.getItemData(item.getParentIdentifier()); if (parent != null) { // have a parent try { ItemData me = conn.getItemData(item.getIdentifier()); if (me != null) { // item already exists message.append("Item already exists in storage: ").append(itemInfo); ownException = new JCRItemExistsException(message.toString(), me.getIdentifier(), ItemState.ADDED, e); throw ownException; } me = conn.getItemData(parent, new QPathEntry(item.getQPath().getName(), item.getQPath().getIndex()), ItemType.getItemType(item)); if (me != null) { message.append("Item already exists in storage: ").append(itemInfo); ownException = new JCRItemExistsException(message.toString(), me.getIdentifier(), ItemState.ADDED, e); throw ownException; } } catch (Exception ep) { // item not found or other things but error of item reading if (ownException != null) throw ownException; } // MySQL violation if (e.getClass().getName().indexOf("MySQLIntegrityConstraintViolationException") >= 0 && errMessage.indexOf(item.getIdentifier()) >= 0) { // it's JCR_PK_ITEM violation message.append("Item already exists. Condition: ID. ").append(itemInfo); throw new JCRInvalidItemStateException(message.toString(), item.getIdentifier(), ItemState.ADDED, e); } // MySQL 5.1 violation with batching update if (e.getClass().getName().indexOf("BatchUpdateException") >= 0 && errMessage.indexOf(item.getIdentifier()) >= 0) { // it's JCR_PK_ITEM violation message.append("Item already exists. Condition: ID. ").append(itemInfo); throw new JCRInvalidItemStateException(message.toString(), item.getIdentifier(), ItemState.ADDED, e); } // DB2 violation if (e.getClass().getName().indexOf("SqlIntegrityConstraintViolationException") >= 0 && errMessage.indexOf("SQLCODE=-803") >= 0) { message.append("Item already exists.").append(itemInfo); throw new JCRInvalidItemStateException(message.toString(), item.getIdentifier(), ItemState.ADDED, e); } // H2 violation if (e.getClass().getName().indexOf("JdbcSQLException") >= 0 && errMessage.indexOf("Unique index or primary key violation") >= 0) { message.append("Item already exists.").append(itemInfo); throw new JCRInvalidItemStateException(message.toString(), item.getIdentifier(), ItemState.ADDED, e); } message.append("Error of item add. ").append(itemInfo); ownException = new RepositoryException(message.toString(), e); throw ownException; } } catch (Exception ep) { // no parent or error access it if (ownException != null) throw ownException; } message.append("Error of item add. ").append(itemInfo); throw new JCRInvalidItemStateException(message.toString(), item.getIdentifier(), ItemState.ADDED, e); } /** * Handle Add IOException. * * @param e * - an IOException * @param item * - context ItemData * @return String with error message * @throws RepositoryException * if <code>RepositoryException</code> should be thrown * @throws InvalidItemStateException * if <code>InvalidItemStateException</code> should be thrown */ protected String handleAddException(IOException e, ItemData item) throws RepositoryException, InvalidItemStateException { StringBuilder message = new StringBuilder("["); message.append(containerName).append("] ADD ").append(item.isNode() ? "NODE. " : "PROPERTY. "); String errMessage = e.getMessage(); String itemInfo = item.getQPath().getAsString() + ", ID: " + item.getIdentifier() + ", ParentID: " + item.getParentIdentifier() + (errMessage != null ? ". Cause >>>> " + errMessage : ""); // try detect integrity violation RepositoryException ownException = null; try { NodeData parent = (NodeData)conn.getItemData(item.getParentIdentifier()); if (parent != null) { // have a parent try { ItemData me = conn.getItemData(item.getIdentifier()); if (me != null) { // item already exists message.append("Item already exists in storage: ").append(itemInfo); ownException = new ItemExistsException(message.toString(), e); throw ownException; } me = conn.getItemData(parent, new QPathEntry(item.getQPath().getName(), item.getQPath().getIndex()), ItemType.getItemType(item)); if (me != null) { message.append("Item already exists in storage: ").append(itemInfo); ownException = new ItemExistsException(message.toString(), e); throw ownException; } } catch (Exception ep) { // item not found or other things but error of item reading if (ownException != null) throw ownException; } message.append("Error of item add. ").append(itemInfo); ownException = new RepositoryException(message.toString(), e); throw ownException; } } catch (Exception ep) { // no parent or error access it if (ownException != null) throw ownException; } message.append("Error of item add. ").append(itemInfo); throw new JCRInvalidItemStateException(message.toString(), item.getIdentifier(), ItemState.ADDED, e); } /** * Handle delete Exceptions. * * @param e * - an SQLException * @param item * - context ItemData * @return String with error message * @throws RepositoryException * if <code>RepositoryException</code> should be thrown * @throws InvalidItemStateException * if <code>InvalidItemStateException</code> should be thrown */ public String handleDeleteException(SQLException e, ItemData item) throws RepositoryException, InvalidItemStateException { StringBuilder message = new StringBuilder("["); message.append(containerName).append("] DELETE ").append(item.isNode() ? "NODE. " : "PROPERTY. "); String errMessage = e.getMessage(); String itemInfo = item.getQPath().getAsString() + " " + item.getIdentifier() + (errMessage != null ? ". Cause >>>> " + errMessage : ""); if (errMessage != null) { // try detect error by foreign key names String umsg = errMessage.toLowerCase().toUpperCase(); if (umsg.indexOf(conn.JCR_FK_ITEM_PARENT) >= 0) { message.append("Can not delete parent till childs exists. Item ").append(itemInfo); throw new JCRInvalidItemStateException(message.toString(), item.getIdentifier(), ItemState.DELETED, e); } else if (umsg.indexOf(conn.JCR_FK_VALUE_PROPERTY) >= 0) { message.append("[FATAL] Can not delete property item till it contains values. Condition: property ID. ") .append(itemInfo); throw new RepositoryException(message.toString(), e); } } message.append("Error of item delete ").append(itemInfo); throw new RepositoryException(message.toString(), e); } /** * Handle update Exceptions. * * @param e * - an SQLException * @param item * - context ItemData * @return String with error message * @throws RepositoryException * if <code>RepositoryException</code> should be thrown * @throws InvalidItemStateException * if <code>InvalidItemStateException</code> should be thrown */ public String handleUpdateException(SQLException e, ItemData item) throws RepositoryException, InvalidItemStateException { StringBuilder message = new StringBuilder("["); message.append(containerName).append("] EDIT ").append(item.isNode() ? "NODE. " : "PROPERTY. "); String errMessage = e.getMessage(); String itemInfo = item.getQPath().getAsString() + " " + item.getIdentifier() + (errMessage != null ? ". Cause >>>> " + errMessage : ""); if (errMessage != null) // try detect error by foreign key names if (errMessage.toLowerCase().toUpperCase().indexOf(conn.JCR_FK_VALUE_PROPERTY) >= 0) { message.append("Property is not exists but the value is being created. Condition: property ID. ").append( itemInfo); throw new RepositoryException(message.toString(), e); } else if (errMessage.toLowerCase().toUpperCase().indexOf(conn.JCR_PK_ITEM) >= 0) { message.append("Item already exists. Condition: ID. ").append(itemInfo); throw new JCRInvalidItemStateException(message.toString(), item.getIdentifier(), ItemState.UPDATED, e); } // try detect integrity violation RepositoryException ownException = null; try { ItemData me = conn.getItemData(item.getIdentifier()); if (me != null) { // item already exists message.append("Item already exists. But update errors. ").append(itemInfo); ownException = new RepositoryException(message.toString(), e); throw ownException; } } catch (Exception ep) { // item not found or other things but error of item reading if (ownException != null) throw ownException; } message.append("Error of item update. ").append(itemInfo); throw new JCRInvalidItemStateException(message.toString(), item.getIdentifier(), ItemState.UPDATED, e); } }