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}