1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.web

File XWikiAction.java

 

Coverage histogram

../../../../img/srcFileCovDistChart6.png
69% of files have more coverage

Code metrics

126
302
20
1
921
564
109
0.36
15.1
20
5.45

Classes

Class Line # Actions
XWikiAction 121 302 0% 109 186
0.584821458.5%
 

Contributing tests

This file is covered by 44 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.Collections;
26    import java.util.List;
27    import java.util.Vector;
28   
29    import javax.script.ScriptContext;
30    import javax.servlet.ServletException;
31    import javax.servlet.http.HttpServletRequest;
32    import javax.servlet.http.HttpServletResponse;
33   
34    import org.apache.commons.lang.exception.ExceptionUtils;
35    import org.apache.commons.lang3.StringUtils;
36    import org.apache.struts.action.Action;
37    import org.apache.struts.action.ActionForm;
38    import org.apache.struts.action.ActionForward;
39    import org.apache.struts.action.ActionMapping;
40    import org.apache.velocity.VelocityContext;
41    import org.slf4j.Logger;
42    import org.slf4j.LoggerFactory;
43    import org.xwiki.bridge.event.ActionExecutedEvent;
44    import org.xwiki.bridge.event.ActionExecutingEvent;
45    import org.xwiki.component.util.DefaultParameterizedType;
46    import org.xwiki.container.Container;
47    import org.xwiki.container.servlet.ServletContainerException;
48    import org.xwiki.container.servlet.ServletContainerInitializer;
49    import org.xwiki.context.Execution;
50    import org.xwiki.context.ExecutionContext;
51    import org.xwiki.csrf.CSRFToken;
52    import org.xwiki.job.event.status.JobProgressManager;
53    import org.xwiki.job.internal.DefaultJobProgress;
54    import org.xwiki.localization.ContextualLocalizationManager;
55    import org.xwiki.model.EntityType;
56    import org.xwiki.model.reference.DocumentReference;
57    import org.xwiki.model.reference.DocumentReferenceResolver;
58    import org.xwiki.model.reference.EntityReference;
59    import org.xwiki.model.reference.EntityReferenceProvider;
60    import org.xwiki.model.reference.EntityReferenceSerializer;
61    import org.xwiki.model.reference.EntityReferenceValueProvider;
62    import org.xwiki.model.reference.SpaceReference;
63    import org.xwiki.model.reference.WikiReference;
64    import org.xwiki.observation.ObservationManager;
65    import org.xwiki.observation.WrappedThreadEventListener;
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 is blocked until XWiki is initialized.
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  38 toggle protected ContextualLocalizationManager getLocalization()
151    {
152  38 if (this.localization == null) {
153  15 this.localization = Utils.getComponent(ContextualLocalizationManager.class);
154    }
155   
156  38 return this.localization;
157    }
158   
 
159  38 toggle protected String localizePlainOrKey(String key, Object... parameters)
160    {
161  38 return StringUtils.defaultString(getLocalization().getTranslationPlain(key, parameters), key);
162    }
163   
 
164  111745 toggle protected JobProgressManager getProgress()
165    {
166  111774 if (this.progress == null) {
167  355 this.progress = Utils.getComponent(JobProgressManager.class);
168    }
169   
170  111767 return this.progress;
171    }
172   
173    /**
174    * @return the current unmodified {@link ScriptContext} instance
175    * @since 8.3M1
176    */
 
177  256 toggle protected ScriptContext getCurrentScriptContext()
178    {
179  256 if (this.scriptContextManager == null) {
180  73 this.scriptContextManager = Utils.getComponent(ScriptContextManager.class);
181    }
182   
183  256 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  9653 toggle @Override
197    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest req,
198    HttpServletResponse resp) throws Exception
199    {
200  9653 ActionForward actionForward;
201  9652 XWikiContext context = null;
202   
203  9653 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  9652 context = initializeXWikiContext(mapping, form, req, resp);
208   
209    // From this line forward all information can be found in the XWiki Context.
210  9648 actionForward = execute(context);
211    } finally {
212  9653 if (context != null) {
213  9653 cleanupComponents();
214    }
215    }
216   
217  9646 return actionForward;
218    }
219   
 
