/** This {@link ViewHandler} implementation handles both JSP-based and Facelets/PDL-based views. */ public class MultiViewHandler extends ViewHandler { // Log instance for this class private static final Logger logger = FacesLogger.APPLICATION.getLogger(); private String[] configuredExtensions; private PageDeclarationLanguageFactory pdlFactory; // ------------------------------------------------------------ Constructors public MultiViewHandler() { WebConfiguration config = WebConfiguration.getInstance(); String defaultSuffixConfig = config.getOptionValue(WebConfiguration.WebContextInitParameter.DefaultSuffix); configuredExtensions = Util.split(defaultSuffixConfig, " "); pdlFactory = (PageDeclarationLanguageFactory) FactoryFinder.getFactory(FactoryFinder.PAGE_DECLARATION_LANGUAGE_FACTORY); } // ------------------------------------------------ Methods from ViewHandler /** * Do not call the default implementation of {@link * javax.faces.application.ViewHandler#initView(javax.faces.context.FacesContext)} if the {@link * javax.faces.context.ExternalContext#getRequestCharacterEncoding()} returns a <code>non-null * </code> result. * * @see javax.faces.application.ViewHandler#initView(javax.faces.context.FacesContext) */ @Override public void initView(FacesContext context) throws FacesException { if (context.getExternalContext().getRequestCharacterEncoding() == null) { super.initView(context); } } /** * Call {@link PageDeclarationLanguage#renderView(javax.faces.context.FacesContext, * javax.faces.component.UIViewRoot)} if the view can be rendered. * * @see ViewHandler#renderView(javax.faces.context.FacesContext, javax.faces.component.UIViewRoot) */ public void renderView(FacesContext context, UIViewRoot viewToRender) throws IOException, FacesException { Util.notNull("context", context); Util.notNull("viewToRender", viewToRender); pdlFactory .getPageDeclarationLanguage(viewToRender.getViewId()) .renderView(context, viewToRender); } /** * Call {@link PageDeclarationLanguage#restoreView(javax.faces.context.FacesContext, String)}. * * @see ViewHandler#restoreView(javax.faces.context.FacesContext, String) */ public UIViewRoot restoreView(FacesContext context, String viewId) { Util.notNull("context", context); String actualViewId = derivePhysicalViewId(context, viewId); return pdlFactory.getPageDeclarationLanguage(actualViewId).restoreView(context, actualViewId); } /** * @see ViewHandler#retargetAttachedObjects(javax.faces.context.FacesContext, * javax.faces.component.UIComponent, java.util.List) */ @Override public void retargetAttachedObjects( FacesContext context, UIComponent topLevelComponent, List<AttachedObjectHandler> handlers) { BeanInfo componentBeanInfo = (BeanInfo) topLevelComponent.getAttributes().get(UIComponent.BEANINFO_KEY); // PENDING(edburns): log error message if componentBeanInfo is null; if (null == componentBeanInfo) { return; } BeanDescriptor componentDescriptor = componentBeanInfo.getBeanDescriptor(); // There is an entry in targetList for each attached object in the // <composite:interface> section of the composite component. List<AttachedObjectTarget> targetList = (List<AttachedObjectTarget>) componentDescriptor.getValue(AttachedObjectTarget.ATTACHED_OBJECT_TARGETS_KEY); // Each entry in targetList will vend one or more UIComponent instances // that is to serve as the target of an attached object in the consuming // page. List<UIComponent> targetComponents = null; String forAttributeValue, curTargetName, handlerTagId, componentTagId; boolean foundMatch = false; // For each of the attached object handlers... for (AttachedObjectHandler curHandler : handlers) { // Get the name given to this attached object by the page author // in the consuming page. forAttributeValue = curHandler.getFor(); // For each of the attached objects in the <composite:interface> section // of this composite component... foundMatch = false; for (AttachedObjectTarget curTarget : targetList) { if (foundMatch) { break; } // Get the name given to this attached object target by the // composite component author curTargetName = curTarget.getName(); targetComponents = curTarget.getTargets(topLevelComponent); if (curHandler instanceof ActionSource2AttachedObjectHandler && curTarget instanceof ActionSource2AttachedObjectTarget) { if (forAttributeValue.equals(curTargetName)) { for (UIComponent curTargetComponent : targetComponents) { curHandler.applyAttachedObject(context, curTargetComponent); foundMatch = true; } } } else if (curHandler instanceof EditableValueHolderAttachedObjectHandler && curTarget instanceof EditableValueHolderAttachedObjectTarget) { if (forAttributeValue.equals(curTargetName)) { for (UIComponent curTargetComponent : targetComponents) { curHandler.applyAttachedObject(context, curTargetComponent); foundMatch = true; } } } else if (curHandler instanceof ValueHolderAttachedObjectHandler && curTarget instanceof ValueHolderAttachedObjectTarget) { if (forAttributeValue.equals(curTargetName)) { for (UIComponent curTargetComponent : targetComponents) { curHandler.applyAttachedObject(context, curTargetComponent); foundMatch = true; } } } } } } /** * @see ViewHandler#retargetMethodExpressions(javax.faces.context.FacesContext, * javax.faces.component.UIComponent) */ @Override public void retargetMethodExpressions(FacesContext context, UIComponent topLevelComponent) { BeanInfo componentBeanInfo = (BeanInfo) topLevelComponent.getAttributes().get(UIComponent.BEANINFO_KEY); // PENDING(edburns): log error message if componentBeanInfo is null; if (null == componentBeanInfo) { return; } PropertyDescriptor attributes[] = componentBeanInfo.getPropertyDescriptors(); String targets = null, attrName = null, strValue = null, methodSignature = null; UIComponent target = null; ExpressionFactory expressionFactory = null; ValueExpression valueExpression = null; MethodExpression toApply = null; Class expectedReturnType = null; Class expectedParameters[] = null; for (PropertyDescriptor cur : attributes) { // If the current attribute represents a ValueExpression if (null != (valueExpression = (ValueExpression) cur.getValue("type"))) { // take no action on this attribute. continue; } // If the current attribute representes a MethodExpression if (null != (valueExpression = (ValueExpression) cur.getValue("method-signature"))) { methodSignature = (String) valueExpression.getValue(context.getELContext()); if (null != methodSignature) { // This is the name of the attribute on the top level component, // and on the inner component. if (null != (valueExpression = (ValueExpression) cur.getValue("targets"))) { targets = (String) valueExpression.getValue(context.getELContext()); } if (null == targets) { targets = cur.getName(); } if (null == targets || 0 == targets.length()) { // PENDING error message in page? logger.severe("Unable to retarget MethodExpression: " + methodSignature); continue; } String[] targetIds = targets.split(" "); for (String curTarget : targetIds) { attrName = cur.getName(); // Find the attribute on the top level component valueExpression = (ValueExpression) topLevelComponent.getAttributes().get(attrName); if (null == valueExpression) { // PENDING error message in page? logger.severe( "Unable to find attribute with name \"" + attrName + "\" in top level component in consuming page. " + "Page author error."); continue; } // lazily initialize this local variable if (null == expressionFactory) { expressionFactory = context.getApplication().getExpressionFactory(); } // If the attribute is one of the pre-defined // MethodExpression attributes boolean isAction = false, isActionListener = false, isValidator = false, isValueChangeListener = false; if ((isAction = attrName.equals("action")) || (isActionListener = attrName.equals("actionListener")) || (isValidator = attrName.equals("validator")) || (isValueChangeListener = attrName.equals("valueChangeListener"))) { // This is the inner component to which the attribute should // be applied target = topLevelComponent.findComponent(curTarget); if (null == targets) { // PENDING error message in page? logger.severe( "Unable to retarget MethodExpression. " + "Unable to find inner component with id " + targets + "."); continue; } if (isAction) { expectedReturnType = Object.class; expectedParameters = new Class[] {}; toApply = expressionFactory.createMethodExpression( context.getELContext(), valueExpression.getExpressionString(), expectedReturnType, expectedParameters); ((ActionSource2) target).setActionExpression(toApply); } else if (isActionListener) { expectedReturnType = Void.TYPE; expectedParameters = new Class[] {ActionEvent.class}; toApply = expressionFactory.createMethodExpression( context.getELContext(), valueExpression.getExpressionString(), expectedReturnType, expectedParameters); ((ActionSource2) target) .addActionListener(new MethodExpressionActionListener(toApply)); } else if (isValidator) { expectedReturnType = Void.TYPE; expectedParameters = new Class[] {FacesContext.class, UIComponent.class, Object.class}; toApply = expressionFactory.createMethodExpression( context.getELContext(), valueExpression.getExpressionString(), expectedReturnType, expectedParameters); ((EditableValueHolder) target).addValidator(new MethodExpressionValidator(toApply)); } else if (isValueChangeListener) { expectedReturnType = Void.TYPE; expectedParameters = new Class[] {ValueChangeEvent.class}; toApply = expressionFactory.createMethodExpression( context.getELContext(), valueExpression.getExpressionString(), expectedReturnType, expectedParameters); ((EditableValueHolder) target) .addValueChangeListener(new MethodExpressionValueChangeListener(toApply)); } } else { // There is no explicit methodExpression property on // an inner component to which this MethodExpression // should be retargeted. In this case, replace the // ValueExpression with a method expresson. // Pull apart the methodSignature to derive the // expectedReturnType and expectedParameters // PENDING(rlubke,jimdriscoll) bulletproof this assert (null != methodSignature); methodSignature = methodSignature.trim(); // Get expectedReturnType int j, i = methodSignature.indexOf(" "); if (-1 != i) { strValue = methodSignature.substring(0, i); try { expectedReturnType = Util.getTypeFromString(strValue); } catch (ClassNotFoundException cnfe) { logger.log( Level.SEVERE, "Unable to determine expected return type for " + methodSignature, cnfe); continue; } } else { logger.severe("Unable to determine expected return type for " + methodSignature); continue; } // derive the arguments i = methodSignature.indexOf("("); if (-1 != i) { j = methodSignature.indexOf(")", i + 1); if (-1 != j) { strValue = methodSignature.substring(i + 1, j); if (0 < strValue.length()) { String[] params = strValue.split(","); expectedParameters = new Class[params.length]; boolean exceptionThrown = false; for (i = 0; i < params.length; i++) { try { expectedParameters[i] = Util.getTypeFromString(params[i]); } catch (ClassNotFoundException cnfe) { logger.log( Level.SEVERE, "Unable to determine expected return type for " + methodSignature, cnfe); exceptionThrown = true; break; } } if (exceptionThrown) { continue; } } else { expectedParameters = new Class[] {}; } } } assert (null != expectedReturnType); assert (null != expectedParameters); toApply = expressionFactory.createMethodExpression( context.getELContext(), valueExpression.getExpressionString(), expectedReturnType, expectedParameters); topLevelComponent.getAttributes().put(attrName, toApply); } } } } } } /** * Derive the actual view ID (i.e. the physical resource) and call call {@link * PageDeclarationLanguage#createView(javax.faces.context.FacesContext, String)}. * * @see ViewHandler#restoreView(javax.faces.context.FacesContext, String) */ public UIViewRoot createView(FacesContext context, String viewId) { Util.notNull("context", context); String actualViewId = derivePhysicalViewId(context, viewId); return pdlFactory.getPageDeclarationLanguage(actualViewId).createView(context, actualViewId); } /** * This code is currently common to all {@link ViewHandlingStrategy} instances. * * @see ViewHandler#calculateLocale(javax.faces.context.FacesContext) */ public Locale calculateLocale(FacesContext context) { Util.notNull("context", context); Locale result = null; // determine the locales that are acceptable to the client based on the // Accept-Language header and the find the best match among the // supported locales specified by the client. Iterator<Locale> locales = context.getExternalContext().getRequestLocales(); while (locales.hasNext()) { Locale perf = locales.next(); result = findMatch(context, perf); if (result != null) { break; } } // no match is found. if (result == null) { if (context.getApplication().getDefaultLocale() == null) { result = Locale.getDefault(); } else { result = context.getApplication().getDefaultLocale(); } } return result; } /** * This code is currently common to all {@link ViewHandlingStrategy} instances. * * @see ViewHandler#calculateRenderKitId(javax.faces.context.FacesContext) */ public String calculateRenderKitId(FacesContext context) { Util.notNull("context", context); Map<String, String> requestParamMap = context.getExternalContext().getRequestParameterMap(); String result = requestParamMap.get(ResponseStateManager.RENDER_KIT_ID_PARAM); if (result == null) { if (null == (result = context.getApplication().getDefaultRenderKitId())) { result = RenderKitFactory.HTML_BASIC_RENDER_KIT; } } return result; } /** * This code is currently common to all {@link ViewHandlingStrategy} instances. * * @see ViewHandler#writeState(javax.faces.context.FacesContext) */ public void writeState(FacesContext context) throws IOException { Util.notNull("context", context); if (!context.getPartialViewContext().isAjaxRequest()) { if (logger.isLoggable(Level.FINE)) { logger.fine("Begin writing marker for viewId " + context.getViewRoot().getViewId()); } WriteBehindStateWriter writer = WriteBehindStateWriter.getCurrentInstance(); if (writer != null) { writer.writingState(); } context.getResponseWriter().write(RIConstants.SAVESTATE_FIELD_MARKER); if (logger.isLoggable(Level.FINE)) { logger.fine("End writing marker for viewId " + context.getViewRoot().getViewId()); } } } /** * This code is currently common to all {@link ViewHandlingStrategy} instances. * * @see ViewHandler#getActionURL(javax.faces.context.FacesContext, String) */ public String getActionURL(FacesContext context, String viewId) { Util.notNull("context", context); Util.notNull("viewId", viewId); if (viewId.charAt(0) != '/') { String message = MessageUtils.getExceptionMessageString(MessageUtils.ILLEGAL_VIEW_ID_ID, viewId); if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, "jsf.illegal_view_id_error", viewId); } throw new IllegalArgumentException(message); } // Acquire the context path, which we will prefix on all results ExternalContext extContext = context.getExternalContext(); String contextPath = extContext.getRequestContextPath(); // Acquire the mapping used to execute this request (if any) String mapping = Util.getFacesMapping(context); // If no mapping can be identified, just return a server-relative path if (mapping == null) { return (contextPath + viewId); } // Deal with prefix mapping if (Util.isPrefixMapped(mapping)) { if (mapping.equals("/*")) { return (contextPath + viewId); } else { return (contextPath + mapping + viewId); } } // Deal with extension mapping int period = viewId.lastIndexOf('.'); if (period < 0) { return (contextPath + viewId + mapping); } else if (!viewId.endsWith(mapping)) { return (contextPath + viewId.substring(0, period) + mapping); } else { return (contextPath + viewId); } } /** * This code is currently common to all {@link ViewHandlingStrategy} instances. * * @see ViewHandler#getResourceURL(javax.faces.context.FacesContext, String) */ public String getResourceURL(FacesContext context, String path) { ExternalContext extContext = context.getExternalContext(); if (path.charAt(0) == '/') { return (extContext.getRequestContextPath() + path); } else { return path; } } /** @see ViewHandler#getPageDeclarationLanguage(javax.faces.context.FacesContext, String) */ @Override public PageDeclarationLanguage getPageDeclarationLanguage(FacesContext context, String viewId) { String actualViewId = derivePhysicalViewId(context, viewId); return pdlFactory.getPageDeclarationLanguage(actualViewId); } // ------------------------------------------------------- Protected Methods /** * Attempts to find a matching locale based on <code>pref</code> and list of supported locales, * using the matching algorithm as described in JSTL 8.3.2. * * @param context the <code>FacesContext</code> for the current request * @param pref the preferred locale * @return the Locale based on pref and the matching alogritm specified in JSTL 8.3.2 */ protected Locale findMatch(FacesContext context, Locale pref) { Locale result = null; Iterator<Locale> it = context.getApplication().getSupportedLocales(); while (it.hasNext()) { Locale supportedLocale = it.next(); if (pref.equals(supportedLocale)) { // exact match result = supportedLocale; break; } else { // Make sure the preferred locale doesn't have country // set, when doing a language match, For ex., if the // preferred locale is "en-US", if one of supported // locales is "en-UK", even though its language matches // that of the preferred locale, we must ignore it. if (pref.getLanguage().equals(supportedLocale.getLanguage()) && supportedLocale.getCountry().length() == 0) { result = supportedLocale; } } } // if it's not in the supported locales, if (null == result) { Locale defaultLocale = context.getApplication().getDefaultLocale(); if (defaultLocale != null) { if (pref.equals(defaultLocale)) { // exact match result = defaultLocale; } else { // Make sure the preferred locale doesn't have country // set, when doing a language match, For ex., if the // preferred locale is "en-US", if one of supported // locales is "en-UK", even though its language matches // that of the preferred locale, we must ignore it. if (pref.getLanguage().equals(defaultLocale.getLanguage()) && defaultLocale.getCountry().length() == 0) { result = defaultLocale; } } } } return result; } /** * if the specified mapping is a prefix mapping, and the provided request URI (usually the value * from <code>ExternalContext.getRequestServletPath()</code>) starts with <code>mapping + '/' * </code>, prune the mapping from the URI and return it, otherwise, return the original URI. * * @param uri the servlet request path * @param mapping the FacesServlet mapping used for this request * @return the URI without additional FacesServlet mappings * @since 1.2 */ protected String normalizeRequestURI(String uri, String mapping) { if (mapping == null || !Util.isPrefixMapped(mapping)) { return uri; } else { int length = mapping.length() + 1; StringBuilder builder = new StringBuilder(length); builder.append(mapping).append('/'); String mappingMod = builder.toString(); boolean logged = false; while (uri.startsWith(mappingMod)) { if (!logged && logger.isLoggable(Level.WARNING)) { logged = true; logger.log( Level.WARNING, "jsf.viewhandler.requestpath.recursion", new Object[] {uri, mapping}); } uri = uri.substring(length - 1); } return uri; } } /** * Send {@link HttpServletResponse#SC_NOT_FOUND} (404) to the client. * * @param context the {@link FacesContext} for the current request */ protected void send404Error(FacesContext context) { HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse(); try { context.responseComplete(); response.sendError(HttpServletResponse.SC_NOT_FOUND); } catch (IOException ioe) { throw new FacesException(ioe); } } /** * Adjust the viewID per the requirements of {@link #renderView}. * * @param context current {@link javax.faces.context.FacesContext} * @param viewId incoming view ID * @return the view ID with an altered suffix mapping (if necessary) */ protected String convertViewId(FacesContext context, String viewId) { // if the viewId doesn't already use the above suffix, // replace or append. StringBuilder buffer = new StringBuilder(viewId); for (String ext : configuredExtensions) { if (viewId.endsWith(ext)) { return viewId; } int extIdx = viewId.lastIndexOf('.'); if (extIdx != -1) { buffer.replace(extIdx, viewId.length(), ext); } else { // no extension in the provided viewId, append the suffix buffer.append(ext); } String convertedViewId = buffer.toString(); try { if (context.getExternalContext().getResource(convertedViewId) != null) { // RELEASE_PENDING (rlubke,driscoll) cache the lookup return convertedViewId; } else { // reset the buffer to check for the next extension buffer.setLength(0); buffer.append(viewId); } } catch (MalformedURLException e) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, e.toString(), e); } } } // unable to find any resource match that the default ViewHandler // can deal with. Return the viewId as it was passed. There is // probably another ViewHandler in the stack that will handle this. return viewId; } protected String derivePhysicalViewId(FacesContext ctx, String viewId) { if (viewId != null) { String mapping = Util.getFacesMapping(ctx); if (mapping != null) { if (!Util.isPrefixMapped(mapping)) { viewId = convertViewId(ctx, viewId); } else { viewId = normalizeRequestURI(viewId, mapping); if (viewId.equals(mapping)) { // The request was to the FacesServlet only - no // path info // on some containers this causes a recursion in the // RequestDispatcher and the request appears to hang. // If this is detected, return status 404 send404Error(ctx); } } } } return viewId; } }
/** * <strong>NavigationHandlerImpl</strong> is the class that implements default navigation handling. * Refer to section 7.4.2 of the specification for more details. PENDING: Make independent of * ApplicationAssociate. */ public class NavigationHandlerImpl extends ConfigurableNavigationHandler { // // Protected Constants // // Log instance for this class private static final Logger logger = FacesLogger.APPLICATION.getLogger(); // // Class Variables // // Instance Variables /** <code>Map</code> containing configured navigation cases. */ private Map<String, List<NavigationCase>> caseListMap; /** <code>Set</code> containing wildcard navigation cases. */ private Set<String> wildCardSet; /** Flag indicating navigation cases properly consumed and available. */ private boolean navigationConfigured; /** Flag indicated the current mode. */ private boolean development; /** * This constructor uses the current <code>Application</code> instance to obtain the navigation * mappings used to make navigational decisions. */ public NavigationHandlerImpl() { super(); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Created NavigationHandler instance "); } // if the user is using the decorator pattern, this would cause // our ApplicationAssociate to be created, if it isn't already // created. ApplicationFactory aFactory = (ApplicationFactory) FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY); aFactory.getApplication(); ApplicationAssociate associate = ApplicationAssociate.getInstance(FacesContext.getCurrentInstance().getExternalContext()); if (associate != null) { caseListMap = associate.getNavigationCaseListMappings(); wildCardSet = associate.getNavigationWildCardList(); navigationConfigured = (wildCardSet != null && caseListMap != null); development = associate.isDevModeEnabled(); } } NavigationHandlerImpl(ApplicationAssociate associate) { if (associate == null) { throw new NullPointerException(); } else { caseListMap = associate.getNavigationCaseListMappings(); wildCardSet = associate.getNavigationWildCardList(); navigationConfigured = (wildCardSet != null && caseListMap != null); } } @Override public NavigationCase getNavigationCase(FacesContext context, String fromAction, String outcome) { NavigationCase result = null; CaseStruct caseStruct = getViewId(context, fromAction, outcome); if (null != caseStruct) { result = caseStruct.navCase; } return result; } @Override public Map<String, List<NavigationCase>> getNavigationCases() { return caseListMap; } /** * Determine the next view based on the current view (<code>from-view-id</code> stored in <code> * FacesContext</code>), <code>fromAction</code> and <code>outcome</code>. * * @param context The <code>FacesContext</code> * @param fromAction the action reference string * @param outcome the outcome string */ public void handleNavigation(FacesContext context, String fromAction, String outcome) { if (context == null) { String message = MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context"); throw new NullPointerException(message); } if (outcome == null) { return; // Explicitly remain on the current view } CaseStruct caseStruct = getViewId(context, fromAction, outcome); ExternalContext extContext = context.getExternalContext(); if (caseStruct != null) { ViewHandler viewHandler = Util.getViewHandler(context); assert (null != viewHandler); if (caseStruct.navCase.isRedirect()) { // perform a 302 redirect. String newPath = viewHandler.getActionURL(context, caseStruct.viewId); try { if (logger.isLoggable(Level.FINE)) { logger.fine( "Redirecting to path " + newPath + " for outcome " + outcome + "and viewId " + caseStruct.viewId); } // encode the redirect to ensure session state // is maintained extContext.redirect(extContext.encodeActionURL(newPath)); } catch (java.io.IOException ioe) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, "jsf.redirect_failed_error", newPath); } throw new FacesException(ioe.getMessage(), ioe); } context.responseComplete(); if (logger.isLoggable(Level.FINE)) { logger.fine("Response complete for " + caseStruct.viewId); } } else { UIViewRoot newRoot = viewHandler.createView(context, caseStruct.viewId); context.setViewRoot(newRoot); if (logger.isLoggable(Level.FINE)) { logger.fine("Set new view in FacesContext for " + caseStruct.viewId); } } } } /** * This method uses helper methods to determine the new <code>view</code> identifier. Refer to * section 7.4.2 of the specification for more details. * * @param context The Faces Context * @param fromAction The action reference string * @param outcome The outcome string * @return The <code>view</code> identifier. */ private CaseStruct getViewId(FacesContext context, String fromAction, String outcome) { UIViewRoot root = context.getViewRoot(); String viewId = (root != null ? root.getViewId() : null); // if viewId is not null, use its value to find // a navigation match, otherwise look for a match // based soley on the fromAction and outcome CaseStruct caseStruct = null; if (viewId != null) { caseStruct = findExactMatch(viewId, fromAction, outcome); if (caseStruct == null) { caseStruct = findWildCardMatch(viewId, fromAction, outcome); } } if (caseStruct == null) { caseStruct = findDefaultMatch(fromAction, outcome); } if (caseStruct == null && development) { String key; Object[] params; if (fromAction == null) { key = MessageUtils.NAVIGATION_NO_MATCHING_OUTCOME_ID; params = new Object[] {viewId, outcome}; } else { key = MessageUtils.NAVIGATION_NO_MATCHING_OUTCOME_ACTION_ID; params = new Object[] {viewId, fromAction, outcome}; } FacesMessage m = MessageUtils.getExceptionMessage(key, params); m.setSeverity(FacesMessage.SEVERITY_WARN); context.addMessage(null, m); } return caseStruct; } /** * This method finds the List of cases for the current <code>view</code> identifier. After the * cases are found, the <code>from-action</code> and <code>from-outcome</code> values are * evaluated to determine the new <code>view</code> identifier. Refer to section 7.4.2 of the * specification for more details. * * @param viewId The current <code>view</code> identifier. * @param fromAction The action reference string. * @param outcome The outcome string. * @return The <code>view</code> identifier. */ private CaseStruct findExactMatch(String viewId, String fromAction, String outcome) { // if the user has elected to replace the Application instance // entirely if (!navigationConfigured) { return null; } List<NavigationCase> caseList = caseListMap.get(viewId); if (caseList == null) { return null; } // We've found an exact match for the viewId. Now we need to evaluate // from-action/outcome in the following order: // 1) elements specifying both from-action and from-outcome // 2) elements specifying only from-outcome // 3) elements specifying only from-action // 4) elements where both from-action and from-outcome are null return determineViewFromActionOutcome(caseList, fromAction, outcome); } /** * This method traverses the wild card match List (containing <code>from-view-id</code> strings * and finds the List of cases for each <code>from-view-id</code> string. Refer to section 7.4.2 * of the specification for more details. * * @param viewId The current <code>view</code> identifier. * @param fromAction The action reference string. * @param outcome The outcome string. * @return The <code>view</code> identifier. */ private CaseStruct findWildCardMatch(String viewId, String fromAction, String outcome) { CaseStruct result = null; // if the user has elected to replace the Application instance // entirely if (!navigationConfigured) { return null; } for (String fromViewId : wildCardSet) { // See if the entire wildcard string (without the trailing "*" is // contained in the incoming viewId. // Ex: /foobar is contained with /foobarbaz // If so, then we have found our largest pattern match.. // If not, then continue on to the next case; if (!viewId.startsWith(fromViewId)) { continue; } // Append the trailing "*" so we can do our map lookup; String wcFromViewId = new StringBuilder(32).append(fromViewId).append('*').toString(); List<NavigationCase> caseList = caseListMap.get(wcFromViewId); if (caseList == null) { return null; } // If we've found a match, then we need to evaluate // from-action/outcome in the following order: // 1) elements specifying both from-action and from-outcome // 2) elements specifying only from-outcome // 3) elements specifying only from-action // 4) elements where both from-action and from-outcome are null result = determineViewFromActionOutcome(caseList, fromAction, outcome); if (result != null) { break; } } return result; } /** * This method will extract the cases for which a <code>from-view-id</code> is an asterisk "*". * Refer to section 7.4.2 of the specification for more details. * * @param fromAction The action reference string. * @param outcome The outcome string. * @return The <code>view</code> identifier. */ private CaseStruct findDefaultMatch(String fromAction, String outcome) { // if the user has elected to replace the Application instance // entirely if (!navigationConfigured) { return null; } List<NavigationCase> caseList = caseListMap.get("*"); if (caseList == null) { return null; } // We need to evaluate from-action/outcome in the follow // order: 1)elements specifying both from-action and from-outcome // 2) elements specifying only from-outcome // 3) elements specifying only from-action // 4) elements where both from-action and from-outcome are null return determineViewFromActionOutcome(caseList, fromAction, outcome); } /** * This method will attempt to find the <code>view</code> identifier based on action reference and * outcome. Refer to section 7.4.2 of the specification for more details. * * @param caseList The list of navigation cases. * @param fromAction The action reference string. * @param outcome The outcome string. * @return The <code>view</code> identifier. */ private CaseStruct determineViewFromActionOutcome( List<NavigationCase> caseList, String fromAction, String outcome) { CaseStruct result = new CaseStruct(); for (NavigationCase cnc : caseList) { String cncFromAction = cnc.getFromAction(); String fromOutcome = cnc.getFromOutcome(); String toViewId = cnc.getToViewId(); if ((cncFromAction != null) && (fromOutcome != null)) { if ((cncFromAction.equals(fromAction)) && (fromOutcome.equals(outcome))) { result.viewId = toViewId; result.navCase = cnc; return result; } } if ((cncFromAction == null) && (fromOutcome != null)) { if (fromOutcome.equals(outcome)) { result.viewId = toViewId; result.navCase = cnc; return result; } } if ((cncFromAction != null) && (fromOutcome == null)) { if (cncFromAction.equals(fromAction)) { result.viewId = toViewId; result.navCase = cnc; return result; } } if ((cncFromAction == null) && (fromOutcome == null)) { result.viewId = toViewId; result.navCase = cnc; return result; } } return null; } private static class CaseStruct { String viewId; NavigationCase navCase; } }
/** * This <code>StateHelper</code> provides the functionality associated with server-side state * saving, though in actuallity, it is a hybrid between client and server. */ public class ServerSideStateHelper extends StateHelper { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); /** Key to store the <code>AtomicInteger</code> used to generate unique state map keys. */ public static final String STATEMANAGED_SERIAL_ID_KEY = ServerSideStateHelper.class.getName() + ".SerialId"; /** The top level attribute name for storing the state structures within the session. */ public static final String LOGICAL_VIEW_MAP = ServerSideStateHelper.class.getName() + ".LogicalViewMap"; /** The number of logical views as configured by the user. */ protected final Integer numberOfLogicalViews; /** The number of views as configured by the user. */ protected final Integer numberOfViews; /** Flag determining how server state IDs are generated. */ protected boolean generateUniqueStateIds; /** Flag determining whether or not javax.faces.ViewState should be namespaced. */ protected boolean namespaceParameters; /** Used to generate unique server state IDs. */ protected final Random random; // ------------------------------------------------------------ Constructors /** Construct a new <code>ServerSideStateHelper</code> instance. */ public ServerSideStateHelper() { numberOfLogicalViews = getIntegerConfigValue(NumberOfLogicalViews); numberOfViews = getIntegerConfigValue(NumberOfViews); WebConfiguration webConfig = WebConfiguration.getInstance(); generateUniqueStateIds = webConfig.isOptionEnabled(GenerateUniqueServerStateIds); if (generateUniqueStateIds) { random = new Random(System.nanoTime() + webConfig.getServletContext().hashCode()); } else { random = null; } namespaceParameters = webConfig.isOptionEnabled(NamespaceParameters); } // ------------------------------------------------ Methods from StateHelper /** * <p> * Stores the provided state within the session obtained from the provided * <code>FacesContext</code> * </p> * * <p>If <code>stateCapture</code> is <code>null</code>, the composite * key used to look up the actual and logical views will be written to * the client as a hidden field using the <code>ResponseWriter</code> * from the provided <code>FacesContext</code>.</p> * * <p>If <code>stateCapture</code> is not <code>null</code>, the composite * key will be appended to the <code>StringBuilder<code> without any markup * included or any content written to the client. */ public void writeState(FacesContext ctx, Object state, StringBuilder stateCapture) throws IOException { Util.notNull("context", ctx); String id; UIViewRoot viewRoot = ctx.getViewRoot(); if (!viewRoot.isTransient()) { if (!ctx.getAttributes().containsKey("com.sun.faces.ViewStateValue")) { Util.notNull("state", state); Object[] stateToWrite = (Object[]) state; ExternalContext externalContext = ctx.getExternalContext(); Object sessionObj = externalContext.getSession(true); Map<String, Object> sessionMap = externalContext.getSessionMap(); //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (sessionObj) { Map<String, Map> logicalMap = TypedCollections.dynamicallyCastMap( (Map) sessionMap.get(LOGICAL_VIEW_MAP), String.class, Map.class); if (logicalMap == null) { logicalMap = Collections.synchronizedMap(new LRUMap<String, Map>(numberOfLogicalViews)); sessionMap.put(LOGICAL_VIEW_MAP, logicalMap); } Object structure = stateToWrite[0]; Object savedState = handleSaveState(stateToWrite[1]); String idInLogicalMap = (String) RequestStateManager.get(ctx, RequestStateManager.LOGICAL_VIEW_MAP); if (idInLogicalMap == null) { idInLogicalMap = ((generateUniqueStateIds) ? createRandomId() : createIncrementalRequestId(ctx)); } String idInActualMap = null; if (ctx.getPartialViewContext().isPartialRequest()) { // If partial request, do not change actual view Id, because page not actually changed. // Otherwise partial requests will soon overflow cache with values that would be never // used. idInActualMap = (String) RequestStateManager.get(ctx, RequestStateManager.ACTUAL_VIEW_MAP); } if (null == idInActualMap) { idInActualMap = ((generateUniqueStateIds) ? createRandomId() : createIncrementalRequestId(ctx)); } Map<String, Object[]> actualMap = TypedCollections.dynamicallyCastMap( logicalMap.get(idInLogicalMap), String.class, Object[].class); if (actualMap == null) { actualMap = new LRUMap<String, Object[]>(numberOfViews); logicalMap.put(idInLogicalMap, actualMap); } id = idInLogicalMap + ':' + idInActualMap; Object[] stateArray = actualMap.get(idInActualMap); // reuse the array if possible if (stateArray != null) { stateArray[0] = structure; stateArray[1] = savedState; } else { actualMap.put(idInActualMap, new Object[] {structure, savedState}); } // always call put/setAttribute as we may be in a clustered environment. sessionMap.put(LOGICAL_VIEW_MAP, logicalMap); ctx.getAttributes().put("com.sun.faces.ViewStateValue", id); } } else { id = (String) ctx.getAttributes().get("com.sun.faces.ViewStateValue"); } } else { id = "stateless"; } if (stateCapture != null) { stateCapture.append(id); } else { ResponseWriter writer = ctx.getResponseWriter(); writer.startElement("input", null); writer.writeAttribute("type", "hidden", null); String viewStateParam = ResponseStateManager.VIEW_STATE_PARAM; if ((namespaceParameters) && (viewRoot instanceof NamingContainer)) { String namingContainerId = viewRoot.getContainerClientId(ctx); if (namingContainerId != null) { viewStateParam = namingContainerId + viewStateParam; } } writer.writeAttribute("name", viewStateParam, null); if (webConfig.isOptionEnabled(EnableViewStateIdRendering)) { String viewStateId = Util.getViewStateId(ctx); writer.writeAttribute("id", viewStateId, null); } writer.writeAttribute("value", id, null); if (webConfig.isOptionEnabled(AutoCompleteOffOnViewState)) { writer.writeAttribute("autocomplete", "off", null); } writer.endElement("input"); writeClientWindowField(ctx, writer); writeRenderKitIdField(ctx, writer); } } /** * Inspects the incoming request parameters for the standardized state parameter name. In this * case, the parameter value will be the composite ID generated by * ServerSideStateHelper#writeState(FacesContext, Object, StringBuilder). * * <p>The composite key will be used to find the appropriate view within the session obtained from * the provided <code>FacesContext</code> */ public Object getState(FacesContext ctx, String viewId) { String compoundId = getStateParamValue(ctx); if (compoundId == null) { return null; } if ("stateless".equals(compoundId)) { return "stateless"; } int sep = compoundId.indexOf(':'); assert (sep != -1); assert (sep < compoundId.length()); String idInLogicalMap = compoundId.substring(0, sep); String idInActualMap = compoundId.substring(sep + 1); ExternalContext externalCtx = ctx.getExternalContext(); Object sessionObj = externalCtx.getSession(false); // stop evaluating if the session is not available if (sessionObj == null) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log( Level.FINE, "Unable to restore server side state for view ID {0} as no session is available", viewId); } return null; } //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (sessionObj) { Map logicalMap = (Map) externalCtx.getSessionMap().get(LOGICAL_VIEW_MAP); if (logicalMap != null) { Map actualMap = (Map) logicalMap.get(idInLogicalMap); if (actualMap != null) { RequestStateManager.set(ctx, RequestStateManager.LOGICAL_VIEW_MAP, idInLogicalMap); Object[] state = (Object[]) actualMap.get(idInActualMap); Object[] restoredState = new Object[2]; restoredState[0] = state[0]; restoredState[1] = state[1]; if (state != null) { RequestStateManager.set(ctx, RequestStateManager.ACTUAL_VIEW_MAP, idInActualMap); if (state.length == 2 && state[1] != null) { restoredState[1] = handleRestoreState(state[1]); } } return restoredState; } } } return null; } // ------------------------------------------------------- Protected Methods /** * Utility method for obtaining the <code>Integer</code> based configuration values used to change * the behavior of the <code>ServerSideStateHelper</code>. * * @param param the paramter to parse * @return the Integer representation of the parameter value */ protected Integer getIntegerConfigValue(WebContextInitParameter param) { String noOfViewsStr = webConfig.getOptionValue(param); Integer value = null; try { value = Integer.valueOf(noOfViewsStr); } catch (NumberFormatException nfe) { String defaultValue = param.getDefaultValue(); if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log( Level.WARNING, "jsf.state.server.cannot.parse.int.option", new Object[] {param.getQualifiedName(), defaultValue}); } try { value = Integer.valueOf(defaultValue); } catch (NumberFormatException ne) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Unable to convert number", ne); } } } return value; } /** * @param state the object returned from <code>UIView.processSaveState</code> * @return If {@link * com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter#SerializeServerStateDeprecated} * is <code>true</code>, serialize and return the state, otherwise, return <code>state</code> * unchanged. */ protected Object handleSaveState(Object state) { if (webConfig.isOptionEnabled(SerializeServerStateDeprecated) || webConfig.isOptionEnabled(SerializeServerState)) { ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); ObjectOutputStream oas = null; try { oas = serialProvider.createObjectOutputStream( ((compressViewState) ? new GZIPOutputStream(baos, 1024) : baos)); //noinspection NonSerializableObjectPassedToObjectStream oas.writeObject(state); oas.flush(); } catch (Exception e) { throw new FacesException(e); } finally { if (oas != null) { try { oas.close(); } catch (IOException ioe) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Closing stream", ioe); } } } } return baos.toByteArray(); } else { return state; } } /** * @param state the state as it was stored in the session * @return an object that can be passed to <code>UIViewRoot.processRestoreState</code>. If {@link * com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter#SerializeServerStateDeprecated} * de-serialize the state prior to returning it, otherwise return <code>state</code> as is. */ protected Object handleRestoreState(Object state) { if (webConfig.isOptionEnabled(SerializeServerStateDeprecated) || webConfig.isOptionEnabled(SerializeServerState)) { ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) state); ObjectInputStream ois = null; try { ois = serialProvider.createObjectInputStream( ((compressViewState) ? new GZIPInputStream(bais, 1024) : bais)); return ois.readObject(); } catch (Exception e) { throw new FacesException(e); } finally { if (ois != null) { try { ois.close(); } catch (IOException ioe) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Closing stream", ioe); } } } } } else { return state; } } /** * @param ctx the <code>FacesContext</code> for the current request * @return a unique ID for building the keys used to store views within a session */ private String createIncrementalRequestId(FacesContext ctx) { Map<String, Object> sm = ctx.getExternalContext().getSessionMap(); AtomicInteger idgen = (AtomicInteger) sm.get(STATEMANAGED_SERIAL_ID_KEY); if (idgen == null) { idgen = new AtomicInteger(1); } // always call put/setAttribute as we may be in a clustered environment. sm.put(STATEMANAGED_SERIAL_ID_KEY, idgen); return (UIViewRoot.UNIQUE_ID_PREFIX + idgen.getAndIncrement()); } private String createRandomId() { return Long.valueOf(random.nextLong()).toString(); } /** * Is stateless. * * @param facesContext the Faces context. * @param viewId the view id. * @return true if stateless, false otherwise. * @throws IllegalStateException when the request was not a postback. */ @Override public boolean isStateless(FacesContext facesContext, String viewId) throws IllegalStateException { if (facesContext.isPostback()) { Object stateObject = getState(facesContext, viewId); if (stateObject instanceof String && "stateless".equals((String) stateObject)) { return true; } return false; } throw new IllegalStateException("Cannot determine whether or not the request is stateless"); } }
/** This {@link ViewHandlingStrategy} handles JSP-based views. */ public class JspViewHandlingStrategy extends ViewHandlingStrategy { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); private int responseBufferSize; // ------------------------------------------------------------ Constructors public JspViewHandlingStrategy() { try { responseBufferSize = Integer.parseInt(webConfig.getOptionValue(ResponseBufferSize)); } catch (NumberFormatException nfe) { responseBufferSize = Integer.parseInt(ResponseBufferSize.getDefaultValue()); } } // ------------------------------------ Methods from ViewDeclarationLanguage /** * Not supported in JSP-based views. * * @see * javax.faces.view.ViewDeclarationLanguage#getComponentMetadata(javax.faces.context.FacesContext, * javax.faces.application.Resource) */ @Override public BeanInfo getComponentMetadata(FacesContext context, Resource componentResource) { throw new UnsupportedOperationException(); } /** * Not supported in JSP-based views. * * @see javax.faces.view.ViewDeclarationLanguage#getViewMetadata(javax.faces.context.FacesContext, * String) */ @Override public ViewMetadata getViewMetadata(FacesContext context, String viewId) { return null; } /** * Not supported in JSP-based views. * * @see * javax.faces.view.ViewDeclarationLanguage#getScriptComponentResource(javax.faces.context.FacesContext, * javax.faces.application.Resource) */ @Override public Resource getScriptComponentResource(FacesContext context, Resource componentResource) { throw new UnsupportedOperationException(); } /** * @see javax.faces.view.ViewDeclarationLanguage#buildView(javax.faces.context.FacesContext, * javax.faces.component.UIViewRoot) * @param context * @param view * @throws IOException */ public void buildView(FacesContext context, UIViewRoot view) throws IOException { if (Util.isViewPopulated(context, view)) { return; } try { if (executePageToBuildView(context, view)) { context.getExternalContext().responseFlushBuffer(); if (associate != null) { associate.responseRendered(); } context.responseComplete(); return; } } catch (IOException e) { throw new FacesException(e); } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Completed building view for : \n" + view.getViewId()); } context .getApplication() .publishEvent(context, PostAddToViewEvent.class, UIViewRoot.class, view); Util.setViewPopulated(context, view); } /** * @see javax.faces.view.ViewDeclarationLanguage#renderView(javax.faces.context.FacesContext, * javax.faces.component.UIViewRoot) */ public void renderView(FacesContext context, UIViewRoot view) throws IOException { // suppress rendering if "rendered" property on the component is // false if (!view.isRendered() || context.getResponseComplete()) { return; } ExternalContext extContext = context.getExternalContext(); if (!Util.isViewPopulated(context, view)) { buildView(context, view); } // set up the ResponseWriter RenderKitFactory renderFactory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY); RenderKit renderKit = renderFactory.getRenderKit(context, view.getRenderKitId()); ResponseWriter oldWriter = context.getResponseWriter(); WriteBehindStateWriter stateWriter = new WriteBehindStateWriter( extContext.getResponseOutputWriter(), context, responseBufferSize); ResponseWriter newWriter; if (null != oldWriter) { newWriter = oldWriter.cloneWithWriter(stateWriter); } else { newWriter = renderKit.createResponseWriter( stateWriter, null, extContext.getRequestCharacterEncoding()); } context.setResponseWriter(newWriter); // Don't call startDoc and endDoc on a partial response if (context.getPartialViewContext().isPartialRequest()) { doRenderView(context, view); try { extContext.getFlash().doPostPhaseActions(context); } catch (UnsupportedOperationException uoe) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine( "ExternalContext.getFlash() throw UnsupportedOperationException -> Flash unavailable"); } } } else { // render the view to the response newWriter.startDocument(); doRenderView(context, view); try { extContext.getFlash().doPostPhaseActions(context); } catch (UnsupportedOperationException uoe) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine( "ExternalContext.getFlash() throw UnsupportedOperationException -> Flash unavailable"); } } newWriter.endDocument(); } // replace markers in the body content and write it to response. // flush directly to the response if (stateWriter.stateWritten()) { stateWriter.flushToWriter(); } // clear the ThreadLocal reference. stateWriter.release(); if (null != oldWriter) { context.setResponseWriter(oldWriter); } // write any AFTER_VIEW_CONTENT to the response // side effect: AFTER_VIEW_CONTENT removed ViewHandlerResponseWrapper wrapper = (ViewHandlerResponseWrapper) RequestStateManager.remove(context, RequestStateManager.AFTER_VIEW_CONTENT); if (null != wrapper) { wrapper.flushToWriter( extContext.getResponseOutputWriter(), extContext.getResponseCharacterEncoding()); } extContext.responseFlushBuffer(); } @Override public StateManagementStrategy getStateManagementStrategy(FacesContext context, String viewId) { return null; } // --------------------------------------- Methods from ViewHandlingStrategy /** * This {@link ViewHandlingStrategy} <em>should</em> be the last one queried and as such we return * <code>true</code>. * * @see com.sun.faces.application.view.ViewHandlingStrategy#handlesViewId(String) */ @Override public boolean handlesViewId(String viewId) { return true; } // --------------------------------------------------------- Private Methods /** * Execute the target view. If the HTTP status code range is not 2xx, then return true to indicate * the response should be immediately flushed by the caller so that conditions such as 404 are * properly handled. * * @param context the <code>FacesContext</code> for the current request * @param viewToExecute the view to build * @return <code>true</code> if the response should be immediately flushed to the client, * otherwise <code>false</code> * @throws java.io.IOException if an error occurs executing the page */ private boolean executePageToBuildView(FacesContext context, UIViewRoot viewToExecute) throws IOException { if (null == context) { String message = MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context"); throw new NullPointerException(message); } if (null == viewToExecute) { String message = MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "viewToExecute"); throw new NullPointerException(message); } ExternalContext extContext = context.getExternalContext(); if ("/*".equals(RequestStateManager.get(context, RequestStateManager.INVOCATION_PATH))) { throw new FacesException( MessageUtils.getExceptionMessageString(MessageUtils.FACES_SERVLET_MAPPING_INCORRECT_ID)); } String requestURI = viewToExecute.getViewId(); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("About to execute view " + requestURI); } // update the JSTL locale attribute in request scope so that JSTL // picks up the locale from viewRoot. This attribute must be updated // before the JSTL setBundle tag is called because that is when the // new LocalizationContext object is created based on the locale. if (extContext.getRequest() instanceof ServletRequest) { Config.set( (ServletRequest) extContext.getRequest(), Config.FMT_LOCALE, context.getViewRoot().getLocale()); } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Before dispacthMessage to viewId " + requestURI); } // save the original response Object originalResponse = extContext.getResponse(); // replace the response with our wrapper ViewHandlerResponseWrapper wrapped = getWrapper(extContext); extContext.setResponse(wrapped); try { // build the view by executing the page extContext.dispatch(requestURI); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("After dispacthMessage to viewId " + requestURI); } } finally { // replace the original response extContext.setResponse(originalResponse); } // Follow the JSTL 1.2 spec, section 7.4, // on handling status codes on a forward if (wrapped.getStatus() < 200 || wrapped.getStatus() > 299) { // flush the contents of the wrapper to the response // this is necessary as the user may be using a custom // error page - this content should be propagated wrapped.flushContentToWrappedResponse(); return true; } // Put the AFTER_VIEW_CONTENT into request scope // temporarily RequestStateManager.set(context, RequestStateManager.AFTER_VIEW_CONTENT, wrapped); return false; } /** * This is a separate method to account for handling the content after the view tag. * * <p>Create a new ResponseWriter around this response's Writer. Set it into the FacesContext, * saving the old one aside. * * <p>call encodeBegin(), encodeChildren(), encodeEnd() on the argument <code>UIViewRoot</code>. * * <p>Restore the old ResponseWriter into the FacesContext. * * <p>Write out the after view content to the response's writer. * * <p>Flush the response buffer, and remove the after view content from the request scope. * * @param context the <code>FacesContext</code> for the current request * @param viewToRender the view to render * @throws java.io.IOException if an error occurs rendering the view to the client * @throws javax.faces.FacesException if some error occurs within the framework processing */ private void doRenderView(FacesContext context, UIViewRoot viewToRender) throws IOException { if (null != associate) { associate.responseRendered(); } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "About to render view " + viewToRender.getViewId()); } viewToRender.encodeAll(context); } /** * Simple utility method to wrap the current response with the {@link ViewHandlerResponseWrapper}. * * @param extContext the {@link ExternalContext} for this request * @return the current response wrapped with ViewHandlerResponseWrapper */ private static ViewHandlerResponseWrapper getWrapper(ExternalContext extContext) { Object response = extContext.getResponse(); if (response instanceof HttpServletResponse) { return new ViewHandlerResponseWrapper((HttpServletResponse) response); } throw new IllegalArgumentException(); } }
/** * Break out the things that are associated with the Application, but need to be present even when * the user has replaced the Application instance. * * <p> * * <p>For example: the user replaces ApplicationFactory, and wants to intercept calls to * createValueExpression() and createMethodExpression() for certain kinds of expressions, but allow * the existing application to handle the rest. */ public class ApplicationAssociate { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); private ApplicationImpl app = null; /** * Overall Map containing <code>from-view-id</code> key and <code>Set</code> of <code> * NavigationCase</code> objects for that key; The <code>from-view-id</code> strings in this map * will be stored as specified in the configuration file - some of them will have a trailing * asterisk "*" signifying wild card, and some may be specified as an asterisk "*". */ private Map<String, Set<NavigationCase>> navigationMap = null; // Flag indicating that a response has been rendered. private boolean responseRendered = false; private static final String ASSOCIATE_KEY = RIConstants.FACES_PREFIX + "ApplicationAssociate"; private static ThreadLocal<ApplicationAssociate> instance = new ThreadLocal<ApplicationAssociate>() { protected ApplicationAssociate initialValue() { return (null); } }; private List<ELResolver> elResolversFromFacesConfig = null; @SuppressWarnings("deprecation") private VariableResolver legacyVRChainHead = null; private VariableResolverChainWrapper legacyVRChainHeadWrapperForJsp = null; private VariableResolverChainWrapper legacyVRChainHeadWrapperForFaces = null; @SuppressWarnings("deprecation") private PropertyResolver legacyPRChainHead = null; private ExpressionFactory expressionFactory = null; @SuppressWarnings("deprecation") private PropertyResolver legacyPropertyResolver = null; @SuppressWarnings("deprecation") private VariableResolver legacyVariableResolver = null; private FacesCompositeELResolver facesELResolverForJsp = null; private InjectionProvider injectionProvider; private ResourceCache resourceCache; private String contextName; private boolean requestServiced; private boolean errorPagePresent; private BeanManager beanManager; private GroovyHelper groovyHelper; private AnnotationManager annotationManager; private boolean devModeEnabled; private Compiler compiler; private FaceletFactory faceletFactory; private ResourceManager resourceManager; private ApplicationStateInfo applicationStateInfo; private PropertyEditorHelper propertyEditorHelper; private NamedEventManager namedEventManager; public ApplicationAssociate(ApplicationImpl appImpl) { app = appImpl; propertyEditorHelper = new PropertyEditorHelper(appImpl); FacesContext ctx = FacesContext.getCurrentInstance(); if (ctx == null) { throw new IllegalStateException( MessageUtils.getExceptionMessageString( MessageUtils.APPLICATION_ASSOCIATE_CTOR_WRONG_CALLSTACK_ID)); } ExternalContext externalContext = ctx.getExternalContext(); if (null != externalContext.getApplicationMap().get(ASSOCIATE_KEY)) { throw new IllegalStateException( MessageUtils.getExceptionMessageString(MessageUtils.APPLICATION_ASSOCIATE_EXISTS_ID)); } externalContext.getApplicationMap().put(ASSOCIATE_KEY, this); //noinspection CollectionWithoutInitialCapacity navigationMap = new ConcurrentHashMap<String, Set<NavigationCase>>(); injectionProvider = InjectionProviderFactory.createInstance(externalContext); WebConfiguration webConfig = WebConfiguration.getInstance(externalContext); beanManager = new BeanManager(injectionProvider, webConfig.isOptionEnabled(EnableLazyBeanValidation)); // install the bean manager as a system event listener for custom // scopes being destoryed. app.subscribeToEvent(PreDestroyCustomScopeEvent.class, ScopeContext.class, beanManager); annotationManager = new AnnotationManager(); groovyHelper = GroovyHelper.getCurrentInstance(); devModeEnabled = (appImpl.getProjectStage() == ProjectStage.Development); // initialize Facelets if (!webConfig.isOptionEnabled(DisableFaceletJSFViewHandler)) { compiler = createCompiler(webConfig); faceletFactory = createFaceletFactory(compiler, webConfig); } if (!devModeEnabled) { resourceCache = new ResourceCache(); } resourceManager = new ResourceManager(resourceCache); namedEventManager = new NamedEventManager(); applicationStateInfo = new ApplicationStateInfo(); } public static ApplicationAssociate getInstance(ExternalContext externalContext) { if (externalContext == null) { return null; } Map applicationMap = externalContext.getApplicationMap(); return ((ApplicationAssociate) applicationMap.get(ASSOCIATE_KEY)); } public static ApplicationAssociate getInstance(ServletContext context) { if (context == null) { return null; } return (ApplicationAssociate) context.getAttribute(ASSOCIATE_KEY); } public static void setCurrentInstance(ApplicationAssociate associate) { if (associate == null) { instance.remove(); } else { instance.set(associate); } } public static ApplicationAssociate getCurrentInstance() { ApplicationAssociate associate = instance.get(); if (associate == null) { // Fallback to ExternalContext lookup FacesContext fc = FacesContext.getCurrentInstance(); if (fc != null) { ExternalContext extContext = fc.getExternalContext(); if (extContext != null) { return ApplicationAssociate.getInstance(extContext); } } } return associate; } public ApplicationStateInfo getApplicationStateInfo() { return applicationStateInfo; } public ResourceManager getResourceManager() { return resourceManager; } public void setResourceManager(ResourceManager resourceManager) { this.resourceManager = resourceManager; } public ResourceCache getResourceCache() { return resourceCache; } public AnnotationManager getAnnotationManager() { return annotationManager; } public Compiler getCompiler() { return compiler; } public boolean isErrorPagePresent() { return errorPagePresent; } public void setErrorPagePresent(boolean errorPagePresent) { this.errorPagePresent = errorPagePresent; } public FaceletFactory getFaceletFactory() { return faceletFactory; } public static void clearInstance(ExternalContext externalContext) { Map applicationMap = externalContext.getApplicationMap(); ApplicationAssociate me = (ApplicationAssociate) applicationMap.get(ASSOCIATE_KEY); if (null != me) { if (null != me.resourceBundles) { me.resourceBundles.clear(); } } applicationMap.remove(ASSOCIATE_KEY); } public static void clearInstance(ServletContext sc) { ApplicationAssociate me = (ApplicationAssociate) sc.getAttribute(ASSOCIATE_KEY); if (null != me) { if (null != me.resourceBundles) { me.resourceBundles.clear(); } } sc.removeAttribute(ASSOCIATE_KEY); } public BeanManager getBeanManager() { return beanManager; } public GroovyHelper getGroovyHelper() { return groovyHelper; } public void initializeELResolverChains() { // 1. initialize the chains with default values if (null == app.compositeELResolver) { app.compositeELResolver = new DemuxCompositeELResolver(FacesCompositeELResolver.ELResolverChainType.Faces); ELUtils.buildFacesResolver(app.compositeELResolver, this); ELResolverInitPhaseListener.populateFacesELResolverForJsp(app, this); } } public void installProgrammaticallyAddedResolvers() { // Ensure custom resolvers are inserted at the correct place. VariableResolver vr = this.getLegacyVariableResolver(); if (null != vr) { assert (null != this.getLegacyVRChainHeadWrapperForJsp()); this.getLegacyVRChainHeadWrapperForJsp().setWrapped(vr); assert (null != this.getLegacyVRChainHeadWrapperForFaces()); this.getLegacyVRChainHeadWrapperForFaces().setWrapped(vr); } } public boolean isDevModeEnabled() { return devModeEnabled; } /** * Obtain the PropertyEditorHelper instance for this app. * * @return The PropertyEditorHeler instance for this app. */ public PropertyEditorHelper getPropertyEditorHelper() { return propertyEditorHelper; } /** * This method is called by <code>ConfigureListener</code> and will contain any <code> * VariableResolvers</code> defined within faces-config configuration files. * * @param resolver VariableResolver */ @SuppressWarnings("deprecation") public void setLegacyVRChainHead(VariableResolver resolver) { this.legacyVRChainHead = resolver; } @SuppressWarnings("deprecation") public VariableResolver getLegacyVRChainHead() { return legacyVRChainHead; } public VariableResolverChainWrapper getLegacyVRChainHeadWrapperForJsp() { return legacyVRChainHeadWrapperForJsp; } public void setLegacyVRChainHeadWrapperForJsp( VariableResolverChainWrapper legacyVRChainHeadWrapper) { this.legacyVRChainHeadWrapperForJsp = legacyVRChainHeadWrapper; } public VariableResolverChainWrapper getLegacyVRChainHeadWrapperForFaces() { return legacyVRChainHeadWrapperForFaces; } public void setLegacyVRChainHeadWrapperForFaces( VariableResolverChainWrapper legacyVRChainHeadWrapperForFaces) { this.legacyVRChainHeadWrapperForFaces = legacyVRChainHeadWrapperForFaces; } /** * This method is called by <code>ConfigureListener</code> and will contain any <code> * PropertyResolvers</code> defined within faces-config configuration files. * * @param resolver PropertyResolver */ @SuppressWarnings("deprecation") public void setLegacyPRChainHead(PropertyResolver resolver) { this.legacyPRChainHead = resolver; } @SuppressWarnings("deprecation") public PropertyResolver getLegacyPRChainHead() { return legacyPRChainHead; } public FacesCompositeELResolver getFacesELResolverForJsp() { return facesELResolverForJsp; } public void setFacesELResolverForJsp(FacesCompositeELResolver celr) { facesELResolverForJsp = celr; } public void setELResolversFromFacesConfig(List<ELResolver> resolvers) { this.elResolversFromFacesConfig = resolvers; } public List<ELResolver> getELResolversFromFacesConfig() { return elResolversFromFacesConfig; } public void setExpressionFactory(ExpressionFactory expressionFactory) { this.expressionFactory = expressionFactory; } public ExpressionFactory getExpressionFactory() { return this.expressionFactory; } public CompositeELResolver getApplicationELResolvers() { return app.getApplicationELResolvers(); } public InjectionProvider getInjectionProvider() { return injectionProvider; } public void setContextName(String contextName) { this.contextName = contextName; } public String getContextName() { return contextName; } /** * Maintains the PropertyResolver called through Application.setPropertyResolver() * * @param resolver PropertyResolver */ @SuppressWarnings("deprecation") public void setLegacyPropertyResolver(PropertyResolver resolver) { this.legacyPropertyResolver = resolver; } /** @return the PropertyResolver called through Application.getPropertyResolver() */ @SuppressWarnings("deprecation") public PropertyResolver getLegacyPropertyResolver() { return legacyPropertyResolver; } /** * Maintains the PropertyResolver called through Application.setVariableResolver() * * @param resolver VariableResolver */ @SuppressWarnings("deprecation") public void setLegacyVariableResolver(VariableResolver resolver) { this.legacyVariableResolver = resolver; } /** @return the VariableResolver called through Application.getVariableResolver() */ @SuppressWarnings("deprecation") public VariableResolver getLegacyVariableResolver() { return legacyVariableResolver; } /** * Called by application code to indicate we've processed the first request to the application. */ public void setRequestServiced() { this.requestServiced = true; } /** @return <code>true</code> if we've processed a request, otherwise <code>false</code> */ public boolean hasRequestBeenServiced() { return requestServiced; } /** * Add a navigation case to the internal case set. If a case set does not already exist in the * case list map containing this case (identified by <code>from-view-id</code>), start a new list, * add the case to it, and store the set in the case set map. If a case set already exists, * overwrite the previous case. * * @param navigationCase the navigation case containing navigation mapping information from the * configuration file. */ public void addNavigationCase(NavigationCase navigationCase) { String fromViewId = navigationCase.getFromViewId(); Set<NavigationCase> caseSet = navigationMap.get(fromViewId); if (caseSet == null) { //noinspection CollectionWithoutInitialCapacity caseSet = new LinkedHashSet<NavigationCase>(); caseSet.add(navigationCase); navigationMap.put(fromViewId, caseSet); } else { // if there already is a case existing for the // fromviewid/fromaction.fromoutcome combination, // replace it ... (last one wins). caseSet.add(navigationCase); } } public NamedEventManager getNamedEventManager() { return namedEventManager; } /** * Return a <code>Map</code> of navigation mappings loaded from the configuration system. The key * for the returned <code>Map</code> is <code>from-view-id</code>, and the value is a <code>List * </code> of navigation cases. * * @return Map the map of navigation mappings. */ public Map<String, Set<NavigationCase>> getNavigationCaseListMappings() { if (navigationMap == null) { return Collections.emptyMap(); } return navigationMap; } public ResourceBundle getResourceBundle(FacesContext context, String var) { ApplicationResourceBundle bundle = resourceBundles.get(var); if (bundle == null) { return null; } UIViewRoot root; // Start out with the default locale Locale locale; Locale defaultLocale = Locale.getDefault(); locale = defaultLocale; // See if this FacesContext has a ViewRoot if (null != (root = context.getViewRoot())) { // If so, ask it for its Locale if (null == (locale = root.getLocale())) { // If the ViewRoot has no Locale, fall back to the default. locale = defaultLocale; } } assert (null != locale); // ResourceBundleBean bean = resourceBundles.get(var); return bundle.getResourceBundle(locale); } /** * keys: <var> element from faces-config * * <p> * * <p>values: ResourceBundleBean instances. */ @SuppressWarnings({"CollectionWithoutInitialCapacity"}) Map<String, ApplicationResourceBundle> resourceBundles = new HashMap<String, ApplicationResourceBundle>(); public void addResourceBundle(String var, ApplicationResourceBundle bundle) { resourceBundles.put(var, bundle); } public Map<String, ApplicationResourceBundle> getResourceBundles() { return resourceBundles; } // This is called by ViewHandlerImpl.renderView(). public void responseRendered() { responseRendered = true; } public boolean isResponseRendered() { return responseRendered; } protected FaceletFactory createFaceletFactory(Compiler c, WebConfiguration webConfig) { // refresh period String refreshPeriod = webConfig.getOptionValue(FaceletsDefaultRefreshPeriod); long period = Long.parseLong(refreshPeriod); // resource resolver ResourceResolver resolver = new DefaultResourceResolver(); String resolverName = webConfig.getOptionValue(FaceletsResourceResolver); if (resolverName != null && resolverName.length() > 0) { resolver = (ResourceResolver) ReflectionUtil.decorateInstance(resolverName, ResourceResolver.class, resolver); } FaceletCache cache = null; String faceletCacheName = webConfig.getOptionValue(FaceletCache); if (faceletCacheName != null && faceletCacheName.length() > 0) { try { com.sun.faces.facelets.FaceletCache privateApiCache = (com.sun.faces.facelets.FaceletCache) ReflectionUtil.forName(faceletCacheName).newInstance(); cache = new PrivateApiFaceletCacheAdapter(privateApiCache); } catch (Exception e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Error Loading Facelet cache: " + faceletCacheName, e); } } } if (null == cache) { FaceletCacheFactory cacheFactory = (FaceletCacheFactory) FactoryFinder.getFactory(FactoryFinder.FACELET_CACHE_FACTORY); cache = cacheFactory.getFaceletCache(); } // Resource.getResourceUrl(ctx,"/") FaceletFactory factory = new DefaultFaceletFactory(c, resolver, period, cache); // Check to see if a custom Factory has been defined String factoryClass = webConfig.getOptionValue(FaceletFactory); if (factoryClass != null && factoryClass.length() > 0) { factory = (FaceletFactory) ReflectionUtil.decorateInstance(factoryClass, FaceletFactory.class, factory); } return factory; } protected Compiler createCompiler(WebConfiguration webConfig) { Compiler c = new SAXCompiler(); // load decorators String decParam = webConfig.getOptionValue(FaceletsDecorators); if (decParam != null) { decParam = decParam.trim(); String[] decs = Util.split(decParam, ";"); TagDecorator decObj; for (String decorator : decs) { try { decObj = (TagDecorator) ReflectionUtil.forName(decorator).newInstance(); c.addTagDecorator(decObj); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Successfully Loaded Decorator: {0}", decorator); } } catch (Exception e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Error Loading Decorator: " + decorator, e); } } } } // skip params? c.setTrimmingComments( webConfig.isOptionEnabled(BooleanWebContextInitParameter.FaceletsSkipComments)); c.addTagLibrary(new CoreLibrary()); c.addTagLibrary(new HtmlLibrary()); c.addTagLibrary(new UILibrary()); c.addTagLibrary(new JstlCoreLibrary()); c.addTagLibrary( new FunctionLibrary(JstlFunction.class, "http://java.sun.com/jsp/jstl/functions")); if (isDevModeEnabled()) { c.addTagLibrary( new FunctionLibrary(DevTools.class, "http://java.sun.com/mojarra/private/functions")); } c.addTagLibrary(new CompositeLibrary()); c.addTagLibrary(new XmlLibrary()); return c; } }
/** A factory for creating <code>SerializationProvider</code> instances. */ public class SerializationProviderFactory { /** Our default <code>SerializationProvider</code>. */ private static final SerializationProvider JAVA_PROVIDER = new SerializationProviderFactory.JavaSerializationProvider(); /** * The system property that will be checked for alternate <code>SerializationProvider</code> * implementations. */ private static final String SERIALIZATION_PROVIDER_PROPERTY = RIConstants.FACES_PREFIX + "SerializationProvider"; private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); /** * Creates a new instance of the class specified by the <code>com.sun.faces.InjectionProvider * </code> system property. If this propery is not defined, then a default, no-op, <code> * InjectionProvider</code> will be returned. * * @param extContext the ExternalContext for this application * @return an implementation of the <code>InjectionProvider</code> interfaces */ public static SerializationProvider createInstance(ExternalContext extContext) { String providerClass = findProviderClass(extContext); SerializationProvider provider = getProviderInstance(providerClass); if (provider.getClass() != JavaSerializationProvider.class) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log( Level.FINE, "jsf.spi.serialization.provider_configured", new Object[] {provider.getClass().getName()}); } } return provider; } private static SerializationProvider getProviderInstance(String className) { SerializationProvider provider = JAVA_PROVIDER; if (className != null) { try { Class<?> clazz = Util.loadClass(className, SerializationProviderFactory.class); if (implementsSerializationProvider(clazz)) { provider = (SerializationProvider) clazz.newInstance(); } else { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log( Level.SEVERE, "jsf.spi.serialization.provider_not_implemented", new Object[] {className}); } } } catch (ClassNotFoundException cnfe) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log( Level.SEVERE, "jsf.spi.serialization.provider_not_found", new Object[] {className}); } } catch (InstantiationException | IllegalAccessException ie) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log( Level.SEVERE, "jsf.spi.serialization.provider_cannot_instantiate", new Object[] {className}); LOGGER.log(Level.SEVERE, "", ie); } } } return provider; } /** * Determine if the specified class implements the <code>SerializationProvider</code> interfaces. * * @param clazz the class in question * @return <code>true</code> if <code>clazz</code> implements the <code>SerializationProvider * </code> interface */ private static boolean implementsSerializationProvider(Class<?> clazz) { return SerializationProvider.class.isAssignableFrom(clazz); } /** * Tries to find a provider class in a web context parameter. If not present it tries to find it * as a System property. If still not found returns null. * * @param extContext The ExternalContext for this request * @return The provider class name specified in the container configuration, or <code>null</code> * if not found. */ private static String findProviderClass(ExternalContext extContext) { WebConfiguration webConfig = WebConfiguration.getInstance(extContext); String provider = webConfig.getOptionValue(WebContextInitParameter.SerializationProviderClass); if (provider != null) { return provider; } else { return System.getProperty(SERIALIZATION_PROVIDER_PROPERTY); } } /** * An implementation of <code>SerializationProvider</code> which uses standard Java serialization. */ private static final class JavaSerializationProvider implements SerializationProvider { /** * Creates a new <code>ObjectOutputStream</code> wrapping the specified <code>destination</code> * . * * @param destination the destination of the serialized Object(s) * @return an <code>ObjectOutputStream</code> */ @Override public ObjectOutputStream createObjectOutputStream(OutputStream destination) throws IOException { return new ObjectOutputStream(destination); } /** * Creates a new <code>ObjectInputStream</code> wrapping the specified <code>source</code>. * * @param source the source stream from which to read the Object(s) from * @return an <code>ObjectInputStream</code> */ @Override public ObjectInputStream createObjectInputStream(InputStream source) throws IOException { return new ApplicationObjectInputStream(source); } } } // END InjectionProviderFactory
/** This {@link ViewHandlingStrategy} handles Facelets/PDL-based views. */ public class FaceletViewHandlingStrategy extends ViewHandlingStrategy { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); // FaceletFactory singleton for this application private FaceletFactory faceletFactory; // Array of viewId extensions that should be handled by Facelets private String[] extensionsArray; // Array of viewId prefixes that should be handled by Facelets private String[] prefixesArray; public static final String IS_BUILDING_METADATA = FaceletViewHandlingStrategy.class.getName() + ".IS_BUILDING_METADATA"; private StateManagementStrategyImpl stateManagementStrategy; private boolean partialStateSaving; private Set<String> fullStateViewIds; private boolean groovyAvailable; // ------------------------------------------------------------ Constructors public FaceletViewHandlingStrategy() { initialize(); } // ------------------------------------ Methods from ViewDeclarationLanguage @Override public StateManagementStrategy getStateManagementStrategy(FacesContext context, String viewId) { // 'null' return here means we're defaulting to the 1.2 style state saving. return (context.getAttributes().containsKey("partialStateSaving") ? stateManagementStrategy : null); } @Override public BeanInfo getComponentMetadata(FacesContext context, Resource ccResource) { // PENDING this implementation is terribly wasteful. // Must find a better way. CompositeComponentBeanInfo result; FaceletContext ctx = (FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY); FaceletFactory factory = (FaceletFactory) RequestStateManager.get(context, RequestStateManager.FACELET_FACTORY); VariableMapper orig = ctx.getVariableMapper(); UIComponent tmp = context.getApplication().createComponent("javax.faces.NamingContainer"); UIPanel facetComponent = (UIPanel) context.getApplication().createComponent("javax.faces.Panel"); facetComponent.setRendererType("javax.faces.Group"); tmp.getFacets().put(UIComponent.COMPOSITE_FACET_NAME, facetComponent); // We have to put the resource in here just so the classes that eventually // get called by facelets have access to it. tmp.getAttributes().put(Resource.COMPONENT_RESOURCE_KEY, ccResource); Facelet f; try { f = factory.getFacelet(ccResource.getURL()); VariableMapper wrapper = new VariableMapperWrapper(orig) { @Override public ValueExpression resolveVariable(String variable) { return super.resolveVariable(variable); } }; ctx.setVariableMapper(wrapper); context.getAttributes().put(IS_BUILDING_METADATA, Boolean.TRUE); f.apply(context, facetComponent); } catch (Exception e) { if (e instanceof FacesException) { throw (FacesException) e; } else { throw new FacesException(e); } } finally { context.getAttributes().remove(IS_BUILDING_METADATA); ctx.setVariableMapper(orig); } result = (CompositeComponentBeanInfo) tmp.getAttributes().get(UIComponent.BEANINFO_KEY); return result; } @Override public ViewMetadata getViewMetadata(FacesContext context, String viewId) { return new ViewMetadataImpl(viewId); } /** * @see * javax.faces.view.ViewDeclarationLanguage#getScriptComponentResource(javax.faces.context.FacesContext, * javax.faces.application.Resource) */ public Resource getScriptComponentResource(FacesContext context, Resource componentResource) { if (!groovyAvailable) { return null; } Resource result = null; String resourceName = componentResource.getResourceName(); if (resourceName.endsWith(".xhtml")) { resourceName = resourceName.substring(0, resourceName.length() - 6) + ".groovy"; ResourceHandler resourceHandler = context.getApplication().getResourceHandler(); result = resourceHandler.createResource(resourceName, componentResource.getLibraryName()); } return result; } /** * @see javax.faces.view.ViewDeclarationLanguage#renderView(javax.faces.context.FacesContext, * javax.faces.component.UIViewRoot) */ public void renderView(FacesContext ctx, UIViewRoot viewToRender) throws IOException { // suppress rendering if "rendered" property on the component is // false if (!viewToRender.isRendered()) { return; } // log request if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Rendering View: " + viewToRender.getViewId()); } WriteBehindStateWriter stateWriter = null; try { // Only build the view if this view has not yet been built. if (!Util.isViewPopulated(ctx, viewToRender)) { this.buildView(ctx, viewToRender); } // setup writer and assign it to the ctx ResponseWriter origWriter = ctx.getResponseWriter(); if (origWriter == null) { origWriter = createResponseWriter(ctx); } stateWriter = new WriteBehindStateWriter(origWriter, ctx, responseBufferSize); ResponseWriter writer = origWriter.cloneWithWriter(stateWriter); ctx.setResponseWriter(writer); // render the view to the response writer.startDocument(); viewToRender.encodeAll(ctx); writer.endDocument(); // finish writing writer.close(); boolean writtenState = stateWriter.stateWritten(); // flush to origWriter if (writtenState) { stateWriter.flushToWriter(); } } catch (FileNotFoundException fnfe) { this.handleFaceletNotFound(ctx, viewToRender.getViewId(), fnfe.getMessage()); } catch (Exception e) { this.handleRenderException(ctx, e); } finally { if (stateWriter != null) stateWriter.release(); } } /** * If {@link UIDebug#debugRequest(javax.faces.context.FacesContext)}} is <code>true</code>, simply * return a new UIViewRoot(), otherwise, call the default logic. * * @see {@link * javax.faces.view.ViewDeclarationLanguage#restoreView(javax.faces.context.FacesContext, * String)} */ @Override public UIViewRoot restoreView(FacesContext ctx, String viewId) { updateStateSavingType(ctx, viewId); if (UIDebug.debugRequest(ctx)) { ctx.getApplication().createComponent(UIViewRoot.COMPONENT_TYPE); } return super.restoreView(ctx, viewId); } /** * @see javax.faces.view.ViewDeclarationLanguage#createView(javax.faces.context.FacesContext, * String) * @return */ @Override public UIViewRoot createView(FacesContext ctx, String viewId) { if (UIDebug.debugRequest(ctx)) { UIViewRoot root = (UIViewRoot) ctx.getApplication().createComponent(UIViewRoot.COMPONENT_TYPE); root.setViewId(viewId); return root; } return super.createView(ctx, viewId); } // --------------------------------------- Methods from ViewHandlingStrategy /** * @param viewId the view ID to check * @return <code>true</code> if assuming a default configuration and the view ID's extension is * <code>.xhtml</code> Otherwise try to match the view ID based on the configured extendsion * and prefixes. * @see com.sun.faces.config.WebConfiguration.WebContextInitParameter#FaceletsViewMappings */ @Override public boolean handlesViewId(String viewId) { if (viewId != null) { // If there's no extensions array or prefixes array, then // assume defaults. .xhtml extension is handled by // the FaceletViewHandler and .jsp will be handled by // the JSP view handler if ((extensionsArray == null) && (prefixesArray == null)) { return (viewId.endsWith(ViewHandler.DEFAULT_FACELETS_SUFFIX)); } if (extensionsArray != null) { for (int i = 0; i < extensionsArray.length; i++) { String extension = extensionsArray[i]; if (viewId.endsWith(extension)) { return true; } } } if (prefixesArray != null) { for (int i = 0; i < prefixesArray.length; i++) { String prefix = prefixesArray[i]; if (viewId.startsWith(prefix)) { return true; } } } } return false; } /** * Build the view. * * @param ctx the {@link FacesContext} for the current request * @param view the {@link UIViewRoot} to populate based of the Facelet template * @throws IOException if an error occurs building the view. */ @Override public void buildView(FacesContext ctx, UIViewRoot view) throws IOException { if (Util.isViewPopulated(ctx, view)) { return; } updateStateSavingType(ctx, view.getViewId()); view.setViewId(view.getViewId()); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Building View: " + view.getViewId()); } if (faceletFactory == null) { ApplicationAssociate associate = ApplicationAssociate.getInstance(ctx.getExternalContext()); faceletFactory = associate.getFaceletFactory(); assert (faceletFactory != null); } RequestStateManager.set(ctx, RequestStateManager.FACELET_FACTORY, faceletFactory); Facelet f = faceletFactory.getFacelet(view.getViewId()); // populate UIViewRoot f.apply(ctx, view); doPostBuildActions(view); Util.setViewPopulated(ctx, view); } // ------------------------------------------------------- Protected Methods /** Initialize the core Facelets runtime. */ protected void initialize() { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Initializing FaceletViewHandlingStrategy"); } this.initializeMappings(); WebConfiguration config = WebConfiguration.getInstance(); partialStateSaving = config.isOptionEnabled(PartialStateSaving); if (partialStateSaving) { String[] viewIds = config.getOptionValue(FullStateSavingViewIds, ","); fullStateViewIds = new HashSet<String>(viewIds.length, 1.0f); fullStateViewIds.addAll(Arrays.asList(viewIds)); this.stateManagementStrategy = new StateManagementStrategyImpl(this); } groovyAvailable = GroovyHelper.isGroovyAvailable(FacesContext.getCurrentInstance()); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Initialization Successful"); } } /** Initialize mappings, during the first request. */ protected void initializeMappings() { String viewMappings = webConfig.getOptionValue(WebContextInitParameter.FaceletsViewMappings); if ((viewMappings != null) && (viewMappings.length() > 0)) { String[] mappingsArray = Util.split(viewMappings, ";"); List<String> extensionsList = new ArrayList<String>(mappingsArray.length); List<String> prefixesList = new ArrayList<String>(mappingsArray.length); for (int i = 0; i < mappingsArray.length; i++) { String mapping = mappingsArray[i].trim(); int mappingLength = mapping.length(); if (mappingLength <= 1) { continue; } if (mapping.charAt(0) == '*') { extensionsList.add(mapping.substring(1)); } else if (mapping.charAt(mappingLength - 1) == '*') { prefixesList.add(mapping.substring(0, mappingLength - 1)); } } extensionsArray = new String[extensionsList.size()]; extensionsList.toArray(extensionsArray); prefixesArray = new String[prefixesList.size()]; prefixesList.toArray(prefixesArray); } } /** @return a new Compiler for Facelet processing. */ protected Compiler createCompiler() { return new SAXCompiler(); } /** * @param context the {@link FacesContext} for the current request * @return a {@link ResponseWriter} for processing the request * @throws IOException if the writer cannot be created */ protected ResponseWriter createResponseWriter(FacesContext context) throws IOException { ExternalContext extContext = context.getExternalContext(); RenderKit renderKit = context.getRenderKit(); // Avoid a cryptic NullPointerException when the renderkit ID // is incorrectly set if (renderKit == null) { String id = context.getViewRoot().getRenderKitId(); throw new IllegalStateException("No render kit was available for id \"" + id + "\""); } if (responseBufferSizeSet) { // set the buffer for content extContext.setResponseBufferSize(responseBufferSize); } // get our content type String contentType = (String) extContext.getRequestMap().get("facelets.ContentType"); // get the encoding String encoding = (String) extContext.getRequestMap().get("facelets.Encoding"); // Create a dummy ResponseWriter with a bogus writer, // so we can figure out what content type the ReponseWriter // is really going to ask for ResponseWriter writer = renderKit.createResponseWriter(NullWriter.Instance, contentType, encoding); contentType = getResponseContentType(context, writer.getContentType()); encoding = getResponseEncoding(context, writer.getCharacterEncoding()); // apply them to the response extContext.setResponseContentType(contentType); extContext.setResponseCharacterEncoding(encoding); // Now, clone with the real writer writer = writer.cloneWithWriter(extContext.getResponseOutputWriter()); return writer; } /** * Handles the case where rendering throws an Exception. * * @param context the {@link FacesContext} for the current request * @param e the caught Exception * @throws IOException if the custom debug content cannot be written */ protected void handleRenderException(FacesContext context, Exception e) throws IOException { // always log if (LOGGER.isLoggable(Level.SEVERE)) { UIViewRoot root = context.getViewRoot(); StringBuffer sb = new StringBuffer(64); sb.append("Error Rendering View"); if (root != null) { sb.append('['); sb.append(root.getViewId()); sb.append(']'); } LOGGER.log(Level.SEVERE, sb.toString(), e); } if (e instanceof RuntimeException) { throw (RuntimeException) e; } else if (e instanceof IOException) { throw (IOException) e; } else { throw new FacesException(e.getMessage(), e); } } /** * Handles the case where a Facelet cannot be found. * * @param context the {@link FacesContext} for the current request * @param viewId the view ID that was to be mapped to a Facelet * @param message optional message to include in the 404 * @throws IOException if an error occurs sending the 404 to the client */ protected void handleFaceletNotFound(FacesContext context, String viewId, String message) throws IOException { context .getExternalContext() .responseSendError( HttpServletResponse.SC_NOT_FOUND, ((message != null) ? (viewId + ": " + message) : viewId)); context.responseComplete(); } /** * @param context the {@link FacesContext} for the current request * @param orig the original encoding * @return the encoding to be used for this response */ protected String getResponseEncoding(FacesContext context, String orig) { String encoding = orig; // see if we need to override the encoding Map<Object, Object> ctxAttributes = context.getAttributes(); Map<String, Object> sessionMap = context.getExternalContext().getSessionMap(); // 1. check the request attribute if (ctxAttributes.containsKey("facelets.Encoding")) { encoding = (String) ctxAttributes.get("facelets.Encoding"); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Facelet specified alternate encoding {0}", encoding); } sessionMap.put(ViewHandler.CHARACTER_ENCODING_KEY, encoding); } // 2. get it from request if (encoding == null) { encoding = context.getExternalContext().getRequestCharacterEncoding(); } // 3. get it from the session if (encoding == null) { encoding = (String) sessionMap.get(ViewHandler.CHARACTER_ENCODING_KEY); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Session specified alternate encoding {0}", encoding); } } // 4. default it if (encoding == null) { encoding = "UTF-8"; if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("ResponseWriter created had a null CharacterEncoding, defaulting to UTF-8"); } } return encoding; } /** * @param context the {@link FacesContext} for the current request * @param orig the original contentType * @return the content type to be used for this response */ protected String getResponseContentType(FacesContext context, String orig) { String contentType = orig; // see if we need to override the contentType Map<Object, Object> m = context.getAttributes(); if (m.containsKey("facelets.ContentType")) { contentType = (String) m.get("facelets.ContentType"); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Facelet specified alternate contentType '" + contentType + "'"); } } // safety check if (contentType == null) { contentType = "text/html"; if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("ResponseWriter created had a null ContentType, defaulting to text/html"); } } return contentType; } private void updateStateSavingType(FacesContext ctx, String viewId) { if (!ctx.getAttributes().containsKey("partialStateSaving")) { ctx.getAttributes().put("partialStateSaving", usePartialSaving(viewId)); } } private boolean usePartialSaving(String viewId) { return (partialStateSaving && !fullStateViewIds.contains(viewId)); } private void doPostBuildActions(UIViewRoot root) { if (usePartialSaving(root.getViewId())) { stateManagementStrategy.notifyTrackChanges(root); } } // ---------------------------------------------------------- Nested Classes /** Simple no-op writer. */ protected static final class NullWriter extends Writer { static final NullWriter Instance = new NullWriter(); public void write(char[] buffer) {} public void write(char[] buffer, int off, int len) {} public void write(String str) {} public void write(int c) {} public void write(String str, int off, int len) {} public void close() {} public void flush() {} } // END NullWriter }
public class GroovyScriptManager implements ScriptManager { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); private static final String SCRIPT_PATH = "/WEB-INF/groovy/"; private static final String SUFFIX = ".groovy"; private ServletContext servletContext; // Borrowed from AnnotationScanner. Perhaps this should be made public somewhere public static final Set<String> FACES_ANNOTATIONS; static { HashSet<String> annotations = new HashSet<>(15, 1.0f); Collections.addAll( annotations, "javax.faces.component.FacesComponent", "javax.faces.component.*", "javax.faces.convert.FacesConverter", "javax.faces.convert.*", "javax.faces.validator.FacesValidator", "javax.faces.validator.*", "javax.faces.render.FacesRenderer", "javax.faces.render.*", "javax.faces.bean.ManagedBean", "javax.faces.bean.*", "javax.faces.event.NamedEvent", "javax.faces.event.*", "javax.faces.component.behavior.FacesBehavior", "javax.faces.component.behavior.*", "javax.faces.render.FacesBehaviorRenderer"); FACES_ANNOTATIONS = Collections.unmodifiableSet(annotations); } public GroovyScriptManager(ServletContext servletContext) { this.servletContext = servletContext; } public Set<String> getScripts() { Set<String> scripts = new HashSet<>(); processWebInfGroovy(servletContext, servletContext.getResourcePaths(SCRIPT_PATH), scripts); return scripts; } private void processWebInfGroovy(ServletContext sc, Set<String> paths, Set<String> classList) { if (paths != null && !paths.isEmpty()) { for (String pathElement : paths) { if (pathElement.endsWith("/")) { processWebInfGroovy(sc, sc.getResourcePaths(pathElement), classList); } else { if (pathElement.endsWith(SUFFIX)) { String cname = convertToClassName(SCRIPT_PATH, pathElement); if (containsAnnotation(sc, pathElement)) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "[WEB-INF/groovy] Found annotated Class: {0}", cname); } classList.add(cname); } } } } } } private boolean containsAnnotation(ServletContext sc, String pathElement) { boolean containsAnnotation = false; URL url; try { url = sc.getResource(pathElement); } catch (MalformedURLException ex) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, null, ex); } return false; } try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), RIConstants.CHAR_ENCODING))) { String line = in.readLine(); while ((line != null) && (!containsAnnotation)) { line = line.trim(); if (line.length() != 0) { for (String pattern : FACES_ANNOTATIONS) { if (line.contains(pattern)) { containsAnnotation = true; break; } } } line = in.readLine(); } } catch (Exception ioe) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, null, ioe); } } return containsAnnotation; } /** * Utility method for converting paths to fully qualified class names. * * @param prefix the prefix that should be stripped from the class name before converting it * @param pathEntry a path to a class file * @return a fully qualified class name using dot notation */ private String convertToClassName(String prefix, String pathEntry) { String className = pathEntry; if (prefix != null) { // remove the prefix className = className.substring(prefix.length()); } // remove the .class suffix className = className.substring(0, (className.length() - 7)); return className.replace('/', '.'); } }
/** * This <code>StateHelper</code> provides the functionality associated with client-side state * saving. */ public class ClientSideStateHelper extends StateHelper { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); /** * Enabled encryption of view state. Encryption is disabled by default. * * @see {@link * com.sun.faces.config.WebConfiguration.WebEnvironmentEntry#ClientStateSavingPassword} */ private ByteArrayGuard guard; /** * Flag indicating whether or not client view state will be manipulated for and checked against a * configured timeout value. * * <p>This flag is configured via the <code>WebContextInitParameter.ClientStateTimeout</code> * configuration option of <code>WebConfiguration</code> and is disabled by default. * * @see {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateTimeout} */ private boolean stateTimeoutEnabled; /** * If <code>stateTimeoutEnabled</code> is <code>true</code> this value will represent the time in * seconds that a particular client view state is valid for. * * @see {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateTimeout} */ private long stateTimeout; /** * Client state is generally large, so this allows some tuning to control the buffer that's used * to write the client state. * * <p>The value specified must be divisable by two as the buffer is split between character and * bytes (due to how client state is written). By default, the buffer size is 8192 (per request). * * @see {@link * com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateWriteBufferSize} */ private int csBuffSize; // ------------------------------------------------------------ Constructors /** Construct a new <code>ClientSideStateHelper</code> instance. */ public ClientSideStateHelper() { init(); } // ------------------------------------------------ Methods from StateHelper /** * Writes the view state as a String generated by Base64 encoding the Java Serialziation * representation of the provided <code>state</code> * * <p>If <code>stateCapture</code> is <code>null</code>, the Base64 encoded state will be written * to the client as a hidden field using the <code>ResponseWriter</code> from the provided <code> * FacesContext</code>. * * <p>If <code>stateCapture</code> is not <code>null</code>, the Base64 encoded state will be * appended to the provided <code>StringBuilder</code> without any markup included or any content * written to the client. * * @see {@link com.sun.faces.renderkit.StateHelper#writeState(javax.faces.context.FacesContext, * Object, StringBuilder)} */ public void writeState(FacesContext ctx, Object state, StringBuilder stateCapture) throws IOException { if (stateCapture != null) { doWriteState(state, new StringBuilderWriter(stateCapture)); } else { ResponseWriter writer = ctx.getResponseWriter(); writer.write(stateFieldStart); String viewStateId = Util.getViewStateId(ctx); writer.write(viewStateId); writer.write(fieldMiddle); doWriteState(state, writer); writer.write(fieldEnd); writeWindowIdField(ctx, writer); writeRenderKitIdField(ctx, writer); } } /** * Inspects the incoming request parameters for the standardized state parameter name. In this * case, the parameter value will be a Base64 encoded string previously encoded by {@link * com.sun.faces.renderkit.ServerSideStateHelper#writeState(javax.faces.context.FacesContext, * Object, StringBuilder)}. * * <p>The string will be Base64-decoded and the state reconstructed using standard Java * serialization. * * @see {@link com.sun.faces.renderkit.StateHelper#getState(javax.faces.context.FacesContext, * String)} */ public Object getState(FacesContext ctx, String viewId) throws IOException { String stateString = getStateParamValue(ctx); if (stateString == null) { return null; } return doGetState(stateString); } // ------------------------------------------------------- Protected Methods /** * Rebuilds the view state from the Base64 included String included with the request. * * @param stateString the Base64 encoded view state * @return the view state reconstructed from <code>stateString</code> */ protected Object doGetState(String stateString) { ObjectInputStream ois = null; InputStream bis = new Base64InputStream(stateString); try { if (guard != null) { byte[] bytes = stateString.getBytes(RIConstants.CHAR_ENCODING); int numRead = bis.read(bytes, 0, bytes.length); byte[] decodedBytes = new byte[numRead]; bis.reset(); bis.read(decodedBytes, 0, decodedBytes.length); bytes = guard.decrypt(decodedBytes); if (bytes == null) return null; bis = new ByteArrayInputStream(bytes); } if (compressViewState) { bis = new GZIPInputStream(bis); } ois = serialProvider.createObjectInputStream(bis); long stateTime = 0; if (stateTimeoutEnabled) { try { stateTime = ois.readLong(); } catch (IOException ioe) { // we've caught an exception trying to read the time // marker. This most likely means a view that has been // around before upgrading to the release that included // this feature. So, no marker, return null now to // cause a ViewExpiredException if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine( "Client state timeout is enabled, but unable to find the " + "time marker in the serialized state. Assuming state " + "to be old and returning null."); } return null; } } Object structure = ois.readObject(); Object state = ois.readObject(); if (stateTime != 0 && hasStateExpired(stateTime)) { // return null if state has expired. This should cause // a ViewExpiredException to be thrown return null; } return new Object[] {structure, state}; } catch (java.io.OptionalDataException ode) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, ode.getMessage(), ode); } throw new FacesException(ode); } catch (ClassNotFoundException cnfe) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, cnfe.getMessage(), cnfe); } throw new FacesException(cnfe); } catch (IOException iox) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, iox.getMessage(), iox); } throw new FacesException(iox); } finally { if (ois != null) { try { ois.close(); } catch (IOException ioe) { // ignore } } } } /** * Serializes and Base64 encodes the provided <code>state</code> to the provided <code>writer * </code>/ * * @param state view state * @param writer the <code>Writer</code> to write the content to * @throws IOException if an error occurs writing the state to the client */ protected void doWriteState(Object state, Writer writer) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream base = null; if (compressViewState) { base = new GZIPOutputStream(baos, csBuffSize); } else { base = baos; } ObjectOutputStream oos = null; try { oos = serialProvider.createObjectOutputStream(new BufferedOutputStream(base)); if (stateTimeoutEnabled) { oos.writeLong(System.currentTimeMillis()); } Object[] stateToWrite = (Object[]) state; //noinspection NonSerializableObjectPassedToObjectStream oos.writeObject(stateToWrite[0]); //noinspection NonSerializableObjectPassedToObjectStream oos.writeObject(stateToWrite[1]); oos.flush(); oos.close(); oos = null; // get bytes for encrypting byte[] bytes = baos.toByteArray(); if (guard != null) { // this will MAC bytes = guard.encrypt(bytes); } // Base 64 encode Base64OutputStreamWriter bos = new Base64OutputStreamWriter(bytes.length, writer); bos.write(bytes, 0, bytes.length); bos.finish(); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log( Level.FINE, "Client State: total number of characters written: {0}", bos.getTotalCharsWritten()); } } finally { if (oos != null) { try { oos.close(); } catch (IOException ioe) { // ignore } } } } /** * If the {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateTimeout} * init parameter is set, calculate the elapsed time between the time the client state was written * and the time this method was invoked during restore. If the client state has expired, return * <code>true</code>. If the client state hasn't expired, or the init parameter wasn't set, return * <code>false</code>. * * @param stateTime the time in milliseconds that the state was written to the client * @return <code>false</code> if the client state hasn't timed out, otherwise return <code>true * </code> */ protected boolean hasStateExpired(long stateTime) { if (stateTimeoutEnabled) { long elapsed = (System.currentTimeMillis() - stateTime) / 60000; return (elapsed > stateTimeout); } else { return false; } } /** Initialze the various configuration options for client-side sate saving. */ protected void init() { if (!webConfig.isSet(BooleanWebContextInitParameter.DisableClientStateEncryption)) { guard = new ByteArrayGuard(); } else { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "jsf.config.webconfig.enventry.clientencrypt"); } } stateTimeoutEnabled = webConfig.isSet(ClientStateTimeout); if (stateTimeoutEnabled) { String timeout = webConfig.getOptionValue(ClientStateTimeout); try { stateTimeout = Long.parseLong(timeout); } catch (NumberFormatException nfe) { stateTimeout = Long.parseLong(ClientStateTimeout.getDefaultValue()); } } String size = webConfig.getOptionValue(ClientStateWriteBufferSize); String defaultSize = ClientStateWriteBufferSize.getDefaultValue(); try { csBuffSize = Integer.parseInt(size); if (csBuffSize % 2 != 0) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log( Level.WARNING, "jsf.renderkit.resstatemgr.clientbuf_div_two", new Object[] {ClientStateWriteBufferSize.getQualifiedName(), size, defaultSize}); } csBuffSize = Integer.parseInt(defaultSize); } else { csBuffSize /= 2; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Using client state buffer size of " + csBuffSize); } } } catch (NumberFormatException nfe) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log( Level.WARNING, "jsf.renderkit.resstatemgr.clientbuf_not_integer", new Object[] {ClientStateWriteBufferSize.getQualifiedName(), size, defaultSize}); } csBuffSize = Integer.parseInt(defaultSize); } } // ----------------------------------------------------------- Inner Classes /** * A simple <code>Writer</code> implementation to encapsulate a <code>StringBuilder</code> * instance. */ protected static final class StringBuilderWriter extends Writer { private StringBuilder sb; // -------------------------------------------------------- Constructors protected StringBuilderWriter(StringBuilder sb) { this.sb = sb; } // ------------------------------------------------- Methods from Writer @Override public void write(int c) throws IOException { sb.append((char) c); } @Override public void write(char cbuf[]) throws IOException { sb.append(cbuf); } @Override public void write(String str) throws IOException { sb.append(str); } @Override public void write(String str, int off, int len) throws IOException { sb.append(str.toCharArray(), off, len); } @Override public Writer append(CharSequence csq) throws IOException { sb.append(csq); return this; } @Override public Writer append(CharSequence csq, int start, int end) throws IOException { sb.append(csq, start, end); return this; } @Override public Writer append(char c) throws IOException { sb.append(c); return this; } public void write(char cbuf[], int off, int len) throws IOException { sb.append(cbuf, off, len); } public void flush() throws IOException { // no-op } public void close() throws IOException { // no-op } } // END StringBuilderWriter }
/** Helper class to interface with the Groovy runtime. */ public class GroovyHelperImpl extends GroovyHelper { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); private static final String SCRIPT_PATH = "/WEB-INF/groovy/"; private MojarraGroovyClassLoader loader; // ------------------------------------------------------------ Constructors GroovyHelperImpl() throws Exception { FacesContext facesContext = FacesContext.getCurrentInstance(); ExternalContext extContext = facesContext.getExternalContext(); ClassLoader curLoader = Thread.currentThread().getContextClassLoader(); URL combinedRoots[] = getResourceRoots(extContext, curLoader); if (0 < combinedRoots.length) { GroovyScriptEngine engine = new GroovyScriptEngine(combinedRoots, curLoader); // Class<?> c = Util.loadClass("groovy.util.GroovyScriptEngine", // GroovyHelperFactory.class); // Constructor<?> ctor = c.getConstructor(URL[].class, ClassLoader.class); // GroovyScriptEngine engine = (GroovyScriptEngine)ctor.newInstance(combinedRoots, // curLoader); loader = new MojarraGroovyClassLoader(engine); if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, "Groovy support enabled."); } extContext.getApplicationMap().put("com.sun.faces.groovyhelper", this); ((ServletContext) (extContext.getContext())).setAttribute("com.sun.faces.groovyhelper", this); } } private URL[] getResourceRoots(ExternalContext extContext, ClassLoader curLoader) throws IOException { URL[] combinedRoots; Enumeration<URL> classpathResourceEnumeration = curLoader.getResources("META-INF/resources/"); List<URL> classpathResourceList = new ArrayList<URL>(); while (classpathResourceEnumeration.hasMoreElements()) { classpathResourceList.add(classpathResourceEnumeration.nextElement()); } // only called during init - safe to cast and save. URL u = extContext.getResource(SCRIPT_PATH); URL webappRoots[] = getWebappResourceRoots(extContext), classpathRoots[] = new URL[classpathResourceList.size()]; classpathResourceList.toArray(classpathRoots); if (null != u || 0 < webappRoots.length || 0 < classpathRoots.length) { combinedRoots = new URL[webappRoots.length + classpathRoots.length + (null != u ? 1 : 0)]; System.arraycopy(webappRoots, 0, combinedRoots, 0, webappRoots.length); System.arraycopy(classpathRoots, 0, combinedRoots, webappRoots.length, classpathRoots.length); if (null != u) { combinedRoots[webappRoots.length + classpathRoots.length] = u; } } else { combinedRoots = new URL[0]; } return combinedRoots; } private URL[] getWebappResourceRoots(ExternalContext extContext) { URL[] result = null; int size = 0, i = 0; Set<String> resourceRoots = extContext.getResourcePaths("/resources/"); if (null != resourceRoots && !resourceRoots.isEmpty()) { // Determine the size of script roots that end with "/" for (String cur : resourceRoots) { if (cur.endsWith("/")) { size++; } } result = new URL[size]; for (String cur : resourceRoots) { if (cur.endsWith("/")) { try { result[i++] = extContext.getResource(cur); } catch (MalformedURLException ex) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, null, ex); } } } } } if (null == result) { result = new URL[0]; } return result; } public void addURL(URL toAdd) { loader.getGroovyScriptEngine().getGroovyClassLoader().addURL(toAdd); } // ---------------------------------------------------------- Public Methods public Class<?> loadScript(String name) { try { String script = name; if (script.endsWith(".groovy")) { script = script.substring(0, script.indexOf(".groovy")); } // return engine.loadScriptByName(script); return Util.loadClass(script, this); } catch (Exception e) { throw new FacesException(e); } } public void setClassLoader() { if (loader != null) { Thread.currentThread().setContextClassLoader(loader); } } // ----------------------------------------------------------- Inner Classes public static final class MojarraGroovyClassLoader extends URLClassLoader { private GroovyScriptEngine gse; public MojarraGroovyClassLoader(GroovyScriptEngine gse) { super(new URL[0], gse.getGroovyClassLoader()); gse.getGroovyClassLoader().setShouldRecompile(Boolean.TRUE); this.gse = gse; } public GroovyScriptEngine getGroovyScriptEngine() { return gse; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (name == null) { throw new NullPointerException(); } Class<?> c; try { c = gse.loadScriptByName(name); } catch (Exception e) { try { c = gse.getGroovyClassLoader().loadClass(name); } catch (ClassNotFoundException cnfe) { throw new ClassNotFoundException(name, cnfe); } } if (c == null) { throw new ClassNotFoundException(name); } return c; } } }
/** * Break out the things that are associated with the Application, but need to be present even when * the user has replaced the Application instance. * * <p> * * <p>For example: the user replaces ApplicationFactory, and wants to intercept calls to * createValueExpression() and createMethodExpression() for certain kinds of expressions, but allow * the existing application to handle the rest. */ public class ApplicationAssociate { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); private ApplicationImpl app = null; /** * Overall Map containing <code>from-view-id</code> key and <code>ArrayList</code> of <code> * ConfigNavigationCase</code> objects for that key; The <code>from-view-id</code> strings in this * map will be stored as specified in the configuration file - some of them will have a trailing * asterisk "*" signifying wild card, and some may be specified as an asterisk "*". */ private Map<String, List<ConfigNavigationCase>> caseListMap = null; /** * The List that contains all view identifier strings ending in an asterisk "*". The entries are * stored without the trailing asterisk. */ private TreeSet<String> wildcardMatchList = null; // Flag indicating that a response has been rendered. private boolean responseRendered = false; private static final String ASSOCIATE_KEY = RIConstants.FACES_PREFIX + "ApplicationAssociate"; private static ThreadLocal<ApplicationAssociate> instance = new ThreadLocal<ApplicationAssociate>() { protected ApplicationAssociate initialValue() { return (null); } }; private List<ELResolver> elResolversFromFacesConfig = null; @SuppressWarnings("deprecation") private VariableResolver legacyVRChainHead = null; @SuppressWarnings("deprecation") private PropertyResolver legacyPRChainHead = null; private ExpressionFactory expressionFactory = null; @SuppressWarnings("deprecation") private PropertyResolver legacyPropertyResolver = null; @SuppressWarnings("deprecation") private VariableResolver legacyVariableResolver = null; private CompositeELResolver facesELResolverForJsp = null; private InjectionProvider injectionProvider; private ResourceCache resourceCache; private String contextName; private boolean requestServiced; private BeanManager beanManager; private GroovyHelper groovyHelper; private AnnotationManager annotationManager; private boolean devModeEnabled; private Compiler compiler; private FaceletFactory faceletFactory; private ResourceManager resourceManager; private PropertyEditorHelper propertyEditorHelper; public ApplicationAssociate(ApplicationImpl appImpl) { app = appImpl; propertyEditorHelper = new PropertyEditorHelper(appImpl); FacesContext ctx = FacesContext.getCurrentInstance(); if (ctx == null) { throw new IllegalStateException( MessageUtils.getExceptionMessageString( MessageUtils.APPLICATION_ASSOCIATE_CTOR_WRONG_CALLSTACK_ID)); } ExternalContext externalContext = ctx.getExternalContext(); if (null != externalContext.getApplicationMap().get(ASSOCIATE_KEY)) { throw new IllegalStateException( MessageUtils.getExceptionMessageString(MessageUtils.APPLICATION_ASSOCIATE_EXISTS_ID)); } externalContext.getApplicationMap().put(ASSOCIATE_KEY, this); //noinspection CollectionWithoutInitialCapacity caseListMap = new HashMap<String, List<ConfigNavigationCase>>(); wildcardMatchList = new TreeSet<String>(new SortIt()); injectionProvider = InjectionProviderFactory.createInstance(externalContext); WebConfiguration webConfig = WebConfiguration.getInstance(externalContext); beanManager = new BeanManager( injectionProvider, webConfig.isOptionEnabled(BooleanWebContextInitParameter.EnableLazyBeanValidation)); annotationManager = new AnnotationManager(); groovyHelper = GroovyHelper.getCurrentInstance(); // initialize Facelets if (!webConfig.isOptionEnabled(BooleanWebContextInitParameter.DisableFaceletJSFViewHandler)) { compiler = createCompiler(webConfig); faceletFactory = createFaceletFactory(compiler, webConfig); devModeEnabled = (appImpl.getProjectStage() == ProjectStage.Development); } if (devModeEnabled) { resourceCache = new ResourceCache(); } resourceManager = new ResourceManager(resourceCache); } public static ApplicationAssociate getInstance(ExternalContext externalContext) { if (externalContext == null) { return null; } Map applicationMap = externalContext.getApplicationMap(); return ((ApplicationAssociate) applicationMap.get(ASSOCIATE_KEY)); } public static ApplicationAssociate getInstance(ServletContext context) { if (context == null) { return null; } return (ApplicationAssociate) context.getAttribute(ASSOCIATE_KEY); } public static void setCurrentInstance(ApplicationAssociate associate) { if (associate == null) { instance.remove(); } else { instance.set(associate); } } public static ApplicationAssociate getCurrentInstance() { ApplicationAssociate associate = instance.get(); if (associate == null) { // Fallback to ExternalContext lookup FacesContext fc = FacesContext.getCurrentInstance(); if (fc != null) { ExternalContext extContext = fc.getExternalContext(); if (extContext != null) { return ApplicationAssociate.getInstance(extContext); } } } return associate; } public ResourceManager getResourceManager() { return resourceManager; } public void setResourceManager(ResourceManager resourceManager) { this.resourceManager = resourceManager; } public ResourceCache getResourceCache() { return resourceCache; } public AnnotationManager getAnnotationManager() { return annotationManager; } public Compiler getCompiler() { return compiler; } public FaceletFactory getFaceletFactory() { return faceletFactory; } public static void clearInstance(ExternalContext externalContext) { Map applicationMap = externalContext.getApplicationMap(); ApplicationAssociate me = (ApplicationAssociate) applicationMap.get(ASSOCIATE_KEY); if (null != me) { if (null != me.resourceBundles) { me.resourceBundles.clear(); } } applicationMap.remove(ASSOCIATE_KEY); } public BeanManager getBeanManager() { return beanManager; } public GroovyHelper getGroovyHelper() { return groovyHelper; } public boolean isDevModeEnabled() { return devModeEnabled; } /** * Obtain the PropertyEditorHelper instance for this app. * * @return */ public PropertyEditorHelper getPropertyEditorHelper() { return propertyEditorHelper; } /** * This method is called by <code>ConfigureListener</code> and will contain any <code> * VariableResolvers</code> defined within faces-config configuration files. * * @param resolver VariableResolver */ @SuppressWarnings("deprecation") public void setLegacyVRChainHead(VariableResolver resolver) { this.legacyVRChainHead = resolver; } @SuppressWarnings("deprecation") public VariableResolver getLegacyVRChainHead() { return legacyVRChainHead; } /** * This method is called by <code>ConfigureListener</code> and will contain any <code> * PropertyResolvers</code> defined within faces-config configuration files. * * @param resolver PropertyResolver */ @SuppressWarnings("deprecation") public void setLegacyPRChainHead(PropertyResolver resolver) { this.legacyPRChainHead = resolver; } @SuppressWarnings("deprecation") public PropertyResolver getLegacyPRChainHead() { return legacyPRChainHead; } public CompositeELResolver getFacesELResolverForJsp() { return facesELResolverForJsp; } public void setFacesELResolverForJsp(CompositeELResolver celr) { facesELResolverForJsp = celr; } public void setELResolversFromFacesConfig(List<ELResolver> resolvers) { this.elResolversFromFacesConfig = resolvers; } public List<ELResolver> getELResolversFromFacesConfig() { return elResolversFromFacesConfig; } public void setExpressionFactory(ExpressionFactory expressionFactory) { this.expressionFactory = expressionFactory; } public ExpressionFactory getExpressionFactory() { return this.expressionFactory; } public List<ELResolver> getApplicationELResolvers() { return app.getApplicationELResolvers(); } public InjectionProvider getInjectionProvider() { return injectionProvider; } public void setContextName(String contextName) { this.contextName = contextName; } public String getContextName() { return contextName; } /** * Maintains the PropertyResolver called through Application.setPropertyResolver() * * @param resolver PropertyResolver */ @SuppressWarnings("deprecation") public void setLegacyPropertyResolver(PropertyResolver resolver) { this.legacyPropertyResolver = resolver; } /** @return the PropertyResolver called through Application.getPropertyResolver() */ @SuppressWarnings("deprecation") public PropertyResolver getLegacyPropertyResolver() { return legacyPropertyResolver; } /** * Maintains the PropertyResolver called through Application.setVariableResolver() * * @param resolver VariableResolver */ @SuppressWarnings("deprecation") public void setLegacyVariableResolver(VariableResolver resolver) { this.legacyVariableResolver = resolver; } /** @return the VariableResolver called through Application.getVariableResolver() */ @SuppressWarnings("deprecation") public VariableResolver getLegacyVariableResolver() { return legacyVariableResolver; } /** * Called by application code to indicate we've processed the first request to the application. */ public void setRequestServiced() { this.requestServiced = true; } /** @return <code>true</code> if we've processed a request, otherwise <code>false</code> */ public boolean hasRequestBeenServiced() { return requestServiced; } /** * Add a navigation case to the internal case list. If a case list does not already exist in the * case list map containing this case (identified by <code>from-view-id</code>), start a new list, * add the case to it, and store the list in the case list map. If a case list already exists, see * if a case entry exists in the list with a matching <code>from-view-id</code><code>from-action * </code> <code>from-outcome</code> combination. If there is suach an entry, overwrite it with * this new case. Otherwise, add the case to the list. * * @param navigationCase the navigation case containing navigation mapping information from the * configuration file. */ public void addNavigationCase(ConfigNavigationCase navigationCase) { String fromViewId = navigationCase.getFromViewId(); List<ConfigNavigationCase> caseList = caseListMap.get(fromViewId); if (caseList == null) { //noinspection CollectionWithoutInitialCapacity caseList = new ArrayList<ConfigNavigationCase>(); caseList.add(navigationCase); caseListMap.put(fromViewId, caseList); } else { String key = navigationCase.getKey(); boolean foundIt = false; for (int i = 0; i < caseList.size(); i++) { ConfigNavigationCase navCase = caseList.get(i); // if there already is a case existing for the // fromviewid/fromaction.fromoutcome combination, // replace it ... (last one wins). // if (key.equals(navCase.getKey())) { caseList.set(i, navigationCase); foundIt = true; break; } } if (!foundIt) { caseList.add(navigationCase); } } if (fromViewId.endsWith("*")) { fromViewId = fromViewId.substring(0, fromViewId.lastIndexOf('*')); wildcardMatchList.add(fromViewId); } } /** * Return a <code>Map</code> of navigation mappings loaded from the configuration system. The key * for the returned <code>Map</code> is <code>from-view-id</code>, and the value is a <code>List * </code> of navigation cases. * * @return Map the map of navigation mappings. */ public Map<String, List<ConfigNavigationCase>> getNavigationCaseListMappings() { if (caseListMap == null) { return Collections.emptyMap(); } return caseListMap; } /** * Return all navigation mappings whose <code>from-view-id</code> contained a trailing "*". * * @return <code>TreeSet</code> The navigation mappings sorted in descending order. */ public TreeSet<String> getNavigationWildCardList() { return wildcardMatchList; } public ResourceBundle getResourceBundle(FacesContext context, String var) { ApplicationResourceBundle bundle = resourceBundles.get(var); if (bundle == null) { return null; } UIViewRoot root; // Start out with the default locale Locale locale; Locale defaultLocale = Locale.getDefault(); locale = defaultLocale; // See if this FacesContext has a ViewRoot if (null != (root = context.getViewRoot())) { // If so, ask it for its Locale if (null == (locale = root.getLocale())) { // If the ViewRoot has no Locale, fall back to the default. locale = defaultLocale; } } assert (null != locale); // ResourceBundleBean bean = resourceBundles.get(var); return bundle.getResourceBundle(locale); } /** * keys: <var> element from faces-config * * <p> * * <p>values: ResourceBundleBean instances. */ @SuppressWarnings({"CollectionWithoutInitialCapacity"}) Map<String, ApplicationResourceBundle> resourceBundles = new HashMap<String, ApplicationResourceBundle>(); public void addResourceBundle(String var, ApplicationResourceBundle bundle) { resourceBundles.put(var, bundle); } public Map<String, ApplicationResourceBundle> getResourceBundles() { return resourceBundles; } // This is called by ViewHandlerImpl.renderView(). public void responseRendered() { responseRendered = true; } public boolean isResponseRendered() { return responseRendered; } protected FaceletFactory createFaceletFactory(Compiler c, WebConfiguration webConfig) { // refresh period String refreshPeriod = webConfig.getOptionValue( WebConfiguration.WebContextInitParameter.FaceletsDefaultRefreshPeriod); long period = Long.parseLong(refreshPeriod); // resource resolver ResourceResolver resolver = new DefaultResourceResolver(); String resolverName = webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.FaceletsResourceResolver); if (resolverName != null && resolverName.length() > 0) { try { resolver = (ResourceResolver) ReflectionUtil.forName(resolverName).newInstance(); } catch (Exception e) { throw new FacesException("Error Initializing ResourceResolver[" + resolverName + "]", e); } } // Resource.getResourceUrl(ctx,"/") return new DefaultFaceletFactory(c, resolver, period); } protected Compiler createCompiler(WebConfiguration webConfig) { Compiler c = new SAXCompiler(); // load decorators String decParam = webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.FaceletsDecorators); if (decParam != null) { decParam = decParam.trim(); String[] decs = Util.split(decParam, ";"); TagDecorator decObj; for (int i = 0; i < decs.length; i++) { try { decObj = (TagDecorator) ReflectionUtil.forName(decs[i]).newInstance(); c.addTagDecorator(decObj); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Successfully Loaded Decorator: {0}", decs[i]); } } catch (Exception e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Error Loading Decorator: " + decs[i], e); } } } } // skip params? c.setTrimmingComments( webConfig.isOptionEnabled(BooleanWebContextInitParameter.FaceletsSkipComments)); c.addTagLibrary(new CoreLibrary()); c.addTagLibrary(new HtmlLibrary()); c.addTagLibrary(new UILibrary()); c.addTagLibrary(new JstlCoreLibrary()); c.addTagLibrary(new JstlFnLibrary()); c.addTagLibrary(new CompositeLibrary()); return c; } /** * This Comparator class will help sort the <code>ConfigNavigationCase</code> objects based on * their <code>fromViewId</code> properties in descending order - largest string to smallest * string. */ static class SortIt implements Comparator<String> { public int compare(String fromViewId1, String fromViewId2) { return -(fromViewId1.compareTo(fromViewId2)); } } }
/** This {@link ViewHandler} implementation handles both JSP-based and Facelets/PDL-based views. */ public class MultiViewHandler extends ViewHandler { // Log instance for this class private static final Logger logger = FacesLogger.APPLICATION.getLogger(); private String[] configuredExtensions; private boolean extensionsSet; private ViewDeclarationLanguageFactory vdlFactory; // ------------------------------------------------------------ Constructors public MultiViewHandler() { WebConfiguration config = WebConfiguration.getInstance(); configuredExtensions = config.getOptionValue(WebConfiguration.WebContextInitParameter.DefaultSuffix, " "); extensionsSet = config.isSet(WebConfiguration.WebContextInitParameter.DefaultSuffix); vdlFactory = (ViewDeclarationLanguageFactory) FactoryFinder.getFactory(FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY); } // ------------------------------------------------ Methods from ViewHandler /** * Do not call the default implementation of {@link * javax.faces.application.ViewHandler#initView(javax.faces.context.FacesContext)} if the {@link * javax.faces.context.ExternalContext#getRequestCharacterEncoding()} returns a <code>non-null * </code> result. * * @see javax.faces.application.ViewHandler#initView(javax.faces.context.FacesContext) */ @Override public void initView(FacesContext context) throws FacesException { if (context.getExternalContext().getRequestCharacterEncoding() == null) { super.initView(context); } } /** * Call {@link ViewDeclarationLanguage#renderView(javax.faces.context.FacesContext, * javax.faces.component.UIViewRoot)} if the view can be rendered. * * @see ViewHandler#renderView(javax.faces.context.FacesContext, javax.faces.component.UIViewRoot) */ public void renderView(FacesContext context, UIViewRoot viewToRender) throws IOException, FacesException { Util.notNull("context", context); Util.notNull("viewToRender", viewToRender); vdlFactory .getViewDeclarationLanguage(viewToRender.getViewId()) .renderView(context, viewToRender); } /** * Call {@link ViewDeclarationLanguage#restoreView(javax.faces.context.FacesContext, String)}. * * @see ViewHandler#restoreView(javax.faces.context.FacesContext, String) */ public UIViewRoot restoreView(FacesContext context, String viewId) { Util.notNull("context", context); String actualViewId = derivePhysicalViewId(context, viewId, false); return vdlFactory.getViewDeclarationLanguage(actualViewId).restoreView(context, actualViewId); } /** * Derive the actual view ID (i.e. the physical resource) and call call {@link * ViewDeclarationLanguage#createView(javax.faces.context.FacesContext, String)}. * * @see ViewHandler#restoreView(javax.faces.context.FacesContext, String) */ public UIViewRoot createView(FacesContext context, String viewId) { Util.notNull("context", context); String actualViewId = derivePhysicalViewId(context, viewId, false); return vdlFactory.getViewDeclarationLanguage(actualViewId).createView(context, actualViewId); } /** * This code is currently common to all {@link ViewHandlingStrategy} instances. * * @see ViewHandler#calculateLocale(javax.faces.context.FacesContext) */ public Locale calculateLocale(FacesContext context) { Util.notNull("context", context); Locale result = null; // determine the locales that are acceptable to the client based on the // Accept-Language header and the find the best match among the // supported locales specified by the client. Iterator<Locale> locales = context.getExternalContext().getRequestLocales(); while (locales.hasNext()) { Locale perf = locales.next(); result = findMatch(context, perf); if (result != null) { break; } } // no match is found. if (result == null) { if (context.getApplication().getDefaultLocale() == null) { result = Locale.getDefault(); } else { result = context.getApplication().getDefaultLocale(); } } return result; } /** * This code is currently common to all {@link ViewHandlingStrategy} instances. * * @see ViewHandler#calculateRenderKitId(javax.faces.context.FacesContext) */ public String calculateRenderKitId(FacesContext context) { Util.notNull("context", context); Map<String, String> requestParamMap = context.getExternalContext().getRequestParameterMap(); String result = requestParamMap.get(ResponseStateManager.RENDER_KIT_ID_PARAM); if (result == null) { if (null == (result = context.getApplication().getDefaultRenderKitId())) { result = RenderKitFactory.HTML_BASIC_RENDER_KIT; } } return result; } /** * This code is currently common to all {@link ViewHandlingStrategy} instances. * * @see ViewHandler#writeState(javax.faces.context.FacesContext) */ public void writeState(FacesContext context) throws IOException { Util.notNull("context", context); if (!context.getPartialViewContext().isAjaxRequest() && !context.getViewRoot().isTransient()) { if (logger.isLoggable(Level.FINE)) { logger.fine("Begin writing marker for viewId " + context.getViewRoot().getViewId()); } WriteBehindStateWriter writer = WriteBehindStateWriter.getCurrentInstance(); if (writer != null) { writer.writingState(); } context.getResponseWriter().write(RIConstants.SAVESTATE_FIELD_MARKER); if (logger.isLoggable(Level.FINE)) { logger.fine("End writing marker for viewId " + context.getViewRoot().getViewId()); } } } /** * This code is currently common to all {@link ViewHandlingStrategy} instances. * * @see ViewHandler#getActionURL(javax.faces.context.FacesContext, String) */ public String getActionURL(FacesContext context, String viewId) { Util.notNull("context", context); Util.notNull("viewId", viewId); if (viewId.charAt(0) != '/') { String message = MessageUtils.getExceptionMessageString(MessageUtils.ILLEGAL_VIEW_ID_ID, viewId); if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, "jsf.illegal_view_id_error", viewId); } throw new IllegalArgumentException(message); } // Acquire the context path, which we will prefix on all results ExternalContext extContext = context.getExternalContext(); String contextPath = extContext.getRequestContextPath(); // Acquire the mapping used to execute this request (if any) String mapping = Util.getFacesMapping(context); // If no mapping can be identified, just return a server-relative path if (mapping == null) { return (contextPath + viewId); } // Deal with prefix mapping if (Util.isPrefixMapped(mapping)) { if (mapping.equals("/*")) { return (contextPath + viewId); } else { return (contextPath + mapping + viewId); } } // Deal with extension mapping int period = viewId.lastIndexOf('.'); if (period < 0) { return (contextPath + viewId + mapping); } else if (!viewId.endsWith(mapping)) { for (String ext : configuredExtensions) { if (viewId.endsWith(ext)) { return (contextPath + viewId.substring(0, viewId.indexOf(ext)) + mapping); } } return (contextPath + viewId.substring(0, period) + mapping); } else { return (contextPath + viewId); } } /** * This code is currently common to all {@link ViewHandlingStrategy} instances. * * @see ViewHandler#getResourceURL(javax.faces.context.FacesContext, String) */ public String getResourceURL(FacesContext context, String path) { ExternalContext extContext = context.getExternalContext(); if (path.charAt(0) == '/') { return (extContext.getRequestContextPath() + path); } else { return path; } } @Override public String getBookmarkableURL( FacesContext context, String viewId, Map<String, List<String>> parameters, boolean includeViewParams) { Map<String, List<String>> params; if (includeViewParams) { params = getFullParameterList(context, viewId, parameters); } else { params = parameters; } ExternalContext ectx = context.getExternalContext(); return ectx.encodeActionURL( ectx.encodeBookmarkableURL( Util.getViewHandler(context).getActionURL(context, viewId), params)); } /** * @see ViewHandler#getRedirectURL(javax.faces.context.FacesContext, String, java.util.Map, * boolean) */ @Override public String getRedirectURL( FacesContext context, String viewId, Map<String, List<String>> parameters, boolean includeViewParams) { Map<String, List<String>> params; if (includeViewParams) { params = getFullParameterList(context, viewId, parameters); } else { params = parameters; } ExternalContext ectx = context.getExternalContext(); return ectx.encodeActionURL( ectx.encodeRedirectURL(Util.getViewHandler(context).getActionURL(context, viewId), params)); } /** @see ViewHandler#getViewDeclarationLanguage(javax.faces.context.FacesContext, String) */ @Override public ViewDeclarationLanguage getViewDeclarationLanguage(FacesContext context, String viewId) { String actualViewId = derivePhysicalViewId(context, viewId, false); return vdlFactory.getViewDeclarationLanguage(actualViewId); } @Override public String deriveViewId(FacesContext context, String rawViewId) { return derivePhysicalViewId(context, rawViewId, true); } // ------------------------------------------------------- Protected Methods /** * if the specified mapping is a prefix mapping, and the provided request URI (usually the value * from <code>ExternalContext.getRequestServletPath()</code>) starts with <code>mapping + '/' * </code>, prune the mapping from the URI and return it, otherwise, return the original URI. * * @param uri the servlet request path * @param mapping the FacesServlet mapping used for this request * @return the URI without additional FacesServlet mappings * @since 1.2 */ protected String normalizeRequestURI(String uri, String mapping) { if (mapping == null || !Util.isPrefixMapped(mapping)) { return uri; } else { int length = mapping.length() + 1; StringBuilder builder = new StringBuilder(length); builder.append(mapping).append('/'); String mappingMod = builder.toString(); boolean logged = false; while (uri.startsWith(mappingMod)) { if (!logged && logger.isLoggable(Level.WARNING)) { logged = true; logger.log( Level.WARNING, "jsf.viewhandler.requestpath.recursion", new Object[] {uri, mapping}); } uri = uri.substring(length - 1); } return uri; } } /** * Adjust the viewID per the requirements of {@link #renderView}. * * @param context current {@link javax.faces.context.FacesContext} * @param viewId incoming view ID * @return the view ID with an altered suffix mapping (if necessary) */ protected String convertViewId(FacesContext context, String viewId) { // if the viewId doesn't already use the above suffix, // replace or append. int extIdx = viewId.lastIndexOf('.'); int length = viewId.length(); StringBuilder buffer = new StringBuilder(length); for (String ext : configuredExtensions) { if (viewId.endsWith(ext)) { return viewId; } appendOrReplaceExtension(viewId, ext, length, extIdx, buffer); String convertedViewId = buffer.toString(); try { if (context.getExternalContext().getResource(convertedViewId) != null) { // RELEASE_PENDING (rlubke,driscoll) cache the lookup return convertedViewId; } } catch (MalformedURLException e) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, e.toString(), e); } } } // unable to find any resource match that the default ViewHandler // can deal with. Fall back to legacy (JSF 1.2) id conversion. return legacyConvertViewId(viewId, length, extIdx, buffer); } protected String derivePhysicalViewId(FacesContext ctx, String rawViewId, boolean checkPhysical) { if (rawViewId != null) { String mapping = Util.getFacesMapping(ctx); String viewId; if (mapping != null) { if (!Util.isPrefixMapped(mapping)) { viewId = convertViewId(ctx, rawViewId); } else { viewId = normalizeRequestURI(rawViewId, mapping); if (viewId.equals(mapping)) { // The request was to the FacesServlet only - no // path info // on some containers this causes a recursion in the // RequestDispatcher and the request appears to hang. // If this is detected, return status 404 send404Error(ctx); } } try { if (checkPhysical) { return ((ctx.getExternalContext().getResource(viewId) != null) ? viewId : null); } else { return viewId; } } catch (MalformedURLException mue) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, mue.toString(), mue); } return null; } } } return rawViewId; } protected Map<String, List<String>> getFullParameterList( FacesContext ctx, String viewId, Map<String, List<String>> existingParameters) { Map<String, List<String>> copy; if (existingParameters == null || existingParameters.isEmpty()) { copy = new LinkedHashMap<String, List<String>>(4); } else { copy = new LinkedHashMap<String, List<String>>(existingParameters); } addViewParameters(ctx, viewId, copy); return copy; } protected void addViewParameters( FacesContext ctx, String viewId, Map<String, List<String>> existingParameters) { UIViewRoot currentRoot = ctx.getViewRoot(); String currentViewId = currentRoot.getViewId(); Collection<UIViewParameter> toViewParams; Collection<UIViewParameter> currentViewParams; boolean currentIsSameAsNew = false; currentViewParams = ViewMetadata.getViewParameters(currentRoot); if (currentViewId.equals(viewId)) { currentIsSameAsNew = true; toViewParams = currentViewParams; } else { ViewDeclarationLanguage pdl = getViewDeclarationLanguage(ctx, viewId); ViewMetadata viewMetadata = pdl.getViewMetadata(ctx, viewId); UIViewRoot root = viewMetadata.createMetadataView(ctx); toViewParams = ViewMetadata.getViewParameters(root); } if (toViewParams.isEmpty()) { return; } for (UIViewParameter viewParam : toViewParams) { String value; // don't bother looking at view parameter if it's been overridden if (existingParameters.containsKey(viewParam.getName())) { continue; } else if (paramHasValueExpression(viewParam)) { value = viewParam.getStringValueFromModel(ctx); } else { // Anonymous view parameter: // Get string value from UIViewParameter instance stored in current view if (currentIsSameAsNew) { value = viewParam.getStringValue(ctx); } // ...or transfer string value from matching UIViewParameter instance stored in current view else { value = getStringValueToTransfer(ctx, viewParam, currentViewParams); } } if (value != null) { List<String> existing = existingParameters.get(viewParam.getName()); if (existing == null) { existing = new ArrayList<String>(4); existingParameters.put(viewParam.getName(), existing); } existing.add(value); } } } /** * Attempts to find a matching locale based on <code>pref</code> and list of supported locales, * using the matching algorithm as described in JSTL 8.3.2. * * @param context the <code>FacesContext</code> for the current request * @param pref the preferred locale * @return the Locale based on pref and the matching alogritm specified in JSTL 8.3.2 */ protected Locale findMatch(FacesContext context, Locale pref) { Locale result = null; Iterator<Locale> it = context.getApplication().getSupportedLocales(); while (it.hasNext()) { Locale supportedLocale = it.next(); if (pref.equals(supportedLocale)) { // exact match result = supportedLocale; break; } else { // Make sure the preferred locale doesn't have country // set, when doing a language match, For ex., if the // preferred locale is "en-US", if one of supported // locales is "en-UK", even though its language matches // that of the preferred locale, we must ignore it. if (pref.getLanguage().equals(supportedLocale.getLanguage()) && supportedLocale.getCountry().length() == 0) { result = supportedLocale; } } } // if it's not in the supported locales, if (null == result) { Locale defaultLocale = context.getApplication().getDefaultLocale(); if (defaultLocale != null) { if (pref.equals(defaultLocale)) { // exact match result = defaultLocale; } else { // Make sure the preferred locale doesn't have country // set, when doing a language match, For ex., if the // preferred locale is "en-US", if one of supported // locales is "en-UK", even though its language matches // that of the preferred locale, we must ignore it. if (pref.getLanguage().equals(defaultLocale.getLanguage()) && defaultLocale.getCountry().length() == 0) { result = defaultLocale; } } } } return result; } /** * Send {@link HttpServletResponse#SC_NOT_FOUND} (404) to the client. * * @param context the {@link FacesContext} for the current request */ protected void send404Error(FacesContext context) { try { context.responseComplete(); context.getExternalContext().responseSendError(HttpServletResponse.SC_NOT_FOUND, ""); } catch (IOException ioe) { throw new FacesException(ioe); } } // --------------------------------------------------------- Private Methods private static boolean paramHasValueExpression(UIViewParameter param) { return (param.getValueExpression("value") != null); } private static String getStringValueToTransfer( FacesContext context, UIViewParameter param, Collection<UIViewParameter> viewParams) { if (viewParams != null && !viewParams.isEmpty()) { for (UIViewParameter candidate : viewParams) { if ((null != candidate.getName() && null != param.getName()) && candidate.getName().equals(param.getName())) { return candidate.getStringValue(context); } else { return param.getStringValue(context); } } } return null; } // Utility method used by viewId conversion. Appends the extension // if no extension is present. Otherwise, replaces the extension. private void appendOrReplaceExtension( String viewId, String ext, int length, int extIdx, StringBuilder buffer) { buffer.setLength(0); buffer.append(viewId); if (extIdx != -1) { buffer.replace(extIdx, length, ext); } else { // no extension in the provided viewId, append the suffix buffer.append(ext); } } private String legacyConvertViewId(String viewId, int length, int extIdx, StringBuilder buffer) { // In 1.2, the viewId was converted by replacing the extension // with the single extension specified by javax.faces.DEFAULT_SUFFIX, // which defaulted to ".jsp". In 2.0, javax.faces.DEFAULT_SUFFIX // may specify multiple extensions. If javax.faces.DEFAULT_SUFFIX is // explicitly set, we honor it and pick off the first specified // extension. If javax.faces.DEFAULT_SUFFIX is not explicitly set, // we honor the default 1.2 behavior and use ".jsp" as the suffix. String ext = (extensionsSet && !(configuredExtensions.length == 0)) ? configuredExtensions[0] : ".jsp"; if (viewId.endsWith(ext)) { return viewId; } appendOrReplaceExtension(viewId, ext, length, extIdx, buffer); return buffer.toString(); } }