1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.web

File XWikiAction.java

 

Coverage histogram

../../../../img/srcFileCovDistChart8.png
56% of files have more coverage

Code metrics

128
311
20
1
947
575
110
0.35
15.55
20
5.5

Classes

Class Line # Actions
XWikiAction 121 311 0% 110 128
0.721132972.1%
 

Contributing tests

This file is covered by 45 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.web;
21   
22    import java.io.IOException;
23    import java.net.URL;
24    import java.util.Arrays;
25    import java.util.List;
26    import java.util.Vector;
27   
28    import javax.script.ScriptContext;
29    import javax.servlet.ServletException;
30    import javax.servlet.http.HttpServletRequest;
31    import javax.servlet.http.HttpServletResponse;
32   
33    import org.apache.commons.lang3.StringUtils;
34    import org.apache.commons.lang3.exception.ExceptionUtils;
35    import org.apache.struts.action.Action;
36    import org.apache.struts.action.ActionForm;
37    import org.apache.struts.action.ActionForward;
38    import org.apache.struts.action.ActionMapping;
39    import org.apache.velocity.VelocityContext;
40    import org.slf4j.Logger;
41    import org.slf4j.LoggerFactory;
42    import org.xwiki.bridge.event.ActionExecutedEvent;
43    import org.xwiki.bridge.event.ActionExecutingEvent;
44    import org.xwiki.component.util.DefaultParameterizedType;
45    import org.xwiki.container.Container;
46    import org.xwiki.container.servlet.ServletContainerException;
47    import org.xwiki.container.servlet.ServletContainerInitializer;
48    import org.xwiki.context.Execution;
49    import org.xwiki.context.ExecutionContext;
50    import org.xwiki.csrf.CSRFToken;
51    import org.xwiki.job.event.status.JobProgressManager;
52    import org.xwiki.job.internal.DefaultJobProgress;
53    import org.xwiki.localization.ContextualLocalizationManager;
54    import org.xwiki.model.EntityType;
55    import org.xwiki.model.reference.DocumentReference;
56    import org.xwiki.model.reference.DocumentReferenceResolver;
57    import org.xwiki.model.reference.EntityReference;
58    import org.xwiki.model.reference.EntityReferenceProvider;
59    import org.xwiki.model.reference.EntityReferenceSerializer;
60    import org.xwiki.model.reference.EntityReferenceValueProvider;
61    import org.xwiki.model.reference.SpaceReference;
62    import org.xwiki.model.reference.WikiReference;
63    import org.xwiki.observation.ObservationManager;
64    import org.xwiki.observation.WrappedThreadEventListener;
65    import org.xwiki.rendering.async.AsyncContext;
66    import org.xwiki.rendering.internal.transformation.MutableRenderingContext;
67    import org.xwiki.rendering.syntax.Syntax;
68    import org.xwiki.rendering.transformation.RenderingContext;
69    import org.xwiki.resource.NotFoundResourceHandlerException;
70    import org.xwiki.resource.ResourceReference;
71    import org.xwiki.resource.ResourceReferenceHandler;
72    import org.xwiki.resource.ResourceReferenceManager;
73    import org.xwiki.resource.ResourceType;
74    import org.xwiki.resource.entity.EntityResourceReference;
75    import org.xwiki.resource.internal.DefaultResourceReferenceHandlerChain;
76    import org.xwiki.script.ScriptContextManager;
77    import org.xwiki.template.TemplateManager;
78    import org.xwiki.velocity.VelocityManager;
79   
80    import com.xpn.xwiki.XWiki;
81    import com.xpn.xwiki.XWikiContext;
82    import com.xpn.xwiki.XWikiException;
83    import com.xpn.xwiki.doc.XWikiDocument;
84    import com.xpn.xwiki.monitor.api.MonitorPlugin;
85    import com.xpn.xwiki.objects.BaseObject;
86    import com.xpn.xwiki.plugin.fileupload.FileUploadPlugin;
87   
88    /**
89    * <p>
90    * Root class for most XWiki actions. It provides a common framework that allows actions to execute just the specific
91    * action code, handling the extra activities, such as preparing the context and retrieving the document corresponding
92    * to the URL.
93    * </p>
94    * <p>
95    * It defines two methods, {@link #action(XWikiContext)} and {@link #render(XWikiContext)}, that should be overridden by
96    * specific actions. {@link #action(XWikiContext)} should contain the processing part of the action.
97    * {@link #render(XWikiContext)} should return the name of a template that should be rendered, or manually write to the
98    * {@link XWikiResponse response} stream.
99    * </p>
100    * <p>
101    * Serving a request goes through the following phases:
102    * </p>
103    * <ul>
104    * <li>Wrapping the request and response object in XWiki specific wrappers</li>
105    * <li>Prepare the request {@link XWikiContext XWiki-specific context}</li>
106    * <li>Initialize/retrieve the XWiki object corresponding to the requested wiki</li>
107    * <li>Handle file uploads</li>
108    * <li>Prepare the velocity context</li>
109    * <li>Prepare the document objects corresponding to the requested URL</li>
110    * <li>Send action pre-notifications to listeners</li>
111    * <li>Run the overridden {@link #action(XWikiContext)}</li>
112    * <li>If {@link #action(XWikiContext)} returns true, run the overridden {@link #render(XWikiContext)}</li>
113    * <li>If {@link #render(XWikiContext)} returned a string (template name), render the template with that name</li>
114    * <li>Send action post-notifications to listeners</li>
115    * </ul>
116    * <p>
117    * During this process, also handle specific errors, like when a document does not exist, or the user does not have the
118    * right to perform the current action.
119    * </p>
120    */
 
