/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package org.apereo.portal.layout.dlm; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apereo.portal.PortalException; import org.apereo.portal.layout.IUserLayoutStore; import org.apereo.portal.security.IPerson; import org.apereo.portal.spring.locator.UserLayoutStoreLocator; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Looks for, applies against the ilf, and updates accordingly the delete set within a plf. * * @since 2.5 */ public class DeleteManager { private static final Log LOG = LogFactory.getLog(DeleteManager.class); private static IUserLayoutStore dls = null; /** * Hands back the single instance of RDBMDistributedLayoutStore. There is already a method for * aquiring a single instance of the configured layout store so we delegate over there so that * all references refer to the same instance. This method is solely for convenience so that we * don't have to keep calling UserLayoutStoreFactory and casting the resulting class. */ private static IUserLayoutStore getDLS() { if (dls == null) { dls = UserLayoutStoreLocator.getUserLayoutStore(); } return dls; } /** * Get the delete set if any from the plf and process each delete command removing any that fail * from the delete set so that the delete set is self cleaning. */ static void applyAndUpdateDeleteSet(Document plf, Document ilf, IntegrationResult result) { Element dSet = null; try { dSet = getDeleteSet(plf, null, false); } catch (Exception e) { LOG.error("Exception occurred while getting user's DLM delete-set.", e); } if (dSet == null) return; NodeList deletes = dSet.getChildNodes(); for (int i = deletes.getLength() - 1; i >= 0; i--) { if (applyDelete((Element) deletes.item(i), ilf) == false) { dSet.removeChild(deletes.item(i)); result.setChangedPLF(true); } else { result.setChangedILF(true); } } if (dSet.getChildNodes().getLength() == 0) { plf.getDocumentElement().removeChild(dSet); result.setChangedPLF(true); } } /** * Attempt to apply a single delete command and return true if it succeeds or false otherwise. * If the delete is disallowed or the target element no longer exists in the document the delete * command fails and returns false. */ private static boolean applyDelete(Element delete, Document ilf) { String nodeID = delete.getAttribute(Constants.ATT_NAME); Element e = ilf.getElementById(nodeID); if (e == null) return false; String deleteAllowed = e.getAttribute(Constants.ATT_DELETE_ALLOWED); if (deleteAllowed.equals("false")) return false; Element p = (Element) e.getParentNode(); e.setIdAttribute(Constants.ATT_ID, false); p.removeChild(e); return true; } /** * Get the delete set if any stored in the root of the document or create it is passed in create * flag is true. */ private static Element getDeleteSet(Document plf, IPerson person, boolean create) throws PortalException { Node root = plf.getDocumentElement(); Node child = root.getFirstChild(); while (child != null) { if (child.getNodeName().equals(Constants.ELM_DELETE_SET)) return (Element) child; child = child.getNextSibling(); } if (create == false) return null; String ID = null; try { ID = getDLS().getNextStructDirectiveId(person); } catch (Exception e) { throw new PortalException( "Exception encountered while " + "generating new delete set node " + "Id for userId=" + person.getID(), e); } Element delSet = plf.createElement(Constants.ELM_DELETE_SET); delSet.setAttribute(Constants.ATT_TYPE, Constants.ELM_DELETE_SET); delSet.setAttribute(Constants.ATT_ID, ID); root.appendChild(delSet); return delSet; } /** * Create and append a delete directive to delete the node identified by the passed in element * id. If this node contains any incorporated elements then they must also have a delete * directive added in here to prevent incorporated channels originating in another column from * reappearing in that column because the position set entry that pulled them into this column * was now removed. (ie: the user moved an inc'd channel to this column and then deleted the * column means that the inc'd channel should be deleted also.) This was designed to add a * delete directive for each nested element having an ID so as to work for the future case of a * tree view. */ public static void addDeleteDirective(Element compViewNode, String elementID, IPerson person) throws PortalException { Document plf = (Document) person.getAttribute(Constants.PLF); Element delSet = getDeleteSet(plf, person, true); addDeleteDirective(compViewNode, elementID, person, plf, delSet); } /** * This method does the actual work of adding a delete directive and then recursively calling * itself for any incoporated children that need to be deleted as well. */ private static void addDeleteDirective( Element compViewNode, String elementID, IPerson person, Document plf, Element delSet) throws PortalException { String ID = null; try { ID = getDLS().getNextStructDirectiveId(person); } catch (Exception e) { throw new PortalException( "Exception encountered while " + "generating new delete node " + "Id for userId=" + person.getID(), e); } Element delete = plf.createElement(Constants.ELM_DELETE); delete.setAttribute(Constants.ATT_TYPE, Constants.ELM_DELETE); delete.setAttribute(Constants.ATT_ID, ID); delete.setAttributeNS(Constants.NS_URI, Constants.ATT_NAME, elementID); delSet.appendChild(delete); // now pass through children and add delete directives for those with // IDs indicating that they were incorporated Element child = (Element) compViewNode.getFirstChild(); while (child != null) { String childID = child.getAttribute("ID"); if (childID.startsWith(Constants.FRAGMENT_ID_USER_PREFIX)) addDeleteDirective(child, childID, person, plf, delSet); child = (Element) child.getNextSibling(); } } }