org.javersion.json.web.JsonStoreController.java Source code

Java tutorial

Introduction

Here is the source code for org.javersion.json.web.JsonStoreController.java

Source

/*
 * Copyright 2014 Samppa Saarela
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.javersion.json.web;

import static org.javersion.core.Version.DEFAULT_BRANCH;
import static org.javersion.path.PropertyPath.ROOT;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import javax.inject.Inject;

import org.javersion.core.Merge;
import org.javersion.core.Persistent;
import org.javersion.core.Revision;
import org.javersion.core.Version;
import org.javersion.json.JsonSerializer;
import org.javersion.path.PropertyPath;
import org.javersion.store.jdbc.ObjectVersionStoreJdbc;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.google.common.collect.ImmutableSet;

/**
 * GET: /objects/{objectId} - get default branch
 * PUT: /objects/{objectId} - update default branch
 *
 * GET: /objects/{objectId}/{branch|revision}
 * PUT: /objects/{objectId}/{branch|revision}
 *
 * GET: /versions/{objectId}
 * PUT: /versions/{objectId} - rewrite
 */
@RestController
public class JsonStoreController {

    private final JsonStoreConfig config;

    @Inject
    ObjectVersionStoreJdbc<Void> objectVersionStore;

    private final TypeMappings metaTypeMappings = TypeMappings.builder().withMapping(new VersionPropertyMapping())
            .build();

    private final ObjectSerializer<VersionMetadata> metaSerializer = new ObjectSerializer<>(VersionMetadata.class,
            metaTypeMappings);

    private final JsonSerializer jsonSerializer = new JsonSerializer(new JsonSerializer.Config(false, false, ""),
            metaSerializer.schemaRoot);

    public JsonStoreController() {
        this(new JsonStoreConfig());
    }

    public JsonStoreController(JsonStoreConfig config) {
        this.config = config;
    }

    @RequestMapping(value = "/objects", method = POST)
    public ResponseEntity<String> postObject(@RequestBody String json) {
        String objectId = UUID.randomUUID().toString();
        return putObject(objectId, json, DEFAULT_BRANCH, true);
    }

    @RequestMapping(value = "/objects/{objectId}", method = PUT)
    public ResponseEntity<String> putObject(@PathVariable("objectId") String objectId, @RequestBody String json) {
        return putObject(objectId, json, DEFAULT_BRANCH, false);
    }

    @RequestMapping(value = "/objects/{objectId}/branches/{branch}", method = PUT)
    public ResponseEntity<String> putObjectOnBranch(@PathVariable("objectId") String objectId,
            @PathVariable("branch") String branch, @RequestBody String json) {
        return putObject(objectId, json, branch, false);
    }

    @RequestMapping(value = "/objects/{objectId}", method = GET)
    public ResponseEntity<String> getObject(@PathVariable("objectId") String objectId,
            @RequestParam(value = "merge", required = false) Set<String> merge) {
        return getBranch(objectId, DEFAULT_BRANCH, merge);
    }

    @RequestMapping(value = "/objects/{objectId}/branches/{branch}", method = GET)
    public ResponseEntity<String> getBranch(@PathVariable("objectId") String objectId,
            @PathVariable("branch") String branch,
            @RequestParam(value = "merge", required = false) Set<String> merge) {
        if (merge == null) {
            return getObject(objectId, null, branchesBuilder().add(branch).build(), false);
        }
        return getObject(objectId, null, branchesBuilder().add(branch).addAll(merge).build(), false);
    }

    @RequestMapping(value = "/objects/{objectId}/versions/{revision}", method = GET)
    public ResponseEntity<String> getVersion(@PathVariable("objectId") String objectId,
            @PathVariable("revision") Revision revision,
            @RequestParam(value = "merge", required = false) Set<String> merge) {
        if (merge == null) {
            return getObject(objectId, revision, branchesBuilder().build(), false);
        }
        return getObject(objectId, revision, branchesBuilder().addAll(merge).build(), false);
    }