121    public abstract class XWikiAction extends Action
122    {
123    public static final String ACTION_PROGRESS = "actionprogress";
124   
125    private static final Logger LOGGER = LoggerFactory.getLogger(XWikiAction.class);
126   
127    /**
128    * Actions that need to be resolved on the main wiki instead of the current non-existing wiki. This is used to be
129    * able to render the skin even on a wiki that doesn't exist.
130    */
131    private static final List<String> ACTIONS_IGNORED_WHEN_WIKI_DOES_NOT_EXIST =
132    Arrays.asList("skin", "ssx", "jsx", "download");
133   
134    /**
135    * Indicate if the action allow asynchronous display (among which the XWiki initialization).
136    */
137    protected boolean waitForXWikiInitialization = true;
138   
139    /**
140    * Indicate if the XWiki.RedirectClass is handled by the action (see handleRedirectObject()).
141    */
142    protected boolean handleRedirectObject = false;
143   
144    private ContextualLocalizationManager localization;
145   
146    private JobProgressManager progress;
147   
148    private ScriptContextManager scriptContextManager;
149   
 
150  148 toggle protected ContextualLocalizationManager getLocalization()
151    {
152  148 if (this.localization == null) {
153  37 this.localization = Utils.getComponent(ContextualLocalizationManager.class);
154    }
155   
156  148 return this.localization;
157    }
158   
 
159  148 toggle protected String localizePlainOrKey(String key, Object... parameters)
160    {
161  148 return StringUtils.defaultString(getLocalization().getTranslationPlain(key, parameters), key);
162    }
163   
 
164  442659 toggle protected JobProgressManager getProgress()
165    {
166  442820 if (this.progress == null) {
167  483 this.progress = Utils.getComponent(JobProgressManager.class);
168    }
169   
170  442795 return this.progress;
171    }
172   
173    /**
174    * @return the current unmodified {@link ScriptContext} instance
175    * @since 8.3M1
176    */
 
177  795 toggle protected ScriptContext getCurrentScriptContext()
178    {
179  795 if (this.scriptContextManager == null) {
180  82 this.scriptContextManager = Utils.getComponent(ScriptContextManager.class);
181    }
182   
183  795 return this.scriptContextManager.getCurrentScriptContext();
184    }
185   
186    /**
187    * Handle server requests.
188    *
189    * @param mapping The ActionMapping used to select this instance
190    * @param form The optional ActionForm bean for this request (if any)
191    * @param req The HTTP request we are processing
192    * @param resp The HTTP response we are creating
193    * @throws IOException if an input/output error occurs
194    * @throws ServletException if a servlet exception occurs
195    */
 
196  40582 toggle @Override
197    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest req,
198    HttpServletResponse resp) throws Exception
199    {
200  40586 ActionForward actionForward;
201  40587 XWikiContext context = null;
202   
203  40584 try {
204    // Initialize the XWiki Context which is the main object used to pass information across
205    // classes/methods. It's also wrapping the request, response, and all container objects
206    // in general.
207  40584 context = initializeXWikiContext(mapping, form, req, resp);
208   
209    // From this line forward all information can be found in the XWiki Context.
210  40564 actionForward = execute(context);
211    } finally {
212  40583 if (context != null) {
213  40570 cleanupComponents();
214    }
215    }
216   
217  40575 return actionForward;
218    }
219   
 
