001// Generated by delombok at Mon Oct 12 22:51:05 BST 2020
002/*
003 *  Licensed to the Apache Software Foundation (ASF) under one
004 *  or more contributor license agreements.  See the NOTICE file
005 *  distributed with this work for additional information
006 *  regarding copyright ownership.  The ASF licenses this file
007 *  to you under the Apache License, Version 2.0 (the
008 *  "License"); you may not use this file except in compliance
009 *  with the License.  You may obtain a copy of the License at
010 *
011 *        http://www.apache.org/licenses/LICENSE-2.0
012 *
013 *  Unless required by applicable law or agreed to in writing,
014 *  software distributed under the License is distributed on an
015 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 *  KIND, either express or implied.  See the License for the
017 *  specific language governing permissions and limitations
018 *  under the License.
019 */
020package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
021
022import java.util.List;
023import java.util.Map;
024import java.util.function.Function;
025import java.util.stream.Collectors;
026import javax.annotation.PostConstruct;
027import javax.inject.Inject;
028import javax.inject.Named;
029import javax.inject.Singleton;
030import com.fasterxml.jackson.databind.node.NullNode;
031import org.springframework.beans.factory.annotation.Qualifier;
032import org.springframework.context.annotation.Primary;
033import org.springframework.core.annotation.Order;
034import org.springframework.stereotype.Service;
035import org.apache.isis.applib.annotation.OrderPrecedence;
036import org.apache.isis.commons.internal.base._NullSafe;
037import org.apache.isis.commons.internal.collections._Maps;
038import org.apache.isis.commons.internal.exceptions._Exceptions;
039import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
040import org.apache.isis.core.metamodel.facets.object.parseable.TextEntryParseException;
041import org.apache.isis.core.metamodel.spec.ManagedObject;
042import org.apache.isis.core.metamodel.spec.ObjectSpecId;
043import org.apache.isis.core.metamodel.spec.ObjectSpecification;
044import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
045import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
046import static org.apache.isis.commons.internal.base._With.requires;
047
048/**
049 * Similar to Isis' value encoding, but with additional support for JSON
050
051 * primitives.
052 */
053@Service
054@Named("isisRoRendering.JsonValueEncoder")
055@Order(OrderPrecedence.EARLY)
056@Primary
057@Qualifier("Default")
058@Singleton
059public class JsonValueEncoder {
060    @Inject
061    private SpecificationLoader specificationLoader;
062
063    @PostConstruct
064    public void init() {
065        //XXX no lombok val here
066        Function<Object, ManagedObject> pojoToAdapter = pojo -> ManagedObject.of(specificationLoader::loadSpecification, pojo);
067        new JsonValueEncoder_Converters().asList(pojoToAdapter).forEach(this::registerConverter);
068    }
069
070    private Map<ObjectSpecId, JsonValueConverter> converterBySpecId = _Maps.newLinkedHashMap();
071
072    private void registerConverter(JsonValueConverter jvc) {
073        for (final org.apache.isis.core.metamodel.spec.ObjectSpecId specId : jvc.getSpecIds()) {
074            converterBySpecId.put(specId, jvc);
075        }
076    }
077
078    public ManagedObject asAdapter(final ObjectSpecification objectSpec, final JsonRepresentation argValueRepr, final String format) {
079        if (argValueRepr == null) {
080            return null;
081        }
082        if (objectSpec == null) {
083            throw new IllegalArgumentException("ObjectSpecification is required");
084        }
085        if (!argValueRepr.isValue()) {
086            throw new IllegalArgumentException("Representation must be of a value");
087        }
088        final EncodableFacet encodableFacet = objectSpec.getFacet(EncodableFacet.class);
089        if (encodableFacet == null) {
090            String reason = "ObjectSpec expected to have an EncodableFacet";
091            throw new IllegalArgumentException(reason);
092        }
093        final ObjectSpecId specId = objectSpec.getSpecId();
094        final JsonValueConverter jvc = converterBySpecId.get(specId);
095        if (jvc == null) {
096            // best effort
097            if (argValueRepr.isString()) {
098                final String argStr = argValueRepr.asString();
099                return encodableFacet.fromEncodedString(argStr);
100            }
101            throw new IllegalArgumentException("Unable to parse value");
102        }
103        final ManagedObject asAdapter = jvc.asAdapter(argValueRepr, format);
104        if (asAdapter != null) {
105            return asAdapter;
106        }
107        // last attempt
108        if (argValueRepr.isString()) {
109            final String argStr = argValueRepr.asString();
110            try {
111                return encodableFacet.fromEncodedString(argStr);
112            } catch (TextEntryParseException ex) {
113                throw new IllegalArgumentException(ex.getMessage());
114            }
115        }
116        throw new IllegalArgumentException("Could not parse value \'" + argValueRepr.asString() + "\' as a " + objectSpec.getFullIdentifier());
117    }
118
119    public Object appendValueAndFormat(ManagedObject objectAdapter, ObjectSpecification objectSpecification, JsonRepresentation repr, String format, boolean suppressExtensions) {
120        final org.apache.isis.core.metamodel.spec.ObjectSpecId specId = objectSpecification.getSpecId();
121        final org.apache.isis.viewer.restfulobjects.rendering.domainobjects.JsonValueEncoder.JsonValueConverter jsonValueConverter = converterBySpecId.get(specId);
122        if (jsonValueConverter != null) {
123            return jsonValueConverter.appendValueAndFormat(objectAdapter, format, repr, suppressExtensions);
124        } else {
125            final org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet encodableFacet = objectSpecification.getFacet(EncodableFacet.class);
126            if (encodableFacet == null) {
127                throw _Exceptions.illegalArgument("objectSpec \'%s\' expected to have EncodableFacet or a registered JsonValueConverter", specId);
128            }
129            Object value = objectAdapter != null ? encodableFacet.toEncodedString(objectAdapter) : NullNode.getInstance();
130            repr.mapPut("value", value);
131            appendFormats(repr, "string", "string", suppressExtensions);
132            return value;
133        }
134    }
135
136    public Object asObject(final ManagedObject adapter, final String format) {
137        requires(adapter, "adapter");
138        final org.apache.isis.core.metamodel.spec.ObjectSpecification objectSpec = adapter.getSpecification();
139        final org.apache.isis.viewer.restfulobjects.rendering.domainobjects.JsonValueEncoder.JsonValueConverter jsonValueConverter = converterBySpecId.get(objectSpec.getSpecId());
140        if (jsonValueConverter != null) {
141            return jsonValueConverter.asObject(adapter, format);
142        }
143        // else
144        final org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet encodableFacet = objectSpec.getFacet(EncodableFacet.class);
145        if (encodableFacet == null) {
146            throw new IllegalArgumentException("objectSpec expected to have EncodableFacet");
147        }
148        return encodableFacet.toEncodedString(adapter);
149    }
150
151    static void appendFormats(JsonRepresentation repr, String format, String xIsisFormat, boolean suppressExtensions) {
152        if (format != null) {
153            repr.mapPut("format", format);
154        }
155        if (!suppressExtensions && xIsisFormat != null) {
156            repr.mapPut("extensions.x-isis-format", xIsisFormat);
157        }
158    }
159
160    static Object unwrapAsObjectElseNullNode(ManagedObject adapter) {
161        return adapter != null ? adapter.getPojo() : NullNode.getInstance();
162    }
163
164//    ManagedObject adapterFor(Object pojo) {
165//        return objectAdapterProvider.adapterFor(pojo);
166//    }
167    // -- NESTED TYPE DECLARATIONS
168    public static class ExpectedStringRepresentingValueException extends IllegalArgumentException {
169        private static final long serialVersionUID = 1L;
170    }
171
172
173    public static abstract class JsonValueConverter {
174        protected final String format;
175        protected final String xIsisFormat;
176        private final Class<?>[] classes;
177
178        public JsonValueConverter(String format, String xIsisFormat, Class<?>... classes) {
179            this.format = format;
180            this.xIsisFormat = xIsisFormat;
181            this.classes = classes;
182        }
183
184        public List<ObjectSpecId> getSpecIds() {
185            return _NullSafe.stream(classes).map((Class<?> cls) -> ObjectSpecId.of(cls.getName())).collect(Collectors.toList());
186        }
187
188        /**
189         * The value, otherwise <tt>null</tt>.
190         */
191        public abstract ManagedObject asAdapter(JsonRepresentation repr, String format);
192
193        public Object appendValueAndFormat(ManagedObject objectAdapter, String format, JsonRepresentation repr, boolean suppressExtensions) {
194            final Object value = unwrapAsObjectElseNullNode(objectAdapter);
195            repr.mapPut("value", value);
196            appendFormats(repr, this.format, this.xIsisFormat, suppressExtensions);
197            return value;
198        }
199
200        public Object asObject(ManagedObject objectAdapter, String format) {
201            return objectAdapter.getPojo();
202        }
203    }
204
205    /**
206     * JUnit support
207     */
208    public static JsonValueEncoder forTesting(SpecificationLoader specificationLoader) {
209        final org.apache.isis.viewer.restfulobjects.rendering.domainobjects.JsonValueEncoder jsonValueEncoder = new JsonValueEncoder();
210        jsonValueEncoder.specificationLoader = specificationLoader;
211        jsonValueEncoder.init();
212        return jsonValueEncoder;
213    }
214}