    @RequestMapping(value = "/objects/{objectId}/versions", method = GET)
    public List<Version<PropertyPath, Object, Void>> getVersions(@PathVariable("objectId") String objectId) {
        ObjectVersionGraph<Void> versionGraph = objectVersionStore.load(objectId);
        if (versionGraph.isEmpty()) {
            throw new NotFoundException();
        }

        return versionGraph.getVersions();
    }

    private ResponseEntity<String> getObject(String objectId, Revision revision, Set<String> mergeBranches,
            boolean create) {
        ObjectVersionGraph<Void> versionGraph = objectVersionStore.load(objectId);
        if (versionGraph.isEmpty()) {
            throw new NotFoundException();
        }
        return getResponse(objectId, versionGraph, revision, mergeBranches, create);
    }

    private ResponseEntity<String> putObject(String objectId, String json, String branch, boolean create) {
        JsonSerializer.JsonPaths paths = jsonSerializer.parse(json);
        if (!(paths.properties.get(ROOT) instanceof Persistent.Object)) {
            throw new IllegalArgumentException(
                    "Expected root object to be an Object, got " + paths.properties.get(ROOT));
        }
        VersionMetadata ref = metaSerializer.fromPropertyMap(paths.meta);
        ObjectVersionGraph<Void> versionGraph = objectVersionStore.load(objectId);
        ObjectVersionBuilder<Void> versionBuilder = new ObjectVersionBuilder<>();
        if (ref != null && ref._revs != null) {
            versionBuilder.parents(ref._revs);
        } else if (!versionGraph.isEmpty()) {
            versionBuilder.parents(getParentsForUnreferencedUpdate(versionGraph, branch));
        }
        versionBuilder.branch(branch);
        versionBuilder.changeset(paths.properties, versionGraph);
        ObjectVersion<Void> version = versionBuilder.build();
        objectVersionStore.append(objectId, versionGraph.getVersionNode(version.revision));
        objectVersionStore.commit();
        return getObject(objectId, null, ImmutableSet.of(branch), create);
    }

    private Iterable<Revision> getParentsForUnreferencedUpdate(ObjectVersionGraph<Void> versionGraph,
            String branch) {
        for (JsonStoreConfig.UnreferencedUpdateStrategy strategy : config.unreferencedUpdateStrategy) {
            Iterable<Revision> parents = strategy.getParents(versionGraph, branch);
            if (parents != null) {
                return parents;
            }
        }
        throw new IllegalArgumentException("_revs missing, unreferenced update not allowed");
    }

    private ResponseEntity<String> getResponse(String objectId, ObjectVersionGraph<Void> versionGraph,
            Revision revision, Set<String> mergeBranches, boolean create) {
        if (revision != null) {
            versionGraph = versionGraph.at(revision);
        }
        if (mergeBranches.isEmpty()) {
            // Specific version requested
            return getResponse(objectId, versionGraph.getVersionNode(revision), create);
        } else {
            // If revision is given, merge branches of that time
            return getResponse(objectId, versionGraph.mergeBranches(mergeBranches), create);
        }
    }

    private ResponseEntity<String> getResponse(String objectId, Merge<PropertyPath, Object, Void> merge,
            boolean create) {
        Map<PropertyPath, Object> properties = new HashMap<>(merge.getProperties());
        VersionMetadata ref = new VersionMetadata(objectId, merge.getMergeHeads(), merge.conflicts);
        properties.putAll(metaSerializer.toPropertyMap(ref));
        HttpHeaders headers = new HttpHeaders();
        headers.set("Content-Type", "application/json;charset=UTF-8");
        HttpStatus status;
        if (create) {
            status = CREATED;
            try {
                headers.setLocation(new URI("/objects/" + objectId));
            } catch (URISyntaxException e) {
                throw new Error(e);
            }
        } else {
            status = OK;
        }
        return new ResponseEntity<String>(jsonSerializer.serialize(properties), headers, status);
    }

    private static ImmutableSet.Builder<String> branchesBuilder() {
        return ImmutableSet.<String>builder();
    }

}