220  40551 toggle public ActionForward execute(XWikiContext context) throws Exception
221    {
222  40583 MonitorPlugin monitor = null;
223  40573 FileUploadPlugin fileupload = null;
224  40579 DefaultJobProgress actionProgress = null;
225  40565 ObservationManager om = Utils.getComponent(ObservationManager.class);
226  40545 Execution execution = Utils.getComponent(Execution.class);
227  40563 String docName = "";
228   
229  40565 boolean debug = StringUtils.equals(context.getRequest().get("debug"), "true");
230   
231  40558 String sasync = context.getRequest().get("async");
232   
233  40578 try {
234  40583 String action = context.getAction();
235   
236    // Start progress
237  40566 if (debug && om != null && execution != null) {
238  0 actionProgress = new DefaultJobProgress(context.getURL().toExternalForm());
239  0 om.addListener(new WrappedThreadEventListener(actionProgress));
240   
241    // Register the action progress in the context
242  0 ExecutionContext econtext = execution.getContext();
243  0 if (econtext != null) {
244  0 econtext.setProperty(XWikiAction.ACTION_PROGRESS, actionProgress);
245    }
246    }
247   
248  40551 getProgress().pushLevelProgress(2, this);
249   
250  40549 getProgress().startStep(this, "Get XWiki instance");
251   
252    // Initialize context.getWiki() with the main wiki
253  40558 XWiki xwiki;
254   
255    // Verify that the requested wiki exists
256  40584 try {
257    // Don't show init screen if async is forced to false
258  40577 xwiki = XWiki.getXWiki(this.waitForXWikiInitialization || StringUtils.equals(sasync, "false"), context);
259   
260    // If XWiki is still initializing display initialization template
261  40575 if (xwiki == null) {
262    // Display initialization template
263  0 renderInit(context);
264   
265    // Initialization template has been displayed, stop here.
266  0 return null;
267    }
268    } catch (XWikiException e) {
269    // If the wiki asked by the user doesn't exist, then we first attempt to use any existing global
270    // redirects. If there are none, then we display the specific error template.
271  0 if (e.getCode() == XWikiException.ERROR_XWIKI_DOES_NOT_EXIST) {
272  0 xwiki = XWiki.getMainXWiki(context);
273   
274    // Initialize the url factory
275  0 XWikiURLFactory urlf = xwiki.getURLFactoryService().createURLFactory(context.getMode(), context);
276  0 context.setURLFactory(urlf);
277   
278    // Initialize the velocity context and its bindings so that it may be used in the velocity templates
279    // that we
280    // are parsing below.
281  0 VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
282  0 VelocityContext vcontext = velocityManager.getVelocityContext();
283   
284  0 if (!sendGlobalRedirect(context.getResponse(), context.getURL().toString(), context)) {
285    // Starting XWiki 5.0M2, 'xwiki.virtual.redirect' was removed. Warn users still using it.
286  0 if (!StringUtils.isEmpty(context.getWiki().Param("xwiki.virtual.redirect"))) {
287  0 LOGGER.warn(String.format("%s %s", "'xwiki.virtual.redirect' is no longer supported.",
288    "Please update your configuration and/or see XWIKI-8914 for more details."));
289    }
290   
291    // Display the error template only for actions that are not ignored
292  0 if (!ACTIONS_IGNORED_WHEN_WIKI_DOES_NOT_EXIST.contains(action)) {
293   
294    // Add localization resources to the context
295  0 xwiki.prepareResources(context);
296   
297    // Set the main home page in the main space of the main wiki as the current requested entity
298    // since we cannot set the non existing one as it would generate errors obviously...
299  0 EntityReferenceValueProvider valueProvider =
300    Utils.getComponent(EntityReferenceValueProvider.class);
301  0 xwiki.setPhonyDocument(new DocumentReference(valueProvider.getDefaultValue(EntityType.WIKI),
302    valueProvider.getDefaultValue(EntityType.SPACE),
303    valueProvider.getDefaultValue(EntityType.DOCUMENT)), context, vcontext);
304   
305    // Parse the error template
306  0 Utils.parseTemplate(context.getWiki().Param("xwiki.wiki_exception", "wikidoesnotexist"),
307    context);
308   
309    // Error template was displayed, stop here.
310  0 return null;
311    }
312   
313    // At this point, we allow regular execution of the ignored action because even if the wiki
314    // does not exist, we still need to allow UI resources to be retrieved (from the filesystem
315    // and the main wiki) or our error template will not be rendered properly.
316   
317    // Proceed with serving the main wiki
318   
319    } else {
320    // Global redirect was executed, stop here.
321  0 return null;
322    }
323    } else {
324  0 LOGGER.error("Uncaught exception during XWiki initialisation:", e);
325  0 throw e;
326    }
327    }
328   
329    // Send global redirection (if any)
330  40576 if (sendGlobalRedirect(context.getResponse(), context.getURL().toString(), context)) {
331  0 return null;
332    }
333   
334  40565 XWikiURLFactory urlf = xwiki.getURLFactoryService().createURLFactory(context.getMode(), context);
335  40551 context.setURLFactory(urlf);
336   
337    // Handle ability to enter space URLs and convert them to page URLs (Nested Documents)
338  40546 if (redirectSpaceURLs(action, urlf, xwiki, context)) {
339  3552 return null;
340    }
341   
342  37012 String sajax = context.getRequest().get("ajax");
343  37008 boolean ajax = false;
344  37011 if (sajax != null && !sajax.trim().equals("") && !sajax.equals("0")) {
345  1550 ajax = true;
346    }
347  37016 context.put("ajax", ajax);
348   
349  37016 boolean async = false;
350  37013 if (StringUtils.isNotEmpty(sasync)) {
351  129 async = sasync.equals("true");
352    } else {
353    // By default allow asynchronous rendering for "human oriented" actions which are not executing an ajax
354    // request
355  36882 async = !ajax && !this.waitForXWikiInitialization;
356    }
357  37010 Utils.getComponent(AsyncContext.class).setEnabled(async);
358   
359    // Any error before this will be treated using a redirection to an error page
360   
361  37021 if (monitor != null) {
362  0 monitor.startTimer("request");
363    }
364   
365  37004 getProgress().startStep(this, "Execute request");
366   
367  37016 VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
368  37009 VelocityContext vcontext = velocityManager.getVelocityContext();
369   
370  37010 getProgress().pushLevelProgress(7, this);
371   
372  37017 boolean eventSent = false;
373  37025 try {
374  37026 getProgress().startStep(this, "Prepare documents and put them in the context");
375   
376    // Prepare documents and put them in the context
377  36705 if (!xwiki.prepareDocuments(context.getRequest(), context, vcontext)) {
378  0 return null;
379    }
380   
381    // Start monitoring timer
382  36708 monitor = (MonitorPlugin) xwiki.getPlugin("monitor", context);
383  36725 if (monitor != null) {
384  28019 monitor.startRequest("", context.getAction(), context.getURL());
385  28014 monitor.startTimer("multipart");
386    }
387   
388  36710 getProgress().startStep(this, "Parses multipart");
389   
390    // Parses multipart so that params in multipart are available for all actions
391  36720 fileupload = Utils.handleMultipart(context.getRequest().getHttpServletRequest(), context);
392  36718 if (monitor != null) {
393  28021 monitor.endTimer("multipart");
394    }
395   
396  36709 if (monitor != null) {
397  28020 monitor.setWikiPage(context.getDoc().getFullName());
398    }
399   
400  36717 getProgress().startStep(this, "Send [" + context.getAction() + "] action start event");
401   
402    // For the moment we're sending the XWiki context as the data, but this will be
403    // changed in the future, when the whole platform will be written using components
404    // and there won't be a need for the context.
405  36717 try {
406  36725 ActionExecutingEvent event = new ActionExecutingEvent(context.getAction());
407  36710 om.notify(event, context.getDoc(), context);
408  36717 eventSent = true;
409  36714 if (event.isCanceled()) {
410    // Action has been canceled
411    // TODO: do something special ?
412  0 return null;
413    }
414    } catch (Throwable ex) {
415  0 LOGGER.error("Cannot send action notifications for document [" + context.getDoc()
416    + " using action [" + context.getAction() + "]", ex);
417    }
418   
419  36709 if (monitor != null) {
420  28014 monitor.endTimer("prenotify");
421    }
422   
423    // Call the Actions
424   
425  36717 getProgress().startStep(this, "Search and execute entity resource handler");
426   
427    // Call the new Entity Resource Reference Handler.
428  36722 ResourceReferenceHandler entityResourceReferenceHandler = Utils.getComponent(
429    new DefaultParameterizedType(null, ResourceReferenceHandler.class, ResourceType.class), "bin");
430  36709 EntityResourceReference entityResourceReference =
431    (EntityResourceReference) Utils.getComponent(ResourceReferenceManager.class).getResourceReference();
432   
433    // We save the current action set since:
434    // - by default the action is set to "view" for Extensions not installed as root and contributing some
435    // new Entity Action (see https://jira.xwiki.org/browse/XWIKI-15182).
436    // - we want to set back the action in case no ResourceReferenceHandler was found to handle the URL
437    // TODO: Remove once https://jira.xwiki.org/browse/XWIKI-14947 is fixed
438  36711 String originalAction = context.getAction();
439  36728 try {
440    // Force the action in the context because of https://jira.xwiki.org/browse/XWIKI-15182.
441    // TODO: Remove once https://jira.xwiki.org/browse/XWIKI-14947 is fixed
442  36722 context.setAction(entityResourceReference.getAction().getActionName());
443  36706 entityResourceReferenceHandler.handle(entityResourceReference,
444    DefaultResourceReferenceHandlerChain.EMPTY);
445    // Don't let the old actions kick in!
446  0 return null;
447    } catch (NotFoundResourceHandlerException e) {
448    // No Entity Resource Action has been found. Don't do anything and let it go through
449    // so that the old Action system kicks in...
450    // Put back the action, because of https://jira.xwiki.org/browse/XWIKI-15182
451    // TODO: Remove once https://jira.xwiki.org/browse/XWIKI-14947 is fixed
452  36698 context.setAction(originalAction);
453    } catch (Throwable e) {
454    // Some real failure, log it since it's a problem but still allow the old Action system a chance
455    // to do something...
456  0 LOGGER.error("Failed to handle Action for Resource [{}]", entityResourceReference, e);
457    }
458   
459  36711 getProgress().startStep(this, "Execute action render");
460   
461    // Handle the XWiki.RedirectClass object that can be attached to the current document
462  36715 boolean hasRedirect = false;
463  36707 if (handleRedirectObject) {
464  27970 hasRedirect = handleRedirectObject(context);
465    }
466   
467    // Then call the old Actions for backward compatibility (and because a lot of them have not been
468    // migrated to new Actions yet).
469  36706 String renderResult = null;
470  36709 XWikiDocument doc = context.getDoc();
471  36724 docName = doc.getFullName();
472  36712 if (!hasRedirect && action(context)) {
473  34461 renderResult = render(context);
474    }
475   
476  36710 if (renderResult != null) {
477  29755 if (doc.isNew() && "view".equals(context.getAction())
478    && !"recyclebin".equals(context.getRequest().get("viewer"))
479    && !"children".equals(context.getRequest().get("viewer"))
480    && !"siblings".equals(context.getRequest().get("viewer"))) {
481  4390 String page = Utils.getPage(context.getRequest(), "docdoesnotexist");
482   
483  4390 getProgress().startStep(this, "Execute template [" + page + "]");
484  4390 Utils.parseTemplate(page, context);
485    } else {
486  25362 String page = Utils.getPage(context.getRequest(), renderResult);
487   
488  25363 getProgress().startStep(this, "Execute template [" + page + "]");
489  25367 Utils.parseTemplate(page, !page.equals("direct"), context);
490    }
491    }
492  36694 return null;
493    } catch (Throwable e) {
494  327 if (e instanceof IOException) {
495  0 e = new XWikiException(XWikiException.MODULE_XWIKI_APP,
496    XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION, "Exception while sending response", e);
497    }
498   
499  327 if (!(e instanceof XWikiException)) {
500  4 e = new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_UNKNOWN,
501    "Uncaught exception", e);
502    }
503   
504  327 try {
505  327 XWikiException xex = (XWikiException) e;
506  327 if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION) {
507    // Connection aborted from the client side, there's not much we can do on the server side. We
508    // simply ignore it.
509  10 LOGGER.debug("Connection aborted", e);
510    // We don't write any other message to the response, as the connection is broken, anyway.
511  10 return null;
512  317 } else if (xex.getCode() == XWikiException.ERROR_XWIKI_ACCESS_DENIED) {
513  308 Utils.parseTemplate(context.getWiki().Param("xwiki.access_exception", "accessdenied"), context);
514  308 return null;
515  9 } else if (xex.getCode() == XWikiException.ERROR_XWIKI_USER_INACTIVE) {
516  0 Utils.parseTemplate(context.getWiki().Param("xwiki.user_exception", "userinactive"), context);
517  0 return null;
518  9 } else if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_ATTACHMENT_NOT_FOUND) {
519  5 context.put("message", "attachmentdoesnotexist");
520  5 Utils.parseTemplate(
521    context.getWiki().Param("xwiki.attachment_exception", "attachmentdoesnotexist"), context);
522  5 return null;
523  4 } else if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_URL_EXCEPTION) {
524  0 vcontext.put("message", localizePlainOrKey("platform.core.invalidUrl"));
525  0 xwiki.setPhonyDocument(xwiki.getDefaultSpace(context) + "." + xwiki.getDefaultPage(context),
526    context, vcontext);
527  0 context.getResponse().setStatus(HttpServletResponse.SC_BAD_REQUEST);
528  0 Utils.parseTemplate(context.getWiki().Param("xwiki.invalid_url_exception", "error"), context);
529  0 return null;
530    }
531  4 vcontext.put("exp", e);
532  4 if (LOGGER.isWarnEnabled()) {
533    // Don't log "Broken Pipe" exceptions since they're not real errors and we don't want to pollute
534    // the logs with unnecessary stack traces. It just means the client side has cancelled the
535    // connection.
536  4 if (ExceptionUtils.getRootCauseMessage(e).equals("IOException: Broken pipe")) {
537  0 return null;
538    }
539  4 LOGGER.warn("Uncaught exception: " + e.getMessage(), e);
540    }
541    // If the request is an AJAX request, we don't return a whole HTML page, but just the exception
542    // inline.
543  4 String exceptionTemplate = ajax ? "exceptioninline" : "exception";
544  4 Utils.parseTemplate(Utils.getPage(context.getRequest(), exceptionTemplate), context);
545  4 return null;
546    } catch (XWikiException ex) {
547  0 if (ex.getCode() == XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION) {
548  0 LOGGER.error("Connection aborted");
549    }
550    } catch (Exception e2) {
551    // I hope this never happens
552  0 LOGGER.error("Uncaught exceptions (inner): ", e);
553  0 LOGGER.error("Uncaught exceptions (outer): ", e2);
554    }
555  0 return null;
556    } finally {
557    // Let's make sure we have flushed content and closed
558  37020 try {
559  37027 context.getResponse().getWriter().flush();
560    } catch (Throwable e) {
561    // This might happen if the connection was closed, for example.
562    // If we can't flush, then there's nothing more we can send to the client.
563    }
564   
565  37033 if (monitor != null) {
566  28027 monitor.endTimer("request");
567  28025 monitor.startTimer("notify");
568    }
569   
570  37030 if (eventSent) {
571    // For the moment we're sending the XWiki context as the data, but this will be
572    // changed in the future, when the whole platform will be written using components
573    // and there won't be a need for the context.
574  36713 try {
575  36721 om.notify(new ActionExecutedEvent(context.getAction()), context.getDoc(), context);
576    } catch (Throwable ex) {
577  0 LOGGER.error("Cannot send action notifications for document [" + docName + " using action ["
578    + context.getAction() + "]", ex);
579    }
580    }
581   
582  37028 if (monitor != null) {
583  28017 monitor.endTimer("notify");
584    }
585   
586  37030 getProgress().startStep(this, "Cleanup database connections");
587   
588    // Make sure we cleanup database connections
589    // There could be cases where we have some
590  37026 xwiki.getStore().cleanUp(context);
591   
592  37037 getProgress().popLevelProgress(this);
593    }
594    } finally {
595    // End request
596  40586 if (monitor != null) {
597  28027 monitor.endRequest();
598    }
599   
600    // Stop progress
601  40583 if (actionProgress != null) {
602  0 getProgress().popLevelProgress(this);
603   
604  0 om.removeListener(actionProgress.getName());
605    }
606   
607  40574 if (fileupload != null) {
608  19 fileupload.cleanFileList(context);
609    }
610    }
611    }
612   
 
