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}