View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.pipeline.valve.impl;
18  
19  import java.io.IOException;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  
24  import javax.portlet.PortletException;
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletResponse;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.jetspeed.PortalReservedParameters;
31  import org.apache.jetspeed.cache.ContentCacheKey;
32  import org.apache.jetspeed.cache.JetspeedContentCache;
33  import org.apache.jetspeed.container.window.PortletWindowAccessor;
34  import org.apache.jetspeed.container.state.MutableNavigationalState;
35  import org.apache.jetspeed.exception.JetspeedException;
36  import org.apache.jetspeed.om.common.portlet.MutablePortletEntity;
37  import org.apache.jetspeed.om.common.portlet.PortletDefinitionComposite;
38  import org.apache.jetspeed.om.page.ContentFragment;
39  import org.apache.jetspeed.om.page.ContentFragmentImpl;
40  import org.apache.jetspeed.om.page.ContentPage;
41  import org.apache.jetspeed.om.page.Fragment;
42  import org.apache.jetspeed.om.page.Page;
43  import org.apache.jetspeed.pipeline.PipelineException;
44  import org.apache.jetspeed.pipeline.valve.AbstractValve;
45  import org.apache.jetspeed.pipeline.valve.ActionValve;
46  import org.apache.jetspeed.pipeline.valve.ValveContext;
47  import org.apache.jetspeed.request.RequestContext;
48  import org.apache.pluto.PortletContainer;
49  import org.apache.pluto.PortletContainerException;
50  import org.apache.pluto.om.entity.PortletEntity;
51  import org.apache.pluto.om.window.PortletWindow;
52  
53  /***
54   * <p>
55   * ActionValveImpl
56   * </p>
57   * 
58   * Default implementation of the ActionValve interface.  Expects to be
59   * called after the ContainerValve has set up the appropriate action window
60   * within the request context.  This should come before ANY rendering takes
61   * place.
62   * 
63   * @author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
64   * @version $Id: ActionValveImpl.java 589933 2007-10-30 01:51:50Z woonsan $
65   *
66   */
67  public class ActionValveImpl extends AbstractValve implements ActionValve
68  {
69  
70      private static final Log log = LogFactory.getLog(ActionValveImpl.class);
71      private PortletContainer container;
72      private PortletWindowAccessor windowAccessor;
73      private boolean patchResponseCommitted = false;
74      private JetspeedContentCache portletContentCache;
75  
76      public ActionValveImpl(PortletContainer container, PortletWindowAccessor windowAccessor, JetspeedContentCache portletContentCache)
77      {
78          this.container = container;
79          this.windowAccessor = windowAccessor;
80          this.portletContentCache = portletContentCache;
81      }
82      
83      public ActionValveImpl(PortletContainer container, PortletWindowAccessor windowAccessor, JetspeedContentCache portletContentCache, boolean patchResponseCommitted)
84      {
85          this.container = container;
86          this.windowAccessor = windowAccessor;
87          this.portletContentCache = portletContentCache;        
88          this.patchResponseCommitted = patchResponseCommitted;
89      }
90  
91      /***
92       * @see org.apache.jetspeed.pipeline.valve.Valve#invoke(org.apache.jetspeed.request.RequestContext, org.apache.jetspeed.pipeline.valve.ValveContext)
93       */
94      public void invoke(RequestContext request, ValveContext context) throws PipelineException
95      {     
96          boolean responseCommitted = false;
97          try
98          {            
99              PortletWindow actionWindow = request.getActionWindow();
100             if (actionWindow != null)
101             {
102                 // If portlet entity is null, try to refresh the actionWindow.
103                 // Under some clustered environments, a cached portlet window could have null entity.
104                 if (null == actionWindow.getPortletEntity())
105                 {
106                     try 
107                     {
108                         Fragment fragment = request.getPage().getFragmentById(actionWindow.getId().toString());
109                         
110                         if (fragment != null)
111                         {
112                             ContentFragment contentFragment = new ContentFragmentImpl(fragment, new HashMap());
113                             actionWindow = this.windowAccessor.getPortletWindow(contentFragment);
114                         }
115                     } 
116                     catch (Exception e)
117                     {
118                         log.error("Failed to refresh action window.", e);
119                     }
120                 }
121                 
122                 if (actionWindow.getPortletEntity() == null)
123                 {
124                     // a session is expired and the target actionWindow doesn't have portlet entity.
125                     // Redirect the user back to the target page (with possibly retaining the other windows navigational state).
126                     log.warn("Portlet action was canceled because the session was expired. The actionWindow's id is " + actionWindow.getId());
127                     
128                     request.setActionWindow(null);
129                     MutableNavigationalState state = (MutableNavigationalState) request.getPortalURL().getNavigationalState();
130                     
131                     if (state != null)
132                     {
133                         state.removeState(actionWindow);
134                         state.sync(request);
135                         request.getResponse().sendRedirect(request.getPortalURL().getPortalURL());
136                         return;
137                     }
138                 }
139 
140                 initWindow(actionWindow, request);
141                 HttpServletResponse response = request.getResponseForWindow(actionWindow);
142                 HttpServletRequest requestForWindow = request.getRequestForWindow(actionWindow);
143                 requestForWindow.setAttribute(PortalReservedParameters.REQUEST_CONTEXT_ATTRIBUTE, request);
144                 
145                 //PortletMessagingImpl msg = new PortletMessagingImpl(windowAccessor);
146                 
147                 requestForWindow.setAttribute("JETSPEED_ACTION", request);
148                 container.processPortletAction(
149                     actionWindow,
150                     requestForWindow,
151                     response);
152                 // The container redirects the client after PortletAction processing
153                 // so there is no need to continue the pipeline
154                 
155                 //msg.processActionMessage("todo", request);
156                 
157                 // clear the cache for all portlets on the current page
158                 clearPortletCacheForPage(request, actionWindow);
159                 
160                 if (patchResponseCommitted)
161                 {
162                     responseCommitted = true;
163                 }
164                 else
165                 {
166                     responseCommitted = response.isCommitted();
167                 }
168                 request.setAttribute(PortalReservedParameters.PIPELINE, null); // clear the pipeline
169             }
170         }
171         catch (PortletContainerException e)
172         {
173             log.fatal("Unable to retrieve portlet container!", e);
174             throw new PipelineException("Unable to retrieve portlet container!", e);
175         }
176         catch (PortletException e)
177         {
178             log.warn("Unexpected PortletException in ActionValveImpl", e);
179             //  throw new PipelineException("Unexpected PortletException in ActionValveImpl", e);
180 
181         }
182         catch (IOException e)
183         {
184             log.error("Unexpected IOException in ActionValveImpl", e);
185             // throw new PipelineException("Unexpected IOException in ActionValveImpl", e);
186         }
187         catch (IllegalStateException e)
188         {
189             log.error("Illegal State Exception. Response was written to in Action Phase", e);
190             responseCommitted = true;
191         }
192         catch (Throwable t)
193         {
194             log.error("Unknown exception processing Action", t);
195         }
196         finally
197         {
198             // Check if an action was processed and if its response has been committed
199             // (Pluto will redirect the client after PorletAction processing)
200             if ( responseCommitted )
201             {
202                 log.info("Action processed and response committed (pipeline processing stopped)");
203             }
204             else
205             {
206                 // Pass control to the next Valve in the Pipeline
207                 context.invokeNext(request);
208             }
209         }
210 
211     }
212 
213     protected void clearPortletCacheForPage(RequestContext request, PortletWindow actionWindow)
214     throws JetspeedException
215     {
216         ContentPage page = request.getPage();
217         if (null == page)
218         {
219             throw new JetspeedException("Failed to find PSML Pin ContentPageAggregator.build");
220         }
221         ContentFragment root = page.getRootContentFragment();
222         if (root == null)
223         {
224             throw new JetspeedException("No root ContentFragment found in ContentPage");
225         }
226         if (!isNonStandardAction(actionWindow))
227         {
228             notifyFragments(root, request, page);
229             
230             // if the fragment is rendered from a decorator template, the target cache would not be cleared by the above notification.
231             // so, let's clear target cache of action window directly again.
232             String fragmentId = actionWindow.getId().toString();
233             if (page.getFragmentById(fragmentId) == null)
234             {
235                 clearTargetCache(fragmentId, request);
236             }
237         }
238         else
239         {
240             ContentFragment fragment = page.getContentFragmentById(actionWindow.getId().toString());
241             
242             if (fragment != null)
243             {
244                 clearTargetCache(fragment, request);
245             }
246             else
247             {
248                 clearTargetCache(actionWindow.getId().toString(), request);
249             }
250         }
251     }
252     
253     /***
254      * Actions can be marked as non-standard if they don't participate in
255      * JSR-168 standard action behavior. By default, actions are supposed
256      * to clear the cache of all other portlets on the page.
257      * By setting this parameter, we can ignore the standard behavior
258      * and not clear the cache on actions. This is useful for portlets
259      * which never participate with other portlets.
260      * 
261      */    
262     protected boolean isNonStandardAction(PortletWindow actionWindow)
263     {
264         PortletEntity entity = actionWindow.getPortletEntity();
265         if (entity != null)
266         {
267             PortletDefinitionComposite portletDefinition = (PortletDefinitionComposite)entity.getPortletDefinition();
268             if (portletDefinition != null)
269             {
270                 Collection actionList = null;
271         
272                 if (portletDefinition != null)
273                 {
274                     actionList = portletDefinition.getMetadata().getFields(PortalReservedParameters.PORTLET_EXTENDED_DESCRIPTOR_NON_STANDARD_ACTION);
275                 }
276                 if (actionList != null) 
277                 {
278                     if (!actionList.isEmpty())
279                         return true;
280                 }
281             }
282         }
283         return false;
284     }
285    
286     protected void notifyFragments(ContentFragment f, RequestContext context, ContentPage page)
287     {
288         if (f.getContentFragments() != null && f.getContentFragments().size() > 0)
289         {
290             Iterator children = f.getContentFragments().iterator();
291             while (children.hasNext())
292             {
293                 ContentFragment child = (ContentFragment) children.next();
294                 if (!"hidden".equals(f.getState()))
295                 {
296                     notifyFragments(child, context, page);
297                 }
298             } 
299         }    
300         ContentCacheKey cacheKey = portletContentCache.createCacheKey(context, f.getId());
301         if (portletContentCache.isKeyInCache(cacheKey))
302         {
303             portletContentCache.remove(cacheKey);
304             portletContentCache.invalidate(context);
305         }
306     }
307 
308     protected void clearTargetCache(ContentFragment f, RequestContext context)
309     {
310         clearTargetCache(f.getId(), context);
311     }
312     
313     protected void clearTargetCache(String fragmentId, RequestContext context)
314     {
315         ContentCacheKey cacheKey = portletContentCache.createCacheKey(context, fragmentId);
316         
317         if (portletContentCache.isKeyInCache(cacheKey))
318         {
319             portletContentCache.remove(cacheKey);
320             portletContentCache.invalidate(context);
321         }
322     }
323     
324     /***
325      * @see java.lang.Object#toString()
326      */
327     public String toString()
328     {
329         // TODO Auto-generated method stub
330         return "ActionValveImpl";
331     }
332     
333     /***
334      * Makes sure that this PortletWindow's PortletEntity is set to have the
335      * current requests fragment.
336      * @param window
337      * @param request
338      */
339     protected void initWindow(PortletWindow window, RequestContext request)
340     {
341         Page page = request.getPage();
342         Fragment fragment = page.getFragmentById(window.getId().toString());
343         
344         if (fragment != null)
345         {
346             ((MutablePortletEntity)window.getPortletEntity()).setFragment(fragment);
347         }
348     }
349 
350 }