613  0 toggle private void renderInit(XWikiContext xcontext) throws Exception
614    {
615  0 RenderingContext renderingContext = Utils.getComponent(RenderingContext.class);
616  0 MutableRenderingContext mutableRenderingContext =
617  0 renderingContext instanceof MutableRenderingContext ? (MutableRenderingContext) renderingContext : null;
618   
619  0 if (mutableRenderingContext != null) {
620  0 mutableRenderingContext.push(renderingContext.getTransformation(), renderingContext.getXDOM(),
621    renderingContext.getDefaultSyntax(), "init.vm", renderingContext.isRestricted(), Syntax.XHTML_1_0);
622    }
623   
624  0 xcontext.getResponse().setStatus(202);
625  0 xcontext.getResponse().setContentType("text/html; charset=UTF-8");
626   
627  0 try {
628  0 Utils.getComponent(TemplateManager.class).render("init.vm", xcontext.getResponse().getWriter());
629    } finally {
630  0 if (mutableRenderingContext != null) {
631  0 mutableRenderingContext.pop();
632    }
633    }
634   
635  0 xcontext.getResponse().flushBuffer();
636   
637  0 xcontext.setFinished(true);
638    }
639   
 
640  40575 toggle protected XWikiContext initializeXWikiContext(ActionMapping mapping, ActionForm form, HttpServletRequest req,
641    HttpServletResponse resp) throws XWikiException, ServletException
642    {
643  40583 String action = mapping.getName();
644   
645  40582 XWikiRequest request = new XWikiServletRequest(req);
646  40559 XWikiResponse response = new XWikiServletResponse(resp);
647  40552 XWikiContext context =
648    Utils.prepareContext(action, request, response, new XWikiServletContext(this.servlet.getServletContext()));
649   
650    // This code is already called by struts.
651    // However struts will also set all the parameters of the form data
652    // directly from the request objects.
653    // However because of bug https://jira.xwiki.org/browse/XWIKI-2422
654    // We need to perform encoding of windows-1252 chars in ISO mode
655    // So we need to make sure this code is called
656    // TODO: completely get rid of struts so that we control this part of the code and can reduce drastically the
657    // number of calls
658  40554 if (form != null) {
659  3175 form.reset(mapping, request);
660    }
661   
662    // Add the form to the context
663  40555 context.setForm((XWikiForm) form);
664   
665    // Initialize the Container component which is the new way of transporting the Context in the new
666    // component architecture.
667  40546 initializeContainerComponent(context);
668   
669  40569 return context;
670    }
671   
 
