001/*
002 * (C) Copyright 2006-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 *     bstefanescu
018 */
019package org.nuxeo.ecm.automation.jaxrs.io;
020
021import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
022
023import java.io.ByteArrayOutputStream;
024import java.io.IOException;
025import java.io.OutputStream;
026import java.util.Arrays;
027import java.util.List;
028
029import org.nuxeo.ecm.automation.AutomationService;
030import org.nuxeo.ecm.automation.OperationDocumentation;
031import org.nuxeo.ecm.automation.OperationDocumentation.Param;
032import org.nuxeo.ecm.automation.OperationException;
033import org.nuxeo.ecm.automation.core.Constants;
034import org.nuxeo.ecm.automation.io.services.codec.ObjectCodec;
035import org.nuxeo.ecm.automation.io.services.codec.ObjectCodecService;
036import org.nuxeo.ecm.automation.jaxrs.LoginInfo;
037import org.nuxeo.ecm.automation.jaxrs.io.operations.AutomationInfo;
038import org.nuxeo.ecm.core.io.marshallers.json.OutputStreamWithJsonWriter;
039import org.nuxeo.ecm.core.io.registry.MarshallerRegistry;
040import org.nuxeo.ecm.core.io.registry.Writer;
041import org.nuxeo.ecm.core.io.registry.context.RenderingContext;
042import org.nuxeo.ecm.core.io.registry.context.RenderingContext.CtxBuilder;
043import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition;
044import org.nuxeo.ecm.webengine.JsonFactoryManager;
045import org.nuxeo.runtime.api.Framework;
046
047import com.fasterxml.jackson.core.JsonEncoding;
048import com.fasterxml.jackson.core.JsonFactory;
049import com.fasterxml.jackson.core.JsonGenerator;
050
051/**
052 * Json writer for operations export.
053 *
054 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
055 * @author <a href="mailto:grenard@nuxeo.com">Guillaume Renard</a>
056 */
057public class JsonWriter {
058
059    private static JsonFactory getFactory() {
060        return Framework.getService(JsonFactoryManager.class).getJsonFactory();
061    }
062
063    private static JsonGenerator createGenerator(OutputStream out) throws IOException {
064        return getFactory().createGenerator(out, JsonEncoding.UTF8);
065    }
066
067    public static void writeAutomationInfo(OutputStream out, AutomationInfo info, boolean prettyPrint)
068            throws IOException {
069        writeAutomationInfo(createGenerator(out), info, prettyPrint);
070    }
071
072    public static void writeAutomationInfo(JsonGenerator jg, AutomationInfo info, boolean prettyPrint)
073            throws IOException {
074        if (prettyPrint) {
075            jg.useDefaultPrettyPrinter();
076        }
077        jg.writeStartObject();
078        writePaths(jg);
079        writeCodecs(jg);
080        writeOperations(jg, info);
081        writeChains(jg, info);
082        jg.writeEndObject();
083        jg.flush();
084    }
085
086    private static void writePaths(JsonGenerator jg) throws IOException {
087        jg.writeObjectFieldStart("paths");
088        jg.writeStringField("login", "login");
089        jg.writeEndObject();
090    }
091
092    private static void writeCodecs(JsonGenerator jg) throws IOException {
093        jg.writeArrayFieldStart("codecs");
094        ObjectCodecService codecs = Framework.getService(ObjectCodecService.class);
095        for (ObjectCodec<?> codec : codecs.getCodecs()) {
096            if (!codec.isBuiltin()) {
097                jg.writeString(codec.getClass().getName());
098            }
099        }
100        jg.writeEndArray();
101    }
102
103    /**
104     * Used to export operations to studio.
105     */
106    public static String exportOperations() throws IOException, OperationException {
107        return exportOperations(false);
108    }
109
110    /**
111     * Used to export operations to studio.
112     *
113     * @param filterNotInStudio if true, operation types not exposed in Studio will be filtered.
114     * @since 5.9.1
115     */
116    public static String exportOperations(boolean filterNotInStudio) throws IOException, OperationException {
117        List<OperationDocumentation> ops = Framework.getService(AutomationService.class).getDocumentation();
118        ByteArrayOutputStream out = new ByteArrayOutputStream();
119        try (JsonGenerator jg = getFactory().createGenerator(out)) {
120            jg.useDefaultPrettyPrinter();
121            jg.writeStartObject();
122            jg.writeArrayFieldStart("operations");
123            for (OperationDocumentation op : ops) {
124                if (filterNotInStudio) {
125                    if (op.addToStudio && !Constants.CAT_CHAIN.equals(op.category)) {
126                        writeOperation(jg, op);
127                    }
128                } else {
129                    writeOperation(jg, op);
130                }
131            }
132            jg.writeEndArray();
133            jg.writeEndObject();
134        }
135        return out.toString("UTF-8");
136    }
137
138    private static void writeOperations(JsonGenerator jg, AutomationInfo info) throws IOException {
139        jg.writeArrayFieldStart("operations");
140        for (OperationDocumentation op : info.getOperations()) {
141            writeOperation(jg, op);
142        }
143        jg.writeEndArray();
144    }
145
146    private static void writeChains(JsonGenerator jg, AutomationInfo info) throws IOException {
147        jg.writeArrayFieldStart("chains");
148        for (OperationDocumentation op : info.getChains()) {
149            writeOperation(jg, op, Constants.CHAIN_ID_PREFIX + op.id);
150        }
151        jg.writeEndArray();
152    }
153
154    public static void writeOperation(OutputStream out, OperationDocumentation op) throws IOException {
155        writeOperation(out, op, false);
156    }
157
158    /**
159     * @since 5.9.4
160     */
161    public static void writeOperation(OutputStream out, OperationDocumentation op, boolean prettyPrint)
162            throws IOException {
163        writeOperation(createGenerator(out), op, prettyPrint);
164    }
165
166    public static void writeOperation(JsonGenerator jg, OperationDocumentation op) throws IOException {
167        writeOperation(jg, op, false);
168    }
169
170    /**
171     * @since 5.9.4
172     */
173    public static void writeOperation(JsonGenerator jg, OperationDocumentation op, boolean prettyPrint)
174            throws IOException {
175        writeOperation(jg, op, op.url, prettyPrint);
176    }
177
178    public static void writeOperation(JsonGenerator jg, OperationDocumentation op, String url) throws IOException {
179        writeOperation(jg, op, url, false);
180    }
181
182    /**
183     * @since 5.9.4
184     */
185    public static void writeOperation(JsonGenerator jg, OperationDocumentation op, String url, boolean prettyPrint)
186            throws IOException {
187        if (prettyPrint) {
188            jg.useDefaultPrettyPrinter();
189        }
190        jg.writeStartObject();
191        jg.writeStringField("id", op.id);
192        if (op.getAliases() != null && op.getAliases().length > 0) {
193            jg.writeArrayFieldStart("aliases");
194            for (String alias : op.getAliases()) {
195                jg.writeString(alias);
196            }
197            jg.writeEndArray();
198        }
199        jg.writeStringField("label", op.label);
200        jg.writeStringField("category", op.category);
201        jg.writeStringField("requires", op.requires);
202        jg.writeStringField("description", op.description);
203        if (op.since != null && op.since.length() > 0) {
204            jg.writeStringField("since", op.since);
205        }
206        jg.writeStringField("url", url);
207        jg.writeArrayFieldStart("signature");
208        for (String s : op.signature) {
209            jg.writeString(s);
210        }
211        jg.writeEndArray();
212        writeParams(jg, Arrays.asList(op.params));
213        if (op.widgetDefinitions != null && op.widgetDefinitions.length > 0) {
214            MarshallerRegistry marshallerRegistry = Framework.getService(MarshallerRegistry.class);
215            RenderingContext renderingCtx = CtxBuilder.get();
216            Class<WidgetDefinition> type = WidgetDefinition.class;
217            Writer<WidgetDefinition> widgetDefWriter = marshallerRegistry.getWriter(renderingCtx, type,
218                    APPLICATION_JSON_TYPE);
219            // instantiate a OutputStreamWithJsonWriter in order to make writer use the same JsonGenerator
220            OutputStreamWithJsonWriter out = new OutputStreamWithJsonWriter(jg);
221
222            jg.writeArrayFieldStart("widgets");
223            for (WidgetDefinition wdef : op.widgetDefinitions) {
224                widgetDefWriter.write(wdef, type, type, APPLICATION_JSON_TYPE, out);
225            }
226            jg.writeEndArray();
227        }
228        jg.writeEndObject();
229        jg.flush();
230    }
231
232    private static void writeParams(JsonGenerator jg, List<Param> params) throws IOException {
233        jg.writeArrayFieldStart("params");
234        for (Param p : params) {
235            jg.writeStartObject();
236            jg.writeStringField("name", p.name);
237            jg.writeStringField("description", p.description);
238            jg.writeStringField("type", p.type);
239            jg.writeBooleanField("required", p.required);
240
241            jg.writeStringField("widget", p.widget);
242            jg.writeNumberField("order", p.order);
243            jg.writeArrayFieldStart("values");
244            for (String value : p.values) {
245                jg.writeString(value);
246            }
247            jg.writeEndArray();
248            jg.writeEndObject();
249        }
250        jg.writeEndArray();
251    }
252
253    public static void writeLogin(OutputStream out, LoginInfo login) throws IOException {
254        writeLogin(createGenerator(out), login);
255    }
256
257    public static void writeLogin(JsonGenerator jg, LoginInfo login) throws IOException {
258        jg.writeStartObject();
259        jg.writeStringField("entity-type", "login");
260        jg.writeStringField("username", login.getUsername());
261        jg.writeBooleanField("isAdministrator", login.isAdministrator());
262        jg.writeArrayFieldStart("groups");
263        for (String group : login.getGroups()) {
264            jg.writeString(group);
265        }
266        jg.writeEndArray();
267        jg.writeEndObject();
268        jg.flush();
269    }
270
271    public static void writePrimitive(OutputStream out, Object value) throws IOException {
272        writePrimitive(createGenerator(out), value);
273    }
274
275    public static void writePrimitive(JsonGenerator jg, Object value) throws IOException {
276        jg.writeStartObject();
277        jg.writeStringField("entity-type", "primitive");
278        if (value != null) {
279            Class<?> type = value.getClass();
280            if (type == String.class) {
281                jg.writeStringField("value", (String) value);
282            } else if (type == Boolean.class) {
283                jg.writeBooleanField("value", (Boolean) value);
284            } else if (type == Long.class) {
285                jg.writeNumberField("value", ((Number) value).longValue());
286            } else if (type == Double.class) {
287                jg.writeNumberField("value", ((Number) value).doubleValue());
288            } else if (type == Integer.class) {
289                jg.writeNumberField("value", ((Number) value).intValue());
290            } else if (type == Float.class) {
291                jg.writeNumberField("value", ((Number) value).floatValue());
292            }
293        } else {
294            jg.writeNullField("value");
295        }
296        jg.writeEndObject();
297        jg.flush();
298    }
299
300}