220  9594 toggle public ActionForward execute(XWikiContext context) throws Exception
221    {
222  9614 MonitorPlugin monitor = null;
223  9633 FileUploadPlugin fileupload = null;
224  9627 DefaultJobProgress actionProgress = null;
225  9625 ObservationManager om = Utils.getComponent(ObservationManager.class);
226  9651 Execution execution = Utils.getComponent(Execution.class);
227  9653 String docName = "";
228   
229  9647 boolean debug = StringUtils.equals(context.getRequest().get("debug"), "true");
230   
231  9640 try {
232  9641 String action = context.getAction();
233   
234    // Start progress
235  9628 if (debug && om != null && execution != null) {
236  0 actionProgress = new DefaultJobProgress(context.getURL().toExternalForm());
237  0 om.addListener(new WrappedThreadEventListener(actionProgress));
238   
239    // Register the action progress in the context
240  0 ExecutionContext econtext = execution.getContext();
241  0 if (econtext != null) {
242  0 econtext.setProperty(XWikiAction.ACTION_PROGRESS, actionProgress);
243    }
244    }
245   
246  9632 getProgress().pushLevelProgress(2, this);
247   
248  9632 getProgress().startStep(this, "Get XWiki instance");
249   
250    // Initialize context.getWiki() with the main wiki
251  9652 XWiki xwiki;
252   
253    // Verify that the requested wiki exists
254  9653 try {
255  9652 xwiki = XWiki.getXWiki(this.waitForXWikiInitialization, context);
256   
257    // If XWiki is still initializing display initialization template
258  9650 if (xwiki == null) {
259    // Display initialization template
260  0 renderInit(context);
261   
262    // Initialization template has been displayed, stop here.
263  0 return null;
264    }
265    } catch (XWikiException e) {
266    // If the wiki asked by the user doesn't exist, then we first attempt to use any existing global
267    // redirects. If there are none, then we display the specific error template.
268  0 if (e.getCode() == XWikiException.ERROR_XWIKI_DOES_NOT_EXIST) {
269  0 xwiki = XWiki.getMainXWiki(context);
270   
271    // Initialize the url factory
272  0 XWikiURLFactory urlf = xwiki.getURLFactoryService().createURLFactory(context.getMode(), context);
273  0 context.setURLFactory(urlf);
274   
275    // Initialize the velocity context and its bindings so that it may be used in the velocity templates
276    // that we
277    // are parsing below.
278  0 VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
279  0 VelocityContext vcontext = velocityManager.getVelocityContext();
280   
281  0 if (!sendGlobalRedirect(context.getResponse(), context.getURL().toString(), context)) {
282    // Starting XWiki 5.0M2, 'xwiki.virtual.redirect' was removed. Warn users still using it.
283  0 if (!StringUtils.isEmpty(context.getWiki().Param("xwiki.virtual.redirect"))) {
284  0 LOGGER.warn(String.format("%s %s", "'xwiki.virtual.redirect' is no longer supported.",
285    "Please update your configuration and/or see XWIKI-8914 for more details."));
286    }
287   
288    // Display the error template only for actions that are not ignored
289  0 if (!ACTIONS_IGNORED_WHEN_WIKI_DOES_NOT_EXIST.contains(action)) {
290   
291    // Add localization resources to the context
292  0 xwiki.prepareResources(context);
293   
294    // Set the main home page in the main space of the main wiki as the current requested entity
295    // since we cannot set the non existing one as it would generate errors obviously...
296  0 EntityReferenceValueProvider valueProvider =
297    Utils.getComponent(EntityReferenceValueProvider.class);
298  0 xwiki.setPhonyDocument(new DocumentReference(valueProvider.getDefaultValue(EntityType.WIKI),
299    valueProvider.getDefaultValue(EntityType.SPACE),
300    valueProvider.getDefaultValue(EntityType.DOCUMENT)), context, vcontext);
301   
302    // Parse the error template
303  0 Utils.parseTemplate(context.getWiki().Param("xwiki.wiki_exception", "wikidoesnotexist"),
304    context);
305   
306    // Error template was displayed, stop here.
307  0 return null;
308    }
309   
310    // At this point, we allow regular execution of the ignored action because even if the wiki
311    // does not exist, we still need to allow UI resources to be retrieved (from the filesystem
312    // and the main wiki) or our error template will not be rendered properly.
313   
314    // Proceed with serving the main wiki
315   
316    } else {
317    // Global redirect was executed, stop here.
318  0 return null;
319    }
320    } else {
321  0 LOGGER.error("Uncaught exception during XWiki initialisation:", e);
322  0 throw e;
323    }
324    }
325   
326    // Send global redirection (if any)
327  9648 if (sendGlobalRedirect(context.getResponse(), context.getURL().toString(), context)) {
328  0 return null;
329    }
330   
331  9636 XWikiURLFactory urlf = xwiki.getURLFactoryService().createURLFactory(context.getMode(), context);
332  9652 context.setURLFactory(urlf);
333   
334    // Handle ability to enter space URLs and convert them to page URLs (Nested Documents)
335  9631 if (redirectSpaceURLs(action, urlf, xwiki, context)) {
336  136 return null;
337    }
338   
339  9485 String sajax = context.getRequest().get("ajax");
340  9505 boolean ajax = false;
341  9504 if (sajax != null && !sajax.trim().equals("") && !sajax.equals("0")) {
342  569 ajax = true;
343    }
344  9504 context.put("ajax", ajax);
345   
346    // Any error before this will be treated using a redirection to an error page
347   
348  9501 if (monitor != null) {
349  0 monitor.startTimer("request");
350    }
351   
352  9502 getProgress().startStep(this, "Execute request");
353   
354  9515 VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
355  9517 VelocityContext vcontext = velocityManager.getVelocityContext();
356   
357  9499 getProgress().pushLevelProgress(7, this);
358   
359  9517 boolean eventSent = false;
360  9517 try {
361  9517 getProgress().startStep(this, "Prepare documents and put them in the context");
362   
363    // Prepare documents and put them in the context
364  9511 if (!xwiki.prepareDocuments(context.getRequest(), context, vcontext)) {
365  0 return null;
366    }
367   
368    // Start monitoring timer
369  9502 monitor = (MonitorPlugin) xwiki.getPlugin("monitor", context);
370  9505 if (monitor != null) {
371  0 monitor.startRequest("", context.getAction(), context.getURL());
372  0 monitor.startTimer("multipart");
373    }
374   
375  9505 getProgress().startStep(this, "Parses multipart");
376   
377    // Parses multipart so that params in multipart are available for all actions
378  9511 fileupload = Utils.handleMultipart(context.getRequest().getHttpServletRequest(), context);
379  9496 if (monitor != null) {
380  0 monitor.endTimer("multipart");
381    }
382   
383  9494 if (monitor != null) {
384  0 monitor.setWikiPage(context.getDoc().getFullName());
385    }
386   
387  9491 getProgress().startStep(this, "Send [" + context.getAction() + "] action start event");
388   
389    // For the moment we're sending the XWiki context as the data, but this will be
390    // changed in the future, when the whole platform will be written using components
391    // and there won't be a need for the context.
392  9512 try {
393  9512 ActionExecutingEvent event = new ActionExecutingEvent(context.getAction());
394  9498 om.notify(event, context.getDoc(), context);
395  9511 eventSent = true;
396  9505 if (event.isCanceled()) {
397    // Action has been canceled
398    // TODO: do something special ?
399  0 return null;
400    }
401    } catch (Throwable ex) {
402  0 LOGGER.error("Cannot send action notifications for document [" + context.getDoc()
403    + " using action [" + context.getAction() + "]", ex);
404    }
405   
406  9496 if (monitor != null) {
407  0 monitor.endTimer("prenotify");
408    }
409   
410    // Call the Actions
411   
412  9490 getProgress().startStep(this, "Search and execute entity resource handler");
413   
414    // Call the new Entity Resource Reference Handler.
415  9512 ResourceReferenceHandler entityResourceReferenceHandler = Utils.getComponent(
416    new DefaultParameterizedType(null, ResourceReferenceHandler.class, ResourceType.class), "bin");
417  9511 ResourceReference resourceReference =
418    Utils.getComponent(ResourceReferenceManager.class).getResourceReference();
419  9511 try {
420  9511 entityResourceReferenceHandler.handle(resourceReference,
421    new DefaultResourceReferenceHandlerChain(Collections.<ResourceReferenceHandler>emptyList()));
422    // Don't let the old actions kick in!
423  0 return null;
424    } catch (NotFoundResourceHandlerException e) {
425    // No Entity Resource Action has been found. Don't do anything and let it go through
426    // so that the old Action system kicks in...
427    } catch (Throwable e) {
428    // Some real failure, log it since it's a problem but still allow the old Action system a chance
429    // to do something...
430  0 LOGGER.error("Failed to handle Action for Resource [{}]", resourceReference, e);
431    }
432   
433  9501 getProgress().startStep(this, "Execute action render");
434   
435    // Handle the XWiki.RedirectClass object that can be attached to the current document
436  9510 boolean hasRedirect = false;
437  9510 if (handleRedirectObject) {
438  6427 hasRedirect = handleRedirectObject(context);
439    }
440   
441    // Then call the old Actions for backward compatibility (and because a lot of them have not been
442    // migrated to new Actions yet).
443  9500 String renderResult = null;
444  9506 XWikiDocument doc = context.getDoc();
445  9509 docName = doc.getFullName();
446  9501 if (!hasRedirect && action(context)) {
447  8750 renderResult = render(context);
448    }
449   
450  9508 if (renderResult != null) {
451  6948 if (doc.isNew() && "view".equals(context.getAction())
452    && !"recyclebin".equals(context.getRequest().get("viewer"))
453    && !"children".equals(context.getRequest().get("viewer"))
454    && !"siblings".equals(context.getRequest().get("viewer"))) {
455  158 String page = Utils.getPage(context.getRequest(), "docdoesnotexist");
456   
457  158 getProgress().startStep(this, "Execute template [" + page + "]");
458  158 Utils.parseTemplate(page, context);
459    } else {
460  6789 String page = Utils.getPage(context.getRequest(), renderResult);
461   
462  6790 getProgress().startStep(this, "Execute template [" + page + "]");
463  6790 Utils.parseTemplate(page, !page.equals("direct"), context);
464    }
465    }
466  9502 return null;
467    } catch (Throwable e) {
468  6 if (e instanceof IOException) {
469  0 e = new XWikiException(XWikiException.MODULE_XWIKI_APP,
470    XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION, "Exception while sending response", e);
471    }
472   
473  6 if (!(e instanceof XWikiException)) {
474  0 e = new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_UNKNOWN,
475    "Uncaught exception", e);
476    }
477   
478  6 try {
479  6 XWikiException xex = (XWikiException) e;
480  6 if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION) {
481    // Connection aborted from the client side, there's not much we can do on the server side. We
482    // simply ignore it.
483  1 LOGGER.debug("Connection aborted", e);
484    // We don't write any other message to the response, as the connection is broken, anyway.
485  1 return null;
486  5 } else if (xex.getCode() == XWikiException.ERROR_XWIKI_ACCESS_DENIED) {
487  5 Utils.parseTemplate(context.getWiki().Param("xwiki.access_exception", "accessdenied"), context);
488  5 return null;
489  0 } else if (xex.getCode() == XWikiException.ERROR_XWIKI_USER_INACTIVE) {
490  0 Utils.parseTemplate(context.getWiki().Param("xwiki.user_exception", "userinactive"), context);
491  0 return null;
492  0 } else if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_ATTACHMENT_NOT_FOUND) {
493  0 context.put("message", "attachmentdoesnotexist");
494  0 Utils.parseTemplate(
495    context.getWiki().Param("xwiki.attachment_exception", "attachmentdoesnotexist"), context);
496  0 return null;
497  0 } else if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_URL_EXCEPTION) {
498  0 vcontext.put("message", localizePlainOrKey("platform.core.invalidUrl"));
499  0 xwiki.setPhonyDocument(xwiki.getDefaultSpace(context) + "." + xwiki.getDefaultPage(context),
500    context, vcontext);
501  0 context.getResponse().setStatus(HttpServletResponse.SC_BAD_REQUEST);
502  0 Utils.parseTemplate(context.getWiki().Param("xwiki.invalid_url_exception", "error"), context);
503  0 return null;
504    }
505  0 vcontext.put("exp", e);
506  0 if (LOGGER.isWarnEnabled()) {
507    // Don't log "Broken Pipe" exceptions since they're not real errors and we don't want to pollute
508    // the logs with unnecessary stack traces. It just means the client side has cancelled the
509    // connection.
510  0 if (ExceptionUtils.getRootCauseMessage(e).equals("IOException: Broken pipe")) {
511  0 return null;
512    }
513  0 LOGGER.warn("Uncaught exception: " + e.getMessage(), e);
514    }
515    // If the request is an AJAX request, we don't return a whole HTML page, but just the exception
516    // inline.
517  0 String exceptionTemplate = ajax ? "exceptioninline" : "exception";
518  0 Utils.parseTemplate(Utils.getPage(context.getRequest(), exceptionTemplate), context);
519  0 return null;
520    } catch (XWikiException ex) {
521  0 if (ex.getCode() == XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION) {
522  0 LOGGER.error("Connection aborted");
523    }
524    } catch (Exception e2) {
525    // I hope this never happens
526  0 LOGGER.error("Uncaught exceptions (inner): ", e);
527  0 LOGGER.error("Uncaught exceptions (outer): ", e2);
528    }
529  0 return null;
530    } finally {
531    // Let's make sure we have flushed content and closed
532  9514 try {
533  9513 context.getResponse().getWriter().flush();
534    } catch (Throwable e) {
535    // This might happen if the connection was closed, for example.
536    // If we can't flush, then there's nothing more we can send to the client.
537    }
538   
539  9513 if (monitor != null) {
540  0 monitor.endTimer("request");
541  0 monitor.startTimer("notify");
542    }
543   
544  9516 if (eventSent) {
545    // For the moment we're sending the XWiki context as the data, but this will be
546    // changed in the future, when the whole platform will be written using components
547    // and there won't be a need for the context.
548  9503 try {
549  9503 om.notify(new ActionExecutedEvent(context.getAction()), context.getDoc(), context);
550    } catch (Throwable ex) {
551  0 LOGGER.error("Cannot send action notifications for document [" + docName + " using action ["
552    + context.getAction() + "]", ex);
553    }
554    }
555   
556  9510 if (monitor != null) {
557  0 monitor.endTimer("notify");
558    }
559   
560  9506 getProgress().startStep(this, "Cleanup database connections");
561   
562    // Make sure we cleanup database connections
563    // There could be cases where we have some
564  9515 xwiki.getStore().cleanUp(context);
565   
566  9517 getProgress().popLevelProgress(this);
567    }
568    } finally {
569    // End request
570  9653 if (monitor != null) {
571  0 monitor.endRequest();
572    }
573   
574    // Stop progress
575  9653 if (actionProgress != null) {
576  0 getProgress().popLevelProgress(this);
577   
578  0 om.removeListener(actionProgress.getName());
579    }
580   
581  9653 if (fileupload != null) {
582  10 fileupload.cleanFileList(context);
583    }
584    }
585    }
586   
 
