com.sun.faces.application.ViewHandlerImpl Maven / Gradle / Ivy
Show all versions of jsf-impl Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
// ViewHandlerImpl.java
package com.sun.faces.application;
import com.sun.faces.RIConstants;
import com.sun.faces.config.WebConfiguration;
import com.sun.faces.config.WebConfiguration.WebContextInitParameter;
import com.sun.faces.io.FastStringWriter;
import com.sun.faces.util.MessageUtils;
import com.sun.faces.util.Util;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.RequestStateManager;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.StateManager;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.ResponseStateManager;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.jstl.core.Config;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.net.MalformedURLException;
/**
*
* This is the default implementation for JSF 1.2.
*
*
* While this class isn't used by the 2.0 runtime, it's kept for binary
* compatibility with those that extend from this class directly.
*
*
* @deprecated Refer to {@link com.sun.faces.application.view.MultiViewHandler}
*/
public class ViewHandlerImpl extends ViewHandler {
// Log instance for this class
private static final Logger logger = FacesLogger.APPLICATION.getLogger();
private ApplicationAssociate associate;
private String[] configuredExtensions;
private int bufSize = -1;
public ViewHandlerImpl() {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,"Created ViewHandler instance ");
}
WebConfiguration config = WebConfiguration.getInstance();
String defaultSuffixConfig =
config.getOptionValue(WebConfiguration.WebContextInitParameter.DefaultSuffix);
Map appMap = FacesContext.getCurrentInstance().getExternalContext().getApplicationMap();
configuredExtensions = Util.split(appMap, defaultSuffixConfig, " ");
}
/**
* Do not call the default implementation of {@link ViewHandler#initView(javax.faces.context.FacesContext)}
* if the {@link javax.faces.context.ExternalContext#getRequestCharacterEncoding()} returns a
* non-null
result.
*
* @see ViewHandler#initView(javax.faces.context.FacesContext)
*/
@Override
public void initView(FacesContext context) throws FacesException {
if (context.getExternalContext().getRequestCharacterEncoding() == null) {
super.initView(context);
}
}
public void renderView(FacesContext context,
UIViewRoot viewToRender) throws IOException,
FacesException {
// suppress rendering if "rendered" property on the component is
// false
if (!viewToRender.isRendered()) {
return;
}
ExternalContext extContext = context.getExternalContext();
ServletRequest request = (ServletRequest) extContext.getRequest();
ServletResponse response = (ServletResponse) extContext.getResponse();
try {
if (executePageToBuildView(context, viewToRender)) {
response.flushBuffer();
ApplicationAssociate associate = getAssociate(context);
if (associate != null) {
associate.responseRendered();
}
return;
}
} catch (IOException e) {
throw new FacesException(e);
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Completed building view for : \n" +
viewToRender.getViewId());
}
// set up the ResponseWriter
RenderKitFactory renderFactory = (RenderKitFactory)
FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
RenderKit renderKit =
renderFactory.getRenderKit(context, viewToRender.getRenderKitId());
ResponseWriter oldWriter = context.getResponseWriter();
if (bufSize == -1) {
WebConfiguration webConfig =
WebConfiguration
.getInstance(context.getExternalContext());
try {
bufSize = Integer
.parseInt(webConfig.getOptionValue(
WebContextInitParameter.ResponseBufferSize));
} catch (NumberFormatException nfe) {
bufSize = Integer
.parseInt(WebContextInitParameter.ResponseBufferSize.getDefaultValue());
}
}
WriteBehindStateWriter stateWriter =
new WriteBehindStateWriter(response.getWriter(),
context,
bufSize);
ResponseWriter newWriter;
if (null != oldWriter) {
newWriter = oldWriter.cloneWithWriter(stateWriter);
} else {
newWriter = renderKit.createResponseWriter(stateWriter,
null,
request.getCharacterEncoding());
}
context.setResponseWriter(newWriter);
try {
newWriter.startDocument();
doRenderView(context, viewToRender);
newWriter.endDocument();
// replace markers in the body content and write it to response.
// flush directly to the response
if (stateWriter.stateWritten()) {
stateWriter.flushToWriter();
}
}
finally {
// 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
Map stateMap = RequestStateManager.getStateMap(context);
ViewHandlerResponseWrapper wrapper = (ViewHandlerResponseWrapper)
stateMap.remove(RequestStateManager.AFTER_VIEW_CONTENT);
if (null != wrapper) {
wrapper.flushToWriter(response.getWriter(),
response.getCharacterEncoding());
}
response.flushBuffer();
}
/**
* This is a separate method to account for handling the content
* after the view tag.
*
* Create a new ResponseWriter around this response's Writer.
* Set it into the FacesContext, saving the old one aside.
*
* call encodeBegin(), encodeChildren(), encodeEnd() on the
* argument UIViewRoot
.
*
* Restore the old ResponseWriter into the FacesContext.
*
* Write out the after view content to the response's writer.
*
* Flush the response buffer, and remove the after view content
* from the request scope.
*
* @param context the FacesContext
for the current request
* @param viewToRender the view to render
* @throws IOException if an error occurs rendering the view to the client
* @throws FacesException if some error occurs within the framework
* processing
*/
private void doRenderView(FacesContext context,
UIViewRoot viewToRender)
throws IOException, FacesException {
ApplicationAssociate associate = getAssociate(context);
if (null != associate) {
associate.responseRendered();
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "About to render view " + viewToRender.getViewId());
}
viewToRender.encodeAll(context);
}
public UIViewRoot restoreView(FacesContext context, String viewId) {
if (context == null) {
String message = MessageUtils.getExceptionMessageString
(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
throw new NullPointerException(message);
}
ExternalContext extContext = context.getExternalContext();
String mapping = Util.getFacesMapping(context);
UIViewRoot viewRoot = null;
if (mapping != null) {
if (!Util.isPrefixMapped(mapping)) {
viewId = convertViewId(context, viewId);
} else {
viewId = normalizeRequestURI(viewId, mapping);
}
}
// maping could be null if a non-faces request triggered
// this response.
if (extContext.getRequestPathInfo() == null && mapping != null &&
Util.isPrefixMapped(mapping)) {
// this was probably an initial request
// send them off to the root of the web application
try {
context.responseComplete();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Response Complete for" + viewId);
}
extContext.redirect(extContext.getRequestContextPath());
} catch (IOException ioe) {
throw new FacesException(ioe);
}
} else {
// this is necessary to allow decorated impls.
ViewHandler outerViewHandler =
context.getApplication().getViewHandler();
String renderKitId =
outerViewHandler.calculateRenderKitId(context);
viewRoot = Util.getStateManager(context).restoreView(context,
viewId,
renderKitId);
}
return viewRoot;
}
public UIViewRoot createView(FacesContext context, String viewId) {
if (context == null) {
String message = MessageUtils.getExceptionMessageString
(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
throw new NullPointerException(message);
}
UIViewRoot result = (UIViewRoot)
context.getApplication().createComponent(UIViewRoot.COMPONENT_TYPE);
if (viewId != null) {
String mapping = Util.getFacesMapping(context);
if (mapping != null) {
if (!Util.isPrefixMapped(mapping)) {
viewId = convertViewId(context, viewId);
} else {
viewId = normalizeRequestURI(viewId, mapping);
if (!Util.isPortletRequest(context) && 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(context);
}
}
}
result.setViewId(viewId);
}
Locale locale = null;
String renderKitId = null;
// use the locale from the previous view if is was one which will be
// the case if this is called from NavigationHandler. There wouldn't be
// one for the initial case.
if (context.getViewRoot() != null) {
locale = context.getViewRoot().getLocale();
renderKitId = context.getViewRoot().getRenderKitId();
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Created new view for " + viewId);
}
// PENDING(): not sure if we should set the RenderKitId here.
// The UIViewRoot ctor sets the renderKitId to the default
// one.
// if there was no locale from the previous view, calculate the locale
// for this view.
if (locale == null) {
locale =
context.getApplication().getViewHandler().calculateLocale(
context);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Locale for this view as determined by calculateLocale "
+ locale.toString());
}
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Using locale from previous view "
+ locale.toString());
}
}
if (renderKitId == null) {
renderKitId =
context.getApplication().getViewHandler().calculateRenderKitId(
context);
if (logger.isLoggable(Level.FINE)) {
logger.fine(
"RenderKitId for this view as determined by calculateRenderKitId "
+ renderKitId);
}
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Using renderKitId from previous view "
+ renderKitId);
}
}
result.setLocale(locale);
result.setRenderKitId(renderKitId);
return result;
}
/**
* 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 FacesContext
for the current request
* @param viewToExecute the view to build
* @return true
if the response should be immediately flushed
* to the client, otherwise false
* @throws 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();
Map stateMap = RequestStateManager.getStateMap(context);
if ("/*".equals(stateMap.get(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);
// build the view by executing the page
extContext.dispatch(requestURI);
if (logger.isLoggable(Level.FINE)) {
logger.fine("After dispacthMessage to viewId " + requestURI);
}
// 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
stateMap.put(RequestStateManager.AFTER_VIEW_CONTENT,
wrapped);
return false;
}
public Locale calculateLocale(FacesContext context) {
if (context == null) {
String message = MessageUtils.getExceptionMessageString
(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
throw new NullPointerException(message);
}
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 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;
}
public String calculateRenderKitId(FacesContext context) {
if (context == null) {
String message = MessageUtils.getExceptionMessageString
(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
throw new NullPointerException(message);
}
Map requestParamMap = context.getExternalContext()
.getRequestParameterMap();
String result = requestParamMap.get(
ResponseStateManager.RENDER_KIT_ID_PARAM);
if (result == null) {
result = context.getApplication().getDefaultRenderKitId();
if (null == result) {
result = RenderKitFactory.HTML_BASIC_RENDER_KIT;
}
}
return result;
}
/**
* Attempts to find a matching locale based on pref
and
* list of supported locales, using the matching algorithm
* as described in JSTL 8.3.2.
* @param context the FacesContext
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 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;
}
public void writeState(FacesContext context) throws IOException {
if (context == null) {
String message = MessageUtils.getExceptionMessageString
(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
throw new NullPointerException(message);
}
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());
}
}
public String getActionURL(FacesContext context, String viewId) {
if (context == null) {
String message = MessageUtils.getExceptionMessageString
(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
throw new NullPointerException(message);
}
if (viewId == null) {
String message = MessageUtils.getExceptionMessageString
(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "viewId");
throw new NullPointerException(message);
}
if (0 == viewId.length() || 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
for (String extension : configuredExtensions) {
if (viewId.endsWith(extension)) {
return (contextPath + viewId.substring(0, viewId.lastIndexOf('.')) + mapping);
}
}
return (contextPath + viewId);
}
public String getResourceURL(FacesContext context, String path) {
ExternalContext extContext = context.getExternalContext();
if (path.charAt(0) == '/' && !path.startsWith(extContext.getRequestContextPath())) {
return (extContext.getRequestContextPath() + path);
} else {
return path;
}
}
/**
* if the specified mapping is a prefix mapping, and the provided
* request URI (usually the value from ExternalContext.getRequestServletPath()
)
* starts with mapping + '/'
, 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
*/
private 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;
}
}
private 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 FacesContext}
* @param viewId incoming view ID
* @return the view ID with an altered suffix mapping (if necessary)
*/
private 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) {
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;
}
private ApplicationAssociate getAssociate(FacesContext context) {
if (associate == null) {
associate = ApplicationAssociate.getInstance(context.getExternalContext());
}
return associate;
}
private static ViewHandlerResponseWrapper getWrapper(ExternalContext extContext) {
Object response = extContext.getResponse();
if (response instanceof HttpServletResponse) {
return new ViewHandlerResponseWrapper((HttpServletResponse) response);
}
throw new IllegalArgumentException();
}
// ----------------------------------------------------------- Inner Classes
/**
* Thanks to the Facelets folks for some of the concepts incorporated
* into this class.
*/
private static final class WriteBehindStateWriter extends Writer {
// length of the state marker
private static final int STATE_MARKER_LEN =
RIConstants.SAVESTATE_FIELD_MARKER.length();
private static final ThreadLocal CUR_WRITER =
new ThreadLocal();
private Writer out;
private Writer orig;
private FastStringWriter fWriter;
private boolean stateWritten;
private int bufSize;
private char[] buf;
private FacesContext context;
// -------------------------------------------------------- Constructors
public WriteBehindStateWriter(Writer out, FacesContext context, int bufSize) {
this.out = out;
this.orig = out;
this.context = context;
this.bufSize = bufSize;
this.buf = new char[bufSize];
CUR_WRITER.set(this);
}
// ------------------------------------------------- Methods from Writer
public void write(int c) throws IOException {
out.write(c);
}
public void write(char cbuf[]) throws IOException {
out.write(cbuf);
}
public void write(String str) throws IOException {
out.write(str);
}
public void write(String str, int off, int len) throws IOException {
out.write(str, off, len);
}
public void write(char cbuf[], int off, int len) throws IOException {
out.write(cbuf, off, len);
}
public void flush() throws IOException {
// no-op
}
public void close() throws IOException {
// no-op
}
// ------------------------------------------------------ Public Methods
public static WriteBehindStateWriter getCurrentInstance() {
return CUR_WRITER.get();
}
public void release() {
CUR_WRITER.remove();
}
public void writingState() {
if (!stateWritten) {
this.stateWritten = true;
out = fWriter = new FastStringWriter(1024);
}
}
public boolean stateWritten() {
return stateWritten;
}
/**
* Write directly from our FastStringWriter to the provided
* writer.
* @throws IOException if an error occurs
*/
public void flushToWriter() throws IOException {
// Save the state to a new instance of StringWriter to
// avoid multiple serialization steps if the view contains
// multiple forms.
StateManager stateManager = Util.getStateManager(context);
ResponseWriter origWriter = context.getResponseWriter();
FastStringWriter state =
new FastStringWriter((stateManager.isSavingStateInClient(
context)) ? bufSize : 128);
context.setResponseWriter(origWriter.cloneWithWriter(state));
stateManager.writeState(context, stateManager.saveView(context));
context.setResponseWriter(origWriter);
StringBuilder builder = fWriter.getBuffer();
// begin writing...
int totalLen = builder.length();
StringBuilder stateBuilder = state.getBuffer();
int stateLen = stateBuilder.length();
int pos = 0;
int tildeIdx = getNextDelimiterIndex(builder, pos);
while (pos < totalLen) {
if (tildeIdx != -1) {
if (tildeIdx > pos && (tildeIdx - pos) > bufSize) {
// there's enough content before the first ~
// to fill the entire buffer
builder.getChars(pos, (pos + bufSize), buf, 0);
orig.write(buf);
pos += bufSize;
} else {
// write all content up to the first '~'
builder.getChars(pos, tildeIdx, buf, 0);
int len = (tildeIdx - pos);
orig.write(buf, 0, len);
// now check to see if the state saving string is
// at the begining of pos, if so, write our
// state out.
if (builder.indexOf(
RIConstants.SAVESTATE_FIELD_MARKER,
pos) == tildeIdx) {
// buf is effectively zero'd out at this point
int statePos = 0;
while (statePos < stateLen) {
if ((stateLen - statePos) > bufSize) {
// enough state to fill the buffer
stateBuilder.getChars(statePos,
(statePos + bufSize),
buf,
0);
orig.write(buf);
statePos += bufSize;
} else {
int slen = (stateLen - statePos);
stateBuilder.getChars(statePos,
stateLen,
buf,
0);
orig.write(buf, 0, slen);
statePos += slen;
}
}
// push us past the last '~' at the end of the marker
pos += (len + STATE_MARKER_LEN);
tildeIdx = getNextDelimiterIndex(builder, pos);
} else {
pos = tildeIdx;
tildeIdx = getNextDelimiterIndex(builder,
tildeIdx + 1);
}
}
} else {
// we've written all of the state field markers.
// finish writing content
if (totalLen - pos > bufSize) {
// there's enough content to fill the buffer
builder.getChars(pos, (pos + bufSize), buf, 0);
orig.write(buf);
pos += bufSize;
} else {
// we're near the end of the response
builder.getChars(pos, totalLen, buf, 0);
int len = (totalLen - pos);
orig.write(buf, 0, len);
pos += (len + 1);
}
}
}
// all state has been written. Have 'out' point to the
// response so that all subsequent writes will make it to the
// browser.
out = orig;
}
private static int getNextDelimiterIndex(StringBuilder builder,
int offset) {
return builder.indexOf(RIConstants.SAVESTATE_FIELD_DELIMITER,
offset);
}
}
}