/* * Copyright 2014 Tariq Bugrara * * 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 io.progix.dropwizard.patch; import com.fasterxml.jackson.databind.ObjectMapper; import io.dropwizard.jackson.Jackson; import io.dropwizard.jersey.PATCH; import io.progix.dropwizard.patch.exception.InvalidPatchPathException; import io.progix.dropwizard.patch.operations.AddOperation; import io.progix.dropwizard.patch.operations.CopyOperation; import io.progix.dropwizard.patch.operations.MoveOperation; import io.progix.dropwizard.patch.operations.RemoveOperation; import io.progix.dropwizard.patch.operations.ReplaceOperation; import io.progix.dropwizard.patch.operations.TestOperation; import io.progix.dropwizard.patch.operations.contextual.ContextualAddOperation; import io.progix.dropwizard.patch.operations.contextual.ContextualCopyOperation; import io.progix.dropwizard.patch.operations.contextual.ContextualMoveOperation; import io.progix.dropwizard.patch.operations.contextual.ContextualRemoveOperation; import io.progix.dropwizard.patch.operations.contextual.ContextualReplaceOperation; import io.progix.dropwizard.patch.operations.contextual.ContextualTestOperation; import org.apache.log4j.Logger; import javax.ws.rs.Consumes; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import java.util.Objects; @Path("/users") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class UserResource { private static final Logger logger = Logger.getLogger(UserResource.class); private ObjectMapper mapper = Jackson.newObjectMapper(); private UserStore dao; public UserResource(UserStore store) { this.dao = store; } @PATCH @Path("/default/{id}") public void updateUserDefault(@PathParam("id") int id, DefaultJsonPatch<User> request) { User user = dao.getUsers().get(id); User patchedUser = request.apply(user); dao.getUsers().set(id, patchedUser); } @PATCH @Path("/default-with-custom/{id}") public void updateUserDefaultWithCustom(@PathParam("id") int id, DefaultJsonPatch<User> request) { User user = dao.getUsers().get(id); request.setAdd(new ContextualAddOperation<User>() { @Override public User add(User user, JsonPath path, JsonPatchValue value) { //Makes sure new instances are included in the patch [#11] user = new User(user); if (path.property(0).exists()) { if (path.property(0).is("pets")) { if (path.element(1).exists() && path.endsAt(1)) { if (!path.property(2).exists()) { if(path.element(1).isEndOfArray()) { user.getPets().addAll(value.many(Pet.class)); } else { int petIndex = path.element(1).val(); user.getPets().addAll(petIndex, value.many(Pet.class)); } } else { throw new InvalidPatchPathException(path); } } else if (path.endsAt(0)) { user.getPets().addAll(value.many(Pet.class)); } else { throw new InvalidPatchPathException(path); } } else if (path.property(0).is("name")) { user.setName(value.one(String.class)); } else if (path.property(0).is("emailAddresses")) { if (path.element(1).exists() && path.endsAt(1)) { user.getEmailAddresses().addAll(path.element(1).val(), value.many(String.class)); } else if (path.endsAt(0)) { user.getEmailAddresses().addAll(value.many(String.class)); } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } return user; } }); User patchedUser = request.apply(user); dao.getUsers().set(id, patchedUser); } @PATCH @Path("/contextual/no-operations/{id}") public void noOpContextual(@PathParam("id") int id, ContextualJsonPatch<User> request) { dao.getUsers().set(0, request.apply(dao.getUsers().get(0))); } @PATCH @Path("/contextual/{id}") public void updateUserContextual(@PathParam("id") int id, ContextualJsonPatch<User> request) { request.setAdd(new ContextualAddOperation<User>() { @Override public User add(User user, JsonPath path, JsonPatchValue value) { //Makes sure new instances are included in the patch [#11] user = new User(user); if (path.property(0).exists()) { if (path.property(0).is("pets")) { if (path.element(1).exists() && path.endsAt(1)) { if (!path.property(2).exists()) { if(path.element(1).isEndOfArray()) { user.getPets().addAll(value.many(Pet.class)); } else { int petIndex = path.element(1).val(); user.getPets().addAll(petIndex, value.many(Pet.class)); } } else { throw new InvalidPatchPathException(path); } } else if (path.endsAt(0)) { user.getPets().addAll(value.many(Pet.class)); } else { throw new InvalidPatchPathException(path); } } else if (path.property(0).is("name")) { user.setName(value.one(String.class)); } else if (path.property(0).is("emailAddresses")) { if (path.element(1).exists() && path.endsAt(1)) { user.getEmailAddresses().addAll(path.element(1).val(), value.many(String.class)); } else if (path.endsAt(0)) { user.getEmailAddresses().addAll(value.many(String.class)); } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } return user; } }); request.setCopy(new ContextualCopyOperation<User>() { @Override public User copy(User user, JsonPath from, JsonPath path) { //Makes sure new instances are included in the patch [#11] user = new User(user); if (from.property(0).is("pets") && path.property(0).is("pets") && from.endsAt(1) && path.endsAt(1)) { if (from.element(1).exists() && path.element(1).exists()) { Pet pet = user.getPets().get(from.element(1).val()); user.getPets().add(path.element(1).val(), pet); } else { throw new InvalidPatchPathException(path); } } else if (from.property(0).is("emailAddresses") && path.property(0).is("emailAddresses") && from .endsAt(1) && path.endsAt(1)) { if (from.element(1).exists() && path.element(1).exists()) { String emailAddress = user.getEmailAddresses().get(from.element(1).val()); user.getEmailAddresses().add(path.element(1).val(), emailAddress); } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } return user; } }); request.setMove(new ContextualMoveOperation<User>() { @Override public User move(User user, JsonPath from, JsonPath path) { //Makes sure new instances are included in the patch [#11] user = new User(user); if (from.property(0).is("pets") && path.property(0).is("pets")) { if (from.element(1).exists() && path.element(1).exists() && from.endsAt(1) && path.endsAt(1)) { int fromIndex = from.element(1).val(); Pet pet = user.getPets().get(fromIndex); user.getPets().remove(fromIndex); user.getPets().add(path.element(1).val(), pet); } else { throw new InvalidPatchPathException(path); } } else if (from.property(0).is("emailAddresses") && path.property(0).is("emailAddresses") && from .endsAt(1) && path.endsAt(1)) { if (from.element(1).exists() && path.element(1).exists()) { int fromIndex = from.element(1).val(); String emailAddress = user.getEmailAddresses().get(fromIndex); user.getEmailAddresses().remove(fromIndex); user.getEmailAddresses().add(path.element(1).val(), emailAddress); } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } return user; } }); request.setRemove(new ContextualRemoveOperation<User>() { @Override public User remove(User user, JsonPath path) { if (path.property(0).is("pets") && path.element(1).exists() && path.endsAt(1)) { user.getPets().remove(path.element(1).val()); } else if (path.property(0).is("emailAddresses") && path.element(1).exists() && path.endsAt(1)) { user.getEmailAddresses().remove(path.element(1).val()); } else { throw new InvalidPatchPathException(path); } return user; } }); request.setReplace(new ContextualReplaceOperation<User>() { @Override public User replace(User user, JsonPath path, JsonPatchValue value) { if (path.property(0).exists()) { if (path.property(0).is("pets")) { if (path.element(1).exists() && path.endsAt(1)) { int petIndex = path.element(1).val(); user.getPets().set(petIndex, value.one(Pet.class)); } else if (path.endsAt(0)) { user.setPets(value.many(Pet.class)); } else { throw new InvalidPatchPathException(path); } } else if (path.property(0).is("name") && path.endsAt(0)) { user.setName(value.one(String.class)); } else if (path.property(0).is("emailAddresses")) { if (path.element(1).exists() && path.endsAt(1)) { user.getEmailAddresses().set(path.element(1).val(), value.one(String.class)); } else if (path.endsAt(0)) { user.setEmailAddresses(value.many(String.class)); } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } return user; } }); request.setTest(new ContextualTestOperation<User>() { @Override public boolean test(User user, JsonPath path, JsonPatchValue value) { if (path.property(0).exists()) { if (path.property(0).is("pets")) { if (path.element(1).exists() && path.endsAt(1)) { int petIndex = path.element(1).val(); return Objects.equals(user.getPets().get(petIndex), value.one(Pet.class)); } else if (path.endsAt(0)) { return Objects.equals(user.getPets(), value.many(Pet.class)); } } else if (path.property(0).is("name") && path.endsAt(0)) { return Objects.equals(user.getName(), value.one(String.class)); } else if (path.property(0).is("emailAddresses")) { if (path.element(1).exists() && path.endsAt(1)) { return Objects.equals(user.getEmailAddresses().get(path.element(1).val()), value.one(String.class)); } else if (path.endsAt(0)) { return Objects.equals(user.getEmailAddresses(), value.many(String.class)); } } } throw new InvalidPatchPathException(path); } }); User user = dao.getUsers().get(id); User patchedUser = request.apply(user); dao.getUsers().set(id, patchedUser); } @PATCH @Path("/no-operations/{id}") public void noOp(@PathParam("id") int id, BasicJsonPatch request) { request.apply(); } @PATCH @Path("/{id}") public void updateUser(@PathParam("id") int id, BasicJsonPatch request) { final User user = dao.getUsers().get(id); request.setAdd(new AddOperation() { @Override public void add(JsonPath path, JsonPatchValue value) { if (path.property(0).exists()) { if (path.property(0).is("pets")) { if (path.element(1).exists() && path.endsAt(1)) { if (!path.property(2).exists()) { if(path.element(1).isEndOfArray()) { user.getPets().addAll(value.many(Pet.class)); } else { int petIndex = path.element(1).val(); user.getPets().addAll(petIndex, value.many(Pet.class)); } } else { throw new InvalidPatchPathException(path); } } else if (path.endsAt(0)) { user.getPets().addAll(value.many(Pet.class)); } else { throw new InvalidPatchPathException(path); } } else if (path.property(0).is("name")) { user.setName(value.one(String.class)); } else if (path.property(0).is("emailAddresses")) { if (path.element(1).exists() && path.endsAt(1)) { user.getEmailAddresses().addAll(path.element(1).val(), value.many(String.class)); } else if (path.endsAt(0)) { user.getEmailAddresses().addAll(value.many(String.class)); } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } } }); request.setCopy(new CopyOperation() { @Override public void copy(JsonPath from, JsonPath path) { if (from.property(0).is("pets") && path.property(0).is("pets") && from.endsAt(1) && path.endsAt(1)) { if (from.element(1).exists() && path.element(1).exists()) { Pet pet = user.getPets().get(from.element(1).val()); user.getPets().add(path.element(1).val(), pet); } else { throw new InvalidPatchPathException(path); } } else if (from.property(0).is("emailAddresses") && path.property(0).is("emailAddresses") && from .endsAt(1) && path.endsAt(1)) { if (from.element(1).exists() && path.element(1).exists()) { String emailAddress = user.getEmailAddresses().get(from.element(1).val()); user.getEmailAddresses().add(path.element(1).val(), emailAddress); } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } } }); request.setMove(new MoveOperation() { @Override public void move(JsonPath from, JsonPath path) { if (from.property(0).is("pets") && path.property(0).is("pets")) { if (from.element(1).exists() && path.element(1).exists() && from.endsAt(1) && path.endsAt(1)) { int fromIndex = from.element(1).val(); Pet pet = user.getPets().get(fromIndex); user.getPets().remove(fromIndex); user.getPets().add(path.element(1).val(), pet); } else { throw new InvalidPatchPathException(path); } } else if (from.property(0).is("emailAddresses") && path.property(0).is("emailAddresses") && from .endsAt(1) && path.endsAt(1)) { if (from.element(1).exists() && path.element(1).exists()) { int fromIndex = from.element(1).val(); String emailAddress = user.getEmailAddresses().get(fromIndex); user.getEmailAddresses().remove(fromIndex); user.getEmailAddresses().add(path.element(1).val(), emailAddress); } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } } }); request.setRemove(new RemoveOperation() { @Override public void remove(JsonPath path) { if (path.property(0).is("pets") && path.element(1).exists() && path.endsAt(1)) { user.getPets().remove(path.element(1).val()); } else if (path.property(0).is("emailAddresses") && path.element(1).exists() && path.endsAt(1)) { user.getEmailAddresses().remove(path.element(1).val()); } else { throw new InvalidPatchPathException(path); } } }); request.setReplace(new ReplaceOperation() { @Override public void replace(JsonPath path, JsonPatchValue value) { if (path.property(0).exists()) { if (path.property(0).is("pets")) { if (path.element(1).exists() && path.endsAt(1)) { int petIndex = path.element(1).val(); user.getPets().set(petIndex, value.one(Pet.class)); } else if (path.endsAt(0)) { user.setPets(value.many(Pet.class)); } else { throw new InvalidPatchPathException(path); } } else if (path.property(0).is("name") && path.endsAt(0)) { user.setName(value.one(String.class)); } else if (path.property(0).is("emailAddresses")) { if (path.element(1).exists() && path.endsAt(1)) { user.getEmailAddresses().set(path.element(1).val(), value.one(String.class)); } else if (path.endsAt(0)) { user.setEmailAddresses(value.many(String.class)); } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } } else { throw new InvalidPatchPathException(path); } } }); request.setTest(new TestOperation() { @Override public boolean test(JsonPath path, JsonPatchValue value) { if (path.property(0).exists()) { if (path.property(0).is("pets")) { if (path.element(1).exists() && path.endsAt(1)) { int petIndex = path.element(1).val(); return Objects.equals(user.getPets().get(petIndex), value.one(Pet.class)); } else if (path.endsAt(0)) { return Objects.equals(user.getPets(), value.many(Pet.class)); } } else if (path.property(0).is("name") && path.endsAt(0)) { return Objects.equals(user.getName(), value.one(String.class)); } else if (path.property(0).is("emailAddresses")) { if (path.element(1).exists() && path.endsAt(1)) { return Objects.equals(user.getEmailAddresses().get(path.element(1).val()), value.one(String.class)); } else if (path.endsAt(0)) { return Objects.equals(user.getEmailAddresses(), value.many(String.class)); } } } throw new InvalidPatchPathException(path); } }); request.apply(); } }