587  0 toggle private void renderInit(XWikiContext xcontext) throws Exception
588    {
589  0 RenderingContext renderingContext = Utils.getComponent(RenderingContext.class);
590  0 MutableRenderingContext mutableRenderingContext =
591  0 renderingContext instanceof MutableRenderingContext ? (MutableRenderingContext) renderingContext : null;
592   
593  0 if (mutableRenderingContext != null) {
594  0 mutableRenderingContext.push(renderingContext.getTransformation(), renderingContext.getXDOM(),
595    renderingContext.getDefaultSyntax(), "init.vm", renderingContext.isRestricted(), Syntax.XHTML_1_0);
596    }
597   
598  0 xcontext.getResponse().setStatus(503);
599  0 xcontext.getResponse().setContentType("text/html; charset=UTF-8");
600   
601  0 try {
602  0 Utils.getComponent(TemplateManager.class).render("init.vm", xcontext.getResponse().getWriter());
603    } finally {
604  0 if (mutableRenderingContext != null) {
605  0 mutableRenderingContext.pop();
606    }
607    }
608   
609  0 xcontext.getResponse().flushBuffer();
610   
611  0 xcontext.setFinished(true);
612    }
613   
 
614  9652 toggle protected XWikiContext initializeXWikiContext(ActionMapping mapping, ActionForm form, HttpServletRequest req,
615    HttpServletResponse resp) throws XWikiException, ServletException
616    {
617  9652 String action = mapping.getName();
618   
619  9625 XWikiRequest request = new XWikiServletRequest(req);
620  9631 XWikiResponse response = new XWikiServletResponse(resp);
621  9631 XWikiContext context =
622    Utils.prepareContext(action, request, response, new XWikiServletContext(this.servlet.getServletContext()));
623   
624    // This code is already called by struts.
625    // However struts will also set all the parameters of the form data
626    // directly from the request objects.
627    // However because of bug http://jira.xwiki.org/jira/browse/XWIKI-2422
628    // We need to perform encoding of windows-1252 chars in ISO mode
629    // So we need to make sure this code is called
630    // TODO: completely get rid of struts so that we control this part of the code and can reduce drastically the
631    // number of calls
632  9619 if (form != null) {
633  945 form.reset(mapping, request);
634    }
635   
636    // Add the form to the context
637  9622 context.setForm((XWikiForm) form);
638   
639    // Initialize the Container component which is the new way of transporting the Context in the new
640    // component architecture.
641  9631 initializeContainerComponent(context);
642   
643  9649 return context;
644    }
645   
 
