/**
* 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 java.io.StringWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apereo.portal.AuthorizationException;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.security.IAuthorizationPrincipal;
import org.apereo.portal.security.IPerson;
import org.apereo.portal.services.AuthorizationService;
import org.apereo.portal.utils.DocumentFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Performs merging of layout fragments into a single document containing all incorporated layout
* fragment elements from the set of fragments passed in. This merge is trivial, appending all
* children of each fragment into the composite document and recording their identifiers in the
* document identifier cache. No changes are made to the source fragments passed in.
*
* @since 2.5
*/
public class ILFBuilder {
private static final Log LOG = LogFactory.getLog(ILFBuilder.class);
public static Document constructILF(Document PLF, List<Document> sequence, IPerson person) {
if (LOG.isDebugEnabled()) {
LOG.debug("Constructing ILF for IPerson='" + person + "'");
}
// first construct the destination document and root element. The root
// element should be a complete copy of the PLF's root including its
// node identifier in the new document. This requires the use of
// the implementation class to set the identifier for that node
// in the document.
Document result = DocumentFactory.getThreadDocument();
Element plfLayout = PLF.getDocumentElement();
Element ilfLayout = (Element) result.importNode(plfLayout, false);
result.appendChild(ilfLayout);
Element plfRoot = (Element) plfLayout.getFirstChild();
Element ilfRoot = (Element) result.importNode(plfRoot, false);
ilfLayout.appendChild(ilfRoot);
if (ilfRoot.getAttribute(Constants.ATT_ID) != null)
ilfRoot.setIdAttribute(Constants.ATT_ID, true);
// build the auth principal for determining if pushed channels can be
// used by this user
EntityIdentifier ei = person.getEntityIdentifier();
AuthorizationService authS = AuthorizationService.instance();
IAuthorizationPrincipal ap = authS.newPrincipal(ei.getKey(), ei.getType());
// now merge fragments one at a time into ILF document
for (final Document document : sequence) {
mergeFragment(document, result, ap);
}
return result;
}
/**
* Passes the layout root of each of these documents to mergeChildren causing all children of
* newLayout to be merged into compositeLayout following merging protocal for distributed layout
* management.
*
* @throws AuthorizationException
*/
public static void mergeFragment(
Document fragment, Document composite, IAuthorizationPrincipal ap)
throws AuthorizationException {
Element fragmentLayout = fragment.getDocumentElement();
Element fragmentRoot = (Element) fragmentLayout.getFirstChild();
Element compositeLayout = composite.getDocumentElement();
Element compositeRoot = (Element) compositeLayout.getFirstChild();
mergeChildren(fragmentRoot, compositeRoot, ap, new HashSet());
}
/**
* @param source parent of children
* @param dest receiver of children
* @param ap User's authorization principal for determining if they can view a channel
* @param visitedNodes A Set of nodes from the source tree that have been visited to get to this
* node, used to ensure a loop doesn't exist in the source tree.
* @throws AuthorizationException
*/
private static void mergeChildren(
Element source, Element dest, IAuthorizationPrincipal ap, Set visitedNodes)
throws AuthorizationException {
//Record this node in the visited nodes set. If add returns false a loop has been detected
if (!visitedNodes.add(source)) {
final String msg =
"mergeChildren has encountered a loop in the source DOM. currentNode='"
+ source
+ "', currentDepth='"
+ visitedNodes.size()
+ "', visitedNodes='"
+ visitedNodes
+ "'";
final IllegalStateException ise = new IllegalStateException(msg);
LOG.error(msg, ise);
printNodeToDebug(source, "Source");
printNodeToDebug(dest, "Dest");
throw ise;
}
Document destDoc = dest.getOwnerDocument();
Node item = source.getFirstChild();
while (item != null) {
if (item instanceof Element) {
Element child = (Element) item;
Element newChild = null;
if (null != child && mergeAllowed(child, ap)) {
newChild = (Element) destDoc.importNode(child, false);
dest.appendChild(newChild);
String id = newChild.getAttribute(Constants.ATT_ID);
if (id != null && !id.equals(""))
newChild.setIdAttribute(Constants.ATT_ID, true);
mergeChildren(child, newChild, ap, visitedNodes);
}
}
item = item.getNextSibling();
}
//Remove this node from the visited nodes set
visitedNodes.remove(source);
}
/**
* Tests to see if channels to be merged from ILF can be rendered by the end user. If not then
* they are discarded from the merge.
*
* @param child
* @param person
* @return
* @throws AuthorizationException
* @throws NumberFormatException
*/
private static boolean mergeAllowed(Element child, IAuthorizationPrincipal ap)
throws AuthorizationException {
if (!child.getTagName().equals("channel")) return true;
String channelPublishId = child.getAttribute("chanID");
return ap.canRender(channelPublishId);
}
private static void printNodeToDebug(Node n, String name)
throws TransformerFactoryConfigurationError {
if (!LOG.isDebugEnabled()) {
return;
}
final StringWriter writer = new StringWriter();
try {
final TransformerFactory transFactory = TransformerFactory.newInstance();
final Transformer trans = transFactory.newTransformer();
final Source xmlSource = new DOMSource(n);
final Result transResult = new StreamResult(writer);
trans.transform(xmlSource, transResult);
final String xmlStr = writer.toString();
LOG.debug(name + " DOM Tree:\n\n" + xmlStr);
} catch (Exception e) {
LOG.error("Error printing out " + name + " DOM Tree", e);
final String xmlStr = writer.toString();
LOG.debug("Partial " + name + " DOM Tree:\n\n" + xmlStr);
}
}
}