672  40536 toggle protected void initializeContainerComponent(XWikiContext context) throws ServletException
673    {
674    // Initialize the Container fields (request, response, session).
675    // Note that this is a bridge between the old core and the component architecture.
676    // In the new component architecture we use ThreadLocal to transport the request,
677    // response and session to components which require them.
678    // In the future this Servlet will be replaced by the XWikiPlexusServlet Servlet.
679  40566 ServletContainerInitializer containerInitializer = Utils.getComponent(ServletContainerInitializer.class);
680   
681  40562 try {
682  40570 containerInitializer.initializeRequest(context.getRequest().getHttpServletRequest(), context);
683  40549 containerInitializer.initializeResponse(context.getResponse());
684  40574 containerInitializer.initializeSession(context.getRequest().getHttpServletRequest());
685    } catch (ServletContainerException e) {
686  0 throw new ServletException("Failed to initialize Request/Response or Session", e);
687    }
688    }
689   
 
690  40561 toggle protected void cleanupComponents()
691    {
692  40579 Container container = Utils.getComponent(Container.class);
693  40578 Execution execution = Utils.getComponent(Execution.class);
694   
695    // We must ensure we clean the ThreadLocal variables located in the Container and Execution
696    // components as otherwise we will have a potential memory leak.
697  40573 container.removeRequest();
698  40579 container.removeResponse();
699  40579 container.removeSession();
700  40582 execution.removeContext();
701    }
702   
 
