001/*
002 * (C) Copyright 2015-2020 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 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
018 */
019
020package org.nuxeo.ecm.core.io.marshallers.json.enrichers;
021
022import static com.fasterxml.jackson.core.JsonToken.END_OBJECT;
023import static com.fasterxml.jackson.core.JsonToken.FIELD_NAME;
024
025import java.io.IOException;
026import java.lang.reflect.Type;
027
028import javax.ws.rs.core.MediaType;
029
030import org.apache.logging.log4j.LogManager;
031import org.apache.logging.log4j.Logger;
032import org.nuxeo.ecm.core.io.marshallers.json.AbstractJsonWriter;
033import org.nuxeo.ecm.core.io.marshallers.json.ExtensibleEntityJsonWriter;
034
035import com.fasterxml.jackson.core.JsonGenerator;
036import com.fasterxml.jackson.core.JsonParser;
037import com.fasterxml.jackson.databind.ObjectMapper;
038import com.fasterxml.jackson.databind.util.TokenBuffer;
039
040/**
041 * Base class to write {@link ExtensibleEntityJsonWriter}'s enricher.
042 *
043 * @param <EntityType> The Java type whose the generated JSON will be enriched.
044 * @since 7.2
045 */
046public abstract class AbstractJsonEnricher<EntityType> extends AbstractJsonWriter<Enriched<EntityType>> {
047
048    private static final Logger log = LogManager.getLogger(AbstractJsonEnricher.class);
049
050    public static final String ENTITY_ENRICHER_NAME = "_EntityEnricherName";
051
052    private final String name;
053
054    protected static final ObjectMapper MAPPER = new ObjectMapper();
055
056    public AbstractJsonEnricher(String name) {
057        this.name = name;
058    }
059
060    @Override
061    public boolean accept(Class<?> clazz, Type genericType, MediaType mediatype) {
062        return name.equals(ctx.<String> getParameter(ENTITY_ENRICHER_NAME));
063    }
064
065    @Override
066    public void write(Enriched<EntityType> enrichable, JsonGenerator jg) {
067        try (TokenBuffer tb = new TokenBuffer(MAPPER, false)) {
068            // Write to a temporary output in case of exception during write()
069            tb.writeStartObject();
070            write(tb, enrichable.getEntity());
071            tb.writeEndObject();
072            tb.flush();
073            // Add the complete, well-formed content to the real output
074            try (JsonParser parser = tb.asParser()) {
075                parser.nextToken(); // ignoring START_OBJECT
076                while (parser.nextToken() == FIELD_NAME) {
077                    jg.copyCurrentStructure(parser);
078                }
079                if (parser.currentToken() != END_OBJECT) {
080                    log.error("Enricher: {} failed on current token: {}, output to write: {}", name::toString,
081                            parser::currentToken, () -> safeReadBuffer(tb));
082                }
083            }
084        } catch (Exception e) {
085            if (e instanceof InterruptedException) { // NOSONAR
086                Thread.currentThread().interrupt();
087                throw new RuntimeException("interrupted", e); // NOSONAR
088            } else {
089                // TODO collect exception and return it to the caller
090                log.info("Enricher: {} failed", name, e);
091            }
092        }
093    }
094
095    protected String safeReadBuffer(TokenBuffer tb) {
096        try {
097            return MAPPER.readTree(tb.asParser());
098        } catch (IOException e) {
099            return "malformed content could not be retrieved";
100        }
101    }
102
103    /**
104     * When implementing this method, the provided {@link JsonGenerator} expect you write a field name and a field value
105     * (or many).
106     *
107     * @param jg The {@link JsonGenerator} to use.
108     * @param enriched The enriched entity.
109     * @since 7.2
110     */
111    public abstract void write(JsonGenerator jg, EntityType enriched) throws IOException;
112
113}