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}