com.sun.faces.config.DocumentOrderingWrapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javax.faces Show documentation
Show all versions of javax.faces Show documentation
This is the master POM file for Oracle's Implementation of the JSF 2.2 Specification.
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.faces.config;
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.Logger;
import java.util.logging.Level;
import java.text.MessageFormat;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
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
* javax.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 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 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.
*/
public DocumentOrderingWrapper(DocumentInfo document) {
this.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());
}
/**
* @return true
if this document is before the specified
* id
, otherwise false
*/
public boolean isBefore(String id) {
return (search(beforeIds, id));
}
/**
* @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 Document
s 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<>();
sourceList.addAll(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,
"jsf.configuration.absolute.order.duplicate.document",
new Object[] { name });
}
// only log this once
break;
}
}
if (!found && LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING,
"jsf.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 Document
s per the requirements
* of the 2.0 specification. Note, that this method only provides partial
* ordering and not absolute ordering.
*/
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 (int i = 0; i < documents.length; i++) {
ids.add(documents[i].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)) {
String msg = "Exceeded maximum number of attempts to sort the application's faces-config documents.\nDocument Info\n==================";
for (DocumentOrderingWrapper w : documents) {
msg += (" " + w.toString() + '\n');
}
LOGGER.severe(msg);
}
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 (int ii = 0; ii < wrappers.length; ii++) {
DocumentOrderingWrapper other = wrappers[ii];
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<>();
newBeforeIds.addAll(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 (int ii = 0; ii < wrappers.length; ii++) {
DocumentOrderingWrapper other = wrappers[ii];
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<>();
newAfterIds.addAll(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, new Comparator>() {
@Override
public int compare(Map.Entry a, Map.Entry b) {
return (b.getValue()).compareTo(a.getValue());
}
});
Map result = new LinkedHashMap();
for (Map.Entry entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
// ---------------------------------------------------------- Nested Classes
@SuppressWarnings({"ComparatorNotSerializable"})
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
@SuppressWarnings({"serial"})
private static final class CircularDependencyException extends Exception {
// -------------------------------------------------------- Constructors
public CircularDependencyException() {
super();
}
public CircularDependencyException(String message) {
super(message);
}
public CircularDependencyException(String message, Throwable cause) {
super(message, cause);
}
public CircularDependencyException(Throwable cause) {
super(cause);
}
} // END CircularDependencyException
}