/* * Copyright (C) 2012 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.checker; import org.exoplatform.services.database.utils.JDBCUtils; import org.exoplatform.services.jcr.access.AccessControlList; import org.exoplatform.services.jcr.core.nodetype.NodeDefinitionData; import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager; import org.exoplatform.services.jcr.datamodel.IllegalNameException; import org.exoplatform.services.jcr.datamodel.IllegalPathException; import org.exoplatform.services.jcr.datamodel.InternalQName; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.datamodel.PropertyData; import org.exoplatform.services.jcr.datamodel.QPath; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.impl.dataflow.TransientNodeData; import org.exoplatform.services.jcr.impl.dataflow.persistent.SimpleChangedSizeHandler; import org.exoplatform.services.jcr.impl.storage.JCRInvalidItemStateException; import org.exoplatform.services.jcr.impl.storage.jdbc.DBConstants; import org.exoplatform.services.jcr.impl.storage.jdbc.JDBCDataContainerConfig; import org.exoplatform.services.jcr.impl.storage.jdbc.JDBCStorageConnection; import org.exoplatform.services.jcr.impl.storage.jdbc.PrimaryTypeNotFoundException; import org.exoplatform.services.jcr.impl.storage.jdbc.db.WorkspaceStorageConnectionFactory; import org.exoplatform.services.jcr.impl.util.jdbc.DBInitializerHelper; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.jcr.InvalidItemStateException; import javax.jcr.RepositoryException; /** * Removes broken node and whole subtree. * * @author <a href="abazko@exoplatform.com">Anatoliy Bazko</a> * @version $Id: NodeRemover.java 34360 2009-07-22 23:58:59Z tolusha $ */ public class NodeRemover extends AbstractInconsistencyRepair { private final NodeTypeDataManager nodeTypeManager; /** * JCR item table name. */ private final String iTable; /** * NodeRemover constructor. */ public NodeRemover(WorkspaceStorageConnectionFactory connFactory, JDBCDataContainerConfig containerConfig, NodeTypeDataManager nodeTypeManager) { super(connFactory, containerConfig); this.nodeTypeManager = nodeTypeManager; this.iTable = DBInitializerHelper.getItemTableName(containerConfig); } /** * {@inheritDoc} */ void repairRow(JDBCStorageConnection conn, ResultSet resultSet) throws SQLException { try { validateIfRequiredByParent(conn, resultSet); NodeData data = createNodeData(resultSet); removeChildrenItems(conn, resultSet); conn.delete(data); } catch (JCRInvalidItemStateException e) { // It is ok. Node already removed in previous check if (LOG.isTraceEnabled()) { LOG.trace(e.getMessage(), e); } } catch (IllegalStateException e) { throw new SQLException(e); } catch (RepositoryException e) { throw new SQLException(e); } catch (IllegalNameException e) { throw new SQLException(e); } } /** * Validates if node represented by instance of {@link ResultSet} is mandatory * for parent node. It means should not be removed. Throws {@link SQLException} * in this case with appropriate message. */ private void validateIfRequiredByParent(JDBCStorageConnection conn, ResultSet resultSet) throws RepositoryException, SQLException, IllegalNameException { String parentId = getIdentifier(resultSet, DBConstants.COLUMN_PARENTID); InternalQName nodeName = InternalQName.parse(resultSet.getString(DBConstants.COLUMN_NAME)); NodeData parent = null; try { parent = (NodeData)conn.getItemData(parentId); } catch (PrimaryTypeNotFoundException e) { // It is possible, parent also without primaryType property return; } // parent already removed in previous check if (parent == null) { return; } NodeDefinitionData def = nodeTypeManager.getChildNodeDefinition(nodeName, parent.getPrimaryTypeName(), parent.getMixinTypeNames()); if (!def.isResidualSet()) { throw new SQLException("Node is required by its parent."); } } /** * Removes all children items. */ private void removeChildrenItems(JDBCStorageConnection conn, ResultSet resultSet) throws SQLException, IllegalNameException, IllegalStateException, UnsupportedOperationException, InvalidItemStateException, RepositoryException { String parentId = resultSet.getString(DBConstants.COLUMN_ID); String selectStatement = "select * from " + iTable + " where I_CLASS = 1 and PARENT_ID = '" + parentId + "'"; String deleteStatement = "delete from " + iTable + " where I_CLASS = 1 and PARENT_ID = '" + parentId + "'"; // traversing down to the bottom of the tree PreparedStatement statement = conn.getJdbcConnection().prepareStatement(selectStatement); ResultSet selResult = statement.executeQuery(); try { while (selResult.next()) { removeChildrenItems(conn, selResult); } } finally { JDBCUtils.freeResources(selResult, statement, null); } // remove properties NodeData node = createNodeData(resultSet); for (PropertyData prop : conn.getChildPropertiesData(node)) { conn.delete(prop, new SimpleChangedSizeHandler()); } // remove nodes statement = conn.getJdbcConnection().prepareStatement(deleteStatement); try { statement.execute(); } finally { JDBCUtils.freeResources(null, statement, null); } } /** * Restore {@link NodeData} represented by row in ITEM table. */ private NodeData createNodeData(ResultSet resultSet) throws SQLException, IllegalPathException, IllegalNameException { String nodeId = getIdentifier(resultSet, DBConstants.COLUMN_ID); int orderNum = resultSet.getInt(DBConstants.COLUMN_NORDERNUM); int version = resultSet.getInt(DBConstants.COLUMN_VERSION); QPath path = QPath.makeChildPath(QPath.parse("[]unknown-parent-node-remover"), getQPathEntry(resultSet)); return new TransientNodeData(path, nodeId, version, Constants.NT_UNSTRUCTURED, new InternalQName[0], orderNum, Constants.ROOT_UUID, new AccessControlList()); } }