001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id: StaticNavigationHandler.java 21462 2007-06-26 21:16:36Z sfermigier $ 018 */ 019 020package org.nuxeo.ecm.platform.ui.web.rest; 021 022import java.io.InputStream; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Set; 026 027import javax.faces.FactoryFinder; 028import javax.faces.application.NavigationCase; 029import javax.faces.context.FacesContext; 030import javax.faces.context.FacesContextFactory; 031import javax.faces.lifecycle.Lifecycle; 032import javax.faces.lifecycle.LifecycleFactory; 033import javax.servlet.ServletContext; 034import javax.servlet.http.HttpServletRequest; 035import javax.servlet.http.HttpServletResponse; 036 037import org.apache.commons.logging.Log; 038import org.apache.commons.logging.LogFactory; 039import org.dom4j.DocumentException; 040import org.dom4j.Element; 041import org.dom4j.io.SAXReader; 042import org.jboss.seam.util.DTDEntityResolver; 043import org.nuxeo.runtime.api.Framework; 044 045import com.sun.faces.application.ApplicationAssociate; 046 047/** 048 * View id helper that matches view ids and outcomes thanks to navigation cases defined in a faces-config.xml file. 049 * <p> 050 * Also handle some hot reload cases, by parsing the main faces-config.xml file. 051 */ 052public class StaticNavigationHandler { 053 054 private static final Log log = LogFactory.getLog(StaticNavigationHandler.class); 055 056 private final HashMap<String, String> outcomeToViewId = new HashMap<String, String>(); 057 058 private final HashMap<String, String> viewIdToOutcome = new HashMap<String, String>(); 059 060 public StaticNavigationHandler(ServletContext context, HttpServletRequest request, HttpServletResponse response) { 061 boolean created = false; 062 FacesContext faces = FacesContext.getCurrentInstance(); 063 if (faces == null) { 064 // Acquire the FacesContext instance for this request 065 FacesContextFactory facesContextFactory = (FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY); 066 LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY); 067 // force using default lifecycle instead of performing lookup on 068 // conf 069 Lifecycle lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE); 070 faces = facesContextFactory.getFacesContext(context, request, response, lifecycle); 071 created = true; 072 } 073 try { 074 ApplicationAssociate associate = ApplicationAssociate.getCurrentInstance(); 075 for (Set<NavigationCase> cases : associate.getNavigationCaseListMappings().values()) { 076 for (NavigationCase cnc : cases) { 077 String toViewId = cnc.getToViewId(faces); 078 String fromOutcome = cnc.getFromOutcome(); 079 outcomeToViewId.put(fromOutcome, toViewId); 080 viewIdToOutcome.put(toViewId, fromOutcome); 081 } 082 } 083 if (Framework.isDevModeSet()) { 084 handleHotReloadResources(context); 085 } 086 } finally { 087 if (created) { 088 faces.release(); 089 } 090 } 091 } 092 093 public String getOutcomeFromViewId(String viewId) { 094 if (viewId == null) { 095 return null; 096 } 097 viewId = viewId.replace(".faces", ".xhtml"); 098 if (viewIdToOutcome.containsKey(viewId)) { 099 return viewIdToOutcome.get(viewId); 100 } 101 return viewId; 102 } 103 104 public String getViewIdFromOutcome(String outcome) { 105 if (outcome == null) { 106 return null; 107 } 108 if (outcomeToViewId.containsKey(outcome)) { 109 return outcomeToViewId.get(outcome).replace(".xhtml", ".faces"); 110 } 111 // try to guess the view name 112 String viewId = "/" + outcome + ".faces"; 113 log.warn(String.format("Guessing view id for outcome '%s': use '%s'", outcome, viewId)); 114 return viewId; 115 } 116 117 /** 118 * XXX hack: add manual parsing of the main faces-config.xml file navigation cases, to handle hot reload and work 119 * around the JSF application cache. 120 * <p> 121 * TODO: try to reset and rebuild the app navigation cases by reflection, if it works... 122 * 123 * @since 5.6 124 */ 125 protected void handleHotReloadResources(ServletContext context) { 126 InputStream stream = null; 127 if (context != null) { 128 stream = context.getResourceAsStream("/WEB-INF/faces-config.xml"); 129 } 130 if (stream != null) { 131 parse(stream); 132 } 133 } 134 135 /** 136 * @since 5.6 137 */ 138 @SuppressWarnings("unchecked") 139 protected void parse(InputStream stream) { 140 Element root = getDocumentRoot(stream); 141 List<Element> elements = root.elements("navigation-rule"); 142 for (Element rule : elements) { 143 List<Element> nav_cases = rule.elements("navigation-case"); 144 for (Element nav_case : nav_cases) { 145 Element from_el = nav_case.element("from-outcome"); 146 Element to_el = nav_case.element("to-view-id"); 147 148 if ((from_el != null) && (to_el != null)) { 149 String from = from_el.getTextTrim(); 150 String to = to_el.getTextTrim(); 151 outcomeToViewId.put(from, to); 152 viewIdToOutcome.put(to, from); 153 } 154 } 155 } 156 } 157 158 /** 159 * Gets the root element of the document. 160 * 161 * @since 5.6 162 */ 163 protected static Element getDocumentRoot(InputStream stream) { 164 try { 165 SAXReader saxReader = new SAXReader(); 166 saxReader.setEntityResolver(new DTDEntityResolver()); 167 saxReader.setMergeAdjacentText(true); 168 return saxReader.read(stream).getRootElement(); 169 } catch (DocumentException de) { 170 throw new RuntimeException(de); 171 } 172 } 173 174}