001/*
002 * (C) Copyright 2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *    http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Kevin Leturc <kleturc@nuxeo.com>
018 */
019package org.nuxeo.ecm.platform.forms.layout.export;
020
021import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON;
022import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE;
023import static org.nuxeo.ecm.platform.forms.layout.export.LayoutExportConstants.CATEGORY_PARAMETER;
024import static org.nuxeo.ecm.platform.forms.layout.export.LayoutExportConstants.LAYOUT_CONTEXT_PARAMETER;
025import static org.nuxeo.ecm.platform.forms.layout.export.LayoutExportConstants.WIDGET_CONVERTERS_PARAMETER;
026
027import java.io.IOException;
028import java.io.Serializable;
029import java.util.ArrayList;
030import java.util.Arrays;
031import java.util.List;
032import java.util.Map;
033import java.util.Map.Entry;
034import java.util.TreeMap;
035
036import javax.inject.Inject;
037
038import org.apache.commons.collections.CollectionUtils;
039import org.apache.commons.collections.MapUtils;
040import org.apache.commons.lang3.ArrayUtils;
041import org.apache.commons.lang3.StringUtils;
042import org.apache.commons.logging.Log;
043import org.apache.commons.logging.LogFactory;
044import org.nuxeo.ecm.core.io.registry.reflect.Setup;
045import org.nuxeo.ecm.platform.forms.layout.api.LayoutDefinition;
046import org.nuxeo.ecm.platform.forms.layout.api.LayoutRowDefinition;
047import org.nuxeo.ecm.platform.forms.layout.api.RenderingInfo;
048import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition;
049import org.nuxeo.ecm.platform.forms.layout.api.WidgetReference;
050import org.nuxeo.ecm.platform.forms.layout.api.converters.LayoutConversionContext;
051import org.nuxeo.ecm.platform.forms.layout.api.converters.WidgetDefinitionConverter;
052import org.nuxeo.ecm.platform.forms.layout.api.service.LayoutStore;
053
054import com.fasterxml.jackson.core.JsonGenerator;
055
056/**
057 * @since 10.1
058 */
059@Setup(mode = SINGLETON, priority = REFERENCE)
060public class LayoutDefinitionJsonWriter extends AbstractLayoutJsonWriter<LayoutDefinition> {
061
062    private static final Log log = LogFactory.getLog(LayoutDefinitionJsonWriter.class);
063
064    @Inject
065    private LayoutStore webLayoutManager;
066
067    @Override
068    public void write(LayoutDefinition entity, JsonGenerator jg) throws IOException {
069        jg.writeStartObject();
070        String name = entity.getName();
071        if (StringUtils.isNotBlank(name)) {
072            jg.writeStringField("name", name);
073        }
074
075        String type = entity.getType();
076        if (type != null) {
077            jg.writeStringField("type", type);
078        }
079
080        String typeCat = entity.getTypeCategory();
081        if (typeCat != null) {
082            jg.writeStringField("typeCategory", typeCat);
083        }
084
085        Map<String, String> templates = entity.getTemplates();
086        if (MapUtils.isNotEmpty(templates)) {
087            writeSerializableMapField("templates", new TreeMap<>(templates), jg);
088        }
089
090        Map<String, Map<String, Serializable>> properties = entity.getProperties();
091        if (MapUtils.isNotEmpty(properties) && properties.values().stream().anyMatch(MapUtils::isNotEmpty)) {
092            writeSerializableMapMapField("properties", cleanAndSort(properties), jg);
093        }
094
095        // get category, layout context and widget converters from context
096        String category = ctx.getParameter(CATEGORY_PARAMETER);
097        List<WidgetDefinitionConverter> widgetConverters = ctx.getParameters(WIDGET_CONVERTERS_PARAMETER);
098        LayoutConversionContext layoutCtx = ctx.getParameter(LAYOUT_CONTEXT_PARAMETER);
099
100        LayoutRowDefinition[] rowDefinitions = entity.getRows();
101        List<WidgetReference> widgetsToExport = new ArrayList<>();
102        if (ArrayUtils.isNotEmpty(rowDefinitions)) {
103            jg.writeArrayFieldStart("rows");
104            // use a counter to provide default name
105            int rowIndex = -1;
106            for (LayoutRowDefinition layoutRowDef : rowDefinitions) {
107                rowIndex++;
108                writeRawDefinition(layoutRowDef, layoutRowDef.getDefaultName(rowIndex), jg);
109
110                WidgetReference[] widgets = layoutRowDef.getWidgetReferences();
111                if (widgets != null) {
112                    widgetsToExport.addAll(Arrays.asList(widgets));
113                }
114            }
115            jg.writeEndArray();
116        }
117
118        if (!widgetsToExport.isEmpty()) {
119            jg.writeArrayFieldStart("widgets");
120            for (WidgetReference widgetRef : widgetsToExport) {
121                WidgetDefinition widgetDefinition = getWidgetDefinition(widgetRef, category, entity, layoutCtx,
122                        widgetConverters);
123                if (widgetDefinition != null) {
124                    writeEntity(widgetDefinition, jg);
125
126                    // also export local subwidgets references
127                    WidgetReference[] subWidgets = widgetDefinition.getSubWidgetReferences();
128                    if (subWidgets != null) {
129                        for (WidgetReference subWidgetRef : subWidgets) {
130                            WidgetDefinition subWidgetDefinition = getWidgetDefinition(subWidgetRef, category, entity,
131                                    layoutCtx, widgetConverters);
132                            if (subWidgetDefinition != null) {
133                                writeEntity(subWidgetDefinition, jg);
134                            }
135                        }
136                    }
137                }
138            }
139            jg.writeEndArray();
140        }
141
142        Map<String, List<RenderingInfo>> renderingInfos = entity.getRenderingInfos();
143        if (MapUtils.isNotEmpty(renderingInfos)
144                && renderingInfos.values().stream().anyMatch(CollectionUtils::isNotEmpty)) {
145            jg.writeObjectFieldStart("renderingInfos");
146            // sort so that order is deterministic
147            for (Entry<String, List<RenderingInfo>> entry : new TreeMap<>(renderingInfos).entrySet()) {
148                writeSerializableListField(entry.getKey(), entry.getValue(), jg);
149            }
150            jg.writeEndObject();
151        }
152
153        List<String> aliases = entity.getAliases();
154        if (CollectionUtils.isNotEmpty(aliases)) {
155            writeSerializableListField("aliases", aliases, jg);
156        }
157        jg.writeEndObject();
158    }
159
160    protected void writeRawDefinition(LayoutRowDefinition layoutRowDef, String defaultName, JsonGenerator jg)
161            throws IOException {
162        jg.writeStartObject();
163        String name = layoutRowDef.getName();
164        if (name != null) {
165            jg.writeStringField("name", name);
166        } else if (defaultName != null) {
167            jg.writeStringField("name", defaultName);
168        }
169        // fill selection info only if that's not the default value from the definition
170        if (layoutRowDef.isAlwaysSelected()) {
171            jg.writeBooleanField("alwaysSelected", true);
172        }
173        if (!layoutRowDef.isSelectedByDefault()) {
174            jg.writeBooleanField("selectedByDefault", false);
175        }
176        Map<String, Map<String, Serializable>> properties = layoutRowDef.getProperties();
177        if (MapUtils.isNotEmpty(properties) && properties.values().stream().anyMatch(MapUtils::isNotEmpty)) {
178            writeSerializableMapMapField("properties", cleanAndSort(properties), jg);
179        }
180        WidgetReference[] defWidgets = layoutRowDef.getWidgetReferences();
181        if (ArrayUtils.isNotEmpty(defWidgets)) {
182            writeSerializableListField("widgets", Arrays.asList(defWidgets), jg);
183        }
184        jg.writeEndObject();
185    }
186
187    protected WidgetDefinition getWidgetDefinition(WidgetReference widgetReference, String category,
188            LayoutDefinition layoutDefinition, LayoutConversionContext ctx,
189            List<WidgetDefinitionConverter> widgetConverters) {
190        String widgetName = widgetReference.getName();
191        WidgetDefinition widgetDefinition = layoutDefinition.getWidgetDefinition(widgetName);
192        if (widgetDefinition == null) {
193            String cat = widgetReference.getCategory();
194            if (cat == null) {
195                cat = category;
196            }
197            widgetDefinition = webLayoutManager.getWidgetDefinition(cat, widgetName);
198        }
199        if (widgetDefinition == null) {
200            log.error(String.format("No definition found for widget '%s' in layout '%s' => cannot export", widgetName,
201                    layoutDefinition.getName()));
202        } else {
203            if (widgetConverters != null) {
204                for (WidgetDefinitionConverter conv : widgetConverters) {
205                    widgetDefinition = conv.getWidgetDefinition(widgetDefinition, ctx);
206                }
207            }
208        }
209        return widgetDefinition;
210    }
211
212}