646  9574 toggle protected void initializeContainerComponent(XWikiContext context) throws ServletException
647    {
648    // Initialize the Container fields (request, response, session).
649    // Note that this is a bridge between the old core and the component architecture.
650    // In the new component architecture we use ThreadLocal to transport the request,
651    // response and session to components which require them.
652    // In the future this Servlet will be replaced by the XWikiPlexusServlet Servlet.
653  9604 ServletContainerInitializer containerInitializer = Utils.getComponent(ServletContainerInitializer.class);
654   
655  9636 try {
656  9642 containerInitializer.initializeRequest(context.getRequest().getHttpServletRequest(), context);
657  9651 containerInitializer.initializeResponse(context.getResponse());
658  9646 containerInitializer.initializeSession(context.getRequest().getHttpServletRequest());
659    } catch (ServletContainerException e) {
660  0 throw new ServletException("Failed to initialize Request/Response or Session", e);
661    }
662    }
663   
 
664  9653 toggle protected void cleanupComponents()
665    {
666  9653 Container container = Utils.getComponent(Container.class);
667  9651 Execution execution = Utils.getComponent(Execution.class);
668   
669    // We must ensure we clean the ThreadLocal variables located in the Container and Execution
670    // components as otherwise we will have a potential memory leak.
671  9651 container.removeRequest();
672  9653 container.removeResponse();
673  9651 container.removeSession();
674  9649 execution.removeContext();
675    }
676   
 
677  0 toggle public String getRealPath(String path)
678    {
679  0 return this.servlet.getServletContext().getRealPath(path);
680    }
681   
682    // hook
 
683  2346 toggle public boolean action(XWikiContext context) throws XWikiException
684    {
685  2344 return true;
686    }
687   
688    // hook
 
689  0 toggle public String render(XWikiContext context) throws XWikiException
690    {
691  0 return null;
692    }
693   
694    /**
695    * Redirect the user to an other location if the document holds an XWiki.RedirectClass instance (used when a
696    * document is moved).
697    *
698    * @param context the XWiki context
699    * @return either or not a redirection have been sent
700    * @throws XWikiException if error occurs
701    * @since 8.0RC1
702    * @since 7.4.2
703    */
 
704  6426 toggle protected boolean handleRedirectObject(XWikiContext context) throws XWikiException
705    {
706  6426 WikiReference wikiReference = context.getWikiReference();
707   
708    // Look if the document has a redirect object
709  6420 XWikiDocument doc = context.getDoc();
710  6422 BaseObject redirectObj =
711    doc.getXObject(new DocumentReference("RedirectClass", new SpaceReference("XWiki", wikiReference)));
712  6422 if (redirectObj == null) {
713  6420 return false;
714    }
715   
716    // Get the location
717  3 String location = redirectObj.getStringValue("location");
718  3 if (StringUtils.isBlank(location)) {
719  0 return false;
720    }
721   
722    // Resolve the location to get a reference
723  3 DocumentReferenceResolver<String> resolver = Utils.getComponent(DocumentReferenceResolver.TYPE_STRING);
724  3 EntityReference locationReference = resolver.resolve(location, wikiReference);
725   
726    // Get the type of the current target
727  3 ResourceReference resourceReference = Utils.getComponent(ResourceReferenceManager.class).getResourceReference();
728  3 EntityResourceReference entityResource = (EntityResourceReference) resourceReference;
729  3 EntityReference entityReference = entityResource.getEntityReference();
730   
731    // If the entity is inside a document, compute the new entity with the new document part.
732  3 if (entityReference.getType().ordinal() > EntityType.DOCUMENT.ordinal()) {
733  0 EntityReference parentDocument = entityReference.extractReference(EntityType.DOCUMENT);
734  0 locationReference = entityReference.replaceParent(parentDocument, locationReference);
735    }
736   
737    // Get the URL corresponding to the location
738    // Note: the anchor part is lost in the process, because it is not sent to the server
739    // (see: http://stackoverflow.com/a/4276491)
740  3 String url = context.getWiki().getURL(locationReference, context.getAction(),
741    context.getRequest().getQueryString(), null, context);
742   
743    // Send the redirection
744  3 try {
745  3 context.getResponse().sendRedirect(url);
746    } catch (IOException e) {
747  0 throw new XWikiException("Failed to redirect.", e);
748    }
749   
750  3 return true;
751    }
752   
 
