001: /*
002: * Copyright 2004 The Apache Software Foundation.
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: package org.apache.myfaces.application;
017:
018: import java.io.IOException;
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.Collections;
022: import java.util.Comparator;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027:
028: import javax.faces.FacesException;
029: import javax.faces.application.NavigationHandler;
030: import javax.faces.application.ViewHandler;
031: import javax.faces.component.UIViewRoot;
032: import javax.faces.context.ExternalContext;
033: import javax.faces.context.FacesContext;
034:
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037: import org.apache.myfaces.config.RuntimeConfig;
038: import org.apache.myfaces.config.element.NavigationCase;
039: import org.apache.myfaces.config.element.NavigationRule;
040: import org.apache.myfaces.portlet.PortletUtil;
041: import org.apache.myfaces.shared_impl.util.HashMapUtils;
042:
043: /**
044: * @author Thomas Spiegl (latest modification by $Author: mbr $)
045: * @author Anton Koinov
046: * @version $Revision: 511498 $ $Date: 2007-02-25 14:03:39 +0100 (So, 25 Feb 2007) $
047: */
048: public class NavigationHandlerImpl extends NavigationHandler {
049: private static final Log log = LogFactory
050: .getLog(NavigationHandlerImpl.class);
051:
052: private static final String ASTERISK = "*";
053:
054: private Map<String, List> _navigationCases = null;
055: private List<String> _wildcardKeys = new ArrayList<String>();
056:
057: public NavigationHandlerImpl() {
058: if (log.isTraceEnabled())
059: log.trace("New NavigationHandler instance created");
060: }
061:
062: public void handleNavigation(FacesContext facesContext,
063: String fromAction, String outcome) {
064: if (outcome == null) {
065: // stay on current ViewRoot
066: return;
067: }
068:
069: NavigationCase navigationCase = getNavigationCase(facesContext,
070: fromAction, outcome);
071:
072: if (navigationCase != null) {
073: if (log.isTraceEnabled()) {
074: log.trace("handleNavigation fromAction=" + fromAction
075: + " outcome=" + outcome + " toViewId ="
076: + navigationCase.getToViewId() + " redirect="
077: + navigationCase.isRedirect());
078: }
079: if (navigationCase.isRedirect()
080: && (!PortletUtil.isPortletRequest(facesContext))) { // Spec section 7.4.2 says "redirects not possible" in this case for portlets
081: ExternalContext externalContext = facesContext
082: .getExternalContext();
083: ViewHandler viewHandler = facesContext.getApplication()
084: .getViewHandler();
085: String redirectPath = viewHandler.getActionURL(
086: facesContext, navigationCase.getToViewId());
087:
088: try {
089: externalContext.redirect(externalContext
090: .encodeActionURL(redirectPath));
091: } catch (IOException e) {
092: throw new FacesException(e.getMessage(), e);
093: }
094: } else {
095: ViewHandler viewHandler = facesContext.getApplication()
096: .getViewHandler();
097: //create new view
098: String newViewId = navigationCase.getToViewId();
099: UIViewRoot viewRoot = viewHandler.createView(
100: facesContext, newViewId);
101: facesContext.setViewRoot(viewRoot);
102: facesContext.renderResponse();
103: }
104: } else {
105: // no navigationcase found, stay on current ViewRoot
106: if (log.isTraceEnabled()) {
107: log
108: .trace("handleNavigation fromAction="
109: + fromAction
110: + " outcome="
111: + outcome
112: + " no matching navigation-case found, staying on current ViewRoot");
113: }
114: }
115: }
116:
117: /**
118: * Returns the navigation case that applies for the given action and outcome
119: */
120: public NavigationCase getNavigationCase(FacesContext facesContext,
121: String fromAction, String outcome) {
122: String viewId = facesContext.getViewRoot().getViewId();
123: Map<String, List> casesMap = getNavigationCases(facesContext);
124: NavigationCase navigationCase = null;
125:
126: List casesList = casesMap.get(viewId);
127: if (casesList != null) {
128: // Exact match?
129: navigationCase = calcMatchingNavigationCase(casesList,
130: fromAction, outcome);
131: }
132:
133: if (navigationCase == null) {
134: // Wildcard match?
135: List<String> keys = getSortedWildcardKeys();
136: for (int i = 0, size = keys.size(); i < size; i++) {
137: String fromViewId = keys.get(i);
138: if (fromViewId.length() > 2) {
139: String prefix = fromViewId.substring(0, fromViewId
140: .length() - 1);
141: if (viewId != null && viewId.startsWith(prefix)) {
142: casesList = casesMap.get(fromViewId);
143: if (casesList != null) {
144: navigationCase = calcMatchingNavigationCase(
145: casesList, fromAction, outcome);
146: if (navigationCase != null)
147: break;
148: }
149: }
150: } else {
151: casesList = casesMap.get(fromViewId);
152: if (casesList != null) {
153: navigationCase = calcMatchingNavigationCase(
154: casesList, fromAction, outcome);
155: if (navigationCase != null)
156: break;
157: }
158: }
159: }
160: }
161: return navigationCase;
162: }
163:
164: /**
165: * Returns the view ID that would be created for the given action and outcome
166: */
167: public String getViewId(FacesContext context, String fromAction,
168: String outcome) {
169: return this .getNavigationCase(context, fromAction, outcome)
170: .getToViewId();
171: }
172:
173: /**
174: * TODO
175: * Invoked by the navigation handler before the new view component is created.
176: * @param viewId The view ID to be created
177: * @return The view ID that should be used instead. If null, the view ID passed
178: * in will be used without modification.
179: */
180: public String beforeNavigation(String viewId) {
181: return null;
182: }
183:
184: private NavigationCase calcMatchingNavigationCase(List casesList,
185: String actionRef, String outcome) {
186: for (int i = 0, size = casesList.size(); i < size; i++) {
187: NavigationCase caze = (NavigationCase) casesList.get(i);
188: String cazeOutcome = caze.getFromOutcome();
189: String cazeActionRef = caze.getFromAction();
190: if ((cazeOutcome == null || cazeOutcome.equals(outcome))
191: && (cazeActionRef == null || cazeActionRef
192: .equals(actionRef))) {
193: return caze;
194: }
195: }
196: return null;
197: }
198:
199: private List<String> getSortedWildcardKeys() {
200: return _wildcardKeys;
201: }
202:
203: private Map<String, List> getNavigationCases(
204: FacesContext facesContext) {
205: ExternalContext externalContext = facesContext
206: .getExternalContext();
207: RuntimeConfig runtimeConfig = RuntimeConfig
208: .getCurrentInstance(externalContext);
209:
210: if (_navigationCases == null
211: || runtimeConfig.isNavigationRulesChanged()) {
212: synchronized (this ) {
213: if (_navigationCases == null
214: || runtimeConfig.isNavigationRulesChanged()) {
215: Collection rules = runtimeConfig
216: .getNavigationRules();
217: int rulesSize = rules.size();
218: Map<String, List> cases = new HashMap<String, List>(
219: HashMapUtils.calcCapacity(rulesSize));
220: List<String> wildcardKeys = new ArrayList<String>();
221:
222: for (Iterator iterator = rules.iterator(); iterator
223: .hasNext();) {
224: NavigationRule rule = (NavigationRule) iterator
225: .next();
226: String fromViewId = rule.getFromViewId();
227:
228: //specification 7.4.2 footnote 4 - missing fromViewId is allowed:
229: if (fromViewId == null) {
230: fromViewId = ASTERISK;
231: } else {
232: fromViewId = fromViewId.trim();
233: }
234:
235: List list = cases.get(fromViewId);
236: if (list == null) {
237: list = new ArrayList(rule
238: .getNavigationCases());
239: cases.put(fromViewId, list);
240: if (fromViewId.endsWith(ASTERISK)) {
241: wildcardKeys.add(fromViewId);
242: }
243: } else {
244: list.addAll(rule.getNavigationCases());
245: }
246:
247: }
248: Collections.sort(wildcardKeys, new KeyComparator());
249:
250: synchronized (cases) {
251: // We do not really need this sychronization at all, but this
252: // gives us the peace of mind that some good optimizing compiler
253: // will not rearrange the execution of the assignment to an
254: // earlier time, before all init code completes
255: _navigationCases = cases;
256: _wildcardKeys = wildcardKeys;
257:
258: runtimeConfig.setNavigationRulesChanged(false);
259: }
260: }
261: }
262: }
263: return _navigationCases;
264: }
265:
266: private static final class KeyComparator implements Comparator {
267: public int compare(Object o1, Object o2) {
268: return -(((String) o1).compareTo((String) o2));
269: }
270: }
271: }
|