org.apereo.portal.layout.dlm.NodeReferenceFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.apereo.portal.layout.dlm.NodeReferenceFactory.java

Source

/**
 * 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:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.xpath.XPathConstants;

import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apereo.portal.IUserIdentityStore;
import org.apereo.portal.IUserProfile;
import org.apereo.portal.UserProfile;
import org.apereo.portal.layout.IUserLayoutStore;
import org.apereo.portal.security.provider.BrokenSecurityContext;
import org.apereo.portal.security.provider.PersonImpl;
import org.apereo.portal.utils.Tuple;
import org.apereo.portal.xml.XmlUtilities;
import org.apereo.portal.xml.xpath.XPathOperations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

/**
 * Factory object responsible for creating DLM reference objects like 
 * {@link Pathref}s and {@link Noderef}s.  One instance of NodeReferenceFactory 
 * should be present in the Spring context, whence it also derives its 
 * dependencies.
 * 
 * @author awills
 */
@Component
public final class NodeReferenceFactory {

    private static final Pattern DLM_PATH_REF_DELIM = Pattern.compile("\\:");
    private final static Pattern USER_NODE_PATTERN = Pattern.compile("\\A([a-zA-Z]\\d*)\\z");
    private final static Pattern DLM_NODE_PATTERN = Pattern.compile("u(\\d+)l\\d+([ns]\\d+)");

    private final Log log = LogFactory.getLog(getClass());

    @Autowired
    private IUserLayoutStore layoutStore;

    @Autowired
    private IUserIdentityStore userIdentityStore;

    @Autowired
    private XmlUtilities xmlUtilities;

    @Autowired
    private XPathOperations xPathOperations;

    /*
     * Public API.
     */

    /**
     * Returns a valid {@link Noderef} based on the specified arguments or 
     * <code>null</null> if that's not possible.<br/>
     * 
     * <strong>This method returns <code>null</code> if the pathref cannot be 
     * resolved to a node on the specified layout.</strong>  It is the 
     * responsibility of calling code to handle this case appropriately.
     * 
     * @return a valid {@link Noderef} or <code>null</null>
     */
    public Noderef getNoderefFromPathref(String layoutOwner, String pathref, String fname, boolean isStructRef,
            org.dom4j.Element layoutElement) {

        Validate.notNull(layoutOwner, "Argument 'layoutOwner' cannot be null.");
        Validate.notNull(pathref, "Argument 'pathref' cannot be null.");

        if (log.isTraceEnabled()) {
            StringBuilder msg = new StringBuilder();
            msg.append("getDlmNoderef: [layoutOwner='").append(layoutOwner).append("', pathref='").append(pathref)
                    .append("', fname='").append(fname).append("', isStructRef='").append(isStructRef).append("']");
            log.trace(msg.toString());
            log.trace("getDlmNoderef: user layout document follows...\n" + layoutElement.asXML());
        }

        final String[] pathTokens = DLM_PATH_REF_DELIM.split(pathref);
        if (pathTokens.length <= 1) {
            this.log.warn("Invalid DLM PathRef, no delimiter: " + pathref);
            return null;
        }

        if (pathTokens[0].equals(layoutOwner)) {
            // This an internal reference (our own layout);  we have to 
            // use the layoutExment (instead of load-limited-layout) b/c 
            // our layout may not be in the db...
            final org.dom4j.Element target = (org.dom4j.Element) layoutElement.selectSingleNode(pathTokens[1]);
            if (target != null) {
                return new Noderef(target.valueOf("@ID"));
            }

            this.log.warn("Unable to resolve pathref '" + pathref + "' for layoutOwner '" + layoutOwner + "'");
            return null;
        }

        /*
         * We know this Noderef refers to a node on a DLM fragment
         */

        final String layoutOwnerName = pathTokens[0];
        final String layoutPath = pathTokens[1];

        final Integer layoutOwnerUserId = this.userIdentityStore.getPortalUserId(layoutOwnerName);
        if (layoutOwnerUserId == null) {
            this.log.warn("Unable to resolve pathref '" + pathref + "' for layoutOwner '" + layoutOwner
                    + "', no userId found for userName: " + layoutOwnerName);
            return null;
        }

        final Tuple<String, DistributedUserLayout> userLayoutInfo = getUserLayoutTuple(layoutOwnerName,
                layoutOwnerUserId);
        final Document userLayout = userLayoutInfo.second.getLayout();

        final Node targetNode = this.xPathOperations.evaluate(layoutPath, userLayout, XPathConstants.NODE);
        if (targetNode == null) {
            this.log.warn("No layout node found for pathref: " + pathref);
            return null;
        }

        final NamedNodeMap attributes = targetNode.getAttributes();
        if (fname != null) {
            final Node fnameAttr = attributes.getNamedItem("fname");
            if (fnameAttr == null) {
                this.log.warn("Layout node for pathref does not have fname attribute: " + pathref);
                return null;
            }

            final String nodeFname = fnameAttr.getTextContent();
            if (!fname.equals(nodeFname)) {
                this.log.warn("fname '" + nodeFname + "' on layout node not match specified fname '" + fname
                        + "' for pathref: " + pathref);
                return null;
            }
        }

        final Node structIdAttr = attributes.getNamedItem("struct-id");
        if (structIdAttr != null) {
            final String structId = structIdAttr.getTextContent();

            if (isStructRef) {
                return new Noderef(layoutOwnerUserId, 1 /* TODO:  remove hard-coded layoutId=1 */, "s" + structId);
            }

            return new Noderef(layoutOwnerUserId, 1 /* TODO:  remove hard-coded layoutId=1 */, "n" + structId);
        }

        final Node idAttr = attributes.getNamedItem("ID");
        return new Noderef(layoutOwnerUserId, 1 /* TODO:  remove hard-coded layoutId=1 */, idAttr.getTextContent());

    }

