/*
* (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* Nuxeo - initial API and implementation
*
* $Id: StaticNavigationHandler.java 21462 2007-06-26 21:16:36Z sfermigier $
*/
package org.nuxeo.ecm.platform.ui.web.rest;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import javax.faces.FactoryFinder;
import javax.faces.application.NavigationCase;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jboss.seam.util.DTDEntityResolver;
import org.nuxeo.runtime.api.Framework;
import com.sun.faces.application.ApplicationAssociate;
/**
* View id helper that matches view ids and outcomes thanks to navigation cases
* defined in a faces-config.xml file.
* <p>
* Also handle some hot reload cases, by parsing the main faces-config.xml
* file.
*/
public class StaticNavigationHandler {
private static final Log log = LogFactory.getLog(StaticNavigationHandler.class);
private final HashMap<String, String> outcomeToViewId = new HashMap<String, String>();
private final HashMap<String, String> viewIdToOutcome = new HashMap<String, String>();
public StaticNavigationHandler(ServletContext context,
HttpServletRequest request, HttpServletResponse response) {
boolean created = false;
FacesContext faces = FacesContext.getCurrentInstance();
if (faces == null) {
// Acquire the FacesContext instance for this request
FacesContextFactory facesContextFactory = (FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
// force using default lifecycle instead of performing lookup on
// conf
Lifecycle lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
faces = facesContextFactory.getFacesContext(context, request,
response, lifecycle);
created = true;
}
try {
ApplicationAssociate associate = ApplicationAssociate.getCurrentInstance();
for (Set<NavigationCase> cases : associate.getNavigationCaseListMappings().values()) {
for (NavigationCase cnc : cases) {
String toViewId = cnc.getToViewId(faces);
String fromOutcome = cnc.getFromOutcome();
outcomeToViewId.put(fromOutcome, toViewId);
viewIdToOutcome.put(toViewId, fromOutcome);
}
}
if (Framework.isDevModeSet()) {
handleHotReloadResources(context);
}
} finally {
if (created) {
faces.release();
}
}
}
public String getOutcomeFromViewId(String viewId) {
if (viewId == null) {
return null;
}
viewId = viewId.replace(".faces", ".xhtml");
if (viewIdToOutcome.containsKey(viewId)) {
return viewIdToOutcome.get(viewId);
}
return viewId;
}
public String getViewIdFromOutcome(String outcome) {
if (outcome == null) {
return null;
}
if (outcomeToViewId.containsKey(outcome)) {
return outcomeToViewId.get(outcome).replace(".xhtml", ".faces");
}
// try to guess the view name
String viewId = "/" + outcome + ".faces";
log.warn(String.format("Guessing view id for outcome '%s': use '%s'",
outcome, viewId));
return viewId;
}
/**
* XXX hack: add manual parsing of the main faces-config.xml file
* navigation cases, to handle hot reload and work around the JSF
* application cache.
* <p>
* TODO: try to reset and rebuild the app navigation cases by reflection,
* if it works...
*
* @since 5.6
*/
protected void handleHotReloadResources(ServletContext context) {
InputStream stream = null;
if (context != null) {
stream = context.getResourceAsStream("/WEB-INF/faces-config.xml");
}
if (stream != null) {
parse(stream);
}
}
/**
* @since 5.6
*/
@SuppressWarnings("unchecked")
protected void parse(InputStream stream) {
Element root = getDocumentRoot(stream);
List<Element> elements = root.elements("navigation-rule");
for (Element rule : elements) {
List<Element> nav_cases = rule.elements("navigation-case");
for (Element nav_case : nav_cases) {
Element from_el = nav_case.element("from-outcome");
Element to_el = nav_case.element("to-view-id");
if ((from_el != null) && (to_el != null)) {
String from = from_el.getTextTrim();
String to = to_el.getTextTrim();
outcomeToViewId.put(from, to);
viewIdToOutcome.put(to, from);
}
}
}
}
/**
* Gets the root element of the document.
*
* @since 5.6
*/
protected static Element getDocumentRoot(InputStream stream) {
try {
SAXReader saxReader = new SAXReader();
saxReader.setEntityResolver(new DTDEntityResolver());
saxReader.setMergeAdjacentText(true);
return saxReader.read(stream).getRootElement();
} catch (DocumentException de) {
throw new RuntimeException(de);
}
}
}