All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.faces.config.manager.documents.DocumentOrderingWrapper Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.faces.config.manager.documents;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.sun.faces.config.ConfigurationException;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.Timer;

/**
 * This class is used by the config system to order faces-config documents found on the classpath or
 * configured explicitly via the jakarta.faces.CONFIG_FILES context init parameter.
 */
public class DocumentOrderingWrapper {

    /**
     * Logger for this class.
     */
    private static final Logger LOGGER = FacesLogger.CONFIG.getLogger();

    /**
     * {@link Comparator} implementation to aid in sorting faces-config documents.
     */
    private static final Comparator COMPARATOR = new DocumentOrderingComparator();

    /**
     * This is the limit on the number of attempts made to sort the documents. Any attempt to exceed this limit will result
     * in an Exception being thrown.
     */
    private static final int MAX_SORT_PASSED = 1000;

    /**
     * Constant for the ordering element.
     */
    private static final String ORDERING = "ordering";

    /**
     * Constant for the before element.
     */
    private static final String BEFORE = "before";

    /**
     * Constant for the after element.
     */
    private static final String AFTER = "after";

    /**
     * Constant for the name element.
     */
    private static final String NAME = "name";

    /**
     * Constant for the others element.
     */
    private static final String OTHERS = "others";

    /**
     * Others keyword for sorting.
     */
    private static final String OTHERS_KEY = DocumentOrderingWrapper.class.getName() + ".OTHERS_KEY";

    /**
     * Return code indicating that element n is to be swapped with n + 1
     */
    private static final int SWAP = -1;

    /**
     * Return code indicating that no swap needs to occur for the elements being compared.
     */
    private static final int DO_NOT_SWAP = 0;

    /**
     * The wrapped Document.
     */
    private final DocumentInfo documentInfo;

    /**
     * The wrapped Document's ID.
     */
    private String id;

    /**
     * The wrapped Document's before IDs.
     */
    private String[] beforeIds;

    /**
     * The wrapped Document's before IDs.
     */
    private String[] afterIds;

    // -------------------------------------------------------- Constructors

    /**
     * Constructs a new DocumentOrderingWrapper for the specified Document.
     * @param document the document info
     */
    public DocumentOrderingWrapper(DocumentInfo document) {
        documentInfo = document;
        init();
    }

    // ------------------------------------------------------ Public Methods

    /**
     * @return the wrapped Document
     */
    public DocumentInfo getDocument() {
        return documentInfo;
    }

    /**
     * @return this Document's ID, if any
     */
    public String getDocumentId() {
        return id;
    }

    /**
     * @return this Document's before IDs, if any
     */
    public String[] getBeforeIds() {
        return beforeIds;
    }

    /**
     * @return this Document's after IDs, if any
     */
    public String[] getAfterIds() {
        return afterIds;
    }

    /**
     * @return true if any before IDs are present, otherwise false
     */
    public boolean isBeforeOrdered() {
        return beforeIds.length != 0;
    }

    /**
     * @return true if any after IDs are present, otherwise, false
     */
    public boolean isAfterOrdered() {
        return afterIds.length != 0;
    }

    /**
     * @return true if this document has any before or after IDs, otherwise false
     */
    public boolean isOrdered() {
        return isBeforeOrdered() || isAfterOrdered();
    }

    /**
     * @param id the id to search for
     * @return true if this document is before the specified id, otherwise false
     */
    public boolean isBefore(String id) {
        return search(beforeIds, id);
    }

    /**
     * @param id the id to search for
     * @return true if this document is after the specified id, otherwise false
     */
    public boolean isAfter(String id) {
        return search(afterIds, id);
    }

    /**
     * @return true if this document is after others, otherwise false
     */
    public boolean isAfterOthers() {
        return search(afterIds, OTHERS_KEY);
    }

    /**
     * @return true if this document is before others, otherwise false
     */
    public boolean isBeforeOthers() {
        return search(beforeIds, OTHERS_KEY);
    }

    @Override
    public String toString() {
        return "Document{" + "id='" + id + '\'' + ", beforeIds=" + (beforeIds == null ? null : Arrays.asList(beforeIds)) + ", afterIds="
                + (afterIds == null ? null : Arrays.asList(afterIds)) + '}';
    }