703  0 toggle public String getRealPath(String path)
704    {
705  0 return this.servlet.getServletContext().getRealPath(path);
706    }
707   
708    // hook
 
709  6033 toggle public boolean action(XWikiContext context) throws XWikiException
710    {
711  6042 return true;
712    }
713   
714    // hook
 
715  0 toggle public String render(XWikiContext context) throws XWikiException
716    {
717  0 return null;
718    }
719   
720    /**
721    * Redirect the user to an other location if the document holds an XWiki.RedirectClass instance (used when a
722    * document is moved).
723    *
724    * @param context the XWiki context
725    * @return either or not a redirection have been sent
726    * @throws XWikiException if error occurs
727    * @since 8.0RC1
728    * @since 7.4.2
729    */
 
730  27964 toggle protected boolean handleRedirectObject(XWikiContext context) throws XWikiException
731    {
732  27976 WikiReference wikiReference = context.getWikiReference();
733   
734    // Look if the document has a redirect object
735  27975 XWikiDocument doc = context.getDoc();
736  27979 BaseObject redirectObj =
737    doc.getXObject(new DocumentReference("RedirectClass", new SpaceReference("XWiki", wikiReference)));
738  27975 if (redirectObj == null) {
739  27973 return false;
740    }
741   
742    // Get the location
743  3 String location = redirectObj.getStringValue("location");
744  3 if (StringUtils.isBlank(location)) {
745  0 return false;
746    }
747   
748    // Resolve the location to get a reference
749  3 DocumentReferenceResolver<String> resolver = Utils.getComponent(DocumentReferenceResolver.TYPE_STRING);
750  3 EntityReference locationReference = resolver.resolve(location, wikiReference);
751   
752    // Get the type of the current target
753  3 ResourceReference resourceReference = Utils.getComponent(ResourceReferenceManager.class).getResourceReference();
754  3 EntityResourceReference entityResource = (EntityResourceReference) resourceReference;
755  3 EntityReference entityReference = entityResource.getEntityReference();
756   
757    // If the entity is inside a document, compute the new entity with the new document part.
758  3 if (entityReference.getType().ordinal() > EntityType.DOCUMENT.ordinal()) {
759  0 EntityReference parentDocument = entityReference.extractReference(EntityType.DOCUMENT);
760  0 locationReference = entityReference.replaceParent(parentDocument, locationReference);
761    }
762   
763    // Get the URL corresponding to the location
764    // Note: the anchor part is lost in the process, because it is not sent to the server
765    // (see: http://stackoverflow.com/a/4276491)
766  3 String url = context.getWiki().getURL(locationReference, context.getAction(),
767    context.getRequest().getQueryString(), null, context);
768   
769    // Send the redirection
770  3 try {
771  3 context.getResponse().sendRedirect(url);
772    } catch (IOException e) {
773  0 throw new XWikiException("Failed to redirect.", e);
774    }
775   
776  3 return true;
777    }
778   
 
