View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.context;
21  
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  import org.apache.myfaces.tobago.internal.util.FastStringWriter;
25  
26  import javax.faces.context.FacesContext;
27  import javax.faces.context.ResponseWriter;
28  import java.io.IOException;
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.Map;
32  
33  /**
34   * In some cases the rendered output must be places in the Response in a different order than it was rendered.
35   * The <code>ResponseWriterDivider</code> helps to manage a list of buffers which holds the temporary output.
36   */
37  public final class ResponseWriterDivider {
38  
39    private static final Logger LOG = LoggerFactory.getLogger(ResponseWriterDivider.class);
40  
41    private List<ResponseWriter> writers;
42    private List<FastStringWriter> buffers;
43    
44    private ResponseWriter original;
45    
46    private int current;
47    
48    private String nameInRequest;
49  
50    public static ResponseWriterDivider getInstance(final FacesContext facesContext, final String nameInRequest) {
51      final Map<String, Object> map = facesContext.getExternalContext().getRequestMap();
52      ResponseWriterDivider divider = (ResponseWriterDivider) map.get(nameInRequest);
53      if (divider == null) {
54        divider = new ResponseWriterDivider(facesContext);
55        map.put(nameInRequest, divider);
56        divider.nameInRequest = nameInRequest;
57        
58      }
59      return divider;
60    }
61  
62    private ResponseWriterDivider(final FacesContext facesContext) {
63      writers = new ArrayList<ResponseWriter>();
64      buffers = new ArrayList<FastStringWriter>();
65      current = -1;
66      original = facesContext.getResponseWriter();
67    }
68  
69    /**
70     * Create (if needed) and activate a new branch. 
71     * After this call, all output will be stored in this new branch. 
72     * <p>
73     * It is usually needed to get the response writer again with HtmlRendererUtils.getTobagoResponseWriter();
74     * @return true if the branch was not created new. So the branch was already existent.
75     */
76    public boolean activateBranch(final FacesContext facesContext) {
77  
78      assert writers.size() == buffers.size();
79  
80      boolean created = true;
81      current++;
82      if (writers.size() == current) {
83        final FastStringWriter buffer = new FastStringWriter();
84        buffers.add(buffer);
85        final ResponseWriter newWriter = facesContext.getResponseWriter().cloneWithWriter(buffer);
86        writers.add(newWriter);
87        created = false;
88      }
89      facesContext.setResponseWriter(writers.get(current));
90      if (LOG.isDebugEnabled()) {
91        LOG.debug(this.toString());
92      }
93      return created;
94    }
95    
96    /**
97     * Passivate the current branch. 
98     * After this call, all output will be written in the former branch (if any) or into the original writer.
99     * <p>
100    * It is usually needed to get the response writer again with HtmlRendererUtils.getTobagoResponseWriter();
101    * @return true, if the current writer is not the original writer. So the "stack" is at the bottom. 
102    */
103   public boolean passivateBranch(final FacesContext facesContext) {
104 
105     assert writers.size() == buffers.size();
106     
107     current--;
108     if (current >= 0) {
109       facesContext.setResponseWriter(writers.get(current));
110       if (LOG.isDebugEnabled()) {
111         LOG.debug(this.toString());
112       }
113       return true;
114     } else {
115       facesContext.setResponseWriter(original);
116       if (LOG.isDebugEnabled()) {
117         LOG.debug(this.toString());
118       }
119       return false;
120     }
121   }
122 
123   /**
124    * Write the collected stuff in the original writer.
125    * This is always the last call on this object.
126    */
127   public void writeOutAndCleanUp(final FacesContext facesContext) throws IOException {
128     facesContext.setResponseWriter(original);
129     for (final FastStringWriter buffer : buffers) {
130       original.write(buffer.toString());
131     }
132     // clean up.
133     final Map<String, Object> map = facesContext.getExternalContext().getRequestMap();
134     map.remove(nameInRequest);
135   }
136 
137   @Override
138   public String toString() {
139     final StringBuilder builder = new StringBuilder();
140     builder.append("StringBuilder(");
141     builder.append(System.identityHashCode(this));
142     builder.append(") current=");
143     builder.append(current);
144     builder.append("\n");
145     int i = 0;
146     for (final FastStringWriter buffer : buffers) {
147       builder.append("\n- buffer ");
148       builder.append(i++);
149       builder.append(" ------------------------------------------------------------\n");
150       builder.append(buffer.toString());
151     }
152     builder.append("\n-----------------------------------------------------------------------\n");
153     return builder.toString();
154   }
155 }