    /**
     * Sort the provided array of Documents per the order specified in the List represented by absoluteOrder.
     *
     * @param documents Documents to sort
     * @param absoluteOrder the absolute order as specified in the /WEB-INF/faces-config.xml
     * @return an array of DocumentOrderingWrappers that may be smaller than the input array of wrappers.
     */
    public static DocumentOrderingWrapper[] sort(DocumentOrderingWrapper[] documents, List absoluteOrder) {

        List sourceList = new CopyOnWriteArrayList<>(Arrays.asList(documents));

        List targetList = new ArrayList<>();
        for (String name : absoluteOrder) {
            if ("others".equals(name)) {
                continue;
            }
            boolean found = false;
            for (DocumentOrderingWrapper wrapper : sourceList) {
                if (!found && name.equals(wrapper.getDocumentId())) {
                    found = true;
                    targetList.add(wrapper);
                    sourceList.remove(wrapper);
                } else if (found && name.equals(wrapper.getDocumentId())) {
                    // we've already processed a document with this name
                    if (LOGGER.isLoggable(Level.WARNING)) {
                        LOGGER.log(Level.WARNING, "faces.configuration.absolute.order.duplicate.document", new Object[] { name });
                    }
                    // only log this once
                    break;
                }
            }
            if (!found && LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.log(Level.WARNING, "faces.configuration.absolute.order.missing.document", new Object[] { name });
            }
        }

        int othersIndex = absoluteOrder.indexOf("others");
        if (othersIndex != -1) {
            // any wrappers left in sourceList are considered others.
            // start pushing them into targetList at the index
            for (DocumentOrderingWrapper wrapper : sourceList) {
                targetList.add(othersIndex, wrapper);
            }
        }

        return targetList.toArray(new DocumentOrderingWrapper[targetList.size()]);

    }

    /**
     * Sort the provided array of Documents per the requirements of the 2.0 specification. Note, that this
     * method only provides partial ordering and not absolute ordering.
     * @param documents the documents to sort
     */
    public static void sort(DocumentOrderingWrapper[] documents) {

        Timer t = Timer.getInstance();
        if (t != null) {
            t.startTiming();
        }
        try {
            enhanceOrderingData(documents);
        } catch (CircularDependencyException re) {
            String msg = "Circular dependencies detected!\nDocument Info\n==================\n";
            for (DocumentOrderingWrapper w : documents) {
                msg += "  " + w.toString() + '\n';
            }
            throw new ConfigurationException(msg);
        }

        // Sort the documents such that specified ordering will be considered.
        //
        // It turns out that some of the specified ordering, if it was not discovered by the sort routine
        // until later in its processing, was not being considered correctly in the ordering algorithm.
        //
        // This preSort method puts all of the documents with specified ordering as early on in the
        // list of documents as possible for Mojarra to consider it quickly, and be
        // able to use its ordering algorithm to the best of its ability.
        preSort(documents);

        // original inner sort algorithm
        int numberOfPasses = innerSort(documents);

        // final sort
        for (int i = 0; i < documents.length; i++) {
            LinkedList ids = getIds(documents);
            if (done(documents, ids)) {
                break;
            }
        }

        if (t != null) {
            t.stopTiming();
            t.logResult("\"faces-config\" document sorting complete in " + numberOfPasses + '.');
        }

    }

    // Check to see if the sort is complete, and if not, finish it, if possible.
    public static boolean done(DocumentOrderingWrapper[] documents, LinkedList ids) {

        for (int i = 0; i < documents.length; i++) {
            int ii = 0;
            for (String documentId : ids) {
                if (documents[i].getDocumentId().equals(documentId)) {
                    break;
                }
                if (documents[i].isBefore(documentId)) {
                    if (LOGGER.isLoggable(Level.FINE)) {
                        LOGGER.log(Level.FINE, "done: " + documentId + " should be after " + documents[i].getDocumentId() + " given that it should be before: "
                                + Arrays.asList(documents[i].getBeforeIds()));
                    }

                    // we have a document that is out of order, and his index is ii, he belongs at index i, and all the documents in between
                    // need to be shifted left.
                    DocumentOrderingWrapper temp = null;
                    for (int j = 0; j < documents.length; j++) {
                        // This is one that is out of order and needs to be moved.
                        if (j == ii) {
                            temp = documents[j];
                        }
                        // this is one in between that needs to be shifted left.
                        if (temp != null && j != i) {
                            documents[j] = documents[j + 1];
                        }
                        // this is where the one that is out of order needs to be moved to.
                        if (j == i) {
                            documents[j] = temp;
                            return false;
                        }
                    }
                }
                ii = ii + 1;
            }
        }

        return true;
    }

