001/*
002 * (C) Copyright 2012-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Antoine Taillefer
018 */
019package org.nuxeo.ecm.diff.web;
020
021import static org.jboss.seam.ScopeType.CONVERSATION;
022import static org.jboss.seam.ScopeType.PAGE;
023
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.List;
027
028import org.apache.commons.lang3.StringUtils;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.jboss.seam.annotations.Factory;
032import org.jboss.seam.annotations.In;
033import org.jboss.seam.annotations.Name;
034import org.jboss.seam.annotations.Scope;
035import org.jboss.seam.international.LocaleSelector;
036import org.nuxeo.ecm.core.api.CoreSession;
037import org.nuxeo.ecm.core.api.DocumentModel;
038import org.nuxeo.ecm.core.api.IdRef;
039import org.nuxeo.ecm.core.api.NuxeoException;
040import org.nuxeo.ecm.core.api.VersionModel;
041import org.nuxeo.ecm.core.api.impl.VersionModelImpl;
042import org.nuxeo.ecm.diff.content.ContentDiffHelper;
043import org.nuxeo.ecm.diff.model.DiffDisplayBlock;
044import org.nuxeo.ecm.diff.model.DifferenceType;
045import org.nuxeo.ecm.diff.model.DocumentDiff;
046import org.nuxeo.ecm.diff.service.DiffDisplayService;
047import org.nuxeo.ecm.diff.service.DocumentDiffService;
048import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
049import org.nuxeo.ecm.webapp.documentsLists.DocumentsListsManager;
050import org.nuxeo.ecm.webapp.versioning.VersionedActions;
051import org.nuxeo.runtime.api.Framework;
052
053/**
054 * Handles document diff actions.
055 *
056 * @author <a href="mailto:ataillefer@nuxeo.com">Antoine Taillefer</a>
057 * @since 5.6
058 */
059@Name("diffActions")
060@Scope(CONVERSATION)
061public class DiffActionsBean implements Serializable {
062
063    private static final long serialVersionUID = -5507491210664361778L;
064
065    private static final Log log = LogFactory.getLog(DiffActionsBean.class);
066
067    private static final String DOC_DIFF_VIEW = "view_doc_diff";
068
069    private static final String CONTENT_DIFF_DIFFERENCE_TYPE_MSG_KEY_PREFIX = "diff.content.differenceType.message.";
070
071    private static final String LAST_VERSION_PROPERTY = "lastVersion";
072
073    @In(create = true, required = false)
074    protected transient CoreSession documentManager;
075
076    @In(create = true, required = false)
077    protected transient NavigationContext navigationContext;
078
079    @In(create = true, required = false)
080    protected transient DocumentsListsManager documentsListsManager;
081
082    @In(create = true, required = false)
083    protected transient VersionedActions versionedActions;
084
085    @In(create = true)
086    protected transient LocaleSelector localeSelector;
087
088    protected DocumentModel leftDoc;
089
090    protected DocumentModel rightDoc;
091
092    protected String selectedVersionId;
093
094    protected String diffSelectionType = DiffSelectionType.content.name();
095
096    /**
097     * Checks if the diff action is available for the {@link DocumentsListsManager#CURRENT_DOCUMENT_SELECTION} working
098     * list.
099     *
100     * @return true if can diff the current document selection
101     */
102    public boolean getCanDiffCurrentDocumentSelection() {
103
104        return getCanDiffWorkingList(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION);
105    }
106
107    /**
108     * Checks if the diff action is available for the {@link DocumentsListsManager#CURRENT_DOCUMENT_TRASH_SELECTION}
109     * working list.
110     *
111     * @return true if can diff the current document trash selection
112     */
113    public boolean getCanDiffCurrentTrashSelection() {
114
115        return getCanDiffWorkingList(DocumentsListsManager.CURRENT_DOCUMENT_TRASH_SELECTION);
116    }
117
118    /**
119     * Checks if the diff action is available for the {@link DocumentsListsManager#CURRENT_DOCUMENT_SECTION_SELECTION}
120     * working list.
121     *
122     * @return true if can diff the current section selection
123     */
124    public boolean getCanDiffCurrentSectionSelection() {
125
126        return getCanDiffWorkingList(DocumentsListsManager.CURRENT_DOCUMENT_SECTION_SELECTION);
127    }
128
129    /**
130     * Checks if the diff action is available for the {@link VersionDocumentsListsConstants#CURRENT_VERSION_SELECTION}
131     * working list.
132     *
133     * @return true if can diff the current version selection
134     */
135    public boolean getCanDiffCurrentVersionSelection() {
136
137        return getCanDiffWorkingList(DocumentsListsManager.CURRENT_VERSION_SELECTION);
138    }
139
140    /**
141     * Checks if the diff action is available for the {@link DocumentsListsManager#DEFAULT_WORKING_LIST} working list.
142     *
143     * @return true if can diff the current default working list selection
144     */
145    public boolean getCanDiffCurrentDefaultSelection() {
146
147        return getCanDiffWorkingList(DocumentsListsManager.DEFAULT_WORKING_LIST);
148    }
149
150    /**
151     * Checks if the diff action is available for the {@code listName} working list.
152     * <p>
153     * Condition: the working list has exactly 2 documents.
154     *
155     * @param listName the list name
156     * @return true if can diff the {@code listName} working list
157     */
158    public boolean getCanDiffWorkingList(String listName) {
159
160        List<DocumentModel> currentSelectionWorkingList = documentsListsManager.getWorkingList(listName);
161        return currentSelectionWorkingList != null && currentSelectionWorkingList.size() == 2;
162    }
163
164    /**
165     * Prepares a diff of the current document selection.
166     *
167     * @return the view id
168     */
169    public String prepareCurrentDocumentSelectionDiff() {
170
171        diffSelectionType = DiffSelectionType.content.name();
172        return prepareWorkingListDiff(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION);
173    }
174
175    /**
176     * Prepares a diff of the current document trash selection.
177     *
178     * @return the view id
179     */
180    public String prepareCurrentTrashSelectionDiff() {
181
182        diffSelectionType = DiffSelectionType.trash.name();
183        return prepareWorkingListDiff(DocumentsListsManager.CURRENT_DOCUMENT_TRASH_SELECTION);
184    }
185
186    /**
187     * Prepares a diff of the current section selection.
188     *
189     * @return the view id
190     */
191    public String prepareCurrentSectionSelectionDiff() {
192
193        diffSelectionType = DiffSelectionType.content.name();
194        return prepareWorkingListDiff(DocumentsListsManager.CURRENT_DOCUMENT_SECTION_SELECTION);
195    }
196
197    /**
198     * Prepares a diff of the current version selection.
199     *
200     * @return the view id
201     */
202    public String prepareCurrentVersionSelectionDiff() {
203
204        diffSelectionType = DiffSelectionType.version.name();
205        return prepareWorkingListDiff(DocumentsListsManager.CURRENT_VERSION_SELECTION);
206    }
207
208    /**
209     * Prepares a diff of the current default selection.
210     *
211     * @return the view id
212     */
213    public String prepareCurrentDefaultSelectionDiff() {
214
215        diffSelectionType = DiffSelectionType.content.name();
216        return prepareWorkingListDiff(DocumentsListsManager.DEFAULT_WORKING_LIST);
217    }
218
219    /**
220     * Prepares a diff of the {@code listName} working list.
221     *
222     * @return the view id
223     */
224    public String prepareWorkingListDiff(String listName) {
225
226        List<DocumentModel> workingList = getWorkingList(listName);
227
228        leftDoc = workingList.get(0);
229        rightDoc = workingList.get(1);
230
231        return refresh();
232    }
233
234    /**
235     * Prepare a diff of the current document with a specific version
236     *
237     * @param versionLabel version label to look for, if you want the last version use org.nuxeo.ecm.diff.web
238     *            .DiffActionsBean#LAST_VERSION_PROPERTY
239     */
240    public String prepareCurrentVersionDiff(String versionLabel) {
241        if (StringUtils.isBlank(versionLabel)) {
242            versionLabel = LAST_VERSION_PROPERTY;
243        }
244
245        DocumentModel currentDocument = navigationContext.getCurrentDocument();
246        if (currentDocument.isVersion()) {
247            log.info("Unable to diff, current document is a version document");
248            return null;
249        }
250
251        DocumentModel documentVersion;
252        if (LAST_VERSION_PROPERTY.equals(versionLabel)) {
253            documentVersion = documentManager.getLastDocumentVersion(currentDocument.getRef());
254
255            if (documentVersion == null) {
256                log.info("Unable to diff, current document do not have any versions yet.");
257                return null;
258            }
259        } else {
260            VersionModel versionModel = new VersionModelImpl();
261            versionModel.setLabel(versionLabel);
262            documentVersion = documentManager.getDocumentWithVersion(currentDocument.getRef(), versionModel);
263
264            if (documentVersion == null) {
265                log.info("Unable to found " + versionLabel + " on current document to diff.");
266                return null;
267            }
268        }
269
270        setLeftDoc(currentDocument);
271        setRightDoc(documentVersion);
272
273        diffSelectionType = DiffSelectionType.version.name();
274
275        return DOC_DIFF_VIEW;
276    }
277
278    /**
279     * Prepares a diff of the selected version with the live doc.
280     *
281     * @return the view id
282     */
283    public String prepareCurrentVersionDiff() {
284
285        String selectedVersionId = versionedActions.getSelectedVersionId();
286        if (selectedVersionId != null) {
287            DocumentModel currentDocument = navigationContext.getCurrentDocument();
288            if (currentDocument == null) {
289                throw new NuxeoException(
290                        "Cannot make a diff between selected version and current document since current document is null.");
291            }
292
293            VersionModel selectedVersion = new VersionModelImpl();
294            selectedVersion.setId(selectedVersionId);
295            DocumentModel docVersion = documentManager.getDocumentWithVersion(currentDocument.getRef(), selectedVersion);
296            if (docVersion == null) {
297                throw new NuxeoException(
298                        "Cannot make a diff between selected version and current document since selected version document is null.");
299            }
300
301            leftDoc = docVersion;
302            rightDoc = currentDocument;
303
304            diffSelectionType = DiffSelectionType.version.name();
305
306            return DOC_DIFF_VIEW;
307        }
308        return null;
309    }
310
311    /**
312     * Refreshes the diff between leftDoc and rightDoc.
313     *
314     * @return the view id
315     */
316    public String refresh() {
317
318        // Fetch docs from repository
319        if (isDocumentDiffAvailable()) {
320            leftDoc = documentManager.getDocument(leftDoc.getRef());
321            rightDoc = documentManager.getDocument(rightDoc.getRef());
322        }
323
324        return DOC_DIFF_VIEW;
325    }
326
327    /**
328     * Checks if document diff is available.
329     *
330     * @return true, if is document diff available
331     */
332    public boolean isDocumentDiffAvailable() {
333        return leftDoc != null && rightDoc != null;
334    }
335
336    /**
337     * Gets the document diff.
338     *
339     * @return the document diff between leftDoc and rightDoc if leftDoc and rightDoc aren't null, else null
340     */
341    @Factory(value = "defaultDiffDisplayBlocks", scope = PAGE)
342    public List<DiffDisplayBlock> getDefaultDiffDisplayBlocks() {
343
344        if (leftDoc == null || rightDoc == null) {
345            return new ArrayList<>();
346        }
347
348        DocumentDiff docDiff = getDocumentDiffService().diff(documentManager, leftDoc, rightDoc);
349        return getDiffDisplayService().getDiffDisplayBlocks(docDiff, leftDoc, rightDoc);
350    }
351
352    /**
353     * Gets the content diff fancybox URL for the property with xpath {@code propertyXPath}.
354     *
355     * @param propertyLabel the property label
356     * @param propertyXPath the property xpath
357     * @return the content diff fancybox URL
358     */
359    public String getContentDiffFancyBoxURL(String propertyLabel, String propertyXPath) {
360
361        return getContentDiffFancyBoxURL(propertyLabel, propertyXPath, null);
362    }
363
364    /**
365     * Gets the content diff fancybox URL for the property with xpath {@code propertyXPath} using {@code conversionType}
366     * .
367     *
368     * @param propertyLabel the property label
369     * @param propertyXPath the property xpath
370     * @param conversionType the conversion type
371     * @return the content diff fancybox URL
372     */
373    public String getContentDiffFancyBoxURL(String propertyLabel, String propertyXPath, String conversionType)
374            {
375
376        if (StringUtils.isEmpty(propertyXPath)) {
377            log.error("Cannot get content diff fancybox URL with a null propertyXPath.");
378            return null;
379        }
380        return ContentDiffHelper.getContentDiffFancyBoxURL(navigationContext.getCurrentDocument(), propertyLabel,
381                propertyXPath, conversionType);
382    }
383
384    /**
385     * Gets the content diff URL of two documents independently of the current document
386     *
387     * @param docLeftId a DocumentModel id, not a path.
388     * @param docRightId a DocumentModel id, not a path.
389     */
390    public String getContentDiffURL(String docLeftId, String docRightId, String propertyXPath,
391            String conversionTypeParam) {
392        DocumentModel leftDoc = null;
393        DocumentModel rightDoc = null;
394        if (!StringUtils.isBlank(docLeftId)) {
395            leftDoc = documentManager.getDocument(new IdRef(docLeftId));
396        }
397
398        if (!StringUtils.isBlank(docRightId)) {
399            rightDoc = documentManager.getDocument(new IdRef(docRightId));
400        }
401
402        if (rightDoc == null || leftDoc == null) {
403            log.error("Cannot get content diff URL with a null leftDoc or a null rightDoc.");
404            return null;
405        }
406
407        if (StringUtils.isEmpty(propertyXPath)) {
408            log.error("Cannot get content diff URL with a null schemaName or a null fieldName.");
409            return null;
410        }
411
412        String conversionType = null;
413        if (!StringUtils.isEmpty(conversionTypeParam)) {
414            conversionType = conversionTypeParam;
415        }
416
417        return ContentDiffHelper.getContentDiffURL(navigationContext.getCurrentDocument().getRepositoryName(), leftDoc,
418                rightDoc, propertyXPath, conversionType, localeSelector.getLocaleString());
419    }
420
421    /**
422     * Gets the content diff URL.
423     *
424     * @param propertyXPath the property xpath
425     * @param conversionTypeParam the conversion type param
426     * @return the content diff URL
427     */
428    public String getContentDiffURL(String propertyXPath, String conversionTypeParam) {
429
430        if (leftDoc == null || rightDoc == null) {
431            log.error("Cannot get content diff URL with a null leftDoc or a null rightDoc.");
432            return null;
433        }
434        if (StringUtils.isEmpty(propertyXPath)) {
435            log.error("Cannot get content diff URL with a null schemaName or a null fieldName.");
436            return null;
437        }
438        String conversionType = null;
439        if (!StringUtils.isEmpty(conversionTypeParam)) {
440            conversionType = conversionTypeParam;
441        }
442        return ContentDiffHelper.getContentDiffURL(navigationContext.getCurrentDocument().getRepositoryName(), leftDoc,
443                rightDoc, propertyXPath, conversionType, localeSelector.getLocaleString());
444    }
445
446    /**
447     * Gets the content diff with blob post processing URL.
448     *
449     * @param propertyXPath the property xpath
450     * @param conversionTypeParam the conversion type param
451     * @return the content diff with blob post processing URL
452     */
453    public String getContentDiffWithBlobPostProcessingURL(String propertyXPath, String conversionTypeParam) {
454        return getContentDiffURL(propertyXPath, conversionTypeParam) + "?blobPostProcessing=true";
455    }
456
457    /**
458     * Checks if is different filename.
459     */
460    public boolean isDifferentFilename(DifferenceType differenceType) {
461        return DifferenceType.differentFilename.equals(differenceType);
462    }
463
464    /**
465     * Gets the content diff difference type message key.
466     */
467    public String getContentDiffDifferenceTypeMsgKey(DifferenceType differenceType) {
468        return CONTENT_DIFF_DIFFERENCE_TYPE_MSG_KEY_PREFIX + differenceType.name();
469    }
470
471    /**
472     * Gets the {@code listName} working list.
473     *
474     * @return the {@code listName} working list
475     */
476    protected final List<DocumentModel> getWorkingList(String listName) {
477
478        List<DocumentModel> currentSelectionWorkingList = documentsListsManager.getWorkingList(listName);
479
480        if (currentSelectionWorkingList == null || currentSelectionWorkingList.size() != 2) {
481            throw new NuxeoException(String.format(
482                    "Cannot make a diff of the %s working list: need to have exactly 2 documents in the working list.",
483                    listName));
484        }
485        return currentSelectionWorkingList;
486    }
487
488    /**
489     * Gets the document diff service.
490     *
491     * @return the document diff service
492     */
493    protected final DocumentDiffService getDocumentDiffService() {
494        return Framework.getService(DocumentDiffService.class);
495    }
496
497    /**
498     * Gets the diff display service.
499     *
500     * @return the diff display service
501     */
502    protected final DiffDisplayService getDiffDisplayService() {
503        return Framework.getService(DiffDisplayService.class);
504    }
505
506    public DocumentModel getLeftDoc() {
507        return leftDoc;
508    }
509
510    public void setLeftDoc(DocumentModel leftDoc) {
511        this.leftDoc = leftDoc;
512    }
513
514    public DocumentModel getRightDoc() {
515        return rightDoc;
516    }
517
518    public void setRightDoc(DocumentModel rightDoc) {
519        this.rightDoc = rightDoc;
520    }
521
522    public String getDiffSelectionType() {
523        return diffSelectionType;
524    }
525
526    public void setDiffSelectionType(String diffSelectionType) {
527        this.diffSelectionType = diffSelectionType;
528    }
529}