/* * Copyright (C) 2013 Intel Corporation * All rights reserved. */ package test.patch; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.intel.mtwilson.repository.FilterCriteria; import com.intel.mtwilson.jaxrs2.NoLinks; import com.intel.mtwilson.jaxrs2.Patch; import com.intel.mtwilson.patch.PatchException; import com.intel.mtwilson.jaxrs2.PatchLink; import java.util.List; import java.util.Map; import org.junit.BeforeClass; import org.junit.Test; import com.intel.mtwilson.patch.PatchUtil; import static org.junit.Assert.*; /** * * @author jbuhacoff */ public class RelationalPatchTest { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RelationalPatchTest.class); private static ObjectMapper mapper; @BeforeClass public static void createMapper() { mapper = new ObjectMapper(); mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy()); } @JsonInclude(JsonInclude.Include.NON_EMPTY) // jackson 2.0 public static class Fruit { public String id; public String fruitName; public String fruitColor; public String groveId; public List<String> farmerIds; public List<String> getFarmerIds() { return farmerIds; } public String getFruitColor() { return fruitColor; } public String getFruitName() { return fruitName; } public String getGroveId() { return groveId; } public void setFarmerIds(List<String> farmerIds) { this.farmerIds = farmerIds; } public void setFruitColor(String fruitColor) { this.fruitColor = fruitColor; } public void setFruitName(String fruitName) { this.fruitName = fruitName; } public void setGroveId(String groveId) { this.groveId = groveId; } public String getId() { return id; } public void setId(String id) { this.id = id; } } @JsonInclude(JsonInclude.Include.NON_EMPTY) // jackson 2.0 public static class FruitFilterCriteria implements FilterCriteria<Fruit> { public String id; public String fruitNameEquals; public String fruitColorEquals; public String groveId; public String farmerId; } /** * * XXX Problem with this approach of deserializing the "replace" block * into an instance of the resource class is that any fields which are * missing will be null, and will be indistinguishable from fields that * the client sent that are null specifically to clear them - so we * would have to use a separate block for clearing properties, which * leads to two conclusions: 1) if the second block is key-value then * it's either an instance of the same class again with any non-null * value meaning clear, or it's similar but with all values being true/false * in which case we need another class to represent it, or it's a list * of fields to clear which means we need to map to properties w/o the * automated framework; 2) if we're going to map to prpoerties w/o the * automated framework why not just do that with the "replace" blcok * then we can use nulls there to mean clear and the "wire api" can * be simple, while the server api actually stays simpler than if we * add more objects. if patch functionality is completely automatic * anyway this won't affect resource authors. * * example apple resource: {"id":"1234","fruit_name":"apple","fruit_color":"red","grove_id":"abcd"} * example patch document for an apple: {"select":{"id":"1234"},"replace":{"fruit_color":"green"}} * example apple after applying the patch: {"id":"1234","fruit_name":"apple","fruit_color":"green","grove_id":"abcd"} * @throws JsonProcessingException */ @Test public void testBeanPatch() throws JsonProcessingException, PatchException { // the initial record Fruit apple = new Fruit(); apple.id = "1234"; apple.fruitName = "apple"; apple.fruitColor = "red"; apple.groveId = "abcd"; log.debug("apple: {}", mapper.writeValueAsString(apple)); // a new instance representing a partial update Fruit appleUpdate = new Fruit(); appleUpdate.id = "1234"; appleUpdate.fruitName = "apple"; appleUpdate.fruitColor = "green"; appleUpdate.groveId = "abcd"; // if this patch request is going to be serialized we should note which apple record we want to patch, using the filter criteria FruitFilterCriteria appleSearch = new FruitFilterCriteria(); appleSearch.id = "1234"; // create the patch document Map<String,Object> appleReplace = PatchUtil.diff(apple,appleUpdate); Patch<Fruit,FruitFilterCriteria,NoLinks<Fruit>> patch = new Patch<Fruit,FruitFilterCriteria,NoLinks<Fruit>>(); patch.setSelect(appleSearch); patch.setReplace(appleReplace); log.debug("apple patch: {}", mapper.writeValueAsString(patch)); // apply the patch to the document apply(patch, apple); // show the patched document log.debug("apple patched: {}", mapper.writeValueAsString(apple)); } private <T,F extends FilterCriteria<T>,L extends PatchLink<T>> T apply(Patch<T,F,L> patch, T o) throws PatchException { return PatchUtil.apply(patch.getReplace(), o); } @Test public void testCopy() throws Exception { // the initial record Fruit apple = new Fruit(); apple.id = "1234"; apple.fruitName = "apple"; apple.fruitColor = "red"; apple.groveId = "abcd"; log.debug("apple: {}", mapper.writeValueAsString(apple)); Fruit apple2 = new Fruit(); PatchUtil.copy(apple, apple2); log.debug("apple2: {}", mapper.writeValueAsString(apple2)); assertEquals(mapper.writeValueAsString(apple),mapper.writeValueAsString(apple2)); } }