    public static LinkedList getIds(DocumentOrderingWrapper[] documents) {
        LinkedList ids = new LinkedList<>();
        for (DocumentOrderingWrapper document : documents) {
            ids.add(document.getDocumentId());
        }
        return ids;
    }

    public static int innerSort(DocumentOrderingWrapper[] documents) {

        int numberOfPasses = 0;
        boolean doMore = true;

        while (doMore) {
            numberOfPasses++;
            if (numberOfPasses == MAX_SORT_PASSED) {
                if (LOGGER.isLoggable(Level.SEVERE)) {
                    StringBuilder msg = new StringBuilder("Exceeded maximum number of attempts to sort the application's faces-config documents.\nDocument Info\n==================");
                    for (DocumentOrderingWrapper w : documents) {
                        msg.append("  ").append(w.toString()).append('\n');
                    }
                    LOGGER.severe(msg.toString());
                }
                throw new ConfigurationException("Exceeded maximum number of attempts to sort the faces-config documents.");
            }
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Starting sort pass number {0}...", numberOfPasses);
            }
            doMore = false;
            for (int i = 0; i < documents.length - 1; i++) {
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.log(Level.FINE, "Comparing {0}, {1}", new Object[] { documents[i].id, documents[i + 1].id });
                }
                if (COMPARATOR.compare(documents[i], documents[i + 1]) != DO_NOT_SWAP) {
                    if (LOGGER.isLoggable(Level.FINE)) {
                        LOGGER.log(Level.FINE, "Swapping {0} with {1}", new Object[] { documents[i].id, documents[i + 1].id });
                    }
                    DocumentOrderingWrapper temp = documents[i];
                    documents[i] = documents[i + 1];
                    documents[i + 1] = temp;
                    doMore = true;
                }
            }

