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

File SaveAction.java

 

Coverage histogram

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

Code metrics

46
97
7
1
305
187
40
0.41
13.86
7
5.71

Classes

Class Line # Actions
SaveAction 52 97 0% 40 61
0.593333359.3%
 

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: 7b29d5cdab1055b5ac1babf0828f1880d51ebfa8 $
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  39 toggle public SaveAction()
67    {
68  39 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  138 toggle public boolean save(XWikiContext context) throws XWikiException
81    {
82  138 XWiki xwiki = context.getWiki();
83  138 XWikiRequest request = context.getRequest();
84  138 XWikiDocument doc = context.getDoc();
85  138 EditForm form = (EditForm) context.getForm();
86   
87    // Check save session
88  138 int sectionNumber = 0;
89  138 if (request.getParameter("section") != null && xwiki.hasSectionEdit(context)) {
90  0 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  138 doc = doc.clone();
97   
98  138 String language = form.getLanguage();
99    // FIXME Which one should be used: doc.getDefaultLanguage or
100    // form.getDefaultLanguage()?
101    // String defaultLanguage = ((EditForm) form).getDefaultLanguage();
102  138 XWikiDocument tdoc;
103   
104  138 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  138 tdoc = doc;
109    } else {
110  0 tdoc = doc.getTranslatedDocument(language, context);
111  0 if ((tdoc == doc) && xwiki.isMultiLingual(context)) {
112    // Saving a new document translation.
113  0 tdoc = new XWikiDocument(doc.getDocumentReference());
114  0 tdoc.setLanguage(language);
115  0 tdoc.setStore(doc.getStore());
116  0 } 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  0 tdoc = tdoc.clone();
120    }
121    }
122   
123  138 if (doc.isNew()) {
124  85 doc.setLocale(Locale.ROOT);
125  85 if (doc.getDefaultLocale() == Locale.ROOT) {
126  85 doc.setDefaultLocale(
127    LocaleUtils.toLocale(context.getWiki().getLanguagePreference(context), Locale.ROOT));
128    }
129    }
130   
131  138 try {
132  138 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  138 if (sectionNumber != 0) {
141  0 XWikiDocument sectionDoc = tdoc.clone();
142  0 sectionDoc.readFromForm(form, context);
143  0 String sectionContent = sectionDoc.getContent() + "\n";
144  0 String content = tdoc.updateDocumentSection(sectionNumber, sectionContent);
145  0 tdoc.setContent(content);
146  0 tdoc.setComment(sectionDoc.getComment());
147  0 tdoc.setMinorEdit(sectionDoc.isMinorEdit());
148    } else {
149  138 tdoc.readFromForm(form, context);
150    }
151   
152    // TODO: handle Author
153  138 String username = context.getUser();
154  138 tdoc.setAuthor(username);
155  138 if (tdoc.isNew()) {
156  85 tdoc.setCreator(username);
157    }
158   
159    // Make sure we have at least the meta data dirty status
160  138 tdoc.setMetaDataDirty(true);
161   
162    // Validate the document if we have xvalidate=1 in the request
163  138 if ("1".equals(request.getParameter("xvalidate"))) {
164  0 boolean validationResult = tdoc.validate(context);
165    // If the validation fails we should show the "Inline form" edit mode
166  0 if (validationResult == false) {
167    // Set display context to 'edit'
168  0 context.put("display", "edit");
169    // Set the action used by the "Inline form" edit mode as the context action. See #render(XWikiContext).
170  0 context.setAction(tdoc.getDefaultEditMode(context));
171    // Set the document in the context
172  0 context.put("doc", doc);
173  0 context.put("cdoc", tdoc);
174  0 context.put("tdoc", tdoc);
175    // Force the "Inline form" edit mode.
176  0 getCurrentScriptContext().setAttribute("editor", "inline", ScriptContext.ENGINE_SCOPE);
177   
178  0 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  138 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  138 xwiki.saveDocument(tdoc, tdoc.getComment(), tdoc.isMinorEdit(), context);
191   
192  138 Job createJob = startCreateJob(tdoc.getDocumentReference(), form);
193  138 if (createJob != null) {
194  17 if (isAsync(request)) {
195  14 if (Utils.isAjaxRequest(context)) {
196    // Redirect to the job status URL of the job we have just launched.
197  14 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  3 try {
206  3 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  121 XWikiLock lock = tdoc.getLock(context);
216  121 if (lock != null) {
217  46 tdoc.removeLock(context);
218    }
219    }
220   
221  138 return false;
222    }
223   
 
224  123 toggle @Override
225    public boolean action(XWikiContext context) throws XWikiException
226    {
227    // CSRF prevention
228  123 if (!csrfTokenCheck(context)) {
229  0 return false;
230    }
231   
232  123 if (save(context)) {
233  0 return true;
234    }
235    // forward to view
236  123 if (Utils.isAjaxRequest(context)) {
237  14 context.getResponse().setStatus(HttpServletResponse.SC_NO_CONTENT);
238    } else {
239  109 sendRedirect(context.getResponse(), Utils.getRedirect("view", context));
240    }
241  123 return false;
242    }
243   
 
244  0 toggle @Override
245    public String render(XWikiContext context) throws XWikiException
246    {
247  0 XWikiException e = (XWikiException) context.get("exception");
248  0 if ((e != null) && (e.getCode() == XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY)) {
249  0 return "docalreadyexists";
250    }
251   
252  0 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  0 return context.getAction();
257    }
258   
259  0 return "exception";
260    }
261   
 
262  17 toggle private boolean isAsync(XWikiRequest request)
263    {
264  17 return "true".equals(request.get(ASYNC_PARAM));
265    }
266   
 
267  138 toggle private Job startCreateJob(EntityReference entityReference, EditForm editForm) throws XWikiException
268    {
269  138 if (StringUtils.isBlank(editForm.getTemplate())) {
270    // No template specified, nothing more to do.
271  121 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  17 RefactoringScriptService refactoring =
278    (RefactoringScriptService) Utils.getComponent(ScriptService.class, "refactoring");
279   
280  17 CreateRequest request = refactoring.createCreateRequest(Arrays.asList(entityReference));
281    // Set the target document.
282  17 request.setEntityReferences(Arrays.asList(entityReference));
283    // Set the template to use.
284  17 DocumentReferenceResolver<String> resolver =
285    Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "currentmixed");
286  17 EntityReference templateReference = resolver.resolve(editForm.getTemplate());
287  17 request.setTemplateReference(templateReference);
288    // We`ve already created and populated the fields of the target document, focus only on the remaining children
289    // specified in the template.
290  17 request.setSkippedEntities(Arrays.asList(entityReference));
291   
292  17 Job createJob = refactoring.create(request);
293  17 if (createJob != null) {
294  17 return createJob;
295    } else {
296  0 throw new XWikiException(String.format("Failed to schedule the create job for [%s]", entityReference),
297    refactoring.getLastError());
298    }
299    }
300   
 
301  14 toggle private String serializeJobId(List<String> jobId)
302    {
303  14 return StringUtils.join(jobId, "/");
304    }
305    }