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