            // compare first and last elements
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Comparing {0}, {1}", new Object[] { documents[0].id, documents[documents.length - 1].id });
            }
            if (COMPARATOR.compare(documents[0], documents[documents.length - 1]) != DO_NOT_SWAP) {
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.log(Level.FINE, "Swapping {0} with {1}", new Object[] { documents[0].id, documents[documents.length - 1].id });
                }
                DocumentOrderingWrapper temp = documents[0];
                documents[0] = documents[documents.length - 1];
                documents[documents.length - 1] = temp;
                doMore = true;
            }
        }

        return numberOfPasses;
    }

    // ----------------------------------------------------- Private Methods

    /**
     * Update before/after knowledge of all nodes in the array. Consider the case of A after B, B after C, C after D, and D
     * with no ordering characteristics. When the code below executes, the before/after of specific nodes will be updated so
     * that in the case of the example above we'd have:
     *
     * A -> after B B -> before A, after C C -> before B, A after D D -> before C, B, A
     *
     * So when an attempt is made to sort [A, B, C, D] the end result is [D, C, B, A] No extra enhancement of after ID
     * information is necessary outside of the single node information due to the way the algorithm processes the array.
     *
     * This method also performs cyclic detection. If, after updating the before/after information, the before/after
     * information contains a reference to the document ID we're currently processing, throw an exception.
     */
    private static void enhanceOrderingData(DocumentOrderingWrapper[] wrappers) throws CircularDependencyException {

        for (int i = 0; i < wrappers.length; i++) {
            DocumentOrderingWrapper w = wrappers[i];

            // process before IDs other than 'others'
            for (String id : w.getBeforeIds()) {
                if (OTHERS_KEY.equals(id)) {
                    continue;
                }
                for (DocumentOrderingWrapper other : wrappers) {
                    if (id.equals(other.id)) {
                        String[] afterIds = other.getAfterIds();
                        if (Arrays.binarySearch(afterIds, w.id) < 0) {
                            Set newAfterIds = new HashSet<>(afterIds.length + 1);
                            newAfterIds.addAll(Arrays.asList(afterIds));
                            newAfterIds.add(w.id);
                            other.afterIds = newAfterIds.toArray(new String[newAfterIds.size()]);
                            Arrays.sort(other.afterIds);
                        }

                        String[] otherBeforeIds = other.getBeforeIds();
                        if (otherBeforeIds.length > 0) {

                            String[] currentBeforeIds = w.getBeforeIds();
                            Set newBeforeIds = new HashSet<>(Arrays.asList(currentBeforeIds));
                            for (String bid : otherBeforeIds) {
                                if (OTHERS_KEY.equals(bid)) {
                                    continue;
                                }
                                newBeforeIds.add(bid);
                            }
                            String[] temp = newBeforeIds.toArray(new String[newBeforeIds.size()]);
                            Arrays.sort(temp);
                            if (search(temp, w.id)) {
                                throw new CircularDependencyException();
                            }
                            w.beforeIds = temp;
                        }
                    }
                }

            }

            // process after IDs other than 'others'
            for (String id : w.getAfterIds()) {
                if (OTHERS_KEY.equals(id)) {
                    continue;
                }
                for (DocumentOrderingWrapper other : wrappers) {
                    if (id.equals(other.id)) {
                        String[] beforeIds = other.getBeforeIds();
                        if (Arrays.binarySearch(beforeIds, w.id) < 0) {
                            Set newBeforeIds = new HashSet<>(beforeIds.length + 1);
                            newBeforeIds.addAll(Arrays.asList(beforeIds));
                            newBeforeIds.add(w.id);
                            other.beforeIds = newBeforeIds.toArray(new String[newBeforeIds.size()]);
                            Arrays.sort(other.beforeIds);
                        }
                        String[] otherAfterIds = other.getAfterIds();
                        if (otherAfterIds.length > 0) {
                            String[] currentAfterIds = w.getAfterIds();
                            Set newAfterIds = new HashSet<>(Arrays.asList(currentAfterIds));
                            for (String bid : otherAfterIds) {
                                if (OTHERS_KEY.equals(bid)) {
                                    continue;
                                }
                                newAfterIds.add(bid);
                            }
                            String[] temp = newAfterIds.toArray(new String[newAfterIds.size()]);
                            Arrays.sort(temp);
                            if (search(temp, w.id)) {
                                throw new CircularDependencyException();
                            }
                            w.afterIds = temp;
                        }
                    }
                }
            }
        }
    }

    /**
     * Simple helper method around Arrays.binarySearch().
     *
     * @param ids an array of IDs
     * @param id the ID to search for
     * @return true if ids contains id
     */
    private static boolean search(String[] ids, String id) {

        return Arrays.binarySearch(ids, id) >= 0;

    }

    /**
     * 

* Performs the initialization necessary to allow sorting of faces-config documents. *

*/ private void init() { Element documentElement = documentInfo.getDocument().getDocumentElement(); String namespace = documentElement.getNamespaceURI(); id = getDocumentName(documentElement); NodeList orderingElements = documentElement.getElementsByTagNameNS(namespace, ORDERING); Set beforeIds = null; Set afterIds = null; if (orderingElements.getLength() > 0) { for (int i = 0, len = orderingElements.getLength(); i < len; i++) { Node orderingNode = orderingElements.item(i); NodeList children = orderingNode.getChildNodes(); for (int j = 0, jlen = children.getLength(); j < jlen; j++) { Node n = children.item(j); if (beforeIds == null) { beforeIds = extractIds(n, BEFORE); } if (afterIds == null) { afterIds = extractIds(n, AFTER); } } } } this.beforeIds = beforeIds != null ? beforeIds.toArray(new String[beforeIds.size()]) : new String[0]; this.afterIds = afterIds != null ? afterIds.toArray(new String[afterIds.size()]) : new String[0]; Arrays.sort(this.beforeIds); Arrays.sort(this.afterIds); // ensure any ID defined in the 'before' array isn't present in the // 'after' array and vice versa as a documents can't come before // *and* after another. checkDuplicates(this.beforeIds, this.afterIds); checkDuplicates(this.afterIds, this.beforeIds); } private String getDocumentName(Element documentElement) { NodeList children = documentElement.getChildNodes(); String documentName = ""; if (children != null && children.getLength() > 0) { for (int i = 0, len = children.getLength(); i < len; i++) { Node n = children.item(i); if (NAME.equals(n.getLocalName())) { documentName = getNodeText(n); break; } } } return documentName; } /** * Ensure the IDs in source aren't present in searchTarget. */ private void checkDuplicates(String[] source, String[] searchTarget) { for (String id : source) { if (search(searchTarget, id)) { String msg = MessageFormat.format("Document {0} is specified to come before and after {1}.", documentInfo.getDocument().getDocumentURI(), id); throw new ConfigurationException(msg); } } } /** * Extract and return a Set of IDs contained within the provided Node. */ private Set extractIds(Node n, String nodeName) { Set idsList = null; if (nodeName.equals(n.getLocalName())) { idsList = new HashSet<>(); NodeList ids = n.getChildNodes(); for (int k = 0, klen = ids.getLength(); k < klen; k++) { Node idNode = ids.item(k); if (NAME.equals(idNode.getLocalName())) { String id = getNodeText(idNode); if (id != null) { idsList.add(id); } } if (OTHERS.equals(idNode.getLocalName())) { if (id != null) { idsList.add(OTHERS_KEY); } } } } return idsList; } /** * Return the textual content, if any, of the provided Node. */ private String getNodeText(Node node) { String res = null; if (node != null) { res = node.getTextContent(); if (res != null) { res = res.trim(); } } return res != null && res.length() != 0 ? res : null; } public static HashMap getDocumentHashMap(DocumentOrderingWrapper[] documents) { HashMap configMap = new HashMap<>(); for (DocumentOrderingWrapper document : documents) { String name = document.id; if (name != null && !"".equals(name)) { configMap.put(name, document); } } return configMap; } public static void preSort(DocumentOrderingWrapper[] documents) { List anonymousAndUnorderedList = new ArrayList<>(); Map linkedMap = new LinkedHashMap<>(); DocumentOrderingWrapper[] copyOfDocuments = new DocumentOrderingWrapper[documents.length]; System.arraycopy(documents, 0, copyOfDocuments, 0, documents.length); int i = 0; for (DocumentOrderingWrapper w : documents) { String[] bfs = w.getBeforeIds(); String[] afs = w.getAfterIds(); int knowledge = bfs.length + afs.length; if ((w.id == null || "".equals(w.id)) && !w.isOrdered()) { anonymousAndUnorderedList.add(w); } else { linkedMap.put(i, knowledge); } i++; } linkedMap = descendingByValue(linkedMap); i = 0; for (Map.Entry entry : linkedMap.entrySet()) { Integer index = entry.getKey(); documents[i] = copyOfDocuments[index]; i++; } for (DocumentOrderingWrapper w : anonymousAndUnorderedList) { documents[i] = w; i++; } } public static > Map descendingByValue(Map map) { List> list = new LinkedList<>(map.entrySet()); Collections.sort(list, (a, b) -> b.getValue().compareTo(a.getValue())); Map result = new LinkedHashMap<>(); for (Map.Entry entry : list) { result.put(entry.getKey(), entry.getValue()); } return result; } // ---------------------------------------------------------- Nested Classes private static final class DocumentOrderingComparator implements Comparator { // --------------------------------------------- Methods from Comparator @Override public int compare(DocumentOrderingWrapper wrapper1, DocumentOrderingWrapper wrapper2) { String w1Id = wrapper1.id; String w2Id = wrapper2.id; boolean w1IsOrdered = wrapper1.isOrdered(); boolean w2IsOrdered = wrapper2.isOrdered(); if (w1IsOrdered && !w2IsOrdered) { if (wrapper1.isAfterOrdered() && !wrapper1.isBeforeOthers()) { return SWAP; } } boolean w2IsBeforeW1 = wrapper2.isBefore(w1Id); boolean w1IsAfterW2 = wrapper1.isAfter(w2Id); if (w2IsBeforeW1 || w1IsAfterW2) { return SWAP; // move w2 before w1 } // no explicit ID ordering. Check others ordering boolean w1IsAfterOthers = wrapper1.isAfterOthers(); if (w1IsAfterOthers && !wrapper1.isBefore(w2Id) && !(wrapper1.isAfterOthers() && wrapper2.isAfterOthers())) { return SWAP; } boolean w2IsBeforeOthers = wrapper2.isBeforeOthers(); if (w2IsBeforeOthers && !wrapper2.isAfter(w1Id) && !(wrapper1.isBeforeOthers() && wrapper2.isBeforeOthers())) { return SWAP; } return DO_NOT_SWAP; } } // END FacesConfigComparator private static final class CircularDependencyException extends Exception { // -------------------------------------------------------- Constructors /** * */ private static final long serialVersionUID = 739253127985795440L; public CircularDependencyException() { super(); } } // END CircularDependencyException }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy