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

File SaveAction.java

 

Coverage histogram

../../../../img/srcFileCovDistChart9.png
41% of files have more coverage

Code metrics

46
98
7
1
306
188
40
0.41
14
7
5.71

Classes

Class Line # Actions
SaveAction 52 98 0% 40 16
0.8940397589.4%
 

Contributing tests

No tests hitting this source file were found.

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.util.Arrays;
23    import java.util.List;
24    import java.util.Locale;
25   
26    import javax.script.ScriptContext;
27    import javax.servlet.http.HttpServletResponse;
28   
29    import org.apache.commons.lang3.StringUtils;
30    import org.xwiki.job.Job;
31    import org.xwiki.localization.LocaleUtils;
32    import org.xwiki.model.EntityType;
33    import org.xwiki.model.reference.DocumentReferenceResolver;
34    import org.xwiki.model.reference.EntityReference;
35    import org.xwiki.refactoring.job.CreateRequest;
36    import org.xwiki.refactoring.script.RefactoringScriptService;
37    import org.xwiki.script.service.ScriptService;
38   
39    import com.xpn.xwiki.XWiki;
40    import com.xpn.xwiki.XWikiContext;
41    import com.xpn.xwiki.XWikiException;
42    import com.xpn.xwiki.doc.XWikiDocument;
43    import com.xpn.xwiki.doc.XWikiLock;
44   
45    /**
46    * Action used for saving and proceeding to view the saved page.
47    * <p>
48    * Used as a generic action for saving documents.
49    *
50    * @version $Id: e61abdc91044d64546d2d1266bbbb0cea794eba3 $
51    */
 
52    public class SaveAction extends PreviewAction
53    {
54    /** The identifier of the save action. */
55    public static final String ACTION_NAME = "save";
56   
57    protected static final String ASYNC_PARAM = "async";
58   
59    /**
60    * The redirect class, used to mark pages that are redirect place-holders, i.e. hidden pages that serve only for
61    * redirecting the user to a different page (e.g. when a page has been moved).
62    */
63    private static final EntityReference REDIRECT_CLASS =
64    new EntityReference("RedirectClass", EntityType.DOCUMENT, new EntityReference("XWiki", EntityType.SPACE));
65   
 
66  70 toggle public SaveAction()
67    {
68  70 this.waitForXWikiInitialization = true;
69    }
70   
71    /**
72    * Saves the current document, updated according to the parameters sent in the request.
73    *
74    * @param context The current request {@link XWikiContext context}.
75    * @return <code>true</code> if there was an error and the response needs to render an error page,
76    * <code>false</code> if the document was correctly saved.
77    * @throws XWikiException If an error occured: cannot communicate with the storage module, or cannot update the
78    * document because the request contains invalid parameters.
79    */
 
80  593 toggle public boolean save(XWikiContext context) throws XWikiException
81    {
82  593 XWiki xwiki = context.getWiki();
83  593 XWikiRequest request = context.getRequest();
84  593 XWikiDocument doc = context.getDoc();
85  593 EditForm form = (EditForm) context.getForm();
86   
87    // Check save session
88  593 int sectionNumber = 0;
89  593 if (request.getParameter("section") != null && xwiki.hasSectionEdit(context)) {
90  2 sectionNumber = Integer.parseInt(request.getParameter("section"));
91    }
92   
93    // We need to clone this document first, since a cached storage would return the same object for the
94    // following requests, so concurrent request might get a partially modified object, or worse, if an error
95    // occurs during the save, the cached object will not reflect the actual document at all.
96  593 doc = doc.clone();
97   
98  593 String language = form.getLanguage();
99    // FIXME Which one should be used: doc.getDefaultLanguage or
100    // form.getDefaultLanguage()?
101    // String defaultLanguage = ((EditForm) form).getDefaultLanguage();
102  593 XWikiDocument tdoc;
103   
104  593 if (doc.isNew() || (language == null) || (language.equals("")) || (language.equals("default"))
105    || (language.equals(doc.getDefaultLanguage()))) {
106    // Saving the default document translation.
107    // Need to save parent and defaultLanguage if they have changed
108  589 tdoc = doc;
109    } else {
110  4 tdoc = doc.getTranslatedDocument(language, context);
111  4 if ((tdoc == doc) && xwiki.isMultiLingual(context)) {
112    // Saving a new document translation.
113  2 tdoc = new XWikiDocument(doc.getDocumentReference());
114  2 tdoc.setLanguage(language);
115  2 tdoc.setStore(doc.getStore());
116  2 } else if (tdoc != doc) {
117    // Saving an existing document translation (but not the default one).
118    // Same as above, clone the object retrieved from the store cache.
119  1 tdoc = tdoc.clone();
120    }
121    }
122   
123  593 if (doc.isNew()) {
124  253 doc.setLocale(Locale.ROOT);
125  253 if (doc.getDefaultLocale() == Locale.ROOT) {
126  253 doc.setDefaultLocale(
127    LocaleUtils.toLocale(context.getWiki().getLanguagePreference(context), Locale.ROOT));
128    }
129    }
130   
131  593 try {
132  593 tdoc.readFromTemplate(form.getTemplate(), context);
133    } catch (XWikiException e) {
134  0 if (e.getCode() == XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY) {
135  0 context.put("exception", e);
136  0 return true;
137    }
138    }
139   
140  593 if (sectionNumber != 0) {
141  2 XWikiDocument sectionDoc = tdoc.clone();
142  2 sectionDoc.readFromForm(form, context);
143  2 String sectionContent = sectionDoc.getContent() + "\n";
144  2 String content = tdoc.updateDocumentSection(sectionNumber, sectionContent);
145  2 tdoc.setContent(content);
146  2 tdoc.setComment(sectionDoc.getComment());
147  2 tdoc.setMinorEdit(sectionDoc.isMinorEdit());
148    } else {
149  591 tdoc.readFromForm(form, context);
150    }
151   
152    // TODO: handle Author
153  593 String username = context.getUser();
154  593 tdoc.setAuthor(username);
155  593 if (tdoc.isNew()) {
156  255 tdoc.setCreator(username);
157    }
158   
159    // Make sure we have at least the meta data dirty status
160  593 tdoc.setMetaDataDirty(true);
161   
162    // Validate the document if we have xvalidate=1 in the request
163  593 if ("1".equals(request.getParameter("xvalidate"))) {
164  3 boolean validationResult = tdoc.validate(context);
165    // If the validation fails we should show the "Inline form" edit mode
166  3 if (validationResult == false) {
167    // Set display context to 'edit'
168  1 context.put("display", "edit");
169    // Set the action used by the "Inline form" edit mode as the context action. See #render(XWikiContext).
170  1 context.setAction(tdoc.getDefaultEditMode(context));
171    // Set the document in the context
172  1 context.put("doc", doc);
173  1 context.put("cdoc", tdoc);
174  1 context.put("tdoc", tdoc);
175    // Force the "Inline form" edit mode.
176  1 getCurrentScriptContext().setAttribute("editor", "inline", ScriptContext.ENGINE_SCOPE);
177   
178  1 return true;
179    }
180    }
181   
182    // Remove the redirect object if the save request doesn't update it. This allows users to easily overwrite
183    // redirect place-holders that are created when we move pages around.
184  592 if (tdoc.getXObject(REDIRECT_CLASS) != null && request.getParameter("XWiki.RedirectClass_0_location") == null) {
185  0 tdoc.removeXObjects(REDIRECT_CLASS);
186    }
187   
188    // We get the comment to be used from the document
189    // It was read using readFromForm
190  592 xwiki.saveDocument(tdoc, tdoc.getComment(), tdoc.isMinorEdit(), context);
191   
192  592 Job createJob = startCreateJob(tdoc.getDocumentReference(), form);
193  592 if (createJob != null) {
194  39 if (isAsync(request)) {
195  33 if (Utils.isAjaxRequest(context)) {
196    // Redirect to the job status URL of the job we have just launched.
197  33 sendRedirect(context.getResponse(), String.format("%s/rest/jobstatus/%s?media=json",
198    context.getRequest().getContextPath(), serializeJobId(createJob.getRequest().getId())));
199    }
200   
201    // else redirect normally and the operation will eventually finish in the background.
202    // Note: It is preferred that async mode is called in an AJAX request that can display the progress.
203    } else {
204    // Sync mode, default, wait for the work to finish.
205  6 try {
206  6 createJob.join();
207    } catch (InterruptedException e) {
208  0 throw new XWikiException(String.format(
209    "Interrupted while waiting for template [%s] to be processed when creating the document [%s]",
210    form.getTemplate(), tdoc.getDocumentReference()), e);
211    }
212    }
213    } else {
214    // Nothing more to do, just unlock the document.
215  553 XWikiLock lock = tdoc.getLock(context);
216  553 if (lock != null) {
217  213 tdoc.removeLock(context);
218    }
219    }
220   
221  592 return false;
222    }
223   
 
224  550 toggle @Override
225    public boolean action(XWikiContext context) throws XWikiException
226    {
227    // CSRF prevention
228  550 if (!csrfTokenCheck(context)) {
229  1 return false;
230    }
231   
232  549 if (save(context)) {
233  1 return true;
234    }
235    // forward to view
236  548 if (Utils.isAjaxRequest(context)) {
237  45 context.getResponse().setStatus(HttpServletResponse.SC_NO_CONTENT);
238    } else {
239  503 sendRedirect(context.getResponse(), Utils.getRedirect("view", context));
240    }
241  548 return false;
242    }
243   
 
244  1 toggle @Override
245    public String render(XWikiContext context) throws XWikiException
246    {
247  1 XWikiException e = (XWikiException) context.get("exception");
248  1 if ((e != null) && (e.getCode() == XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY)) {
249  0 return "docalreadyexists";
250    }
251   
252  1 if ("edit".equals(context.get("display"))) {
253    // When form validation (xvalidate) fails the save action forwards to the "Inline form" edit mode. In this
254    // case the context action is not "save" anymore because it was changed in #save(XWikiContext). The context
255    // action should be the action used by the "Inline form" edit mode (either "edit" or "inline").
256  1 return context.getAction();
257    }
258   
259  0 return "exception";
260    }
261   
 
262  39 toggle private boolean isAsync(XWikiRequest request)
263    {
264  39 return "true".equals(request.get(ASYNC_PARAM));
265    }
266   
 
267  592 toggle private Job startCreateJob(EntityReference entityReference, EditForm editForm) throws XWikiException
268    {
269  592 if (StringUtils.isBlank(editForm.getTemplate())) {
270    // No template specified, nothing more to do.
271  553 return null;
272    }
273   
274    // If a template is set in the request, then this is a create action which needs to be handled by a create job,
275    // but skipping the target document, which is now already saved by the save action.
276   
277  39 RefactoringScriptService refactoring =
278    (RefactoringScriptService) Utils.getComponent(ScriptService.class, "refactoring");
279   
280  39 CreateRequest request = refactoring.getRequestFactory().createCreateRequest(Arrays.asList(entityReference));
281  39 request.setCheckAuthorRights(false);
282    // Set the target document.
283  39 request.setEntityReferences(Arrays.asList(entityReference));
284    // Set the template to use.
285  39 DocumentReferenceResolver<String> resolver =
286    Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "currentmixed");
287  39 EntityReference templateReference = resolver.resolve(editForm.getTemplate());
288  39 request.setTemplateReference(templateReference);
289    // We`ve already created and populated the fields of the target document, focus only on the remaining children
290    // specified in the template.
291  39 request.setSkippedEntities(Arrays.asList(entityReference));
292   
293  39 Job createJob = refactoring.create(request);
294  39 if (createJob != null) {
295  39 return createJob;
296    } else {
297  0 throw new XWikiException(String.format("Failed to schedule the create job for [%s]", entityReference),
298    refactoring.getLastError());
299    }
300    }
301   
 
302  33 toggle private String serializeJobId(List<String> jobId)
303    {
304  33 return StringUtils.join(jobId, "/");
305    }
306    }