| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.myfaces.trinidadinternal.application; |
| |
| import java.io.IOException; |
| import java.lang.reflect.Constructor; |
| import java.net.URL; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import javax.faces.FacesException; |
| import javax.faces.application.ProjectStage; |
| import javax.faces.application.ViewHandler; |
| import javax.faces.application.ViewHandlerWrapper; |
| import javax.faces.component.UIViewRoot; |
| import javax.faces.context.ExternalContext; |
| import javax.faces.context.FacesContext; |
| |
| import javax.faces.view.ViewDeclarationLanguage; |
| |
| import org.apache.myfaces.trinidad.context.RequestContext; |
| import org.apache.myfaces.trinidad.logging.TrinidadLogger; |
| import org.apache.myfaces.trinidad.render.ExtendedRenderKitService; |
| import org.apache.myfaces.trinidad.util.Service; |
| import org.apache.myfaces.trinidad.util.URLUtils; |
| import org.apache.myfaces.trinidadinternal.context.RequestContextImpl; |
| import org.apache.myfaces.trinidadinternal.context.TrinidadPhaseListener; |
| import org.apache.myfaces.trinidadinternal.share.config.Configuration; |
| import org.apache.myfaces.trinidadinternal.skin.pregen.SkinPregenerationService; |
| |
| /** |
| * ViewHandler that adds modification detection to the existing ViewHandler, |
| * assuming that the viewId is a valid resource path. |
| * <p> |
| * And now also supports inserting URLs tokens to preserve PageFlowScope. |
| * <p> |
| * @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/application/ViewHandlerImpl.java#0 $) $Date: 05-jan-2006.13:19:09 $ |
| * @todo Rename something less generic |
| * @todo Support extension mapping (*.faces) |
| * @todo The modification detection only works for a single user. That's |
| * OK for now, because it's intended for use while developing |
| */ |
| public class ViewHandlerImpl extends ViewHandlerWrapper |
| { |
| static public final String ALTERNATE_VIEW_HANDLER = |
| "org.apache.myfaces.trinidad.ALTERNATE_VIEW_HANDLER"; |
| |
| public ViewHandlerImpl( |
| ViewHandler delegate) |
| { |
| _delegate = delegate; |
| _timestamps = new HashMap<String, Long>(); |
| _skinPregenerationEnabled = SkinPregenerationService.isEnabled(); |
| } |
| |
| public ViewHandler getWrapped() |
| { |
| return _delegate; |
| } |
| |
| |
| @Override |
| public UIViewRoot createView(FacesContext context, String viewId) |
| { |
| _checkSkinPregeneration(context, viewId); |
| |
| _initIfNeeded(context); |
| |
| if (_isTimestampCheckEnabled(context, viewId)) |
| { |
| try |
| { |
| // Check the timestamp on the physical path |
| String path = _getPath(viewId); |
| synchronized (_timestamps) |
| { |
| Long ts = _timestamps.get(path); |
| if (ts != _NOT_FOUND) |
| { |
| URL url = context.getExternalContext().getResource(path); |
| Long modified = _getLastModified(url); |
| _timestamps.put(path, modified); |
| } |
| } |
| } |
| catch (IOException e) |
| { |
| _LOG.severe(e); |
| } |
| } |
| |
| return super.createView(context, viewId); |
| } |
| |
| @Override |
| public String getActionURL(FacesContext context, String viewId) |
| { |
| String actionURL = super.getActionURL(context, viewId); |
| RequestContext afContext = RequestContext.getCurrentInstance(); |
| if (afContext != null) |
| { |
| actionURL = afContext.getPageResolver().encodeActionURI(actionURL); |
| actionURL = afContext.getPageFlowScopeProvider(). |
| encodeCurrentPageFlowScopeURL(context, actionURL); |
| } |
| |
| return actionURL; |
| } |
| |
| @Override |
| public String getResourceURL( |
| FacesContext context, |
| String path) |
| { |
| return super.getResourceURL(context, path); |
| } |
| |
| |
| @Override |
| public void renderView( |
| FacesContext context, |
| UIViewRoot viewToRender) throws IOException, FacesException |
| { |
| _initIfNeeded(context); |
| |
| // Check whether Trinidad's ViewHandler is registered more than once. |
| // This happens when the implementation jar is loaded multiple times. |
| Map<String, Object> reqMap = context.getExternalContext().getRequestMap(); |
| if (reqMap.get(_RENDER_VIEW_MARKER) != null) |
| { |
| _LOG.warning("DUPLICATE_VIEWHANDLER_REGISTRATION"); |
| } |
| else |
| { |
| reqMap.put(_RENDER_VIEW_MARKER, Boolean.TRUE); |
| } |
| |
| // See if there is a possiblity of short-circuiting the current |
| // Render Response |
| ExtendedRenderKitService service = _getExtendedRenderKitService(context); |
| if ((service != null) && |
| service.shortCircuitRenderView(context)) |
| { |
| // Yup, we don't need to do anything |
| ; |
| } |
| else |
| { |
| try |
| { |
| if (service != null) |
| service.encodeBegin(context); |
| |
| super.renderView(context, viewToRender); |
| |
| if (service != null) |
| service.encodeEnd(context); |
| } |
| finally |
| { |
| if (service != null) |
| service.encodeFinally(context); |
| } |
| } |
| |
| // Remove the 'marker' from the request map just in case the entire tree is rendered again |
| reqMap.remove(_RENDER_VIEW_MARKER); |
| } |
| |
| @Override |
| public UIViewRoot restoreView( |
| FacesContext context, |
| String viewId) |
| { |
| //This code processes a "return" event. Most of this logic was moved to |
| //StateManagerImpl because we ran into a problem with JSF where it didn't |
| //set up the JSF mapping properly if we didn't delegate to the default |
| //ViewHandler. There may be other logic associated with the internalView |
| //which might need to be moved to the StateManager as well. This might also |
| //be able to be further optimized if all the other logic in this method passes |
| //through. |
| if(context.getExternalContext().getRequestMap().get(RequestContextImpl.LAUNCH_VIEW) != null) |
| { |
| return super.restoreView(context, viewId); |
| } |
| |
| boolean uptodate = true; |
| |
| if (_isTimestampCheckEnabled(context, viewId)) |
| { |
| try |
| { |
| // Check the timestamp on the physical path |
| String path = _getPath(viewId); |
| synchronized (_timestamps) |
| { |
| Long ts = _timestamps.get(path); |
| if (ts != _NOT_FOUND) |
| { |
| URL url = context.getExternalContext().getResource(path); |
| Long modified = _getLastModified(url); |
| if (modified == _NOT_FOUND) |
| { |
| _timestamps.put(path, _NOT_FOUND); |
| } |
| else if ((ts == null) || |
| (modified.longValue() > ts.longValue())) |
| { |
| _timestamps.put(path, modified); |
| if (ts != null) |
| { |
| _LOG.fine("View document \"" + path + "\" has been modified, " + |
| "ignoring postback for view \"" + viewId +"\""); |
| } |
| uptodate = false; |
| } |
| } |
| } |
| } |
| catch (IOException e) |
| { |
| _LOG.severe(e); |
| } |
| } |
| |
| if (!uptodate) |
| { |
| return null; |
| } |
| |
| UIViewRoot result = super.restoreView(context, viewId); |
| // If we've successfully restored a view, then assume that |
| // this is a postback request. |
| if (result != null) |
| { |
| TrinidadPhaseListener.markPostback(context); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public void writeState( |
| FacesContext context) throws IOException |
| { |
| // After the move of InteralView loading code to the ViewDeclarationFactoryImpl, |
| // this class was not supposed to do anything with the InternalViews. |
| // Unfortunately, writeState() has not been exposed on ViewDeclarationLanguage, |
| // so we have to override this method here. Without an override, JSF save state |
| // marker gets written straight to the response |
| |
| String viewId = context.getViewRoot().getViewId(); |
| ViewDeclarationLanguage vdl = getViewDeclarationLanguage(context, viewId); |
| if (vdl instanceof InternalViewHandlingStrategy) |
| { |
| InternalViewHandlingStrategy strategy = (InternalViewHandlingStrategy)vdl; |
| if (strategy.__isStateless(context, viewId)) |
| { |
| return; |
| } |
| } |
| |
| ExtendedRenderKitService service = _getExtendedRenderKitService(context); |
| if ((service != null) && |
| service.isStateless(context)) |
| return; |
| |
| super.writeState(context); |
| } |
| |
| |
| synchronized private void _initIfNeeded(FacesContext context) |
| { |
| if (!_inited) |
| { |
| _inited = true; |
| String alternateViewHandler = |
| context.getExternalContext().getInitParameter(ALTERNATE_VIEW_HANDLER); |
| if (alternateViewHandler != null) |
| { |
| ViewHandler viewHandlerInstance = null; |
| try |
| { |
| ClassLoader loader = Thread.currentThread().getContextClassLoader(); |
| Class<?> c = loader.loadClass(alternateViewHandler); |
| try |
| { |
| Constructor<?> constructor = c.getConstructor( |
| new Class[]{ViewHandler.class}); |
| viewHandlerInstance = |
| (ViewHandler) constructor.newInstance(new Object[]{_delegate}); |
| } |
| catch (NoSuchMethodException nsme) |
| { |
| viewHandlerInstance = (ViewHandler) c.newInstance(); |
| } |
| } |
| catch (Exception e) |
| { |
| _LOG.warning("CANNOT_LOAD_VIEWHANDLER", alternateViewHandler); |
| _LOG.warning(e); |
| } |
| |
| if (viewHandlerInstance != null) |
| _delegate = viewHandlerInstance; |
| } |
| } |
| } |
| |
| private ExtendedRenderKitService _getExtendedRenderKitService( |
| FacesContext context) |
| { |
| return Service.getService(context.getRenderKit(), |
| ExtendedRenderKitService.class); |
| } |
| |
| /** |
| * Reads org.apache.myfaces.trinidad.CHECK_FILE_MODIFICATION context param and determines if |
| * the flag is enabled or not. |
| * If the context parameter is not specified, default value used is: "false" for |
| * ProjectStage.Production and "true" for all other stages. |
| * For InternalViews this method always return "false" |
| * |
| * @param context |
| * @param viewId |
| * @return |
| */ |
| private boolean _isTimestampCheckEnabled(FacesContext context, String viewId) |
| { |
| if (_checkTimestamp == null) |
| { |
| boolean productionStage = context.isProjectStage(ProjectStage.Production); |
| boolean checkTimestamp; |
| String checkTimestampContextParam = |
| context.getExternalContext().getInitParameter(Configuration.CHECK_TIMESTAMP_PARAM); |
| |
| if (checkTimestampContextParam != null) |
| { |
| checkTimestamp = "true".equals(checkTimestampContextParam); |
| } |
| else |
| { |
| // if the CHECK_TIMESTAMP_PARAM parameter has NOT been specified, let us |
| // apply the DEFAULT values for the certain Project Stages: |
| // -PRODUCTION we want this value to be FALSE; |
| // -other stages we use TRUE |
| checkTimestamp = !productionStage; |
| } |
| |
| _checkTimestamp = Boolean.valueOf(checkTimestamp); |
| |
| // if Apache MyFaces Trinidad is running in ProjectStage.Production, |
| // then CHECK_TIMESTAMP_PARAM should be FALSE, otherwise we generate a WARNING message |
| if (productionStage && checkTimestamp) |
| { |
| _LOG.warning("TIMESTAMP_CHECKING_ENABLED_SHOULDNOT_IN_PRODUCTION", |
| Configuration.CHECK_TIMESTAMP_PARAM); |
| } |
| } |
| |
| // Even if _isTimestampCheckEnabled is TRUE, we do not want to perform the check for the InternalViews |
| if (_checkTimestamp |
| && getViewDeclarationLanguage(context, viewId) instanceof InternalViewHandlingStrategy) |
| { |
| return false; |
| } |
| |
| return _checkTimestamp; |
| } |
| |
| |
| /** |
| * Return the physical path of a particular URI |
| */ |
| static private String _getPath(String uri) |
| { |
| RequestContext afc = RequestContext.getCurrentInstance(); |
| if (afc != null) |
| { |
| return afc.getPageResolver().getPhysicalURI(uri); |
| } |
| |
| // No RequestContext? Just return the URI |
| return uri; |
| } |
| |
| |
| private Long _getLastModified(URL url) throws IOException |
| { |
| if (url == null) |
| return _NOT_FOUND; |
| |
| return Long.valueOf(URLUtils.getLastModified(url)); |
| } |
| |
| // We do not allow random requests into the application when the skin |
| // pregeneration service is enabled. Only skin pregeneration requests |
| // are allowed. |
| private void _checkSkinPregeneration(FacesContext context, String viewId) |
| { |
| if (_skinPregenerationEnabled && !SkinPregenerationService.isPregenerationRequest(viewId)) |
| { |
| ExternalContext external = context.getExternalContext(); |
| String message = _LOG.getMessage("SKIN_PREGEN_ENABLED"); |
| _LOG.severe(message); |
| _sendError(external, message); |
| |
| // We don't explicitly short-circuit/exception out. Calling |
| // responseComplete() achieves the same result (ie. prevents |
| // subsequent processing/rendering) more gracefully. |
| context.responseComplete(); |
| } |
| } |
| |
| // Sends a response error to the client |
| private static void _sendError(ExternalContext external, String message) |
| { |
| try |
| { |
| external.responseSendError(500, message); |
| } |
| catch (IOException e) |
| { |
| _LOG.warning(e); |
| } |
| } |
| |
| private Boolean _checkTimestamp; |
| // Mostly final, but see _initIfNeeded() |
| private ViewHandler _delegate; |
| private final Map<String, Long> _timestamps; |
| private boolean _inited; |
| |
| private final boolean _skinPregenerationEnabled; |
| |
| private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(ViewHandlerImpl.class); |
| private static final Long _NOT_FOUND = Long.valueOf(0); |
| private static final String _RENDER_VIEW_MARKER = "__trRenderViewEntry"; |
| } |