    /**
     * Returns a valid {@link Pathref} based on the specified arguments or 
     * <code>null</null> if that's not possible.
     * 
     * @param layoutOwnerUsername
     * @param dlmNoderef
     * @param layout
     * @return a valid {@link Pathref} or <code>null</null>
     */
    public Pathref getPathrefFromNoderef(String layoutOwnerUsername, String dlmNoderef, org.dom4j.Element layout) {

        Validate.notNull(layoutOwnerUsername, "Argument 'layoutOwnerUsername' cannot be null.");
        Validate.notNull(dlmNoderef, "Argument 'dlmNoderef' cannot be null.");

        if (log.isTraceEnabled()) {
            StringBuilder msg = new StringBuilder();
            msg.append("createPathref: [layoutOwnerUsername='").append(layoutOwnerUsername)
                    .append("', dlmNoderef='").append(dlmNoderef).append("']");
            log.trace(msg.toString());
        }

        Pathref rslt = null; // default;  signifies we can't match a node

        final Matcher dlmNodeMatcher = DLM_NODE_PATTERN.matcher(dlmNoderef);
        if (dlmNodeMatcher.matches()) {
            final int userId = Integer.valueOf(dlmNodeMatcher.group(1));
            final String nodeId = dlmNodeMatcher.group(2);

            final String userName = this.userIdentityStore.getPortalUserName(userId);
            final Tuple<String, DistributedUserLayout> userLayoutInfo = getUserLayoutTuple(userName, userId);

            if (userLayoutInfo.second == null) {
                this.log.warn("no layout for fragment user '" + userLayoutInfo.first + "' Specified dlmNoderef "
                        + dlmNoderef + " cannot be resolved.");
                return null;
            }

            final Document fragmentLayout = userLayoutInfo.second.getLayout();
            final Node targetElement = this.xPathOperations.evaluate("//*[@ID = $nodeId]",
                    Collections.singletonMap("nodeId", nodeId), fragmentLayout, XPathConstants.NODE);

            // We can only proceed if there's a valid match in the document
            if (targetElement != null) {
                String xpath = this.xmlUtilities.getUniqueXPath(targetElement);
                // Pathref objects that refer to portlets are expected to include 
                // the fname as the 3rd element; other pathref objects should leave 
                // that element blank.
                String fname = null;
                Node fnameAttr = targetElement.getAttributes().getNamedItem("fname");
                if (fnameAttr != null) {
                    fname = fnameAttr.getTextContent();
                }

                rslt = new Pathref(userLayoutInfo.first, xpath, fname);
            }

        }

        final Matcher userNodeMatcher = USER_NODE_PATTERN.matcher(dlmNoderef);
        if (userNodeMatcher.find()) {
            // We need a pathref based on the new style of layout b/c on 
            // import this users own layout will not be in the database 
            // when the path is computed back to an Id...
            final String structId = userNodeMatcher.group(1);
            final org.dom4j.Node target = layout.selectSingleNode("//*[@ID = '" + structId + "']");
            if (target == null) {
                this.log.warn("no match found on layout for user '" + layoutOwnerUsername
                        + "' for the specified dlmNoderef:  " + dlmNoderef);
                return null;
            }

            String fname = null;
            if (target.getName().equals("channel")) {
                fname = target.valueOf("@fname");
            }

            rslt = new Pathref(layoutOwnerUsername, target.getUniquePath(), fname);
        }

        return rslt;

    }

    /*
     * Implementation.
     */

    /**
     * Provides a {@link Tuple} containing the &quot;fragmentized&quot; version 
     * of a DLM fragment owner's layout, together with the username.  This 
     * version of the layout consistent with what DLM uses internally for 
     * fragments, and is created by FragmentActivator.fragmentizeLayout.  It's 
     * important that the version returned by this method matches what DLM uses 
     * internally because it will be used to establish relationships between 
     * fragment layout nodes and user customizations of DLM fragments.
     * 
     * @param userId
     * @return
     */
    /* TODO:  make private */ Tuple<String, DistributedUserLayout> getUserLayoutTuple(String userName, int userId) {

        final PersonImpl person = new PersonImpl();
        person.setUserName(userName);
        person.setID(userId);
        person.setSecurityContext(new BrokenSecurityContext());

        final IUserProfile profile = layoutStore.getUserProfileByFname(person, UserProfile.DEFAULT_PROFILE_FNAME);
        final DistributedUserLayout userLayout = layoutStore.getUserLayout(person, (UserProfile) profile);

        return new Tuple<String, DistributedUserLayout>(userName, userLayout);
    }

}