753  6243 toggle protected void handleRevision(XWikiContext context) throws XWikiException
754    {
755  6243 String rev = context.getRequest().getParameter("rev");
756  6243 if (rev != null) {
757  0 context.put("rev", rev);
758  0 XWikiDocument doc = (XWikiDocument) context.get("doc");
759  0 XWikiDocument tdoc = (XWikiDocument) context.get("tdoc");
760  0 XWikiDocument rdoc = (!doc.getLanguage().equals(tdoc.getLanguage())) ? doc
761    : context.getWiki().getDocument(doc, rev, context);
762  0 XWikiDocument rtdoc = (doc.getLanguage().equals(tdoc.getLanguage())) ? rdoc
763    : context.getWiki().getDocument(tdoc, rev, context);
764  0 context.put("tdoc", rtdoc);
765  0 context.put("cdoc", rdoc);
766  0 context.put("doc", rdoc);
767    }
768    }
769   
770    /**
771    * Send redirection based on a regexp pattern (if any) set at the main wiki level. To enable this feature you must
772    * add xwiki.preferences.redirect=1 to your xwiki.cfg.
773    *
774    * @param response the servlet response
775    * @param url url of the request
776    * @param context the XWiki context
777    * @return true if a redirection has been sent
778    */
 
779  9651 toggle protected boolean sendGlobalRedirect(XWikiResponse response, String url, XWikiContext context) throws Exception
780    {
781  9649 if ("1".equals(context.getWiki().Param("xwiki.preferences.redirect"))) {
782    // Note: This implementation is not performant at all and will slow down the wiki as the number
783    // of redirects increases. A better implementation would use a cache of redirects and would use
784    // the notification mechanism to update the cache when the XWiki.XWikiPreferences document is
785    // modified.
786  0 XWikiDocument globalPreferences = context.getWiki().getDocument("xwiki:XWiki.XWikiPreferences", context);
787  0 Vector<BaseObject> redirects = globalPreferences.getObjects("XWiki.GlobalRedirect");
788   
789  0 if (redirects != null) {
790  0 for (BaseObject redir : redirects) {
791  0 if (redir != null) {
792  0 String p = redir.getStringValue("pattern");
793  0 if (p != null && url.matches(p)) {
794  0 String dest = redir.getStringValue("destination");
795  0 response.sendRedirect(url.replaceAll(p, dest));
796  0 return true;
797    }
798    }
799    }
800    }
801    }
802  9651 return false;
803    }
804   
 