779  27550 toggle protected void handleRevision(XWikiContext context) throws XWikiException
780    {
781  27553 String rev = context.getRequest().getParameter("rev");
782  27556 if (rev != null) {
783  5 context.put("rev", rev);
784  5 XWikiDocument doc = (XWikiDocument) context.get("doc");
785  5 XWikiDocument tdoc = (XWikiDocument) context.get("tdoc");
786  5 XWikiDocument rdoc =
787  5 (!doc.getLocale().equals(tdoc.getLocale())) ? doc : context.getWiki().getDocument(doc, rev, context);
788  1 XWikiDocument rtdoc =
789  1 (doc.getLocale().equals(tdoc.getLocale())) ? rdoc : context.getWiki().getDocument(tdoc, rev, context);
790  1 context.put("tdoc", rtdoc);
791  1 context.put("cdoc", rdoc);
792  1 context.put("doc", rdoc);
793    }
794    }
795   
796    /**
797    * Send redirection based on a regexp pattern (if any) set at the main wiki level. To enable this feature you must
798    * add xwiki.preferences.redirect=1 to your xwiki.cfg.
799    *
800    * @param response the servlet response
801    * @param url url of the request
802    * @param context the XWiki context
803    * @return true if a redirection has been sent
804    */
 
805  40541 toggle protected boolean sendGlobalRedirect(XWikiResponse response, String url, XWikiContext context) throws Exception
806    {
807  40580 if ("1".equals(context.getWiki().Param("xwiki.preferences.redirect"))) {
808    // Note: This implementation is not performant at all and will slow down the wiki as the number
809    // of redirects increases. A better implementation would use a cache of redirects and would use
810    // the notification mechanism to update the cache when the XWiki.XWikiPreferences document is
811    // modified.
812  0 XWikiDocument globalPreferences = context.getWiki().getDocument("xwiki:XWiki.XWikiPreferences", context);
813  0 Vector<BaseObject> redirects = globalPreferences.getObjects("XWiki.GlobalRedirect");
814   
815  0 if (redirects != null) {
816  0 for (BaseObject redir : redirects) {
817  0 if (redir != null) {
818  0 String p = redir.getStringValue("pattern");
819  0 if (p != null && url.matches(p)) {
820  0 String dest = redir.getStringValue("destination");
821  0 response.sendRedirect(url.replaceAll(p, dest));
822  0 return true;
823    }
824    }
825    }
826    }
827    }
828  40566 return false;
829    }
830   
 
