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