805  294 toggle protected void sendRedirect(XWikiResponse response, String url) throws XWikiException
806    {
807  294 try {
808  294 if (url != null) {
809  258 response.sendRedirect(response.encodeRedirectURL(url));
810    }
811    } catch (IOException e) {
812  0 Object[] args = { url };
813  0 throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_REDIRECT_EXCEPTION,
814    "Exception while sending redirect to page {0}", e, args);
815    }
816    }
817   
818    /**
819    * Gets the translated version of a document, in the specified language. If the translation does not exist, a new
820    * document translation is created. If the requested language does not correspond to a translation (is not defined
821    * or is the same as the main document), then the main document is returned.
822    *
823    * @param doc the main (default, untranslated) document to translate
824    * @param language the requested document language
825    * @param context the current request context
826    * @return the translated document, or the original untranslated document if the requested language is not a
827    * translation
828    * @throws XWikiException if the translation cannot be retrieved from the database
829    */
 
830  500 toggle protected XWikiDocument getTranslatedDocument(XWikiDocument doc, String language, XWikiContext context)
831    throws XWikiException
832    {
833  500 XWikiDocument tdoc;
834  500 if (StringUtils.isBlank(language) || language.equals("default") || language.equals(doc.getDefaultLanguage())) {
835  500 tdoc = doc;
836    } else {
837  0 tdoc = doc.getTranslatedDocument(language, context);
838  0 if (tdoc == doc) {
839  0 tdoc = new XWikiDocument(doc.getDocumentReference());
840  0 tdoc.setLanguage(language);
841  0 tdoc.setStore(doc.getStore());
842    }
843  0 tdoc.setTranslation(1);
844    }
845  500 return tdoc;
846    }
847   
848    /**
849    * Perform CSRF check and redirect to the resubmission page if needed. Throws an exception if the access should be
850    * denied, returns false if the check failed and the user will be redirected to a resubmission page.
851    *
852    * @param context current xwiki context containing the request
853    * @return true if the check succeeded, false if resubmission is needed
854    * @throws XWikiException if the check fails
855    */
 