831  838 toggle protected void sendRedirect(XWikiResponse response, String url) throws XWikiException
832    {
833  838 try {
834  838 if (url != null) {
835  803 response.sendRedirect(response.encodeRedirectURL(url));
836    }
837    } catch (IOException e) {
838  0 Object[] args = { url };
839  0 throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_REDIRECT_EXCEPTION,
840    "Exception while sending redirect to page {0}", e, args);
841    }
842    }
843   
844    /**
845    * Gets the translated version of a document, in the specified language. If the translation does not exist, a new
846    * document translation is created. If the requested language does not correspond to a translation (is not defined
847    * or is the same as the main document), then the main document is returned.
848    *
849    * @param doc the main (default, untranslated) document to translate
850    * @param language the requested document language
851    * @param context the current request context
852    * @return the translated document, or the original untranslated document if the requested language is not a
853    * translation
854    * @throws XWikiException if the translation cannot be retrieved from the database
855    */
 
856  1412 toggle protected XWikiDocument getTranslatedDocument(XWikiDocument doc, String language, XWikiContext context)
857    throws XWikiException
858    {
859  1414 XWikiDocument tdoc;
860  1420 if (StringUtils.isBlank(language) || language.equals("default") || language.equals(doc.getDefaultLanguage())) {
861  1354 tdoc = doc;
862    } else {
863  62 tdoc = doc.getTranslatedDocument(language, context);
864  62 if (tdoc == doc) {
865  62 tdoc = new XWikiDocument(doc.getDocumentReference());
866  62 tdoc.setLanguage(language);
867  62 tdoc.setStore(doc.getStore());
868    }
869  62 tdoc.setTranslation(1);
870    }
871  1415 return tdoc;
872    }
873   
874    /**
875    * Perform CSRF check and redirect to the resubmission page if needed. Throws an exception if the access should be
876    * denied, returns false if the check failed and the user will be redirected to a resubmission page.
877    *
878    * @param context current xwiki context containing the request
879    * @return true if the check succeeded, false if resubmission is needed
880    * @throws XWikiException if the check fails
881    */
 
882  902 toggle protected boolean csrfTokenCheck(XWikiContext context) throws XWikiException
883    {
884  902 CSRFToken csrf = Utils.getComponent(CSRFToken.class);
885  902 try {
886  902 String token = context.getRequest().getParameter("form_token");
887  902 if (!csrf.isTokenValid(token)) {
888  3 sendRedirect(context.getResponse(), csrf.getResubmissionURL());
889  3 return false;
890    }
891    } catch (XWikiException exception) {
892    // too bad
893  0 throw new XWikiException(XWikiException.MODULE_XWIKI_ACCESS, XWikiException.ERROR_XWIKI_ACCESS_DENIED,
894    "Access denied, secret token verification failed", exception);
895    }
896  899 return true;
897    }
898   
899    /**
900    * In order to let users enter URLs to Spaces we do the following when receiving {@code /A/B} (where A and B are
901    * spaces):
902    * <ul>
903    * <li>check that the action is "view" (we only support this for the view action since otherwise this would break
904    * apps written before this concept was introduced in XWiki 7.2M1)</li>
905    * <li>if A.B exists then continue</li>
906    * <li>if A.B doesn't exist then forward to A.B.WebHome</li>
907    * </ul>
908    * In order to disable this redirect you should provide the {@code spaceRedirect=false} Query String parameter and
909    * value.
910    *
911    * @since 7.2M1
912    */
 
913  40532 toggle private boolean redirectSpaceURLs(String action, XWikiURLFactory urlf, XWiki xwiki, XWikiContext context)
914    throws Exception
915    {
916  40548 if ("view".equals(action) && !"false".equalsIgnoreCase(context.getRequest().getParameter("spaceRedirect"))) {
917  15677 DocumentReference reference = xwiki.getDocumentReference(context.getRequest(), context);
918  15677 if (!xwiki.exists(reference, context)) {
919  7933 String defaultDocumentName = Utils.getComponent(EntityReferenceProvider.class)
920    .getDefaultReference(EntityType.DOCUMENT).getName();
921    // Avoid an infinite loop by ensuring we're not on a WebHome already
922  7933 if (!reference.getName().equals(defaultDocumentName)) {
923    // Consider the reference as a Space Reference and Construct a new reference to the home of that
924    // Space. Then generate the URL for it and forward to it
925  3552 SpaceReference spaceReference = new SpaceReference(reference.getName(), reference.getParent());
926  3552 EntityReferenceSerializer<String> localSerializer =
927    Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "local");
928    // Extract the anchor
929  3552 String anchor = new URL(context.getRequest().getRequestURL().toString()).getRef();
930  3552 URL forwardURL = urlf.createURL(localSerializer.serialize(spaceReference), defaultDocumentName,
931    action, context.getRequest().getQueryString(), anchor,
932    spaceReference.getWikiReference().getName(), context);
933    // Since createURL() contain the webapp context and since RequestDispatcher should not contain it,
934    // we need to remove it!
935  3552 String webappContext = xwiki.getWebAppPath(context);
936  3552 String relativeURL = urlf.getURL(forwardURL, context);
937  3552 relativeURL = '/' + StringUtils.substringAfter(relativeURL, webappContext);
938  3552 context.getRequest().getRequestDispatcher(relativeURL).forward(context.getRequest(),
939    context.getResponse());
940  3552 return true;
941    }
942    }
943    }
944   
945  36990 return false;
946    }
947    }