001/*
002 * (C) Copyright 2010 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 *     Anahide Tchertchian
018 */
019package org.nuxeo.ecm.platform.forms.layout.io;
020
021import java.io.IOException;
022import java.io.OutputStream;
023import java.io.Serializable;
024import java.io.UnsupportedEncodingException;
025import java.net.URLDecoder;
026import java.net.URLEncoder;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Map;
033
034import org.apache.commons.lang.StringUtils;
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.nuxeo.ecm.platform.forms.layout.api.FieldDefinition;
038import org.nuxeo.ecm.platform.forms.layout.api.Layout;
039import org.nuxeo.ecm.platform.forms.layout.api.LayoutDefinition;
040import org.nuxeo.ecm.platform.forms.layout.api.LayoutRow;
041import org.nuxeo.ecm.platform.forms.layout.api.LayoutRowDefinition;
042import org.nuxeo.ecm.platform.forms.layout.api.LayoutTypeConfiguration;
043import org.nuxeo.ecm.platform.forms.layout.api.LayoutTypeDefinition;
044import org.nuxeo.ecm.platform.forms.layout.api.RenderingInfo;
045import org.nuxeo.ecm.platform.forms.layout.api.Widget;
046import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition;
047import org.nuxeo.ecm.platform.forms.layout.api.WidgetReference;
048import org.nuxeo.ecm.platform.forms.layout.api.WidgetSelectOption;
049import org.nuxeo.ecm.platform.forms.layout.api.WidgetSelectOptions;
050import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeConfiguration;
051import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeDefinition;
052import org.nuxeo.ecm.platform.forms.layout.api.converters.LayoutConversionContext;
053import org.nuxeo.ecm.platform.forms.layout.api.converters.WidgetDefinitionConverter;
054import org.nuxeo.ecm.platform.forms.layout.api.impl.FieldDefinitionImpl;
055import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutDefinitionImpl;
056import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutRowDefinitionImpl;
057import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutTypeConfigurationImpl;
058import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutTypeDefinitionComparator;
059import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutTypeDefinitionImpl;
060import org.nuxeo.ecm.platform.forms.layout.api.impl.RenderingInfoImpl;
061import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetDefinitionImpl;
062import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetReferenceImpl;
063import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetSelectOptionImpl;
064import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetSelectOptionsImpl;
065import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetTypeConfigurationImpl;
066import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetTypeDefinitionComparator;
067import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetTypeDefinitionImpl;
068import org.nuxeo.ecm.platform.forms.layout.api.service.LayoutStore;
069import org.nuxeo.runtime.api.Framework;
070
071import net.sf.json.JSONArray;
072import net.sf.json.JSONObject;
073
074/**
075 * JSON exporter for layout objects
076 *
077 * @author Anahide Tchertchian
078 * @since 5.4.2
079 */
080public class JSONLayoutExporter {
081
082    private static final Log log = LogFactory.getLog(JSONLayoutExporter.class);
083
084    public static final String ENCODED_VALUES_ENCODING = "UTF-8";
085
086    public static String encode(JSONObject jsonObject) throws UnsupportedEncodingException {
087        String json = jsonObject.toString();
088        String encodedValues = Base64.encodeBytes(json.getBytes(), Base64.GZIP | Base64.DONT_BREAK_LINES);
089        return URLEncoder.encode(encodedValues, ENCODED_VALUES_ENCODING);
090    }
091
092    public static JSONObject decode(String json) throws UnsupportedEncodingException {
093        String decodedValues = URLDecoder.decode(json, ENCODED_VALUES_ENCODING);
094        json = new String(Base64.decode(decodedValues));
095        return JSONObject.fromObject(json);
096    }
097
098    /**
099     * @since 5.5
100     * @throws IOException
101     */
102    public static void export(String category, LayoutDefinition layoutDef, LayoutConversionContext ctx,
103            List<WidgetDefinitionConverter> widgetConverters, OutputStream out) throws IOException {
104        JSONObject res = exportToJson(category, layoutDef, ctx, widgetConverters);
105        out.write(res.toString(2).getBytes(ENCODED_VALUES_ENCODING));
106    }
107
108    public static void export(WidgetTypeDefinition def, OutputStream out) throws IOException {
109        JSONObject res = exportToJson(def);
110        out.write(res.toString(2).getBytes(ENCODED_VALUES_ENCODING));
111    }
112
113    public static void export(List<WidgetTypeDefinition> defs, OutputStream out) throws IOException {
114        JSONObject res = new JSONObject();
115        if (defs != null) {
116            // sort so that order is deterministic
117            Collections.sort(defs, new WidgetTypeDefinitionComparator(false));
118        }
119        for (WidgetTypeDefinition def : defs) {
120            res.element(def.getName(), exportToJson(def));
121        }
122        out.write(res.toString(2).getBytes(ENCODED_VALUES_ENCODING));
123    }
124
125    /**
126     * @since 6.0
127     */
128    public static void exportLayoutType(LayoutTypeDefinition def, OutputStream out) throws IOException {
129        JSONObject res = exportToJson(def);
130        out.write(res.toString(2).getBytes(ENCODED_VALUES_ENCODING));
131    }
132
133    /**
134     * @since 6.0
135     */
136    public static void exportLayoutTypes(List<LayoutTypeDefinition> defs, OutputStream out) throws IOException {
137        JSONObject res = new JSONObject();
138        if (defs != null) {
139            // sort so that order is deterministic
140            Collections.sort(defs, new LayoutTypeDefinitionComparator());
141        }
142        for (LayoutTypeDefinition def : defs) {
143            res.element(def.getName(), exportToJson(def));
144        }
145        out.write(res.toString(2).getBytes(ENCODED_VALUES_ENCODING));
146    }
147
148    public static JSONObject exportToJson(WidgetTypeDefinition def) {
149        JSONObject json = new JSONObject();
150        json.element("name", def.getName());
151        List<String> caliases = def.getAliases();
152        if (caliases != null && !caliases.isEmpty()) {
153            JSONArray aliases = new JSONArray();
154            for (String alias : caliases) {
155                aliases.add(alias);
156            }
157            json.element("aliases", aliases);
158        }
159        json.element("handlerClassName", def.getHandlerClassName());
160        JSONObject props = exportStringPropsToJson(def.getProperties());
161        if (!props.isEmpty()) {
162            json.element("properties", props);
163        }
164        WidgetTypeConfiguration conf = def.getConfiguration();
165        if (conf != null) {
166            json.element("configuration", exportToJson(conf));
167        }
168        return json;
169    }
170
171    @SuppressWarnings("unchecked")
172    public static WidgetTypeDefinition importWidgetTypeDefinition(JSONObject jsonDef) {
173        String name = jsonDef.optString("name");
174        String handlerClass = jsonDef.optString("handlerClassName");
175        Map<String, String> properties = importStringProps(jsonDef.optJSONObject("properties"));
176        WidgetTypeConfiguration conf = importWidgetTypeConfiguration(jsonDef.optJSONObject("configuration"));
177        List<String> aliases = new ArrayList<String>();
178        JSONArray jaliases = jsonDef.optJSONArray("aliases");
179        if (jaliases != null) {
180            aliases.addAll(jaliases);
181        }
182        WidgetTypeDefinitionImpl res = new WidgetTypeDefinitionImpl(name, handlerClass, properties, conf);
183        res.setAliases(aliases);
184        return res;
185    }
186
187    public static JSONObject exportToJson(WidgetTypeConfiguration conf) {
188        JSONObject json = new JSONObject();
189        json.element("title", conf.getTitle());
190        json.element("description", conf.getDescription());
191        String demoId = conf.getDemoId();
192        if (demoId != null) {
193            JSONObject demoInfo = new JSONObject();
194            demoInfo.element("id", demoId);
195            demoInfo.element("previewEnabled", conf.isDemoPreviewEnabled());
196            json.element("demo", demoInfo);
197        }
198        json.element("sinceVersion", conf.getSinceVersion());
199        String deprVersion = conf.getDeprecatedVersion();
200        if (!StringUtils.isBlank(deprVersion)) {
201            json.element("deprecatedVersion", deprVersion);
202        }
203        JSONObject confProps = exportPropsToJson(conf.getConfProperties());
204        if (!confProps.isEmpty()) {
205            json.element("confProperties", confProps);
206        }
207
208        JSONArray supportedModes = new JSONArray();
209        List<String> confSupportedModes = conf.getSupportedModes();
210        if (confSupportedModes != null) {
211            supportedModes.addAll(confSupportedModes);
212        }
213        if (!supportedModes.isEmpty()) {
214            json.element("supportedModes", supportedModes);
215        }
216
217        if (conf.isAcceptingSubWidgets()) {
218            json.element("acceptingSubWidgets", conf.isAcceptingSubWidgets());
219        }
220        if (conf.isHandlingLabels()) {
221            json.element("handlingLabels", conf.isHandlingLabels());
222        }
223        JSONArray supportedControls = new JSONArray();
224        List<String> confSupportedControls = conf.getSupportedControls();
225        if (confSupportedControls != null) {
226            supportedControls.addAll(confSupportedControls);
227        }
228        if (!supportedControls.isEmpty()) {
229            json.element("supportedControls", supportedControls);
230        }
231        if (conf.isContainingForm()) {
232            json.element("containingForm", true);
233        }
234
235        JSONObject fields = new JSONObject();
236        fields.element("list", conf.isList());
237        fields.element("complex", conf.isComplex());
238
239        JSONArray supportedTypes = new JSONArray();
240        List<String> confSupportedTypes = conf.getSupportedFieldTypes();
241        if (confSupportedTypes != null) {
242            supportedTypes.addAll(confSupportedTypes);
243        }
244        if (!supportedTypes.isEmpty()) {
245            fields.element("supportedTypes", supportedTypes);
246        }
247
248        JSONArray defaultTypes = new JSONArray();
249        List<String> confDefaultTypes = conf.getDefaultFieldTypes();
250        if (confDefaultTypes != null) {
251            defaultTypes.addAll(confDefaultTypes);
252        }
253        if (!defaultTypes.isEmpty()) {
254            fields.element("defaultTypes", defaultTypes);
255        }
256
257        JSONArray defaultFieldDefs = new JSONArray();
258        List<FieldDefinition> fieldDefs = conf.getDefaultFieldDefinitions();
259        if (fieldDefs != null) {
260            for (FieldDefinition fieldDef : fieldDefs) {
261                defaultFieldDefs.add(exportToJson(fieldDef));
262            }
263        }
264        if (!defaultFieldDefs.isEmpty()) {
265            fields.element("defaultConfiguration", defaultFieldDefs);
266        }
267
268        Map<String, List<LayoutDefinition>> fieldLayouts = conf.getFieldLayouts();
269        if (fieldLayouts != null) {
270            List<String> modes = new ArrayList<String>(fieldLayouts.keySet());
271            // sort so that order is deterministic
272            Collections.sort(modes);
273            JSONObject layouts = new JSONObject();
274            for (String mode : modes) {
275                JSONArray modeLayouts = new JSONArray();
276                for (LayoutDefinition layoutDef : fieldLayouts.get(mode)) {
277                    modeLayouts.add(exportToJson(null, layoutDef));
278                }
279                layouts.element(mode, modeLayouts);
280            }
281            if (!layouts.isEmpty()) {
282                fields.element("layouts", layouts);
283            }
284        }
285
286        json.element("fields", fields);
287
288        JSONArray cats = new JSONArray();
289        List<String> confCats = conf.getCategories();
290        if (confCats != null) {
291            cats.addAll(confCats);
292        }
293        if (!cats.isEmpty()) {
294            json.element("categories", cats);
295        }
296
297        JSONObject props = new JSONObject();
298        Map<String, List<LayoutDefinition>> confLayouts = conf.getPropertyLayouts();
299        if (confLayouts != null) {
300            List<String> modes = new ArrayList<String>(confLayouts.keySet());
301            // sort so that order is deterministic
302            Collections.sort(modes);
303            JSONObject layouts = new JSONObject();
304            for (String mode : modes) {
305                JSONArray modeLayouts = new JSONArray();
306                for (LayoutDefinition layoutDef : confLayouts.get(mode)) {
307                    modeLayouts.add(exportToJson(null, layoutDef));
308                }
309                layouts.element(mode, modeLayouts);
310            }
311            if (!layouts.isEmpty()) {
312                props.element("layouts", layouts);
313            }
314        }
315
316        Map<String, Map<String, Serializable>> defaultPropValues = conf.getDefaultPropertyValues();
317        if (defaultPropValues != null && !defaultPropValues.isEmpty()) {
318            json.element("defaultPropertyValues", exportPropsByModeToJson(defaultPropValues));
319        }
320
321        if (!props.isEmpty()) {
322            json.element("properties", props);
323        }
324
325        Map<String, Map<String, Serializable>> defaultControlValues = conf.getDefaultControlValues();
326        if (defaultControlValues != null && !defaultControlValues.isEmpty()) {
327            json.element("defaultControlValues", exportPropsByModeToJson(defaultControlValues));
328        }
329
330        return json;
331    }
332
333    @SuppressWarnings("unchecked")
334    public static WidgetTypeConfiguration importWidgetTypeConfiguration(JSONObject conf) {
335        WidgetTypeConfigurationImpl res = new WidgetTypeConfigurationImpl();
336        if (conf == null) {
337            return res;
338        }
339        res.setTitle(conf.getString("title"));
340        res.setDescription(conf.optString("description"));
341        res.setSinceVersion(conf.optString("sinceVersion"));
342        res.setDeprecatedVersion(conf.optString("deprecatedVersion"));
343
344        JSONObject demoInfo = conf.optJSONObject("demo");
345        String demoId = null;
346        boolean demoPreviewEnabled = false;
347        if (demoInfo != null && !demoInfo.isNullObject()) {
348            demoId = demoInfo.optString("id");
349            demoPreviewEnabled = demoInfo.optBoolean("previewEnabled");
350        }
351        res.setDemoId(demoId);
352        res.setDemoPreviewEnabled(demoPreviewEnabled);
353
354        res.setProperties(importProps(conf.optJSONObject("confProperties")));
355
356        List<String> confSupportedModes = new ArrayList<String>();
357        JSONArray supportedModes = conf.optJSONArray("supportedModes");
358        if (supportedModes != null) {
359            confSupportedModes.addAll(supportedModes);
360        }
361        res.setSupportedModes(confSupportedModes);
362
363        res.setAcceptingSubWidgets(conf.optBoolean("acceptingSubWidgets", false));
364        res.setHandlingLabels(conf.optBoolean("handlingLabels", false));
365        List<String> confSupportedControls = new ArrayList<String>();
366        JSONArray supportedControls = conf.optJSONArray("supportedControls");
367        if (supportedControls != null) {
368            confSupportedControls.addAll(supportedControls);
369        }
370        res.setSupportedControls(confSupportedControls);
371        res.setContainingForm(conf.optBoolean("containingForm", false));
372
373        JSONObject fields = conf.optJSONObject("fields");
374        boolean list = false;
375        boolean complex = false;
376        List<String> confSupportedTypes = new ArrayList<String>();
377        List<String> confDefaultTypes = new ArrayList<String>();
378        List<FieldDefinition> defaultFieldDefinitions = new ArrayList<FieldDefinition>();
379        Map<String, List<LayoutDefinition>> fieldLayouts = new HashMap<String, List<LayoutDefinition>>();
380        if (fields != null && !fields.isNullObject()) {
381            list = fields.optBoolean("list", false);
382            complex = fields.optBoolean("complex", false);
383            JSONArray supportedTypes = fields.optJSONArray("supportedTypes");
384            if (supportedTypes != null) {
385                confSupportedTypes.addAll(supportedTypes);
386            }
387            JSONArray defaultTypes = fields.optJSONArray("defaultTypes");
388            if (defaultTypes != null) {
389                confDefaultTypes.addAll(defaultTypes);
390            }
391            JSONArray jfields = fields.optJSONArray("defaultConfiguration");
392            if (jfields != null) {
393                for (Object item : jfields) {
394                    defaultFieldDefinitions.add(importFieldDefinition((JSONObject) item));
395                }
396            }
397            JSONObject layouts = fields.optJSONObject("layouts");
398            if (layouts != null && !layouts.isNullObject()) {
399                for (Object item : layouts.keySet()) {
400                    String mode = (String) item;
401                    List<LayoutDefinition> layoutDefs = new ArrayList<LayoutDefinition>();
402                    JSONArray modeLayouts = layouts.getJSONArray(mode);
403                    if (modeLayouts != null && !mode.isEmpty()) {
404                        for (Object subitem : modeLayouts) {
405                            layoutDefs.add(importLayoutDefinition((JSONObject) subitem));
406                        }
407                    }
408                    fieldLayouts.put(mode, layoutDefs);
409                }
410            }
411        }
412        res.setList(list);
413        res.setComplex(complex);
414        res.setSupportedFieldTypes(confSupportedTypes);
415        res.setDefaultFieldTypes(confDefaultTypes);
416        res.setDefaultFieldDefinitions(defaultFieldDefinitions);
417        res.setFieldLayouts(fieldLayouts);
418
419        JSONArray cats = conf.optJSONArray("categories");
420        List<String> confCats = new ArrayList<String>();
421        if (cats != null) {
422            confCats.addAll(cats);
423        }
424        res.setCategories(confCats);
425
426        JSONObject props = conf.optJSONObject("properties");
427        Map<String, List<LayoutDefinition>> confLayouts = new HashMap<String, List<LayoutDefinition>>();
428        if (props != null && !props.isNullObject()) {
429            JSONObject layouts = props.optJSONObject("layouts");
430            if (layouts != null && !layouts.isNullObject()) {
431                for (Object item : layouts.keySet()) {
432                    String mode = (String) item;
433                    List<LayoutDefinition> layoutDefs = new ArrayList<LayoutDefinition>();
434                    JSONArray modeLayouts = layouts.getJSONArray(mode);
435                    if (modeLayouts != null && !mode.isEmpty()) {
436                        for (Object subitem : modeLayouts) {
437                            layoutDefs.add(importLayoutDefinition((JSONObject) subitem));
438                        }
439                    }
440                    confLayouts.put(mode, layoutDefs);
441                }
442            }
443        }
444
445        res.setPropertyLayouts(confLayouts);
446
447        JSONObject defaultPropertyValues = conf.optJSONObject("defaultPropertyValues");
448        Map<String, Map<String, Serializable>> confDefaultProps = importPropsByMode(defaultPropertyValues);
449        res.setDefaultPropertyValues(confDefaultProps);
450
451        JSONObject defaultControlValues = conf.optJSONObject("defaultControlValues");
452        Map<String, Map<String, Serializable>> confDefaultControls = importPropsByMode(defaultControlValues);
453        res.setDefaultControlValues(confDefaultControls);
454
455        return res;
456    }
457
458    /**
459     * @since 6.0
460     */
461    public static JSONObject exportToJson(LayoutTypeDefinition def) {
462        JSONObject json = new JSONObject();
463        json.element("name", def.getName());
464
465        List<String> caliases = def.getAliases();
466        if (caliases != null && !caliases.isEmpty()) {
467            JSONArray aliases = new JSONArray();
468            for (String alias : caliases) {
469                aliases.add(alias);
470            }
471            json.element("aliases", aliases);
472        }
473
474        JSONObject templates = exportStringPropsToJson(def.getTemplates());
475        if (!templates.isEmpty()) {
476            json.element("templates", templates);
477        }
478
479        LayoutTypeConfiguration conf = def.getConfiguration();
480        if (conf != null) {
481            json.element("configuration", exportToJson(conf));
482        }
483        return json;
484    }
485
486    /**
487     * @since 6.0
488     */
489    @SuppressWarnings("unchecked")
490    public static LayoutTypeDefinition importLayoutTypeDefinition(JSONObject jsonDef) {
491        String name = jsonDef.optString("name");
492        Map<String, String> templates = importStringProps(jsonDef.optJSONObject("templates"));
493        LayoutTypeConfiguration conf = importLayoutTypeConfiguration(jsonDef.optJSONObject("configuration"));
494        List<String> aliases = new ArrayList<String>();
495        JSONArray jaliases = jsonDef.optJSONArray("aliases");
496        if (jaliases != null) {
497            aliases.addAll(jaliases);
498        }
499        LayoutTypeDefinitionImpl res = new LayoutTypeDefinitionImpl(name, templates, conf);
500        res.setAliases(aliases);
501        return res;
502    }
503
504    /**
505     * @since 6.0
506     */
507    public static JSONObject exportToJson(LayoutTypeConfiguration conf) {
508        JSONObject json = new JSONObject();
509        json.element("title", conf.getTitle());
510        json.element("description", conf.getDescription());
511        String demoId = conf.getDemoId();
512        if (demoId != null) {
513            JSONObject demoInfo = new JSONObject();
514            demoInfo.element("id", demoId);
515            demoInfo.element("previewEnabled", conf.isDemoPreviewEnabled());
516            json.element("demo", demoInfo);
517        }
518        json.element("sinceVersion", conf.getSinceVersion());
519        String deprVersion = conf.getDeprecatedVersion();
520        if (!StringUtils.isBlank(deprVersion)) {
521            json.element("deprecatedVersion", deprVersion);
522        }
523
524        JSONArray supportedModes = new JSONArray();
525        List<String> confSupportedModes = conf.getSupportedModes();
526        if (confSupportedModes != null) {
527            supportedModes.addAll(confSupportedModes);
528        }
529        if (!supportedModes.isEmpty()) {
530            json.element("supportedModes", supportedModes);
531        }
532
533        if (conf.isHandlingLabels()) {
534            json.element("handlingLabels", conf.isHandlingLabels());
535        }
536        JSONArray supportedControls = new JSONArray();
537        List<String> confSupportedControls = conf.getSupportedControls();
538        if (confSupportedControls != null) {
539            supportedControls.addAll(confSupportedControls);
540        }
541        if (!supportedControls.isEmpty()) {
542            json.element("supportedControls", supportedControls);
543        }
544        if (conf.isContainingForm()) {
545            json.element("containingForm", true);
546        }
547
548        JSONArray cats = new JSONArray();
549        List<String> confCats = conf.getCategories();
550        if (confCats != null) {
551            cats.addAll(confCats);
552        }
553        if (!cats.isEmpty()) {
554            json.element("categories", cats);
555        }
556
557        JSONObject props = new JSONObject();
558        Map<String, List<LayoutDefinition>> confLayouts = conf.getPropertyLayouts();
559        if (confLayouts != null) {
560            List<String> modes = new ArrayList<String>(confLayouts.keySet());
561            // sort so that order is deterministic
562            Collections.sort(modes);
563            JSONObject layouts = new JSONObject();
564            for (String mode : modes) {
565                JSONArray modeLayouts = new JSONArray();
566                for (LayoutDefinition layoutDef : confLayouts.get(mode)) {
567                    modeLayouts.add(exportToJson(null, layoutDef));
568                }
569                layouts.element(mode, modeLayouts);
570            }
571            if (!layouts.isEmpty()) {
572                props.element("layouts", layouts);
573            }
574        }
575
576        Map<String, Map<String, Serializable>> defaultPropValues = conf.getDefaultPropertyValues();
577        if (defaultPropValues != null && !defaultPropValues.isEmpty()) {
578            json.element("defaultPropertyValues", exportPropsByModeToJson(defaultPropValues));
579        }
580
581        if (!props.isEmpty()) {
582            json.element("properties", props);
583        }
584
585        return json;
586
587    }
588
589    /**
590     * @since 6.0
591     */
592    @SuppressWarnings("unchecked")
593    public static LayoutTypeConfiguration importLayoutTypeConfiguration(JSONObject conf) {
594        LayoutTypeConfigurationImpl res = new LayoutTypeConfigurationImpl();
595        if (conf == null) {
596            return res;
597        }
598        res.setTitle(conf.getString("title"));
599        res.setDescription(conf.optString("description"));
600        res.setSinceVersion(conf.optString("sinceVersion"));
601        res.setDeprecatedVersion(conf.optString("deprecatedVersion"));
602
603        JSONObject demoInfo = conf.optJSONObject("demo");
604        String demoId = null;
605        boolean demoPreviewEnabled = false;
606        if (demoInfo != null && !demoInfo.isNullObject()) {
607            demoId = demoInfo.optString("id");
608            demoPreviewEnabled = demoInfo.optBoolean("previewEnabled");
609        }
610        res.setDemoId(demoId);
611        res.setDemoPreviewEnabled(demoPreviewEnabled);
612
613        List<String> confSupportedModes = new ArrayList<String>();
614        JSONArray supportedModes = conf.optJSONArray("supportedModes");
615        if (supportedModes != null) {
616            confSupportedModes.addAll(supportedModes);
617        }
618        res.setSupportedModes(confSupportedModes);
619
620        res.setHandlingLabels(conf.optBoolean("handlingLabels", false));
621        List<String> confSupportedControls = new ArrayList<String>();
622        JSONArray supportedControls = conf.optJSONArray("supportedControls");
623        if (supportedControls != null) {
624            confSupportedControls.addAll(supportedControls);
625        }
626        res.setSupportedControls(confSupportedControls);
627        res.setContainingForm(conf.optBoolean("containingForm", false));
628
629        JSONArray cats = conf.optJSONArray("categories");
630        List<String> confCats = new ArrayList<String>();
631        if (cats != null) {
632            confCats.addAll(cats);
633        }
634        res.setCategories(confCats);
635
636        JSONObject props = conf.optJSONObject("properties");
637        Map<String, List<LayoutDefinition>> confLayouts = new HashMap<String, List<LayoutDefinition>>();
638        if (props != null && !props.isNullObject()) {
639            JSONObject layouts = props.optJSONObject("layouts");
640            if (layouts != null && !layouts.isNullObject()) {
641                for (Object item : layouts.keySet()) {
642                    String mode = (String) item;
643                    List<LayoutDefinition> layoutDefs = new ArrayList<LayoutDefinition>();
644                    JSONArray modeLayouts = layouts.getJSONArray(mode);
645                    if (modeLayouts != null && !mode.isEmpty()) {
646                        for (Object subitem : modeLayouts) {
647                            layoutDefs.add(importLayoutDefinition((JSONObject) subitem));
648                        }
649                    }
650                    confLayouts.put(mode, layoutDefs);
651                }
652            }
653        }
654
655        res.setPropertyLayouts(confLayouts);
656
657        JSONObject defaultPropertyValues = conf.optJSONObject("defaultPropertyValues");
658        Map<String, Map<String, Serializable>> confDefaultProps = importPropsByMode(defaultPropertyValues);
659        res.setDefaultPropertyValues(confDefaultProps);
660
661        return res;
662    }
663
664    public static JSONObject exportToJson(String category, LayoutDefinition layoutDef) {
665        return exportToJson(category, layoutDef, null, null);
666    }
667
668    /**
669     * Returns the JSON export of this layout definition
670     *
671     * @since 5.5
672     * @param category the category of the layout, needed for retrieval of referenced global widgets.
673     * @param layoutDef the layout definition
674     * @param ctx the widget conversion context
675     * @param widgetConverters the list of ordered widget converters to use before export
676     */
677    public static JSONObject exportToJson(String category, LayoutDefinition layoutDef, LayoutConversionContext ctx,
678            List<WidgetDefinitionConverter> widgetConverters) {
679        JSONObject json = new JSONObject();
680        json.element("name", layoutDef.getName());
681
682        String type = layoutDef.getType();
683        if (type != null) {
684            json.element("type", type);
685        }
686
687        String typeCat = layoutDef.getTypeCategory();
688        if (typeCat != null) {
689            json.element("typeCategory", typeCat);
690        }
691
692        JSONObject templates = exportStringPropsToJson(layoutDef.getTemplates());
693        if (!templates.isEmpty()) {
694            json.element("templates", templates);
695        }
696
697        JSONObject props = exportPropsByModeToJson(layoutDef.getProperties());
698        if (!props.isEmpty()) {
699            json.element("properties", props);
700        }
701
702        JSONArray rows = new JSONArray();
703        LayoutRowDefinition[] defRows = layoutDef.getRows();
704        List<WidgetReference> widgetsToExport = new ArrayList<WidgetReference>();
705        if (defRows != null) {
706            int rowIndex = -1;
707            for (LayoutRowDefinition layoutRowDef : defRows) {
708                rowIndex++;
709                rows.add(exportToJson(layoutRowDef, layoutRowDef.getDefaultName(rowIndex)));
710                WidgetReference[] widgets = layoutRowDef.getWidgetReferences();
711                if (widgets != null) {
712                    for (WidgetReference widget : widgets) {
713                        widgetsToExport.add(widget);
714                    }
715                }
716            }
717        }
718
719        if (!rows.isEmpty()) {
720            json.element("rows", rows);
721        }
722        LayoutStore webLayoutManager = Framework.getLocalService(LayoutStore.class);
723        JSONArray widgets = new JSONArray();
724        for (WidgetReference widgetRef : widgetsToExport) {
725            WidgetDefinition widgetDef = exportWidgetReference(widgetRef, category, layoutDef, ctx, webLayoutManager,
726                    widgetConverters);
727            if (widgetDef != null) {
728                widgets.add(exportToJson(widgetDef, ctx, widgetConverters));
729
730                // also export local subwidgets references
731                WidgetReference[] subwidgetRefs = widgetDef.getSubWidgetReferences();
732                if (subwidgetRefs != null) {
733                    for (WidgetReference subwidgetRef : subwidgetRefs) {
734                        WidgetDefinition subwidgetDef = exportWidgetReference(subwidgetRef, category, layoutDef, ctx,
735                                webLayoutManager, widgetConverters);
736                        if (subwidgetDef != null) {
737                            widgets.add(exportToJson(subwidgetDef, ctx, widgetConverters));
738                        }
739                    }
740                }
741            }
742        }
743        if (!widgets.isEmpty()) {
744            json.element("widgets", widgets);
745        }
746
747        JSONObject renderingInfos = exportRenderingInfosByModeToJson(layoutDef.getRenderingInfos());
748        if (!renderingInfos.isEmpty()) {
749            json.element("renderingInfos", renderingInfos);
750        }
751
752        List<String> caliases = layoutDef.getAliases();
753        if (caliases != null && !caliases.isEmpty()) {
754            JSONArray aliases = new JSONArray();
755            for (String alias : caliases) {
756                aliases.add(alias);
757            }
758            json.element("aliases", aliases);
759        }
760
761        return json;
762    }
763
764    protected static WidgetDefinition exportWidgetReference(WidgetReference widgetRef, String category,
765            LayoutDefinition layoutDef, LayoutConversionContext ctx, LayoutStore webLayoutManager,
766            List<WidgetDefinitionConverter> widgetConverters) {
767        String widgetName = widgetRef.getName();
768        WidgetDefinition widgetDef = layoutDef.getWidgetDefinition(widgetName);
769        if (widgetDef == null) {
770            String cat = widgetRef.getCategory();
771            if (cat == null) {
772                cat = category;
773            }
774            widgetDef = webLayoutManager.getWidgetDefinition(cat, widgetName);
775        }
776        if (widgetDef == null) {
777            log.error(String.format("No definition found for widget '%s' in layout '%s' " + "=> cannot export",
778                    widgetName, layoutDef.getName()));
779        } else {
780            if (widgetConverters != null) {
781                for (WidgetDefinitionConverter conv : widgetConverters) {
782                    widgetDef = conv.getWidgetDefinition(widgetDef, ctx);
783                }
784            }
785        }
786        return widgetDef;
787    }
788
789    @SuppressWarnings("unchecked")
790    public static LayoutDefinition importLayoutDefinition(JSONObject layoutDef) {
791        String name = layoutDef.optString("name", null);
792        String type = layoutDef.optString("type", null);
793        String typeCat = layoutDef.optString("typeCategory", null);
794        Map<String, String> templates = importStringProps(layoutDef.optJSONObject("templates"));
795        Map<String, Map<String, Serializable>> properties = importPropsByMode(layoutDef.optJSONObject("properties"));
796
797        List<LayoutRowDefinition> rows = new ArrayList<LayoutRowDefinition>();
798        JSONArray jrows = layoutDef.optJSONArray("rows");
799        if (jrows != null) {
800            for (Object item : jrows) {
801                rows.add(importLayoutRowDefinition((JSONObject) item));
802            }
803        }
804
805        List<WidgetDefinition> widgets = new ArrayList<WidgetDefinition>();
806        JSONArray jwidgets = layoutDef.optJSONArray("widgets");
807        if (jwidgets != null) {
808            for (Object item : jwidgets) {
809                widgets.add(importWidgetDefinition((JSONObject) item));
810            }
811        }
812
813        Map<String, List<RenderingInfo>> renderingInfos = importRenderingInfosByMode(layoutDef.optJSONObject("renderingInfos"));
814
815        List<String> aliases = new ArrayList<String>();
816        JSONArray jaliases = layoutDef.optJSONArray("aliases");
817        if (jaliases != null) {
818            aliases.addAll(jaliases);
819        }
820
821        LayoutDefinitionImpl res = new LayoutDefinitionImpl(name, properties, templates, rows, widgets);
822        res.setRenderingInfos(renderingInfos);
823        res.setType(type);
824        res.setTypeCategory(typeCat);
825        res.setAliases(aliases);
826        return res;
827    }
828
829    /**
830     * @since 6.0
831     */
832    public static JSONObject exportToJson(LayoutRowDefinition layoutRowDef, String defaultRowName) {
833        JSONObject json = new JSONObject();
834        String name = layoutRowDef.getName();
835        if (name != null) {
836            json.element("name", name);
837        } else if (defaultRowName != null) {
838            json.element("name", defaultRowName);
839        }
840        // fill selection info only if that's not the default value from the
841        // definition
842        if (layoutRowDef.isAlwaysSelected()) {
843            json.element("alwaysSelected", true);
844        }
845        if (!layoutRowDef.isSelectedByDefault()) {
846            json.element("selectedByDefault", false);
847        }
848        JSONObject props = exportPropsByModeToJson(layoutRowDef.getProperties());
849        if (!props.isEmpty()) {
850            json.element("properties", props);
851        }
852        JSONArray widgets = new JSONArray();
853        WidgetReference[] defWidgets = layoutRowDef.getWidgetReferences();
854        if (defWidgets != null) {
855            for (WidgetReference widget : defWidgets) {
856                widgets.add(exportToJson(widget));
857            }
858        }
859        if (!widgets.isEmpty()) {
860            json.element("widgets", widgets);
861        }
862        return json;
863    }
864
865    public static JSONObject exportToJson(LayoutRowDefinition layoutRowDef) {
866        return exportToJson(layoutRowDef, null);
867    }
868
869    public static LayoutRowDefinition importLayoutRowDefinition(JSONObject layoutRowDef) {
870        String name = layoutRowDef.optString("name", null);
871
872        boolean alwaysSelected = layoutRowDef.optBoolean("alwaysSelected", false);
873        boolean selectedByDefault = layoutRowDef.optBoolean("selectedByDefault", true);
874
875        Map<String, Map<String, Serializable>> properties = importPropsByMode(layoutRowDef.optJSONObject("properties"));
876
877        List<WidgetReference> widgets = new ArrayList<WidgetReference>();
878        JSONArray jwidgets = layoutRowDef.optJSONArray("widgets");
879        if (jwidgets != null) {
880            for (Object item : jwidgets) {
881                if (item instanceof String) {
882                    // BBB
883                    widgets.add(new WidgetReferenceImpl((String) item));
884                } else {
885                    widgets.add(importWidgetReference((JSONObject) item));
886                }
887            }
888        }
889        return new LayoutRowDefinitionImpl(name, properties, widgets, alwaysSelected, selectedByDefault);
890    }
891
892    /**
893     * @since 5.5
894     * @param widgetDef
895     * @param ctx
896     * @param widgetConverters
897     * @return
898     */
899    @SuppressWarnings("deprecation")
900    public static JSONObject exportToJson(WidgetDefinition widgetDef, LayoutConversionContext ctx,
901            List<WidgetDefinitionConverter> widgetConverters) {
902        JSONObject json = new JSONObject();
903        json.element("name", widgetDef.getName());
904        json.element("type", widgetDef.getType());
905        json.element("typeCategory", widgetDef.getTypeCategory());
906        JSONObject labels = exportStringPropsToJson(widgetDef.getLabels());
907        if (!labels.isEmpty()) {
908            json.element("labels", labels);
909        }
910        JSONObject helpLabels = exportStringPropsToJson(widgetDef.getHelpLabels());
911        if (!helpLabels.isEmpty()) {
912            json.element("helpLabels", helpLabels);
913        }
914        json.element("translated", widgetDef.isTranslated());
915        json.element("handlingLabels", widgetDef.isHandlingLabels());
916        JSONObject widgetModes = exportStringPropsToJson(widgetDef.getModes());
917        if (!widgetModes.isEmpty()) {
918            json.element("widgetModes", widgetModes);
919        }
920
921        JSONArray fields = new JSONArray();
922        FieldDefinition[] fieldDefs = widgetDef.getFieldDefinitions();
923        if (fieldDefs != null) {
924            for (FieldDefinition fieldDef : fieldDefs) {
925                fields.add(exportToJson(fieldDef));
926            }
927        }
928        if (!fields.isEmpty()) {
929            json.element("fields", fields);
930        }
931
932        JSONArray subWidgets = new JSONArray();
933        WidgetDefinition[] subWidgetDefs = widgetDef.getSubWidgetDefinitions();
934        if (subWidgetDefs != null) {
935            for (WidgetDefinition wDef : subWidgetDefs) {
936                subWidgets.add(exportToJson(wDef, ctx, widgetConverters));
937            }
938        }
939        if (!subWidgets.isEmpty()) {
940            json.element("subWidgets", subWidgets);
941        }
942
943        JSONArray subWidgetRefs = new JSONArray();
944        WidgetReference[] subWidgetRefDefs = widgetDef.getSubWidgetReferences();
945        if (subWidgetRefDefs != null) {
946            for (WidgetReference ref : subWidgetRefDefs) {
947                subWidgetRefs.add(exportToJson(ref));
948            }
949        }
950        if (!subWidgetRefs.isEmpty()) {
951            json.element("subWidgetRefs", subWidgetRefs);
952        }
953
954        JSONObject props = exportPropsByModeToJson(widgetDef.getProperties());
955        if (!props.isEmpty()) {
956            json.element("properties", props);
957        }
958        JSONObject widgetModeProps = exportPropsByModeToJson(widgetDef.getWidgetModeProperties());
959        if (!widgetModeProps.isEmpty()) {
960            json.element("propertiesByWidgetMode", widgetModeProps);
961        }
962
963        JSONObject controls = exportPropsByModeToJson(widgetDef.getControls());
964        if (!controls.isEmpty()) {
965            json.element("controls", controls);
966        }
967
968        JSONArray selectOptions = new JSONArray();
969        WidgetSelectOption[] selectOptionDefs = widgetDef.getSelectOptions();
970        if (selectOptionDefs != null) {
971            for (WidgetSelectOption selectOptionDef : selectOptionDefs) {
972                selectOptions.add(exportToJson(selectOptionDef));
973            }
974        }
975        if (!selectOptions.isEmpty()) {
976            json.element("selectOptions", selectOptions);
977        }
978
979        JSONObject renderingInfos = exportRenderingInfosByModeToJson(widgetDef.getRenderingInfos());
980        if (!renderingInfos.isEmpty()) {
981            json.element("renderingInfos", renderingInfos);
982        }
983
984        List<String> caliases = widgetDef.getAliases();
985        if (caliases != null && !caliases.isEmpty()) {
986            JSONArray aliases = new JSONArray();
987            for (String alias : caliases) {
988                aliases.add(alias);
989            }
990            json.element("aliases", aliases);
991        }
992
993        return json;
994    }
995
996    @SuppressWarnings({ "unchecked" })
997    public static WidgetDefinition importWidgetDefinition(JSONObject widgetDef) {
998        String name = widgetDef.getString("name");
999        String type = widgetDef.getString("type");
1000        String typeCategory = widgetDef.optString("typeCategory");
1001        Map<String, String> labels = importStringProps(widgetDef.optJSONObject("labels"));
1002        Map<String, String> helpLabels = importStringProps(widgetDef.optJSONObject("helpLabels"));
1003        boolean translated = widgetDef.optBoolean("translated", false);
1004        boolean handlingLabels = widgetDef.optBoolean("handlingLabels", false);
1005        Map<String, String> modes = importStringProps(widgetDef.optJSONObject("widgetModes"));
1006
1007        List<FieldDefinition> fieldDefinitions = new ArrayList<FieldDefinition>();
1008        JSONArray jfields = widgetDef.optJSONArray("fields");
1009        if (jfields != null) {
1010            for (Object item : jfields) {
1011                fieldDefinitions.add(importFieldDefinition((JSONObject) item));
1012            }
1013        }
1014
1015        List<WidgetDefinition> subWidgets = new ArrayList<WidgetDefinition>();
1016        JSONArray jsubwidgets = widgetDef.optJSONArray("subWidgets");
1017        if (jsubwidgets != null) {
1018            for (Object item : jsubwidgets) {
1019                subWidgets.add(importWidgetDefinition((JSONObject) item));
1020            }
1021        }
1022
1023        List<WidgetReference> subWidgetRefs = new ArrayList<WidgetReference>();
1024        JSONArray jsubwidgetRefs = widgetDef.optJSONArray("subWidgetRefs");
1025        if (jsubwidgetRefs != null) {
1026            for (Object item : jsubwidgetRefs) {
1027                subWidgetRefs.add(importWidgetReference((JSONObject) item));
1028            }
1029        }
1030
1031        Map<String, Map<String, Serializable>> properties = importPropsByMode(widgetDef.optJSONObject("properties"));
1032        Map<String, Map<String, Serializable>> widgetModeProperties = importPropsByMode(widgetDef.optJSONObject("propertiesByWidgetMode"));
1033        Map<String, Map<String, Serializable>> controls = importPropsByMode(widgetDef.optJSONObject("controls"));
1034
1035        // select options
1036        List<WidgetSelectOption> selectOptions = new ArrayList<WidgetSelectOption>();
1037        JSONArray jselectOptions = widgetDef.optJSONArray("selectOptions");
1038        if (jselectOptions != null) {
1039            for (Object item : jselectOptions) {
1040                selectOptions.add(importWidgetSelectionOption((JSONObject) item));
1041            }
1042        }
1043
1044        Map<String, List<RenderingInfo>> renderingInfos = importRenderingInfosByMode(widgetDef.optJSONObject("renderingInfos"));
1045
1046        List<String> aliases = new ArrayList<String>();
1047        JSONArray jaliases = widgetDef.optJSONArray("aliases");
1048        if (jaliases != null) {
1049            aliases.addAll(jaliases);
1050        }
1051
1052        WidgetDefinitionImpl res = new WidgetDefinitionImpl(name, type, labels, helpLabels, translated, modes,
1053                fieldDefinitions.toArray(new FieldDefinition[] {}), properties, widgetModeProperties,
1054                subWidgets.toArray(new WidgetDefinition[] {}), selectOptions.toArray(new WidgetSelectOption[] {}));
1055        res.setRenderingInfos(renderingInfos);
1056        res.setSubWidgetReferences(subWidgetRefs.toArray(new WidgetReference[] {}));
1057        res.setHandlingLabels(handlingLabels);
1058        res.setControls(controls);
1059        res.setTypeCategory(typeCategory);
1060        res.setAliases(aliases);
1061        return res;
1062    }
1063
1064    public static JSONObject exportToJson(FieldDefinition fieldDef) {
1065        JSONObject json = new JSONObject();
1066        json.element("fieldName", fieldDef.getFieldName());
1067        json.element("schemaName", fieldDef.getSchemaName());
1068        json.element("propertyName", fieldDef.getPropertyName());
1069        return json;
1070    }
1071
1072    public static FieldDefinition importFieldDefinition(JSONObject fieldDef) {
1073        // ignore property name: it can be deduced from schema and field name
1074        FieldDefinition res = new FieldDefinitionImpl(fieldDef.optString("schemaName", null),
1075                fieldDef.getString("fieldName"));
1076        return res;
1077    }
1078
1079    public static JSONObject exportToJson(WidgetReference widgetRef) {
1080        JSONObject json = new JSONObject();
1081        json.element("name", widgetRef.getName());
1082        json.element("category", widgetRef.getCategory());
1083        return json;
1084    }
1085
1086    public static WidgetReference importWidgetReference(JSONObject widgetRef) {
1087        WidgetReference res = new WidgetReferenceImpl(widgetRef.optString("category"),
1088                widgetRef.optString("name", null));
1089        return res;
1090    }
1091
1092    /**
1093     * @since 7.3
1094     */
1095    public static JSONObject exportToJson(Layout layout) {
1096        JSONObject json = new JSONObject();
1097        json.element("name", layout.getName());
1098
1099        String type = layout.getType();
1100        if (type != null) {
1101            json.element("type", type);
1102        }
1103
1104        String typeCat = layout.getTypeCategory();
1105        if (typeCat != null) {
1106            json.element("typeCategory", typeCat);
1107        }
1108
1109        json.element("mode", layout.getMode());
1110
1111        String template = layout.getTemplate();
1112        if (template != null) {
1113            json.element("template", template);
1114        }
1115
1116        JSONObject props = exportPropsToJson(layout.getProperties());
1117        if (!props.isEmpty()) {
1118            json.element("properties", props);
1119        }
1120
1121        JSONArray rows = new JSONArray();
1122        LayoutRow[] lRows = layout.getRows();
1123        if (lRows != null) {
1124            for (LayoutRow lRow : lRows) {
1125                rows.add(exportToJson(lRow));
1126            }
1127        }
1128
1129        if (!rows.isEmpty()) {
1130            json.element("rows", rows);
1131        }
1132
1133        return json;
1134    }
1135
1136    /**
1137     * @since 7.3
1138     */
1139    public static JSONObject exportToJson(LayoutRow layoutRow) {
1140        JSONObject json = new JSONObject();
1141        String name = layoutRow.getName();
1142        if (name != null) {
1143            json.element("name", name);
1144        }
1145        // fill selection info only if that's not the default value from the
1146        // definition
1147        if (layoutRow.isAlwaysSelected()) {
1148            json.element("alwaysSelected", true);
1149        }
1150        if (!layoutRow.isSelectedByDefault()) {
1151            json.element("selectedByDefault", false);
1152        }
1153        layoutRow.isSelectedByDefault();
1154        JSONObject props = exportPropsToJson(layoutRow.getProperties());
1155        if (!props.isEmpty()) {
1156            json.element("properties", props);
1157        }
1158        JSONArray widgets = new JSONArray();
1159        Widget[] rowWidgets = layoutRow.getWidgets();
1160        if (rowWidgets != null) {
1161            for (Widget widget : rowWidgets) {
1162                widgets.add(exportToJson(widget));
1163            }
1164        }
1165        if (!widgets.isEmpty()) {
1166            json.element("widgets", widgets);
1167        }
1168        return json;
1169    }
1170
1171    /**
1172     * @since 7.3
1173     */
1174    public static JSONObject exportToJson(Widget widget) {
1175        JSONObject json = new JSONObject();
1176        json.element("name", widget.getName());
1177        json.element("type", widget.getType());
1178        json.element("typeCategory", widget.getTypeCategory());
1179        json.element("mode", widget.getMode());
1180        json.element("label", widget.getLabel());
1181        json.element("helpLabel", widget.getHelpLabel());
1182        json.element("translated", widget.isTranslated());
1183        json.element("handlingLabels", widget.isHandlingLabels());
1184        JSONArray fields = new JSONArray();
1185        FieldDefinition[] fieldDefs = widget.getFieldDefinitions();
1186        if (fieldDefs != null) {
1187            for (FieldDefinition fieldDef : fieldDefs) {
1188                fields.add(exportToJson(fieldDef));
1189            }
1190        }
1191        if (!fields.isEmpty()) {
1192            json.element("fields", fields);
1193        }
1194
1195        JSONArray subWidgets = new JSONArray();
1196        Widget[] wSubWidgets = widget.getSubWidgets();
1197        if (wSubWidgets != null) {
1198            for (Widget wDef : wSubWidgets) {
1199                subWidgets.add(exportToJson(wDef));
1200            }
1201        }
1202        if (!subWidgets.isEmpty()) {
1203            json.element("subWidgets", subWidgets);
1204        }
1205
1206        JSONObject props = exportPropsToJson(widget.getProperties());
1207        if (!props.isEmpty()) {
1208            json.element("properties", props);
1209        }
1210
1211        JSONObject controls = exportPropsToJson(widget.getControls());
1212        if (!controls.isEmpty()) {
1213            json.element("controls", controls);
1214        }
1215
1216        JSONArray selectOptions = new JSONArray();
1217        WidgetSelectOption[] selectOptionDefs = widget.getSelectOptions();
1218        if (selectOptionDefs != null) {
1219            for (WidgetSelectOption selectOptionDef : selectOptionDefs) {
1220                selectOptions.add(exportToJson(selectOptionDef));
1221            }
1222        }
1223        if (!selectOptions.isEmpty()) {
1224            json.element("selectOptions", selectOptions);
1225        }
1226
1227        return json;
1228    }
1229
1230    public static JSONObject exportToJson(WidgetSelectOption selectOption) {
1231        JSONObject json = new JSONObject();
1232        Serializable value = selectOption.getValue();
1233        boolean isMulti = selectOption instanceof WidgetSelectOptions;
1234        if (isMulti) {
1235            json.element("multiple", true);
1236        } else {
1237            json.element("multiple", false);
1238        }
1239        if (value != null) {
1240            json.element("value", value);
1241        }
1242        String var = selectOption.getVar();
1243        if (var != null) {
1244            json.element("var", var);
1245        }
1246        String itemLabel = selectOption.getItemLabel();
1247        if (itemLabel != null) {
1248            json.element("itemLabel", itemLabel);
1249        }
1250        Map<String, String> labels = selectOption.getItemLabels();
1251        if (labels != null && !labels.isEmpty()) {
1252            JSONObject jsonLabels = new JSONObject();
1253            for (Map.Entry<String, String> entry : labels.entrySet()) {
1254                jsonLabels.put(entry.getKey(), entry.getValue());
1255            }
1256            json.element("itemLabels", jsonLabels);
1257        }
1258        String itemValue = selectOption.getItemValue();
1259        if (itemValue != null) {
1260            json.element("itemValue", itemValue);
1261        }
1262        Serializable itemDisabled = selectOption.getItemDisabled();
1263        if (itemDisabled != null) {
1264            json.element("itemDisabled", itemDisabled);
1265        }
1266        Serializable itemRendered = selectOption.getItemRendered();
1267        if (itemRendered != null) {
1268            json.element("itemRendered", itemRendered);
1269        }
1270        if (isMulti) {
1271            WidgetSelectOptions selectOptions = (WidgetSelectOptions) selectOption;
1272            String ordering = selectOptions.getOrdering();
1273            if (ordering != null) {
1274                json.element("ordering", ordering);
1275            }
1276            Boolean caseSensitive = selectOptions.getCaseSensitive();
1277            if (caseSensitive != null) {
1278                json.element("caseSensitive", caseSensitive);
1279            }
1280        }
1281        return json;
1282    }
1283
1284    @SuppressWarnings("unchecked")
1285    public static WidgetSelectOption importWidgetSelectionOption(JSONObject selectOption) {
1286        boolean isMulti = selectOption.getBoolean("multiple");
1287        Serializable value = selectOption.optString("value", null);
1288        String var = selectOption.optString("var", null);
1289        String itemLabel = selectOption.optString("itemLabel", null);
1290
1291        Map<String, String> labels = new HashMap<String, String>();
1292        JSONObject jsonLabels = selectOption.optJSONObject("itemLabels");
1293        if (jsonLabels != null && !jsonLabels.isNullObject()) {
1294            labels.putAll(jsonLabels);
1295        }
1296
1297        String itemValue = selectOption.optString("itemValue", null);
1298        Serializable itemDisabled = selectOption.optString("itemDisabled", null);
1299        Serializable itemRendered = selectOption.optString("itemRendered", null);
1300        if (isMulti) {
1301            String ordering = selectOption.optString("ordering", null);
1302            Boolean caseSensitive = null;
1303            if (selectOption.has("caseSensitive")) {
1304                caseSensitive = new Boolean(selectOption.getBoolean("caseSensitive"));
1305            }
1306            WidgetSelectOptionsImpl res = new WidgetSelectOptionsImpl(value, var, itemLabel, itemValue, itemDisabled,
1307                    itemRendered, ordering, caseSensitive);
1308            res.setItemLabels(labels);
1309            return res;
1310        } else {
1311            WidgetSelectOptionImpl res = new WidgetSelectOptionImpl(value, var, itemLabel, itemValue, itemDisabled,
1312                    itemRendered);
1313            res.setItemLabels(labels);
1314            return res;
1315        }
1316    }
1317
1318    public static JSONObject exportPropsByModeToJson(Map<String, Map<String, Serializable>> propsByMode) {
1319        JSONObject props = new JSONObject();
1320        if (propsByMode != null) {
1321            List<String> defModes = new ArrayList<String>(propsByMode.keySet());
1322            // sort so that order is deterministic
1323            Collections.sort(defModes);
1324            for (String defMode : defModes) {
1325                props.element(defMode, exportPropsToJson(propsByMode.get(defMode)));
1326            }
1327        }
1328        return props;
1329    }
1330
1331    @SuppressWarnings("unchecked")
1332    public static Map<String, Map<String, Serializable>> importPropsByMode(JSONObject propsByMode) {
1333        Map<String, Map<String, Serializable>> props = new HashMap<String, Map<String, Serializable>>();
1334        if (propsByMode != null && !propsByMode.isNullObject()) {
1335            List<String> defModes = new ArrayList<String>(propsByMode.keySet());
1336            // sort so that order is deterministic
1337            Collections.sort(defModes);
1338            for (String defMode : defModes) {
1339                props.put(defMode, importProps(propsByMode.getJSONObject(defMode)));
1340            }
1341        }
1342        return props;
1343    }
1344
1345    @SuppressWarnings({ "rawtypes" })
1346    public static JSONObject exportPropsToJson(Map<String, Serializable> defProps) {
1347        JSONObject props = new JSONObject();
1348        if (defProps != null) {
1349            List<String> defPropNames = new ArrayList<String>(defProps.keySet());
1350            // sort so that order is deterministic
1351            Collections.sort(defPropNames);
1352            for (String defPropName : defPropNames) {
1353                Serializable value = defProps.get(defPropName);
1354                if (value instanceof Collection) {
1355                    JSONArray array = new JSONArray();
1356                    array.addAll((Collection) value);
1357                    props.element(defPropName, array);
1358                } else if (value instanceof Object[]) {
1359                    JSONArray array = new JSONArray();
1360                    for (Object item : (Object[]) value) {
1361                        array.add(item);
1362                    }
1363                    props.element(defPropName, array);
1364                } else {
1365                    props.element(defPropName, value);
1366                }
1367            }
1368        }
1369        return props;
1370    }
1371
1372    @SuppressWarnings("unchecked")
1373    public static Map<String, Serializable> importProps(JSONObject defProps) {
1374        Map<String, Serializable> props = new HashMap<String, Serializable>();
1375        if (defProps != null && !defProps.isNullObject()) {
1376            List<String> defPropNames = new ArrayList<String>(defProps.keySet());
1377            // sort so that order is deterministic
1378            Collections.sort(defPropNames);
1379            for (String defPropName : defPropNames) {
1380                Object value = defProps.opt(defPropName);
1381                if (value instanceof JSONArray) {
1382                    ArrayList<Object> listValue = new ArrayList<Object>();
1383                    listValue.addAll(((JSONArray) value));
1384                } else {
1385                    props.put(defPropName, value.toString());
1386                }
1387            }
1388        }
1389        return props;
1390    }
1391
1392    public static JSONObject exportStringPropsToJson(Map<String, String> defProps) {
1393        JSONObject props = new JSONObject();
1394        if (defProps != null) {
1395            List<String> defPropNames = new ArrayList<String>(defProps.keySet());
1396            // sort so that order is deterministic
1397            Collections.sort(defPropNames);
1398            for (String defPropName : defPropNames) {
1399                props.element(defPropName, defProps.get(defPropName));
1400            }
1401        }
1402        return props;
1403    }
1404
1405    public static Map<String, String> importStringProps(JSONObject defProps) {
1406        Map<String, String> props = new HashMap<String, String>();
1407        if (defProps != null && !defProps.isNullObject()) {
1408            for (Object item : defProps.keySet()) {
1409                String key = (String) item;
1410                props.put(key, defProps.getString(key));
1411            }
1412        }
1413        return props;
1414    }
1415
1416    public static JSONObject exportRenderingInfosByModeToJson(Map<String, List<RenderingInfo>> infosByMode) {
1417        JSONObject props = new JSONObject();
1418        if (infosByMode != null) {
1419            List<String> defModes = new ArrayList<String>(infosByMode.keySet());
1420            // sort so that order is deterministic
1421            Collections.sort(defModes);
1422            for (String defMode : defModes) {
1423                props.element(defMode, exportRenderingInfosToJson(infosByMode.get(defMode)));
1424            }
1425        }
1426        return props;
1427    }
1428
1429    @SuppressWarnings("unchecked")
1430    public static Map<String, List<RenderingInfo>> importRenderingInfosByMode(JSONObject infosByMode) {
1431        Map<String, List<RenderingInfo>> props = new HashMap<String, List<RenderingInfo>>();
1432        if (infosByMode != null && !infosByMode.isNullObject()) {
1433            List<String> defModes = new ArrayList<String>(infosByMode.keySet());
1434            // sort so that order is deterministic
1435            Collections.sort(defModes);
1436            for (String defMode : defModes) {
1437                props.put(defMode, importRenderingInfos(infosByMode.getJSONArray(defMode)));
1438            }
1439        }
1440        return props;
1441    }
1442
1443    public static JSONArray exportRenderingInfosToJson(List<RenderingInfo> infos) {
1444        JSONArray jinfos = new JSONArray();
1445        if (infos != null) {
1446            for (RenderingInfo info : infos) {
1447                jinfos.add(exportToJson(info));
1448            }
1449        }
1450        return jinfos;
1451    }
1452
1453    public static List<RenderingInfo> importRenderingInfos(JSONArray jinfos) {
1454        List<RenderingInfo> infos = new ArrayList<RenderingInfo>();
1455        if (jinfos != null) {
1456            for (Object item : jinfos) {
1457                infos.add(importRenderingInfo((JSONObject) item));
1458            }
1459        }
1460        return infos;
1461    }
1462
1463    public static JSONObject exportToJson(RenderingInfo info) {
1464        JSONObject json = new JSONObject();
1465        json.element("level", info.getLevel());
1466        json.element("message", info.getMessage());
1467        json.element("translated", info.isTranslated());
1468        return json;
1469    }
1470
1471    public static RenderingInfo importRenderingInfo(JSONObject fieldDef) {
1472        RenderingInfo res = new RenderingInfoImpl(fieldDef.optString("level", ""), fieldDef.optString("message"),
1473                fieldDef.optBoolean("translated", false));
1474        return res;
1475    }
1476
1477}