856  252 toggle protected boolean csrfTokenCheck(XWikiContext context) throws XWikiException
857    {
858  252 CSRFToken csrf = Utils.getComponent(CSRFToken.class);
859  252 try {
860  252 String token = context.getRequest().getParameter("form_token");
861  252 if (!csrf.isTokenValid(token)) {
862  2 sendRedirect(context.getResponse(), csrf.getResubmissionURL());
863  2 return false;
864    }
865    } catch (XWikiException exception) {
866    // too bad
867  0 throw new XWikiException(XWikiException.MODULE_XWIKI_ACCESS, XWikiException.ERROR_XWIKI_ACCESS_DENIED,
868    "Access denied, secret token verification failed", exception);
869    }
870  250 return true;
871    }
872   
873    /**
874    * In order to let users enter URLs to Spaces we do the following when receiving {@code /A/B} (where A and B are
875    * spaces):
876    * <ul>
877    * <li>check that the action is "view" (we only support this for the view action since otherwise this would break
878    * apps written before this concept was introduced in XWiki 7.2M1)</li>
879    * <li>if A.B exists then continue</li>
880    * <li>if A.B doesn't exist then forward to A.B.WebHome</li>
881    * </ul>
882    * In order to disable this redirect you should provide the {@code spaceRedirect=false} Query String parameter and
883    * value.
884    *
885    * @since 7.2M1
886    */
 
887  9646 toggle private boolean redirectSpaceURLs(String action, XWikiURLFactory urlf, XWiki xwiki, XWikiContext context)
888    throws Exception
889    {
890  9629 if ("view".equals(action) && !"false".equalsIgnoreCase(context.getRequest().getParameter("spaceRedirect"))) {
891  739 DocumentReference reference = xwiki.getDocumentReference(context.getRequest(), context);
892  739 if (!xwiki.exists(reference, context)) {
893  291 String defaultDocumentName = Utils.getComponent(EntityReferenceProvider.class)
894    .getDefaultReference(EntityType.DOCUMENT).getName();
895    // Avoid an infinite loop by ensuring we're not on a WebHome already
896  291 if (!reference.getName().equals(defaultDocumentName)) {
897    // Consider the reference as a Space Reference and Construct a new reference to the home of that
898    // Space. Then generate the URL for it and forward to it
899  136 SpaceReference spaceReference = new SpaceReference(reference.getName(), reference.getParent());
900  136 EntityReferenceSerializer<String> localSerializer =
901    Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "local");
902    // Extract the anchor
903  136 String anchor = new URL(context.getRequest().getRequestURL().toString()).getRef();
904  136 URL forwardURL = urlf.createURL(localSerializer.serialize(spaceReference), defaultDocumentName,
905    action, context.getRequest().getQueryString(), anchor,
906    spaceReference.getWikiReference().getName(), context);
907    // Since createURL() contain the webapp context and since RequestDispatcher should not contain it,
908    // we need to remove it!
909  136 String webappContext = xwiki.getWebAppPath(context);
910  136 String relativeURL = urlf.getURL(forwardURL, context);
911  136 relativeURL = '/' + StringUtils.substringAfter(relativeURL, webappContext);
912  136 context.getRequest().getRequestDispatcher(relativeURL).forward(context.getRequest(),
913    context.getResponse());
914  136 return true;
915    }
916    }
917    }
918   
919  9509 return false;
920    }
921    }