001/*
002 * (C) Copyright 2012 Nuxeo SA (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.service.impl;
020
021import java.io.Serializable;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Calendar;
025import java.util.HashMap;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.commons.collections.CollectionUtils;
032import org.apache.commons.lang.StringUtils;
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.ecm.core.api.DocumentModel;
036import org.nuxeo.ecm.core.api.NuxeoException;
037import org.nuxeo.ecm.core.schema.SchemaManager;
038import org.nuxeo.ecm.core.schema.types.Field;
039import org.nuxeo.ecm.core.schema.types.FieldImpl;
040import org.nuxeo.ecm.core.schema.types.QName;
041import org.nuxeo.ecm.core.schema.types.Type;
042import org.nuxeo.ecm.diff.content.ContentDiffHelper;
043import org.nuxeo.ecm.diff.model.DiffBlockDefinition;
044import org.nuxeo.ecm.diff.model.DiffComplexFieldDefinition;
045import org.nuxeo.ecm.diff.model.DiffDisplayBlock;
046import org.nuxeo.ecm.diff.model.DiffFieldDefinition;
047import org.nuxeo.ecm.diff.model.DiffFieldItemDefinition;
048import org.nuxeo.ecm.diff.model.DifferenceType;
049import org.nuxeo.ecm.diff.model.DocumentDiff;
050import org.nuxeo.ecm.diff.model.PropertyDiff;
051import org.nuxeo.ecm.diff.model.PropertyDiffDisplay;
052import org.nuxeo.ecm.diff.model.PropertyType;
053import org.nuxeo.ecm.diff.model.SchemaDiff;
054import org.nuxeo.ecm.diff.model.impl.ComplexPropertyDiff;
055import org.nuxeo.ecm.diff.model.impl.ContentDiffDisplayImpl;
056import org.nuxeo.ecm.diff.model.impl.ContentProperty;
057import org.nuxeo.ecm.diff.model.impl.ContentPropertyDiff;
058import org.nuxeo.ecm.diff.model.impl.DiffBlockDefinitionImpl;
059import org.nuxeo.ecm.diff.model.impl.DiffDisplayBlockImpl;
060import org.nuxeo.ecm.diff.model.impl.DiffFieldDefinitionImpl;
061import org.nuxeo.ecm.diff.model.impl.DiffFieldItemDefinitionImpl;
062import org.nuxeo.ecm.diff.model.impl.ListPropertyDiff;
063import org.nuxeo.ecm.diff.model.impl.PropertyDiffDisplayImpl;
064import org.nuxeo.ecm.diff.model.impl.SimplePropertyDiff;
065import org.nuxeo.ecm.diff.service.ComplexPropertyHelper;
066import org.nuxeo.ecm.diff.service.DiffDisplayService;
067import org.nuxeo.ecm.platform.forms.layout.api.BuiltinModes;
068import org.nuxeo.ecm.platform.forms.layout.api.FieldDefinition;
069import org.nuxeo.ecm.platform.forms.layout.api.LayoutDefinition;
070import org.nuxeo.ecm.platform.forms.layout.api.LayoutRowDefinition;
071import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition;
072import org.nuxeo.ecm.platform.forms.layout.api.WidgetReference;
073import org.nuxeo.ecm.platform.forms.layout.api.impl.FieldDefinitionImpl;
074import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutDefinitionImpl;
075import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutRowDefinitionImpl;
076import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetDefinitionImpl;
077import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetReferenceImpl;
078import org.nuxeo.ecm.platform.forms.layout.api.service.LayoutStore;
079import org.nuxeo.runtime.api.Framework;
080import org.nuxeo.runtime.model.ComponentInstance;
081import org.nuxeo.runtime.model.DefaultComponent;
082
083/**
084 * Default implementation of the {@link DiffDisplayService}.
085 *
086 * @author Antoine Taillefer (ataillefer@nuxeo.com)
087 * @since 5.6
088 */
089public class DiffDisplayServiceImpl extends DefaultComponent implements DiffDisplayService {
090
091    private static final long serialVersionUID = 6608445970773402827L;
092
093    private static final Log LOGGER = LogFactory.getLog(DiffDisplayServiceImpl.class);
094
095    protected static final String DIFF_DISPLAY_EXTENSION_POINT = "diffDisplay";
096
097    protected static final String DIFF_DEFAULT_DISPLAY_EXTENSION_POINT = "diffDefaultDisplay";
098
099    protected static final String DIFF_BLOCK_EXTENSION_POINT = "diffBlock";
100
101    protected static final String DIFF_WIDGET_CATEGORY = "diff";
102
103    protected static final String DIFF_BLOCK_DEFAULT_TEMPLATE_PATH = "/layouts/layout_diff_template.xhtml";
104
105    protected static final String DIFF_BLOCK_LABEL_PROPERTY_NAME = "label";
106
107    protected static final String DIFF_BLOCK_DEFAULT_LABEL_PREFIX = "label.diffBlock.";
108
109    protected static final String DIFF_WIDGET_LABEL_PREFIX = "label.";
110
111    protected static final String CONTENT_DIFF_LINKS_WIDGET_NAME = "contentDiffLinks";
112
113    protected static final String CONTENT_DIFF_LINKS_WIDGET_NAME_SUFFIX = "_contentDiffLinks";
114
115    protected static final String DIFF_WIDGET_FIELD_DEFINITION_VALUE = "value";
116
117    protected static final String DIFF_WIDGET_FIELD_DEFINITION_DIFFERENCE_TYPE = "differenceType";
118
119    protected static final String DIFF_WIDGET_FIELD_DEFINITION_STYLE_CLASS = "styleClass";
120
121    protected static final String DIFF_WIDGET_FIELD_DEFINITION_FILENAME = "filename";
122
123    protected static final String DIFF_WIDGET_FIELD_DEFINITION_DISPLAY_HTML_CONVERSION = "displayHtmlConversion";
124
125    protected static final String DIFF_WIDGET_FIELD_DEFINITION_DISPLAY_TEXT_CONVERSION = "displayTextConversion";
126
127    protected static final String DIFF_WIDGET_PROPERTY_DISPLAY_ALL_ITEMS = "displayAllItems";
128
129    protected static final String DIFF_WIDGET_PROPERTY_DISPLAY_ITEM_INDEXES = "displayItemIndexes";
130
131    // TODO: refactor name (not related to widget)
132    protected static final String DIFF_LIST_WIDGET_INDEX_SUBWIDGET_FIELD = "index";
133
134    protected static final String DIFF_LIST_WIDGET_INDEX_SUBWIDGET_TYPE = "int";
135
136    protected static final String DIFF_LIST_WIDGET_INDEX_SUBWIDGET_LABEL = "label.list.index";
137
138    protected static final String DIFF_LIST_WIDGET_VALUE_SUBWIDGET_FIELD = "value";
139
140    /** Diff excluded fields contributions. */
141    protected Map<String, List<String>> diffExcludedFieldsContribs = new HashMap<String, List<String>>();
142
143    /** Diff complex fields contributions. */
144    protected Map<String, Map<String, DiffComplexFieldDefinition>> diffComplexFieldsContribs = new HashMap<String, Map<String, DiffComplexFieldDefinition>>();
145
146    /** Diff display contributions. */
147    protected Map<String, List<String>> diffDisplayContribs = new HashMap<String, List<String>>();
148
149    /** Diff block contributions. */
150    protected Map<String, DiffBlockDefinition> diffBlockContribs = new HashMap<String, DiffBlockDefinition>();
151
152    @Override
153    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
154
155        if (DIFF_DEFAULT_DISPLAY_EXTENSION_POINT.equals(extensionPoint)) {
156            if (contribution instanceof DiffExcludedFieldsDescriptor) {
157                registerDiffExcludedFields((DiffExcludedFieldsDescriptor) contribution);
158            } else if (contribution instanceof DiffComplexFieldDescriptor) {
159                registerDiffComplexField((DiffComplexFieldDescriptor) contribution);
160            }
161        } else if (DIFF_DISPLAY_EXTENSION_POINT.equals(extensionPoint)) {
162            if (contribution instanceof DiffDisplayDescriptor) {
163                registerDiffDisplay((DiffDisplayDescriptor) contribution);
164            }
165        } else if (DIFF_BLOCK_EXTENSION_POINT.equals(extensionPoint)) {
166            if (contribution instanceof DiffBlockDescriptor) {
167                registerDiffBlock((DiffBlockDescriptor) contribution);
168            }
169        }
170        super.registerContribution(contribution, extensionPoint, contributor);
171    }
172
173    @Override
174    public Map<String, List<String>> getDiffExcludedSchemas() {
175        return diffExcludedFieldsContribs;
176    }
177
178    @Override
179    public List<String> getDiffExcludedFields(String schemaName) {
180        return diffExcludedFieldsContribs.get(schemaName);
181    }
182
183    @Override
184    public List<DiffComplexFieldDefinition> getDiffComplexFields() {
185        List<DiffComplexFieldDefinition> diffComplexFields = new ArrayList<DiffComplexFieldDefinition>();
186        for (Map<String, DiffComplexFieldDefinition> diffComplexFieldsBySchema : diffComplexFieldsContribs.values()) {
187            for (DiffComplexFieldDefinition diffComplexField : diffComplexFieldsBySchema.values()) {
188                diffComplexFields.add(diffComplexField);
189            }
190        }
191        return diffComplexFields;
192    }
193
194    @Override
195    public DiffComplexFieldDefinition getDiffComplexField(String schemaName, String fieldName) {
196        Map<String, DiffComplexFieldDefinition> diffComplexFieldsBySchema = diffComplexFieldsContribs.get(schemaName);
197        if (diffComplexFieldsBySchema != null) {
198            return diffComplexFieldsBySchema.get(fieldName);
199        }
200        return null;
201    }
202
203    @Override
204    public Map<String, List<String>> getDiffDisplays() {
205        return diffDisplayContribs;
206    }
207
208    @Override
209    public List<String> getDiffDisplay(String docType) {
210        return diffDisplayContribs.get(docType);
211
212    }
213
214    @Override
215    public Map<String, DiffBlockDefinition> getDiffBlockDefinitions() {
216        return diffBlockContribs;
217    }
218
219    @Override
220    public DiffBlockDefinition getDiffBlockDefinition(String name) {
221        return diffBlockContribs.get(name);
222    }
223
224    @Override
225    public List<DiffDisplayBlock> getDiffDisplayBlocks(DocumentDiff docDiff, DocumentModel leftDoc,
226            DocumentModel rightDoc) {
227
228        String leftDocType = leftDoc.getType();
229        String rightDocType = rightDoc.getType();
230        if (leftDocType.equals(rightDocType)) {
231            LOGGER.info(String.format(
232                    "The 2 documents have the same type '%s' => looking for a diffDisplay contribution defined for this type or the nearest super type.",
233                    leftDocType));
234            List<String> diffBlockRefs = getNearestSuperTypeDiffDisplay(leftDocType);
235            if (diffBlockRefs != null) {
236                LOGGER.info(String.format(
237                        "Found a diffDisplay contribution defined for the type '%s' or one of its super type => using it to display the diff.",
238                        leftDocType));
239                return getDiffDisplayBlocks(getDiffBlockDefinitions(diffBlockRefs), docDiff, leftDoc, rightDoc);
240            } else {
241                LOGGER.info(String.format(
242                        "No diffDisplay contribution was defined for the type '%s' or one of its super type => using default diff display.",
243                        leftDocType));
244            }
245        } else {
246            LOGGER.info(String.format(
247                    "The 2 documents don't have the same type: '%s'/'%s' => looking for a diffDisplay contribution defined for the nearest common super type.",
248                    leftDocType, rightDocType));
249            List<String> diffBlockRefs = getNearestSuperTypeDiffDisplay(leftDocType, rightDocType);
250            if (diffBlockRefs != null) {
251                LOGGER.info(String.format(
252                        "Found a diffDisplay contribution defined for a common super type of the types '%s'/'%s' => using it to display the diff.",
253                        leftDocType, rightDocType));
254                return getDiffDisplayBlocks(getDiffBlockDefinitions(diffBlockRefs), docDiff, leftDoc, rightDoc);
255            } else {
256                LOGGER.info(String.format(
257                        "No diffDisplay contribution was defined for any of the common super types of the types '%s'/'%s' => using default diff display.",
258                        leftDocType, rightDocType));
259            }
260        }
261        return getDefaultDiffDisplayBlocks(docDiff, leftDoc, rightDoc);
262    }
263
264    public List<DiffDisplayBlock> getDefaultDiffDisplayBlocks(DocumentDiff docDiff, DocumentModel leftDoc,
265            DocumentModel rightDoc) {
266
267        return getDiffDisplayBlocks(getDefaultDiffBlockDefinitions(docDiff), docDiff, leftDoc, rightDoc);
268    }
269
270    protected List<String> getNearestSuperTypeDiffDisplay(String docTypeName) {
271
272        List<String> diffDisplay = getDiffDisplay(docTypeName);
273        Type docType = getSchemaManager().getDocumentType(docTypeName);
274        while (diffDisplay == null && docType != null) {
275            Type superType = docType.getSuperType();
276            if (superType != null) {
277                diffDisplay = getDiffDisplay(superType.getName());
278            }
279            docType = superType;
280        }
281        return diffDisplay;
282    }
283
284    protected List<String> getNearestSuperTypeDiffDisplay(String leftDocTypeName, String rightDocTypeName)
285            {
286
287        List<String> diffDisplay = null;
288
289        List<String> leftDocSuperTypeNames = new ArrayList<String>();
290        List<String> rightDocSuperTypeNames = new ArrayList<String>();
291        leftDocSuperTypeNames.add(leftDocTypeName);
292        rightDocSuperTypeNames.add(rightDocTypeName);
293
294        Type leftDocType = getSchemaManager().getDocumentType(leftDocTypeName);
295        Type rightDocType = getSchemaManager().getDocumentType(rightDocTypeName);
296        if (leftDocType != null && rightDocType != null) {
297            Type[] leftDocTypeHierarchy = leftDocType.getTypeHierarchy();
298            for (Type type : leftDocTypeHierarchy) {
299                leftDocSuperTypeNames.add(type.getName());
300            }
301            Type[] rightDocTypeHierarchy = rightDocType.getTypeHierarchy();
302            for (Type type : rightDocTypeHierarchy) {
303                rightDocSuperTypeNames.add(type.getName());
304            }
305        }
306
307        for (String superTypeName : leftDocSuperTypeNames) {
308            if (rightDocSuperTypeNames.contains(superTypeName)) {
309                return getNearestSuperTypeDiffDisplay(superTypeName);
310            }
311        }
312
313        return diffDisplay;
314    }
315
316    /**
317     * Registers a diff excluded fields contrib.
318     */
319    protected final void registerDiffExcludedFields(DiffExcludedFieldsDescriptor descriptor) {
320
321        String schemaName = descriptor.getSchema();
322        if (!StringUtils.isEmpty(schemaName)) {
323            boolean enabled = descriptor.isEnabled();
324            // Check existing diffExcludedFields contrib for this schema
325            List<String> diffExcludedFields = diffExcludedFieldsContribs.get(schemaName);
326            if (diffExcludedFields != null) {
327                // If !enabled remove contrib
328                if (!enabled) {
329                    diffExcludedFieldsContribs.remove(schemaName);
330                }
331                // Else override contrib (no merge)
332                // TODO: implement merge
333                else {
334                    diffExcludedFieldsContribs.put(schemaName, getDiffExcludedFieldRefs(descriptor.getFields()));
335                }
336            }
337            // No existing diffExcludedFields contrib for this
338            // schema and enabled => add contrib
339            else if (enabled) {
340                diffExcludedFieldsContribs.put(schemaName, getDiffExcludedFieldRefs(descriptor.getFields()));
341            }
342        }
343    }
344
345    /**
346     * Registers a diff complex field contrib.
347     */
348    protected final void registerDiffComplexField(DiffComplexFieldDescriptor descriptor) {
349
350        String schemaName = descriptor.getSchema();
351        String fieldName = descriptor.getName();
352        if (!StringUtils.isEmpty(schemaName) && !StringUtils.isEmpty(fieldName)) {
353            // Check existing diffComplexField contrib for this schema/field
354            DiffComplexFieldDefinition diffComplexField = getDiffComplexField(schemaName, fieldName);
355            if (diffComplexField != null) {
356                // Override contrib (no merge)
357                // TODO: implement merge
358                diffComplexFieldsContribs.get(schemaName).put(fieldName, descriptor.getDiffComplexFieldDefinition());
359            }
360            // No existing diffComplexField contrib for this
361            // schema/field => add contrib
362            else {
363                Map<String, DiffComplexFieldDefinition> diffComplexFieldsBySchema = diffComplexFieldsContribs.get(schemaName);
364                if (diffComplexFieldsBySchema == null) {
365                    diffComplexFieldsBySchema = new HashMap<String, DiffComplexFieldDefinition>();
366                    diffComplexFieldsContribs.put(schemaName, diffComplexFieldsBySchema);
367                }
368                diffComplexFieldsBySchema.put(fieldName, descriptor.getDiffComplexFieldDefinition());
369            }
370        }
371    }
372
373    /**
374     * Registers a diff display contrib.
375     *
376     * @param contribution the contribution
377     */
378    protected final void registerDiffDisplay(DiffDisplayDescriptor descriptor) {
379
380        String docType = descriptor.getType();
381        if (!StringUtils.isEmpty(docType)) {
382            boolean enabled = descriptor.isEnabled();
383            // Check existing diffDisplay contrib for this type
384            List<String> diffDisplay = diffDisplayContribs.get(docType);
385            if (diffDisplay != null) {
386                // If !enabled remove contrib
387                if (!enabled) {
388                    diffDisplayContribs.remove(docType);
389                }
390                // Else override contrib (no merge)
391                // TODO: implement merge
392                else {
393                    diffDisplayContribs.put(docType, getDiffBlockRefs(descriptor.getDiffBlocks()));
394                }
395            }
396            // No existing diffDisplay contrib for this
397            // type and enabled => add contrib
398            else if (enabled) {
399                diffDisplayContribs.put(docType, getDiffBlockRefs(descriptor.getDiffBlocks()));
400            }
401        }
402    }
403
404    protected final List<String> getDiffBlockRefs(List<DiffBlockReferenceDescriptor> diffBlocks) {
405
406        List<String> diffBlockRefs = new ArrayList<String>();
407        for (DiffBlockReferenceDescriptor diffBlockRef : diffBlocks) {
408            diffBlockRefs.add(diffBlockRef.getName());
409        }
410        return diffBlockRefs;
411    }
412
413    protected final List<String> getDiffExcludedFieldRefs(List<DiffFieldDescriptor> diffExcludedFields) {
414
415        List<String> diffExcludedFieldRefs = new ArrayList<String>();
416        for (DiffFieldDescriptor diffExcludedFieldRef : diffExcludedFields) {
417            diffExcludedFieldRefs.add(diffExcludedFieldRef.getName());
418        }
419        return diffExcludedFieldRefs;
420    }
421
422    protected final void registerDiffBlock(DiffBlockDescriptor descriptor) {
423
424        String diffBlockName = descriptor.getName();
425        if (!StringUtils.isEmpty(diffBlockName)) {
426            List<DiffFieldDescriptor> fieldDescriptors = descriptor.getFields();
427            // No field descriptors => don't take diff block into account.
428            if (fieldDescriptors == null || fieldDescriptors.isEmpty()) {
429                LOGGER.warn(String.format(
430                        "The diffBlock contribution named '%s' has no fields, it won't be taken into account.",
431                        diffBlockName));
432            } else {
433                List<DiffFieldDefinition> fields = new ArrayList<DiffFieldDefinition>();
434                // Some field descriptors were found => use them to add the
435                // described fields, taking their order into account.
436                for (DiffFieldDescriptor fieldDescriptor : fieldDescriptors) {
437                    String category = fieldDescriptor.getCategory();
438                    String schema = fieldDescriptor.getSchema();
439                    String name = fieldDescriptor.getName();
440                    boolean displayContentDiffLinks = fieldDescriptor.isDisplayContentDiffLinks();
441                    List<DiffFieldItemDescriptor> fieldItemDescriptors = fieldDescriptor.getItems();
442                    if (!StringUtils.isEmpty(schema) && !StringUtils.isEmpty(name)) {
443                        List<DiffFieldItemDefinition> items = new ArrayList<DiffFieldItemDefinition>();
444                        for (DiffFieldItemDescriptor fieldItemDescriptor : fieldItemDescriptors) {
445                            items.add(new DiffFieldItemDefinitionImpl(fieldItemDescriptor.getName(),
446                                    fieldItemDescriptor.isDisplayContentDiffLinks()));
447                        }
448                        fields.add(new DiffFieldDefinitionImpl(category, schema, name, displayContentDiffLinks, items));
449                    }
450                }
451                // TODO: implement merge
452                diffBlockContribs.put(
453                        diffBlockName,
454                        new DiffBlockDefinitionImpl(diffBlockName, descriptor.getTemplates(), fields,
455                                descriptor.getProperties()));
456            }
457        }
458    }
459
460    protected final List<DiffBlockDefinition> getDefaultDiffBlockDefinitions(DocumentDiff docDiff)
461            {
462
463        List<DiffBlockDefinition> diffBlockDefs = new ArrayList<DiffBlockDefinition>();
464
465        for (String schemaName : docDiff.getSchemaNames()) {
466            List<String> diffExcludedFields = getDiffExcludedFields(schemaName);
467            // Only add the schema fields if the whole schema is not excluded
468            if (diffExcludedFields == null || diffExcludedFields.size() > 0) {
469                SchemaDiff schemaDiff = docDiff.getSchemaDiff(schemaName);
470                List<DiffFieldDefinition> fieldDefs = new ArrayList<DiffFieldDefinition>();
471                for (String fieldName : schemaDiff.getFieldNames()) {
472                    // Only add the field if it is not excluded
473                    if (diffExcludedFields == null || !diffExcludedFields.contains(fieldName)) {
474                        List<DiffFieldItemDefinition> fieldItems = new ArrayList<DiffFieldItemDefinition>();
475                        DiffComplexFieldDefinition complexFieldDef = getDiffComplexField(schemaName, fieldName);
476                        if (complexFieldDef != null) {
477                            List<DiffFieldItemDefinition> includedItems = complexFieldDef.getIncludedItems();
478                            List<DiffFieldItemDefinition> excludedItems = complexFieldDef.getExcludedItems();
479                            // Check included field items
480                            if (!CollectionUtils.isEmpty(includedItems)) {
481                                fieldItems.addAll(includedItems);
482                            }
483                            // Check excluded field items
484                            else if (!CollectionUtils.isEmpty(excludedItems)) {
485                                Field complexField = ComplexPropertyHelper.getField(schemaName, fieldName);
486                                if (complexField.getType().isListType()) {
487                                    complexField = ComplexPropertyHelper.getListFieldItem(complexField);
488                                }
489                                if (!complexField.getType().isComplexType()) {
490                                    throw new NuxeoException(
491                                            String.format(
492                                                    "Cannot compute field items for [%s:%s] since it is not a complex nor a complex list property.",
493                                                    schemaName, fieldName));
494                                }
495                                List<Field> complexFieldItems = ComplexPropertyHelper.getComplexFieldItems(complexField);
496                                for (Field complexFieldItem : complexFieldItems) {
497                                    String complexFieldItemName = complexFieldItem.getName().getLocalName();
498                                    boolean isFieldItem = true;
499                                    for (DiffFieldItemDefinition fieldItemDef : excludedItems) {
500                                        if (fieldItemDef.getName().equals(complexFieldItemName)) {
501                                            isFieldItem = false;
502                                            break;
503                                        }
504                                    }
505                                    if (isFieldItem) {
506                                        fieldItems.add(new DiffFieldItemDefinitionImpl(complexFieldItemName));
507                                    }
508                                }
509                            }
510                        }
511                        fieldDefs.add(new DiffFieldDefinitionImpl(DIFF_WIDGET_CATEGORY, schemaName, fieldName,
512                                fieldItems));
513                    }
514                }
515
516                Map<String, String> defaultDiffBlockTemplates = new HashMap<String, String>();
517                defaultDiffBlockTemplates.put(BuiltinModes.ANY, DIFF_BLOCK_DEFAULT_TEMPLATE_PATH);
518
519                Map<String, Map<String, Serializable>> defaultDiffBlockProperties = new HashMap<String, Map<String, Serializable>>();
520                Map<String, Serializable> labelProperty = new HashMap<String, Serializable>();
521                labelProperty.put(DIFF_BLOCK_LABEL_PROPERTY_NAME, DIFF_BLOCK_DEFAULT_LABEL_PREFIX + schemaName);
522                defaultDiffBlockProperties.put(BuiltinModes.ANY, labelProperty);
523
524                diffBlockDefs.add(new DiffBlockDefinitionImpl(schemaName, defaultDiffBlockTemplates, fieldDefs,
525                        defaultDiffBlockProperties));
526            }
527        }
528
529        return diffBlockDefs;
530    }
531
532    protected final List<DiffBlockDefinition> getDiffBlockDefinitions(List<String> diffBlockRefs) {
533
534        List<DiffBlockDefinition> diffBlockDefinitions = new ArrayList<DiffBlockDefinition>();
535        for (String diffBlockRef : diffBlockRefs) {
536            diffBlockDefinitions.add(getDiffBlockDefinition(diffBlockRef));
537        }
538        return diffBlockDefinitions;
539    }
540
541    protected final List<DiffDisplayBlock> getDiffDisplayBlocks(List<DiffBlockDefinition> diffBlockDefinitions,
542            DocumentDiff docDiff, DocumentModel leftDoc, DocumentModel rightDoc) {
543
544        List<DiffDisplayBlock> diffDisplayBlocks = new ArrayList<DiffDisplayBlock>();
545
546        for (DiffBlockDefinition diffBlockDef : diffBlockDefinitions) {
547            if (diffBlockDef != null) {
548                DiffDisplayBlock diffDisplayBlock = getDiffDisplayBlock(diffBlockDef, docDiff, leftDoc, rightDoc);
549                if (!diffDisplayBlock.isEmpty()) {
550                    diffDisplayBlocks.add(diffDisplayBlock);
551                }
552            }
553        }
554
555        return diffDisplayBlocks;
556    }
557
558    protected final DiffDisplayBlock getDiffDisplayBlock(DiffBlockDefinition diffBlockDefinition, DocumentDiff docDiff,
559            DocumentModel leftDoc, DocumentModel rightDoc) {
560
561        Map<String, Map<String, PropertyDiffDisplay>> leftValue = new HashMap<String, Map<String, PropertyDiffDisplay>>();
562        Map<String, Map<String, PropertyDiffDisplay>> rightValue = new HashMap<String, Map<String, PropertyDiffDisplay>>();
563        Map<String, Map<String, PropertyDiffDisplay>> contentDiffValue = new HashMap<String, Map<String, PropertyDiffDisplay>>();
564
565        List<LayoutRowDefinition> layoutRowDefinitions = new ArrayList<LayoutRowDefinition>();
566        List<WidgetDefinition> widgetDefinitions = new ArrayList<WidgetDefinition>();
567
568        List<DiffFieldDefinition> fieldDefinitions = diffBlockDefinition.getFields();
569        for (DiffFieldDefinition fieldDefinition : fieldDefinitions) {
570
571            String category = fieldDefinition.getCategory();
572            if (StringUtils.isEmpty(category)) {
573                category = DIFF_WIDGET_CATEGORY;
574            }
575            String schemaName = fieldDefinition.getSchema();
576            String fieldName = fieldDefinition.getName();
577            boolean displayContentDiffLinks = fieldDefinition.isDisplayContentDiffLinks();
578            List<DiffFieldItemDefinition> fieldItemDefs = fieldDefinition.getItems();
579
580            SchemaDiff schemaDiff = docDiff.getSchemaDiff(schemaName);
581            if (schemaDiff != null) {
582                PropertyDiff fieldDiff = schemaDiff.getFieldDiff(fieldName);
583                if (fieldDiff != null) {
584
585                    Serializable leftProperty = (Serializable) leftDoc.getProperty(schemaName, fieldName);
586                    Serializable rightProperty = (Serializable) rightDoc.getProperty(schemaName, fieldName);
587
588                    // Only include field diff if it is significant
589                    if (isFieldDiffSignificant(leftProperty, rightProperty)) {
590
591                        String propertyName = getPropertyName(schemaName, fieldName);
592                        List<WidgetReference> widgetReferences = new ArrayList<WidgetReference>();
593
594                        // Set property widget definition
595                        WidgetDefinition propertyWidgetDefinition = getWidgetDefinition(category, propertyName,
596                                fieldDiff.getPropertyType(), null, fieldItemDefs, false);
597                        widgetDefinitions.add(propertyWidgetDefinition);
598                        // Set property widget ref
599                        WidgetReferenceImpl propertyWidgetRef = new WidgetReferenceImpl(category, propertyName);
600                        widgetReferences.add(propertyWidgetRef);
601
602                        // Check if must display the content diff links widget
603                        if (!displayContentDiffLinks) {
604                            for (DiffFieldItemDefinition fieldItemDef : fieldItemDefs) {
605                                if (fieldItemDef.isDisplayContentDiffLinks()) {
606                                    displayContentDiffLinks = true;
607                                    break;
608                                }
609                            }
610                        }
611                        // Set content diff links widget definition and ref if
612                        // needed
613                        if (displayContentDiffLinks) {
614                            WidgetDefinition contentDiffLinksWidgetDefinition = getWidgetDefinition(category,
615                                    propertyName, fieldDiff.getPropertyType(), null, fieldItemDefs, true);
616                            widgetDefinitions.add(contentDiffLinksWidgetDefinition);
617                            WidgetReferenceImpl contentDiffLinksWidgetRef = new WidgetReferenceImpl(category,
618                                    propertyName + CONTENT_DIFF_LINKS_WIDGET_NAME_SUFFIX);
619                            widgetReferences.add(contentDiffLinksWidgetRef);
620                        }
621
622                        // Set layout row definition
623                        LayoutRowDefinition layoutRowDefinition = new LayoutRowDefinitionImpl(propertyName, null,
624                                widgetReferences, false, true);
625                        layoutRowDefinitions.add(layoutRowDefinition);
626
627                        // Set diff display field value
628                        boolean isDisplayAllItems = isDisplayAllItems(propertyWidgetDefinition);
629                        boolean isDisplayItemIndexes = isDisplayItemIndexes(propertyWidgetDefinition);
630
631                        // Left diff display
632                        setFieldDiffDisplay(leftProperty, fieldDiff, isDisplayAllItems, isDisplayItemIndexes,
633                                leftValue, schemaName, fieldName, leftDoc,
634                                PropertyDiffDisplay.RED_BACKGROUND_STYLE_CLASS);
635
636                        // Right diff display
637                        setFieldDiffDisplay(rightProperty, fieldDiff, isDisplayAllItems, isDisplayItemIndexes,
638                                rightValue, schemaName, fieldName, rightDoc,
639                                PropertyDiffDisplay.GREEN_BACKGROUND_STYLE_CLASS);
640
641                        // Content diff display
642                        if (displayContentDiffLinks) {
643                            PropertyDiffDisplay contentDiffDisplay = getFieldXPaths(propertyName, fieldDiff,
644                                    leftProperty, rightProperty, isDisplayAllItems, isDisplayItemIndexes, fieldItemDefs);
645                            Map<String, PropertyDiffDisplay> contentDiffSchemaMap = contentDiffValue.get(schemaName);
646                            if (contentDiffSchemaMap == null) {
647                                contentDiffSchemaMap = new HashMap<String, PropertyDiffDisplay>();
648                                contentDiffValue.put(schemaName, contentDiffSchemaMap);
649                            }
650                            contentDiffSchemaMap.put(fieldName, contentDiffDisplay);
651                        }
652                    }
653                }
654            }
655        }
656
657        // Build layout definition
658        LayoutDefinition layoutDefinition = new LayoutDefinitionImpl(diffBlockDefinition.getName(),
659                diffBlockDefinition.getProperties(), diffBlockDefinition.getTemplates(), layoutRowDefinitions,
660                widgetDefinitions);
661
662        // Build diff display block
663        Map<String, Serializable> diffBlockProperties = diffBlockDefinition.getProperties(BuiltinModes.ANY);
664        DiffDisplayBlock diffDisplayBlock = new DiffDisplayBlockImpl(
665                (String) diffBlockProperties.get(DIFF_BLOCK_LABEL_PROPERTY_NAME), leftValue, rightValue,
666                contentDiffValue, layoutDefinition);
667
668        return diffDisplayBlock;
669    }
670
671    /**
672     * Checks if the difference between the two specified properties is significant.
673     * <p>
674     * For example in the case of a date property, checks if the difference is greater than 1 minute since we don't
675     * display seconds in the default date widget.
676     */
677    protected boolean isFieldDiffSignificant(Serializable leftProperty, Serializable rightProperty) {
678
679        if (leftProperty instanceof Calendar && rightProperty instanceof Calendar) {
680            Calendar leftDate = (Calendar) leftProperty;
681            Calendar rightDate = (Calendar) rightProperty;
682            if (Math.abs(leftDate.getTimeInMillis() - rightDate.getTimeInMillis()) <= 60000) {
683                return false;
684            }
685        }
686        return true;
687    }
688
689    protected final boolean isDisplayAllItems(WidgetDefinition wDef) {
690
691        // Check 'displayAllItems' widget property
692        return getBooleanProperty(wDef, BuiltinModes.ANY, DIFF_WIDGET_PROPERTY_DISPLAY_ALL_ITEMS);
693    }
694
695    protected final boolean isDisplayItemIndexes(WidgetDefinition wDef) {
696
697        // Check 'displayItemIndexes' widget property
698        return getBooleanProperty(wDef, BuiltinModes.ANY, DIFF_WIDGET_PROPERTY_DISPLAY_ITEM_INDEXES);
699    }
700
701    protected final boolean getBooleanProperty(WidgetDefinition wDef, String mode, String property) {
702
703        Map<String, Map<String, Serializable>> props = wDef.getProperties();
704        if (props != null) {
705            Map<String, Serializable> modeProps = props.get(mode);
706            if (modeProps != null) {
707                Serializable propertyValue = modeProps.get(property);
708                if (propertyValue instanceof String) {
709                    return Boolean.parseBoolean((String) propertyValue);
710                }
711            }
712        }
713        return false;
714    }
715
716    /**
717     * Sets the field diff display.
718     *
719     * @param property the property
720     * @param fieldDiff the field diff
721     * @param isDisplayAllItems the is display all items
722     * @param isDisplayItemIndexes the is display item indexes
723     * @param value the value
724     * @param schemaName the schema name
725     * @param fieldName the field name
726     * @param doc the doc
727     * @param styleClass the style class
728     */
729    protected void setFieldDiffDisplay(Serializable property, PropertyDiff fieldDiff, boolean isDisplayAllItems,
730            boolean isDisplayItemIndexes, Map<String, Map<String, PropertyDiffDisplay>> value, String schemaName,
731            String fieldName, DocumentModel doc, String styleClass) {
732
733        PropertyDiffDisplay fieldDiffDisplay = getFieldDiffDisplay(property, fieldDiff, isDisplayAllItems,
734                isDisplayItemIndexes, false, styleClass);
735        Map<String, PropertyDiffDisplay> schemaMap = value.get(schemaName);
736        if (schemaMap == null) {
737            schemaMap = new HashMap<String, PropertyDiffDisplay>();
738            value.put(schemaName, schemaMap);
739        }
740        schemaMap.put(fieldName, fieldDiffDisplay);
741        // Handle mime type for the note:note property
742        putMimeTypeDiffDisplay(schemaName, fieldName, schemaMap, doc);
743    }
744
745    protected final void putMimeTypeDiffDisplay(String schemaName, String fieldName,
746            Map<String, PropertyDiffDisplay> schemaMap, DocumentModel doc) {
747
748        if ("note".equals(schemaName) && "note".equals(fieldName) && !schemaMap.containsKey("mime_type")) {
749            schemaMap.put("mime_type", new PropertyDiffDisplayImpl((Serializable) doc.getProperty("note", "mime_type")));
750        }
751    }
752
753    protected final PropertyDiffDisplay getFieldDiffDisplay(Serializable property, PropertyDiff propertyDiff,
754            boolean isDisplayAllItems, boolean isDisplayItemIndexes, boolean mustApplyStyleClass, String styleClass)
755            {
756
757        if (property == null) {
758            String fieldDiffDisplayStyleClass = PropertyDiffDisplay.DEFAULT_STYLE_CLASS;
759            if (mustApplyStyleClass && propertyDiff != null) {
760                fieldDiffDisplayStyleClass = styleClass;
761            }
762            // TODO: add DifferenceType.removed?
763            return new PropertyDiffDisplayImpl(null, fieldDiffDisplayStyleClass);
764        }
765
766        // List type
767        if (isListType(property)) {
768            List<Serializable> listProperty = getListProperty(property);
769            return getListFieldDiffDisplay(listProperty, (ListPropertyDiff) propertyDiff, isDisplayAllItems,
770                    isDisplayItemIndexes, styleClass);
771        }
772        // Other types (scalar, complex, content)
773        else {
774            return getFinalFieldDiffDisplay(property, propertyDiff, mustApplyStyleClass, styleClass);
775        }
776    }
777
778    protected final PropertyDiffDisplay getFinalFieldDiffDisplay(Serializable fieldDiffDisplay,
779            PropertyDiff propertyDiff, boolean mustApplyStyleClass, String styleClass) {
780
781        String finalFieldDiffDisplayStyleClass = PropertyDiffDisplay.DEFAULT_STYLE_CLASS;
782        if (mustApplyStyleClass && propertyDiff != null) {
783            finalFieldDiffDisplayStyleClass = styleClass;
784        }
785        PropertyDiffDisplay finalFieldDiffDisplay;
786        if (isComplexType(fieldDiffDisplay)) {
787            ComplexPropertyDiff complexPropertyDiff = null;
788            if (propertyDiff != null) {
789                if (!propertyDiff.isComplexType()) {
790                    throw new NuxeoException(
791                            "'fieldDiffDisplay' is of complex type whereas 'propertyDiff' is not, this is inconsistent");
792                }
793                complexPropertyDiff = (ComplexPropertyDiff) propertyDiff;
794            }
795            Map<String, Serializable> complexFieldDiffDisplay = getComplexProperty(fieldDiffDisplay);
796            for (String complexItemName : complexFieldDiffDisplay.keySet()) {
797                PropertyDiff complexItemPropertyDiff = null;
798                if (complexPropertyDiff != null) {
799                    complexItemPropertyDiff = complexPropertyDiff.getDiff(complexItemName);
800                }
801                complexFieldDiffDisplay.put(complexItemName,
802                // TODO: shouldn't we call getFieldDiffDisplay in case
803                // of an embedded list?
804                        getFinalFieldDiffDisplay(complexFieldDiffDisplay.get(complexItemName), complexItemPropertyDiff,
805                                true, styleClass));
806            }
807            finalFieldDiffDisplay = new PropertyDiffDisplayImpl((Serializable) complexFieldDiffDisplay);
808        } else if (fieldDiffDisplay instanceof Calendar) {
809            // TODO: add propertyDiff.getDifferenceType()?
810            finalFieldDiffDisplay = new PropertyDiffDisplayImpl(((Calendar) fieldDiffDisplay).getTime(),
811                    finalFieldDiffDisplayStyleClass);
812        } else {
813            // TODO: add propertyDiff.getDifferenceType()?
814            finalFieldDiffDisplay = new PropertyDiffDisplayImpl(fieldDiffDisplay, finalFieldDiffDisplayStyleClass);
815        }
816        return finalFieldDiffDisplay;
817    }
818
819    /**
820     * Gets the list field diff display.
821     *
822     * @param listProperty the list property
823     * @param listPropertyDiff the list property diff
824     * @param isDisplayAllItems the is display all items
825     * @param isDisplayItemIndexes the is display item indexes
826     * @param styleClass the style class
827     * @return the list field diff display
828     */
829    protected final PropertyDiffDisplay getListFieldDiffDisplay(List<Serializable> listProperty,
830            ListPropertyDiff listPropertyDiff, boolean isDisplayAllItems, boolean isDisplayItemIndexes,
831            String styleClass) {
832
833        // Get list property indexes
834        // By default: only items that are different (ie. held by the
835        // propertyDiff)
836        List<Integer> listPropertyIndexes = new ArrayList<Integer>();
837        if (isDisplayAllItems) {
838            // All items
839            for (int index = 0; index < listProperty.size(); index++) {
840                listPropertyIndexes.add(index);
841            }
842        } else {
843            if (listPropertyDiff != null) {
844                listPropertyIndexes = listPropertyDiff.getDiffIndexes();
845            }
846        }
847
848        return getComplexListFieldDiffDisplay(listProperty, listPropertyIndexes, listPropertyDiff, isDisplayAllItems,
849                isDisplayItemIndexes, styleClass);
850    }
851
852    protected final PropertyDiffDisplay getComplexListFieldDiffDisplay(List<Serializable> listProperty,
853            List<Integer> listPropertyIndexes, ListPropertyDiff listPropertyDiff, boolean isDisplayAllItems,
854            boolean isDisplayItemIndexes, String styleClass) {
855
856        if (listPropertyIndexes.isEmpty()) {
857            // TODO: add differenceType?
858            return new PropertyDiffDisplayImpl(new ArrayList<Serializable>());
859        }
860        boolean isComplexListWidget = isDisplayItemIndexes
861                || (listPropertyDiff != null && listPropertyDiff.isComplexListType());
862
863        if (isComplexListWidget) {
864            List<Map<String, Serializable>> listFieldDiffDisplay = new ArrayList<Map<String, Serializable>>();
865            Set<String> complexPropertyItemNames = null;
866            for (int index : listPropertyIndexes) {
867
868                Map<String, Serializable> listItemDiffDisplay = new HashMap<String, Serializable>();
869                // Put item index if wanted
870                if (isDisplayItemIndexes) {
871                    listItemDiffDisplay.put(DIFF_LIST_WIDGET_INDEX_SUBWIDGET_FIELD, index + 1);
872                }
873                // Only put value if index is in list range
874                if (index < listProperty.size()) {
875                    Serializable listPropertyItem = listProperty.get(index);
876                    PropertyDiff listItemPropertyDiff = null;
877                    if (listPropertyDiff != null) {
878                        listItemPropertyDiff = listPropertyDiff.getDiff(index);
879                    }
880                    if (isComplexType(listPropertyItem)) { // Complex
881                                                           // list
882                        ComplexPropertyDiff complexPropertyDiff = null;
883                        if (listItemPropertyDiff != null && listItemPropertyDiff.isComplexType()) {
884                            complexPropertyDiff = (ComplexPropertyDiff) listItemPropertyDiff;
885                        }
886                        Map<String, Serializable> complexProperty = getComplexProperty(listPropertyItem);
887                        complexPropertyItemNames = complexProperty.keySet();
888                        for (String complexPropertyItemName : complexPropertyItemNames) {
889                            Serializable complexPropertyItem = complexProperty.get(complexPropertyItemName);
890                            // TODO: take into account subwidget properties
891                            // 'displayAllItems' and 'displayItemIndexes'
892                            // instead of inheriting them from the parent
893                            // widget.
894                            PropertyDiff complexItemPropertyDiff = null;
895                            if (complexPropertyDiff != null) {
896                                complexItemPropertyDiff = complexPropertyDiff.getDiff(complexPropertyItemName);
897                            }
898                            listItemDiffDisplay.put(
899                                    complexPropertyItemName,
900                                    getFieldDiffDisplay(complexPropertyItem, complexItemPropertyDiff,
901                                            isDisplayAllItems, isDisplayItemIndexes, true, styleClass));
902                        }
903                    } else { // Scalar or content list
904                        listItemDiffDisplay.put(
905                                DIFF_LIST_WIDGET_VALUE_SUBWIDGET_FIELD,
906                                getFinalFieldDiffDisplay(listPropertyItem, listItemPropertyDiff, isDisplayAllItems,
907                                        styleClass));
908                    }
909                } else {// Index not in list range => put null value
910                    if (complexPropertyItemNames != null) {
911                        for (String complexPropertyItemName : complexPropertyItemNames) {
912                            // TODO: add DifferenceType.removed?
913                            listItemDiffDisplay.put(complexPropertyItemName, new PropertyDiffDisplayImpl(null,
914                                    styleClass));
915                        }
916                    } else {
917                        // TODO: add DifferenceType.removed?
918                        listItemDiffDisplay.put(DIFF_LIST_WIDGET_VALUE_SUBWIDGET_FIELD, new PropertyDiffDisplayImpl(
919                                null, isDisplayAllItems ? styleClass : PropertyDiffDisplay.DEFAULT_STYLE_CLASS));
920                    }
921                }
922                listFieldDiffDisplay.add(listItemDiffDisplay);
923            }
924            return new PropertyDiffDisplayImpl((Serializable) listFieldDiffDisplay);
925        } else {
926            List<Serializable> listFieldDiffDisplay = new ArrayList<Serializable>();
927            for (int index : listPropertyIndexes) {
928                // Only put value if index is in list range
929                if (index < listProperty.size()) {
930                    PropertyDiff listItemPropertyDiff = null;
931                    if (listPropertyDiff != null) {
932                        listItemPropertyDiff = listPropertyDiff.getDiff(index);
933                    }
934                    listFieldDiffDisplay.add(getFinalFieldDiffDisplay(listProperty.get(index), listItemPropertyDiff,
935                            isDisplayAllItems, styleClass));
936                } else {// Index not in list range => put null value
937                    // TODO: add DifferenceType.removed?
938                    listFieldDiffDisplay.add(new PropertyDiffDisplayImpl(null, isDisplayAllItems ? styleClass
939                            : PropertyDiffDisplay.DEFAULT_STYLE_CLASS));
940                }
941            }
942            return new PropertyDiffDisplayImpl((Serializable) listFieldDiffDisplay);
943        }
944    }
945
946    protected final PropertyDiffDisplay getFieldXPaths(String propertyName, PropertyDiff propertyDiff,
947            Serializable leftProperty, Serializable rightProperty, boolean isDisplayAllItems,
948            boolean isDisplayItemIndexes, List<DiffFieldItemDefinition> complexFieldItemDefs) {
949
950        PropertyDiffDisplay fieldXPaths = null;
951        if (propertyDiff == null) {
952            throw new NuxeoException("The 'propertyDiff' parameter cannot be null.");
953        }
954
955        boolean isDisplayHtmlConversion = ContentDiffHelper.isDisplayHtmlConversion(leftProperty)
956                && ContentDiffHelper.isDisplayHtmlConversion(rightProperty);
957        boolean isDisplayTextConversion = ContentDiffHelper.isDisplayTextConversion(leftProperty)
958                && ContentDiffHelper.isDisplayTextConversion(rightProperty);
959
960        // Simple type
961        if (propertyDiff.isSimpleType()) {
962            SimplePropertyDiff simplePropertyDiff = (SimplePropertyDiff) propertyDiff;
963            // Keep fieldXPaths null if one of the left or right properties is
964            // empty
965            if (!StringUtils.isEmpty(simplePropertyDiff.getLeftValue())
966                    && !StringUtils.isEmpty(simplePropertyDiff.getRightValue())) {
967                fieldXPaths = new ContentDiffDisplayImpl(propertyName, simplePropertyDiff.getDifferenceType(),
968                        isDisplayHtmlConversion, isDisplayTextConversion);
969            }
970        }
971        // Content type
972        else if (propertyDiff.isContentType()) {
973            ContentPropertyDiff contentPropertyDiff = (ContentPropertyDiff) propertyDiff;
974            ContentProperty leftContent = contentPropertyDiff.getLeftContent();
975            ContentProperty rightContent = contentPropertyDiff.getRightContent();
976            // Keep fieldXPaths null if one of the left or right properties is
977            // empty
978            if (leftContent != null
979                    && rightContent != null
980                    && (!StringUtils.isEmpty(leftContent.getFilename()) && !StringUtils.isEmpty(rightContent.getFilename()))
981                    || (!StringUtils.isEmpty(leftContent.getDigest()) && !StringUtils.isEmpty(rightContent.getDigest()))) {
982                fieldXPaths = new ContentDiffDisplayImpl(propertyName, contentPropertyDiff.getDifferenceType(),
983                        isDisplayHtmlConversion, isDisplayTextConversion);
984            }
985        }
986        // Complex type
987        else if (propertyDiff.isComplexType()) {
988
989            Map<String, Serializable> leftComplexProperty = getComplexPropertyIfNotNull(leftProperty);
990            Map<String, Serializable> rightComplexProperty = getComplexPropertyIfNotNull(rightProperty);
991
992            // TODO (maybe): take into account subwidget properties
993            // 'displayAllItems' and 'displayItemIndexes'
994            // instead of inheriting them from the parent
995            // widget.
996            Map<String, PropertyDiff> complexPropertyDiffMap = ((ComplexPropertyDiff) propertyDiff).getDiffMap();
997            Map<String, Serializable> complexPropertyXPaths = new HashMap<String, Serializable>();
998            if (CollectionUtils.isEmpty(complexFieldItemDefs)) {
999                Iterator<String> complexFieldItemNamesIt = complexPropertyDiffMap.keySet().iterator();
1000                while (complexFieldItemNamesIt.hasNext()) {
1001                    String complexFieldItemName = complexFieldItemNamesIt.next();
1002                    setComplexPropertyXPaths(complexPropertyXPaths, complexFieldItemName,
1003                            getSubPropertyFullName(propertyName, complexFieldItemName), complexPropertyDiffMap,
1004                            leftComplexProperty, rightComplexProperty, isDisplayAllItems, isDisplayItemIndexes);
1005                }
1006            } else {
1007                for (DiffFieldItemDefinition complexFieldItemDef : complexFieldItemDefs) {
1008                    if (complexFieldItemDef.isDisplayContentDiffLinks()) {
1009                        String complexFieldItemName = complexFieldItemDef.getName();
1010                        if (complexPropertyDiffMap.containsKey(complexFieldItemName)) {
1011                            setComplexPropertyXPaths(complexPropertyXPaths, complexFieldItemName,
1012                                    getSubPropertyFullName(propertyName, complexFieldItemName), complexPropertyDiffMap,
1013                                    leftComplexProperty, rightComplexProperty, isDisplayAllItems, isDisplayItemIndexes);
1014                        }
1015                    }
1016                }
1017            }
1018            fieldXPaths = new ContentDiffDisplayImpl((Serializable) complexPropertyXPaths);
1019        }
1020        // List type
1021        else {
1022            List<Serializable> leftListProperty = getListPropertyIfNotNull(leftProperty);
1023            List<Serializable> rightListProperty = getListPropertyIfNotNull(rightProperty);
1024
1025            ListPropertyDiff listPropertyDiff = (ListPropertyDiff) propertyDiff;
1026
1027            // Get list property indexes
1028            // By default: only items that are different (ie. held by the
1029            // propertyDiff)
1030            List<Integer> listPropertyIndexes = new ArrayList<Integer>();
1031            if (isDisplayAllItems) {
1032                // All items
1033                int listPropertySize = Math.min(leftListProperty.size(), rightListProperty.size());
1034
1035                for (int index = 0; index < listPropertySize; index++) {
1036                    listPropertyIndexes.add(index);
1037                }
1038            } else {
1039                listPropertyIndexes = listPropertyDiff.getDiffIndexes();
1040            }
1041            fieldXPaths = getComplexListXPaths(propertyName, listPropertyIndexes, listPropertyDiff, leftListProperty,
1042                    rightListProperty, isDisplayAllItems, isDisplayItemIndexes);
1043        }
1044        return fieldXPaths;
1045    }
1046
1047    protected final PropertyDiffDisplay getComplexListXPaths(String propertyName, List<Integer> listPropertyIndexes,
1048            ListPropertyDiff listPropertyDiff, List<Serializable> leftListProperty,
1049            List<Serializable> rightListProperty, boolean isDisplayAllItems, boolean isDisplayItemIndexes)
1050            {
1051
1052        if (listPropertyIndexes.isEmpty()) {
1053            // TODO: add differenceType?
1054            return new ContentDiffDisplayImpl(new ArrayList<Serializable>());
1055        }
1056        boolean isComplexListWidget = isDisplayItemIndexes
1057                || (listPropertyDiff != null && listPropertyDiff.isComplexListType());
1058
1059        if (isComplexListWidget) {
1060            List<Map<String, Serializable>> listFieldXPaths = new ArrayList<Map<String, Serializable>>();
1061            for (int index : listPropertyIndexes) {
1062
1063                Map<String, Serializable> listItemXPaths = new HashMap<String, Serializable>();
1064                // Put item index if wanted
1065                if (isDisplayItemIndexes) {
1066                    listItemXPaths.put(DIFF_LIST_WIDGET_INDEX_SUBWIDGET_FIELD, index + 1);
1067                }
1068                PropertyDiff listItemPropertyDiff = listPropertyDiff.getDiff(index);
1069                if (listItemPropertyDiff != null) {
1070
1071                    Serializable leftListPropertyItem = null;
1072                    Serializable rightListPropertyItem = null;
1073                    if (index < leftListProperty.size()) {
1074                        leftListPropertyItem = leftListProperty.get(index);
1075                    }
1076                    if (index < rightListProperty.size()) {
1077                        rightListPropertyItem = rightListProperty.get(index);
1078                    }
1079                    Map<String, Serializable> leftComplexProperty = null;
1080                    Map<String, Serializable> rightComplexProperty = null;
1081                    if (isComplexType(leftListPropertyItem)) {
1082                        leftComplexProperty = getComplexProperty(leftListPropertyItem);
1083                    }
1084                    if (isComplexType(rightListPropertyItem)) {
1085                        rightComplexProperty = getComplexProperty(rightListPropertyItem);
1086                    }
1087
1088                    // Complex list
1089                    if (listItemPropertyDiff.isComplexType()) {
1090                        Map<String, PropertyDiff> complexPropertyDiffMap = ((ComplexPropertyDiff) listItemPropertyDiff).getDiffMap();
1091                        Iterator<String> complexPropertyItemNamesIt = complexPropertyDiffMap.keySet().iterator();
1092                        while (complexPropertyItemNamesIt.hasNext()) {
1093                            String complexPropertyItemName = complexPropertyItemNamesIt.next();
1094                            // TODO: take into account subwidget properties
1095                            // 'displayAllItems' and 'displayItemIndexes'
1096                            // instead of inheriting them from the parent
1097                            // widget.
1098                            setComplexPropertyXPaths(
1099                                    listItemXPaths,
1100                                    complexPropertyItemName,
1101                                    getSubPropertyFullName(propertyName,
1102                                            getSubPropertyFullName(String.valueOf(index), complexPropertyItemName)),
1103                                    complexPropertyDiffMap, leftComplexProperty, rightComplexProperty,
1104                                    isDisplayAllItems, isDisplayItemIndexes);
1105                        }
1106                    }
1107                    // Scalar or content list
1108                    else {
1109                        String listItemXPath = null;
1110                        // Keep listItemXPath null if one of the left or right
1111                        // properties is empty
1112                        if (leftListPropertyItem != null && rightListPropertyItem != null) {
1113                            listItemXPath = getSubPropertyFullName(propertyName, String.valueOf(index));
1114                        }
1115                        boolean isDisplayHtmlConversion = ContentDiffHelper.isDisplayHtmlConversion(leftListPropertyItem)
1116                                && ContentDiffHelper.isDisplayHtmlConversion(rightListPropertyItem);
1117                        boolean isDisplayTextConversion = ContentDiffHelper.isDisplayTextConversion(leftListPropertyItem)
1118                                && ContentDiffHelper.isDisplayTextConversion(rightListPropertyItem);
1119                        listItemXPaths.put(DIFF_LIST_WIDGET_VALUE_SUBWIDGET_FIELD, new ContentDiffDisplayImpl(
1120                                listItemXPath, listItemPropertyDiff.getDifferenceType(), isDisplayHtmlConversion,
1121                                isDisplayTextConversion));
1122                    }
1123                }
1124                listFieldXPaths.add(listItemXPaths);
1125            }
1126            return new ContentDiffDisplayImpl((Serializable) listFieldXPaths);
1127        } else {
1128            List<PropertyDiffDisplay> listFieldXPaths = new ArrayList<PropertyDiffDisplay>();
1129            for (int index : listPropertyIndexes) {
1130                PropertyDiffDisplay listItemXPath = null;
1131                // Keep listItemXPath null if one of the left or right
1132                // properties is empty
1133                if (index < leftListProperty.size() && index < rightListProperty.size()) {
1134                    PropertyDiff listItemPropertyDiff = null;
1135                    if (listPropertyDiff != null) {
1136                        listItemPropertyDiff = listPropertyDiff.getDiff(index);
1137                    }
1138                    DifferenceType differenceType = DifferenceType.different;
1139                    if (listItemPropertyDiff != null) {
1140                        differenceType = listItemPropertyDiff.getDifferenceType();
1141                    }
1142                    Serializable leftListPropertyItem = leftListProperty.get(index);
1143                    Serializable rightListPropertyItem = rightListProperty.get(index);
1144                    boolean isDisplayHtmlConversion = ContentDiffHelper.isDisplayHtmlConversion(leftListPropertyItem)
1145                            && ContentDiffHelper.isDisplayHtmlConversion(rightListPropertyItem);
1146                    boolean isDisplayTextConversion = ContentDiffHelper.isDisplayTextConversion(leftListPropertyItem)
1147                            && ContentDiffHelper.isDisplayTextConversion(rightListPropertyItem);
1148                    listItemXPath = new ContentDiffDisplayImpl(getSubPropertyFullName(propertyName,
1149                            String.valueOf(index)), differenceType, isDisplayHtmlConversion, isDisplayTextConversion);
1150                }
1151                listFieldXPaths.add(listItemXPath);
1152            }
1153            return new ContentDiffDisplayImpl((Serializable) listFieldXPaths);
1154        }
1155    }
1156
1157    /**
1158     * Sets the complex property xpaths.
1159     *
1160     * @param complexPropertyXPaths the complex property xpaths
1161     * @param complexFieldItemName the complex field item name
1162     * @param subPropertyFullName the sub property full name
1163     * @param complexPropertyDiffMap the complex property diff map
1164     * @param leftComplexProperty the left complex property
1165     * @param rightComplexProperty the right complex property
1166     * @param isDisplayAllItems the is display all items
1167     * @param isDisplayItemIndexes the is display item indexes
1168     */
1169    protected void setComplexPropertyXPaths(Map<String, Serializable> complexPropertyXPaths,
1170            String complexFieldItemName, String subPropertyFullName, Map<String, PropertyDiff> complexPropertyDiffMap,
1171            Map<String, Serializable> leftComplexProperty, Map<String, Serializable> rightComplexProperty,
1172            boolean isDisplayAllItems, boolean isDisplayItemIndexes) {
1173
1174        Serializable leftComplexPropertyItemValue = null;
1175        Serializable rightComplexPropertyItemValue = null;
1176        if (leftComplexProperty != null) {
1177            leftComplexPropertyItemValue = leftComplexProperty.get(complexFieldItemName);
1178        }
1179        if (rightComplexProperty != null) {
1180            rightComplexPropertyItemValue = rightComplexProperty.get(complexFieldItemName);
1181        }
1182        complexPropertyXPaths.put(
1183                complexFieldItemName,
1184                getFieldXPaths(subPropertyFullName, complexPropertyDiffMap.get(complexFieldItemName),
1185                        leftComplexPropertyItemValue, rightComplexPropertyItemValue, isDisplayAllItems,
1186                        isDisplayItemIndexes, null));
1187    }
1188
1189    protected boolean isListType(Serializable property) {
1190
1191        return property instanceof List<?> || property instanceof Serializable[];
1192    }
1193
1194    protected boolean isComplexType(Serializable property) {
1195
1196        return property instanceof Map<?, ?>;
1197    }
1198
1199    /**
1200     * Casts or convert a {@link Serializable} property to {@link List <Serializable>}.
1201     *
1202     * @param property the property
1203     * @return the list property
1204     * @throws ClassCastException if the {@code property} is not a {@link List <Serializable>} nor an array.
1205     */
1206    @SuppressWarnings("unchecked")
1207    protected List<Serializable> getListProperty(Serializable property) {
1208        List<Serializable> listProperty;
1209        if (property instanceof List<?>) { // List
1210            listProperty = (List<Serializable>) property;
1211        } else { // Array
1212            listProperty = Arrays.asList((Serializable[]) property);
1213        }
1214        return listProperty;
1215    }
1216
1217    /**
1218     * Gets the list property if the {@code property} is not null.
1219     *
1220     * @param property the property
1221     * @return the list property if the {@code property} is not null, null otherwise
1222     * @throws NuxeoException if the {@code property} is not a list.
1223     */
1224    protected List<Serializable> getListPropertyIfNotNull(Serializable property) {
1225
1226        if (property != null) {
1227            if (!isListType(property)) {
1228                throw new NuxeoException(
1229                        "Tryed to get a list property from a Serializable property that is not a list, this is inconsistent.");
1230            }
1231            return getListProperty(property);
1232        }
1233        return null;
1234    }
1235
1236    /**
1237     * Casts a {@link Serializable} property to {@link Map<String, Serializable>}.
1238     *
1239     * @param property the property
1240     * @return the complex property
1241     * @throws ClassCastException if the {@code property} is not a {@link Map <String, Serializable>}.
1242     */
1243    @SuppressWarnings("unchecked")
1244    protected Map<String, Serializable> getComplexProperty(Serializable property) {
1245        return (Map<String, Serializable>) property;
1246    }
1247
1248    /**
1249     * Gets the complex property if the {@code property} is not null.
1250     *
1251     * @param property the property
1252     * @return the complex property if the {@code property} is not null, null otherwise
1253     * @throws NuxeoException if the {@code property} is not a list.
1254     */
1255    protected Map<String, Serializable> getComplexPropertyIfNotNull(Serializable property) {
1256
1257        if (property != null) {
1258            if (!isComplexType(property)) {
1259                throw new NuxeoException(
1260                        "Tryed to get a complex property from a Serializable property that is not a map, this is inconsistent.");
1261            }
1262            return getComplexProperty(property);
1263        }
1264        return null;
1265    }
1266
1267    // TODO: better separate regular and contentDiffLinksWidget cases (call
1268    // submethods)
1269    protected final WidgetDefinition getWidgetDefinition(String category, String propertyName, String propertyType,
1270            Field field, List<DiffFieldItemDefinition> complexFieldItemDefs, boolean isContentDiffLinksWidget)
1271            {
1272
1273        boolean isGeneric = false;
1274        boolean isCloned = false;
1275
1276        WidgetDefinition wDef = null;
1277        if (!isContentDiffLinksWidget) {
1278            // Look for a specific widget in the "diff" category named with the
1279            // property name
1280            wDef = getLayoutStore().getWidgetDefinition(category, propertyName);
1281            if (wDef == null) {
1282                isGeneric = true;
1283                // Fallback on a generic widget in the "diff" category named
1284                // with the property type
1285                wDef = getLayoutStore().getWidgetDefinition(category, propertyType);
1286                if (wDef == null) {
1287                    throw new NuxeoException(
1288                            String.format(
1289                                    "Could not find any specific widget named '%s', nor any generic widget named '%s'. Please make sure at least a generic widget is defined for this type.",
1290                                    propertyName, propertyType));
1291                }
1292            }
1293        } else {
1294            isGeneric = true;
1295            if (PropertyType.isSimpleType(propertyType) || PropertyType.isContentType(propertyType)) {
1296                wDef = getLayoutStore().getWidgetDefinition(category, CONTENT_DIFF_LINKS_WIDGET_NAME);
1297                if (wDef == null) {
1298                    throw new NuxeoException(
1299                            String.format(
1300                                    "Could not find any generic widget named '%s'. Please make sure a generic widget is defined with this name.",
1301                                    CONTENT_DIFF_LINKS_WIDGET_NAME));
1302                }
1303            } else {
1304                // Get the generic widget in the "diff" category named with
1305                // the property type
1306                wDef = getLayoutStore().getWidgetDefinition(category, propertyType);
1307                if (wDef == null) {
1308                    throw new NuxeoException(
1309                            String.format(
1310                                    "Could not find any generic widget named '%s'. Please make sure a generic widget is defined for this type.",
1311                                    propertyType));
1312                }
1313            }
1314        }
1315
1316        if (isGeneric) {
1317            // Clone widget definition
1318            wDef = wDef.clone();
1319            isCloned = true;
1320            // Set widget name
1321            String widgetName = propertyName;
1322            if (isContentDiffLinksWidget) {
1323                widgetName += CONTENT_DIFF_LINKS_WIDGET_NAME_SUFFIX;
1324            }
1325            wDef.setName(widgetName);
1326
1327            // Set labels
1328            Map<String, String> labels = new HashMap<String, String>();
1329            labels.put(BuiltinModes.ANY, DIFF_WIDGET_LABEL_PREFIX + getPropertyLabel(propertyName));
1330            wDef.setLabels(labels);
1331
1332            // Set translated
1333            wDef.setTranslated(true);
1334        }
1335
1336        // Set field definitions if generic or specific and not already set in
1337        // widget definition
1338        if (isGeneric || !isFieldDefinitions(wDef)) {
1339
1340            String fieldName = propertyName;
1341            if (field != null) {
1342                fieldName = field.getName().getLocalName();
1343            }
1344
1345            FieldDefinition[] fieldDefinitions;
1346            if (isContentDiffLinksWidget) {
1347                fieldDefinitions = new FieldDefinition[4];
1348                fieldDefinitions[0] = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1349                        DIFF_WIDGET_FIELD_DEFINITION_VALUE));
1350                fieldDefinitions[1] = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1351                        DIFF_WIDGET_FIELD_DEFINITION_DIFFERENCE_TYPE));
1352                fieldDefinitions[2] = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1353                        DIFF_WIDGET_FIELD_DEFINITION_DISPLAY_HTML_CONVERSION));
1354                fieldDefinitions[3] = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1355                        DIFF_WIDGET_FIELD_DEFINITION_DISPLAY_TEXT_CONVERSION));
1356            } else {
1357                int fieldCount = 2;
1358                if (PropertyType.isContentType(propertyType) || ("note:note".equals(propertyName))) {
1359                    fieldCount = 3;
1360                }
1361                fieldDefinitions = new FieldDefinition[fieldCount];
1362                fieldDefinitions[0] = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1363                        DIFF_WIDGET_FIELD_DEFINITION_VALUE));
1364
1365                FieldDefinition styleClassFieldDef = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1366                        DIFF_WIDGET_FIELD_DEFINITION_STYLE_CLASS));
1367                if (PropertyType.isContentType(propertyType)) {
1368                    fieldDefinitions[1] = new FieldDefinitionImpl(null, getFieldDefinition(
1369                            getFieldDefinition(fieldName, DIFF_WIDGET_FIELD_DEFINITION_VALUE),
1370                            DIFF_WIDGET_FIELD_DEFINITION_FILENAME));
1371                    fieldDefinitions[2] = styleClassFieldDef;
1372                } else if ("note:note".equals(propertyName)) {
1373                    fieldDefinitions[1] = new FieldDefinitionImpl(null, getFieldDefinition("note:mime_type",
1374                            DIFF_WIDGET_FIELD_DEFINITION_VALUE));
1375                    fieldDefinitions[2] = styleClassFieldDef;
1376                } else {
1377                    fieldDefinitions[1] = styleClassFieldDef;
1378                }
1379            }
1380
1381            // Clone if needed
1382            if (!isCloned) {
1383                wDef = wDef.clone();
1384                isCloned = true;
1385            }
1386            wDef.setFieldDefinitions(fieldDefinitions);
1387        }
1388
1389        // Set subwidgets if not already set
1390        if (!isSubWidgets(wDef)) {
1391            if (PropertyType.isListType(propertyType) || PropertyType.isComplexType(propertyType)) {
1392
1393                Field declaringField = field;
1394                if (declaringField == null) {
1395                    declaringField = ComplexPropertyHelper.getField(getPropertySchema(propertyName),
1396                            getPropertyField(propertyName));
1397                }
1398                // Clone if needed
1399                if (!isCloned) {
1400                    wDef = wDef.clone();
1401                    isCloned = true;
1402                }
1403                wDef.setSubWidgetDefinitions(getSubWidgetDefinitions(category, propertyName, propertyType,
1404                        declaringField, complexFieldItemDefs, isDisplayItemIndexes(wDef), isContentDiffLinksWidget));
1405            }
1406        }
1407
1408        return wDef;
1409    }
1410
1411    protected final boolean isSubWidgets(WidgetDefinition wDef) {
1412
1413        WidgetDefinition[] subWidgetDefs = wDef.getSubWidgetDefinitions();
1414        return subWidgetDefs != null && subWidgetDefs.length > 0;
1415    }
1416
1417    protected final boolean isFieldDefinitions(WidgetDefinition wDef) {
1418
1419        FieldDefinition[] fieldDefs = wDef.getFieldDefinitions();
1420        return fieldDefs != null && fieldDefs.length > 0;
1421    }
1422
1423    protected final String getFieldDefinition(String fieldName, String subPropertyName) {
1424
1425        return fieldName + "/" + subPropertyName;
1426    }
1427
1428    protected final WidgetDefinition[] getSubWidgetDefinitions(String category, String propertyName,
1429            String propertyType, Field field, List<DiffFieldItemDefinition> complexFieldItemDefs,
1430            boolean isDisplayItemIndexes, boolean isContentDiffLinks) {
1431
1432        WidgetDefinition[] subWidgetDefs = null;
1433        // Complex
1434        if (PropertyType.isComplexType(propertyType)) {
1435            subWidgetDefs = getComplexSubWidgetDefinitions(category, propertyName, field, complexFieldItemDefs, false,
1436                    isContentDiffLinks);
1437        }
1438        // Scalar or content list
1439        else if (PropertyType.isScalarListType(propertyType) || PropertyType.isContentListType(propertyType)) {
1440            Field listFieldItem = ComplexPropertyHelper.getListFieldItem(field);
1441            subWidgetDefs = initSubWidgetDefinitions(isDisplayItemIndexes, 1);
1442            subWidgetDefs[subWidgetDefs.length - 1] = getWidgetDefinition(category,
1443                    getSubPropertyFullName(propertyName, listFieldItem.getName().getLocalName()),
1444                    ComplexPropertyHelper.getFieldType(listFieldItem), new FieldImpl(new QName(
1445                            DIFF_LIST_WIDGET_VALUE_SUBWIDGET_FIELD), field.getType(), listFieldItem.getType()), null,
1446                    isContentDiffLinks);
1447        }
1448        // Complex list
1449        else if (PropertyType.isComplexListType(propertyType)) {
1450            Field listFieldItem = ComplexPropertyHelper.getListFieldItem(field);
1451            subWidgetDefs = getComplexSubWidgetDefinitions(category, propertyName, listFieldItem, complexFieldItemDefs,
1452                    isDisplayItemIndexes, isContentDiffLinks);
1453        }
1454        return subWidgetDefs;
1455    }
1456
1457    protected final WidgetDefinition[] getComplexSubWidgetDefinitions(String category, String propertyName,
1458            Field field, List<DiffFieldItemDefinition> complexFieldItemDefs, boolean isDisplayItemIndexes,
1459            boolean isContentDiffLinks) {
1460
1461        WidgetDefinition[] subWidgetDefs;
1462        int subWidgetIndex = isDisplayItemIndexes ? 1 : 0;
1463
1464        if (CollectionUtils.isEmpty(complexFieldItemDefs)) {
1465            List<Field> complexFieldItems = ComplexPropertyHelper.getComplexFieldItems(field);
1466            subWidgetDefs = initSubWidgetDefinitions(isDisplayItemIndexes, complexFieldItems.size());
1467
1468            for (Field complexFieldItem : complexFieldItems) {
1469                subWidgetDefs[subWidgetIndex] = getWidgetDefinition(category,
1470                        getSubPropertyFullName(propertyName, complexFieldItem.getName().getLocalName()),
1471                        ComplexPropertyHelper.getFieldType(complexFieldItem), complexFieldItem, null,
1472                        isContentDiffLinks);
1473                subWidgetIndex++;
1474            }
1475        } else {
1476            int subWidgetCount = complexFieldItemDefs.size();
1477            // Only add a subwidget for the items marked to display the content
1478            // diff links
1479            if (isContentDiffLinks) {
1480                subWidgetCount = 0;
1481                for (DiffFieldItemDefinition complexFieldItemDef : complexFieldItemDefs) {
1482                    if (complexFieldItemDef.isDisplayContentDiffLinks()) {
1483                        subWidgetCount++;
1484                    }
1485                }
1486            }
1487            subWidgetDefs = initSubWidgetDefinitions(isDisplayItemIndexes, subWidgetCount);
1488
1489            for (DiffFieldItemDefinition complexFieldItemDef : complexFieldItemDefs) {
1490                if (!isContentDiffLinks || complexFieldItemDef.isDisplayContentDiffLinks()) {
1491                    String complexFieldItemName = complexFieldItemDef.getName();
1492                    Field complexFieldItem = ComplexPropertyHelper.getComplexFieldItem(field, complexFieldItemName);
1493                    if (complexFieldItem != null) {
1494                        subWidgetDefs[subWidgetIndex] = getWidgetDefinition(category,
1495                                getSubPropertyFullName(propertyName, complexFieldItemName),
1496                                ComplexPropertyHelper.getFieldType(complexFieldItem), complexFieldItem, null,
1497                                isContentDiffLinks);
1498                        subWidgetIndex++;
1499                    }
1500                }
1501            }
1502        }
1503        return subWidgetDefs;
1504    }
1505
1506    protected final WidgetDefinition[] initSubWidgetDefinitions(boolean isDisplayItemIndexes, int subWidgetCount) {
1507
1508        WidgetDefinition[] subWidgetDefs;
1509        if (isDisplayItemIndexes) {
1510            subWidgetDefs = new WidgetDefinition[subWidgetCount + 1];
1511            subWidgetDefs[0] = getIndexSubwidgetDefinition();
1512        } else {
1513            subWidgetDefs = new WidgetDefinition[subWidgetCount];
1514        }
1515
1516        return subWidgetDefs;
1517    }
1518
1519    protected final WidgetDefinition getIndexSubwidgetDefinition() {
1520
1521        FieldDefinition[] fieldDefinitions = { new FieldDefinitionImpl(null, DIFF_LIST_WIDGET_INDEX_SUBWIDGET_FIELD) };
1522
1523        return new WidgetDefinitionImpl(DIFF_LIST_WIDGET_INDEX_SUBWIDGET_FIELD, DIFF_LIST_WIDGET_INDEX_SUBWIDGET_TYPE,
1524                DIFF_LIST_WIDGET_INDEX_SUBWIDGET_LABEL, null, true, null, Arrays.asList(fieldDefinitions), null, null);
1525    }
1526
1527    /**
1528     * Gets the property name.
1529     *
1530     * @param schema the schema
1531     * @param field the field
1532     * @return the property name
1533     */
1534    protected final String getPropertyName(String schema, String field) {
1535
1536        StringBuilder sb = new StringBuilder();
1537        if (!StringUtils.isEmpty(schema)) {
1538            sb.append(schema);
1539            sb.append(":");
1540        }
1541        sb.append(field);
1542        return sb.toString();
1543    }
1544
1545    protected final String getSubPropertyFullName(String basePropertyName, String subPropertyName) {
1546
1547        if (StringUtils.isEmpty(subPropertyName)) {
1548            return basePropertyName;
1549        }
1550        StringBuilder sb = new StringBuilder(basePropertyName);
1551        sb.append("/");
1552        sb.append(subPropertyName);
1553        return sb.toString();
1554    }
1555
1556    protected final String getPropertySchema(String propertyName) {
1557
1558        int indexOfColon = propertyName.indexOf(':');
1559        if (indexOfColon > -1) {
1560            return propertyName.substring(0, indexOfColon);
1561        }
1562        return null;
1563    }
1564
1565    protected final String getPropertyField(String propertyName) {
1566
1567        int indexOfColon = propertyName.indexOf(':');
1568        if (indexOfColon > -1 && indexOfColon < propertyName.length() - 1) {
1569            return propertyName.substring(indexOfColon + 1);
1570        }
1571        return propertyName;
1572    }
1573
1574    protected final String getPropertyLabel(String propertyName) {
1575
1576        return propertyName.replaceAll(":", ".").replaceAll("/", ".");
1577    }
1578
1579    /**
1580     * Gets the schema manager.
1581     *
1582     * @return the schema manager
1583     */
1584    protected final SchemaManager getSchemaManager() {
1585        SchemaManager schemaManager = Framework.getService(SchemaManager.class);
1586        if (schemaManager == null) {
1587            throw new NuxeoException("SchemaManager service is null.");
1588        }
1589        return schemaManager;
1590    }
1591
1592    /**
1593     * Gets the layout store service.
1594     *
1595     * @return the layout store service
1596     */
1597    protected final LayoutStore getLayoutStore() {
1598        LayoutStore layoutStore = Framework.getService(LayoutStore.class);
1599        if (layoutStore == null) {
1600            throw new NuxeoException("LayoutStore service is null.");
1601        }
1602        return layoutStore;
1603    }
1604}