Permalink
Browse files

Create FacesConfigXml.java

  • Loading branch information...
1 parent 89fa05e commit 51060ffa0f10ba73fc9261ae73175c88ab19011c @mmariotti mmariotti committed Jan 14, 2015
Showing with 257 additions and 0 deletions.
  1. +257 −0 src/main/java/org/omnifaces/config/FacesConfigXml.java
@@ -0,0 +1,257 @@
+package org.omnifaces.config;
+
+import static org.omnifaces.util.Faces.getServletContext;
+import static org.omnifaces.util.Faces.hasContext;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.faces.context.FacesContext;
+import javax.faces.webapp.FacesServlet;
+import javax.servlet.Filter;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextListener;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+import org.omnifaces.util.Faces;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+
+/**
+ * <p>
+ * This configuration enum parses the <code>/WEB-INF/faces-config.xml</code> and all <code>/META-INF/faces-config.xml</code> files
+ * found in the classpath and offers methods to obtain information from them which is not available by the standard
+ * JSF API.
+ *
+ * <h3>Usage</h3>
+ * <p>
+ * Some examples:
+ * <pre>
+ * // Get the &lt;resource-bundle&gt; (which are essentially mappings of resource bundle base name and variable name).
+ * Map&lt;String, String&gt; resourceBundles = FacesConfigXml.INSTANCE.getResourceBundles();
+ * </pre>
+ *
+ * @author Bauke Scholtz
+ * @author Michele Mariotti
+ * @since 2.1
+ */
+public enum FacesConfigXml
+{
+ // Enum singleton -------------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the lazily loaded enum singleton instance.
+ * <p>
+ * Note: if this is needed in e.g. a {@link Filter} which is called before the {@link FacesServlet} is invoked,
+ * then it won't work if the <code>INSTANCE</code> hasn't been referenced before. Since JSF installs a special
+ * "init" {@link FacesContext} during startup, one option for doing this initial referencing is in a
+ * {@link ServletContextListener}. The data this enum encapsulates will then be available even where there is no
+ * {@link FacesContext} available. If there's no other option, then you need to manually invoke
+ * {@link #init(ServletContext)} whereby you pass the desired {@link ServletContext}.
+ */
+ INSTANCE;
+
+ // Private constants ----------------------------------------------------------------------------------------------
+
+ private static final Logger logger = Logger.getLogger(FacesConfigXml.class.getName());
+
+ private static final String APP_FACES_CONFIG_XML = "/WEB-INF/faces-config.xml";
+
+ private static final String LIB_FACES_CONFIG_XML = "META-INF/faces-config.xml";
+
+ private static final String XPATH_RESOURCE_BUNDLE = "application/resource-bundle";
+
+ private static final String XPATH_VAR = "var";
+
+ private static final String XPATH_BASE_NAME = "base-name";
+
+ private static final String ERROR_NOT_INITIALIZED =
+ "FacesConfigXml is not initialized yet. Please use #init(ServletContext) method to manually initialize it.";
+
+ private static final String LOG_INITIALIZATION_ERROR =
+ "FacesConfigXml failed to initialize. Perhaps your faces-config.xml contains a typo?";
+
+ // Properties -----------------------------------------------------------------------------------------------------
+
+ private final AtomicBoolean initialized = new AtomicBoolean();
+
+ private Map<String, String> resourceBundles;
+
+ // Init -----------------------------------------------------------------------------------------------------------
+
+ /**
+ * Perform automatic initialization whereby the servlet context is obtained from the faces context.
+ */
+ private void init()
+ {
+ if(!initialized.get() && hasContext())
+ {
+ init(getServletContext());
+ }
+ }
+
+ /**
+ * Perform manual initialization with the given servlet context, if not null and not already initialized yet.
+ * @param servletContext The servlet context to obtain the faces-config.xml from.
+ * @return The current {@link FacesConfigXml} instance, initialized and all.
+ */
+ public FacesConfigXml init(ServletContext servletContext)
+ {
+ if(servletContext != null && !initialized.getAndSet(true))
+ {
+ try
+ {
+ Element facesConfigXml = loadFacesConfigXml(servletContext).getDocumentElement();
+ XPath xpath = XPathFactory.newInstance().newXPath();
+ resourceBundles = parseResourceBundles(facesConfigXml, xpath);
+ }
+ catch(Exception e)
+ {
+ initialized.set(false);
+ logger.log(Level.SEVERE, LOG_INITIALIZATION_ERROR, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ return this;
+ }
+
+ // Actions --------------------------------------------------------------------------------------------------------
+
+ // currently no action.
+
+ // Getters --------------------------------------------------------------------------------------------------------
+
+ /**
+ * Returns a mapping of all resource bundle base names by var.
+ * @return A mapping of all resource bundle base names by var.
+ */
+ public Map<String, String> getResourceBundles()
+ {
+ checkInitialized();
+ return resourceBundles;
+ }
+
+ private void checkInitialized()
+ {
+ // This init() call is performed here instead of in constructor, because WebLogic loads this enum as a CDI
+ // managed bean (in spite of having a VetoAnnotatedTypeExtension) which in turn implicitly invokes the enum
+ // constructor and thus causes an init while JSF context isn't fully initialized and thus the faces context
+ // isn't available yet. Perhaps it's fixed in newer WebLogic versions.
+ init();
+
+ if(!initialized.get())
+ {
+ throw new IllegalStateException(ERROR_NOT_INITIALIZED);
+ }
+ }
+
+ // Helpers --------------------------------------------------------------------------------------------------------
+
+ /**
+ * Load, merge and return all <code>faces-config.xml</code> files found in the classpath
+ * into a single {@link Document}.
+ */
+ private static Document loadFacesConfigXml(ServletContext context) throws Exception
+ {
+ DocumentBuilder builder = createDocumentBuilder();
+ Document document = builder.newDocument();
+ document.appendChild(document.createElement("all-faces-configs"));
+ URL url = context.getResource(APP_FACES_CONFIG_XML);
+
+ if(url != null)
+ { // faces-config.xml is optional.
+ parseAndAppendChildren(url, builder, document);
+ }
+
+ Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(LIB_FACES_CONFIG_XML);
+
+ while(urls.hasMoreElements())
+ {
+ parseAndAppendChildren(urls.nextElement(), builder, document);
+ }
+
+ return document;
+ }
+
+ /**
+ * Returns an instance of {@link DocumentBuilder} which doesn't validate, nor is namespace aware nor expands entity
+ * references (to keep it as lenient as possible).
+ */
+ private static DocumentBuilder createDocumentBuilder() throws Exception
+ {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setValidating(false);
+ factory.setNamespaceAware(false);
+ factory.setExpandEntityReferences(false);
+ return factory.newDocumentBuilder();
+ }
+
+ /**
+ * Parse the given URL as a document using the given builder and then append all its child nodes to the given
+ * document.
+ */
+ private static void parseAndAppendChildren(URL url, DocumentBuilder builder, Document document) throws Exception
+ {
+ URLConnection connection = url.openConnection();
+ connection.setUseCaches(false);
+
+ try(InputStream input = connection.getInputStream())
+ {
+ NodeList children = builder.parse(input).getDocumentElement().getChildNodes();
+
+ for(int i = 0; i < children.getLength(); i++)
+ {
+ document.getDocumentElement().appendChild(document.importNode(children.item(i), true));
+ }
+ }
+ }
+
+ /**
+ * Create and return a mapping of all resource bundle base names by var found in the given document.
+ */
+ private static Map<String, String> parseResourceBundles(Element facesConfigXml, XPath xpath) throws Exception
+ {
+ Map<String, String> resourceBundles = new LinkedHashMap<>();
+ NodeList resourceBundleNodes = getNodeList(facesConfigXml, xpath, XPATH_RESOURCE_BUNDLE);
+
+ for(int i = 0; i < resourceBundleNodes.getLength(); i++)
+ {
+ Node node = resourceBundleNodes.item(i);
+
+ String var = xpath.compile(XPATH_VAR).evaluate(node).trim();
+ String baseName = xpath.compile(XPATH_BASE_NAME).evaluate(node).trim();
+
+ if(!resourceBundles.containsKey(var))
+ {
+ resourceBundles.put(var, baseName);
+ }
+ }
+
+ return Collections.unmodifiableMap(resourceBundles);
+ }
+
+ // Helpers of helpers (JAXP hell) ---------------------------------------------------------------------------------
+
+ private static NodeList getNodeList(Node node, XPath xpath, String expression) throws Exception
+ {
+ return (NodeList) xpath.compile(expression).evaluate(node, XPathConstants.NODESET);
+ }
+}

0 comments on commit 51060ff

Please sign in to comment.