-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3ab256c
commit 094b820
Showing
9 changed files
with
412 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package dk.apaq.rest; | ||
|
||
import org.apache.commons.beanutils.PropertyUtils; | ||
import org.apache.commons.lang.Validate; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.lang.reflect.InvocationTargetException; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
public class EntityMerger<T> { | ||
|
||
private static final Logger LOG = LoggerFactory.getLogger(EntityMerger.class); | ||
private final List<String> defaultIgnoredFields; | ||
|
||
public EntityMerger() { | ||
this.defaultIgnoredFields = Collections.emptyList(); | ||
} | ||
|
||
public EntityMerger(List<String> defaultIgnoredFields) { | ||
if(defaultIgnoredFields == null) { | ||
defaultIgnoredFields = Collections.emptyList(); | ||
} | ||
this.defaultIgnoredFields = defaultIgnoredFields; | ||
} | ||
|
||
public T mergeEntities(T existingEntity, T newEntity, Iterable<String> dirtyFields) { | ||
return this.mergeEntities(existingEntity, newEntity, dirtyFields, Collections.emptyList()); | ||
} | ||
|
||
public T mergeEntities(T existingEntity, T newEntity, Iterable<String> dirtyFields, List<String> ignoredFields) { | ||
Validate.notNull(existingEntity, "existingEntity must be specified."); | ||
Validate.notNull(newEntity, "newEntity must be specified."); | ||
Validate.notNull(dirtyFields, "dirtyField must be specified."); | ||
Validate.notNull(ignoredFields, "ignoredField must be specified."); | ||
|
||
dirtyFields.iterator().forEachRemaining(item -> { | ||
if (!defaultIgnoredFields.contains(item) && !ignoredFields.contains(item)) { | ||
try { | ||
PropertyUtils.setProperty(existingEntity, item, PropertyUtils.getProperty(newEntity, item)); | ||
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | IndexOutOfBoundsException ex) { | ||
LOG.error("Error occured while merging entities.", ex); | ||
throw new IllegalArgumentException("The parameter '" + item + "' does not apply to this resource."); | ||
} | ||
} | ||
}); | ||
return existingEntity; | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package dk.apaq.rest; | ||
|
||
import java.util.Collection; | ||
|
||
@FunctionalInterface | ||
public interface PropertyReferenceConverter<T> { | ||
|
||
public Collection<String> translate(T input); | ||
} |
38 changes: 38 additions & 0 deletions
38
src/main/java/dk/apaq/rest/form/FormPropertyReferenceConverter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package dk.apaq.rest.form; | ||
|
||
import dk.apaq.rest.PropertyReferenceConverter; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.regex.Pattern; | ||
|
||
public class FormPropertyReferenceConverter implements PropertyReferenceConverter<Map<String, String[]>> { | ||
|
||
/** | ||
* Pattern for matching map references defined as array, fx. 'meta[color]'. | ||
*/ | ||
private static final Pattern FORM_MAP_REFERENCE_PATTERN = Pattern.compile("((\\[)([a-zA-Z]{1}[a-zA-Z0-9\\_]*)(\\]))"); | ||
|
||
/** | ||
* Translates Form input map to list of fields. | ||
* This method will use all keys directly as fields. Only exception is that map references defined as arrays with have | ||
* square brackets replaced with parantheses. | ||
* | ||
* __Example__ | ||
* meta[color] becomes meta(color) | ||
* | ||
* @param input Map of properties | ||
* @return Returns list of fields | ||
*/ | ||
@Override | ||
public Collection<String> translate(Map<String, String[]> input) { | ||
List<String> refs = new ArrayList<>(); | ||
for(String key: input.keySet()) { | ||
refs.add(FORM_MAP_REFERENCE_PATTERN.matcher(key).replaceAll("($3)")); | ||
} | ||
return refs; | ||
} | ||
|
||
} |
21 changes: 21 additions & 0 deletions
21
src/main/java/dk/apaq/rest/jackson/JacksonTreeNodeMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package dk.apaq.rest.jackson; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.TreeNode; | ||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
import java.io.IOException; | ||
|
||
/** | ||
* Jackson ObjectMapper that puts each new treenode into TreeNodeHolder. | ||
*/ | ||
public class JacksonTreeNodeMapper extends ObjectMapper { | ||
|
||
@Override | ||
protected Object _readMapAndClose(JsonParser jp, JavaType valueType) throws IOException { | ||
TreeNode node = jp.readValueAsTree(); | ||
TreeNodeHolder.set(node); | ||
return super._readMapAndClose(node.traverse(), valueType); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package dk.apaq.rest.jackson; | ||
|
||
import com.fasterxml.jackson.core.TreeNode; | ||
|
||
public class TreeNodeHolder { | ||
|
||
private static final ThreadLocal<TreeNode> TREE_NODE = new ThreadLocal<>(); | ||
|
||
private TreeNodeHolder() { | ||
throw new IllegalAccessError("Utility class"); | ||
} | ||
|
||
public static TreeNode get() { | ||
return TREE_NODE.get(); | ||
} | ||
|
||
public static void set(TreeNode treenode) { | ||
TREE_NODE.set(treenode); | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
src/main/java/dk/apaq/rest/jackson/TreeNodePropertyReferenceConverter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package dk.apaq.rest.jackson; | ||
|
||
import com.fasterxml.jackson.core.TreeNode; | ||
import dk.apaq.rest.PropertyReferenceConverter; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
|
||
/** | ||
* Converter for Jackson TreeNodes into a Collection of fieldnames. | ||
*/ | ||
public class TreeNodePropertyReferenceConverter implements PropertyReferenceConverter<TreeNode> { | ||
|
||
@Override | ||
public Collection<String> translate(TreeNode input) { | ||
List<String> refs = new ArrayList<>(); | ||
if(input.isObject()) { | ||
traverseObject(input, refs, new ArrayList()); | ||
} | ||
return refs; | ||
} | ||
|
||
private void traverseArray(TreeNode treeNode, List<String> fields, List<String> path) { | ||
|
||
for(int i=0;i<treeNode.size();i++) { | ||
path.add("[" + i + "]"); | ||
TreeNode child = treeNode.get(i); | ||
traverse(child, fields, path); | ||
path.remove(path.size()-1); | ||
} | ||
} | ||
|
||
private void traverseObject(TreeNode treeNode, List<String> fields, List<String> path) { | ||
treeNode.fieldNames().forEachRemaining(item -> { | ||
path.add("." + item); | ||
TreeNode child = treeNode.get(item); | ||
traverse(child, fields, path); | ||
path.remove(path.size()-1); | ||
}); | ||
} | ||
|
||
private void traverse(TreeNode child, List<String> fields, List<String> path) { | ||
if(child.isObject()) { | ||
traverseObject(child, fields, path); | ||
} | ||
|
||
if(child.isArray()) { | ||
// We currently do not support setting single array elements. Consider it a value, | ||
//traverseArray(child, fields, path); | ||
|
||
traverseValue(path, fields); | ||
} | ||
|
||
if(child.isValueNode()) { | ||
traverseValue(path, fields); | ||
} | ||
} | ||
|
||
private void traverseValue(List<String> path, List<String> fields) { | ||
String strPath = String.join("", path); | ||
if(strPath.startsWith(".")) { | ||
strPath = strPath.substring(1); | ||
} | ||
fields.add(strPath); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package dk.apaq.rest; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class DummyEntity { | ||
private String text; | ||
private int number; | ||
private String[] array; | ||
private List<String> list = new ArrayList<>(); | ||
private DummyEntity child; | ||
|
||
public DummyEntity() { } | ||
|
||
public DummyEntity(String text, int number, String[] array, List<String> list) { | ||
this.text = text; | ||
this.number = number; | ||
this.array = array; | ||
this.list = list; | ||
} | ||
|
||
public String getText() { | ||
return text; | ||
} | ||
|
||
public void setText(String text) { | ||
this.text = text; | ||
} | ||
|
||
public int getNumber() { | ||
return number; | ||
} | ||
|
||
public void setNumber(int number) { | ||
this.number = number; | ||
} | ||
|
||
public String[] getArray() { | ||
return array; | ||
} | ||
|
||
public void setArray(String[] array) { | ||
this.array = array; | ||
} | ||
|
||
public List<String> getList() { | ||
return list; | ||
} | ||
|
||
public void setList(List<String> list) { | ||
this.list = list; | ||
} | ||
|
||
public DummyEntity getChild() { | ||
return child; | ||
} | ||
|
||
public void setChild(DummyEntity child) { | ||
this.child = child; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package dk.apaq.rest; | ||
|
||
import static org.junit.Assert.*; | ||
import org.junit.Test; | ||
|
||
import java.util.Collections; | ||
|
||
public class EntityMergerTest { | ||
|
||
|
||
private EntityMerger<DummyEntity> merger = new EntityMerger<>(); | ||
|
||
@Test | ||
public void testMergeString() { | ||
DummyEntity patch = new DummyEntity("ytrewq", 0, null, null); | ||
DummyEntity persistence = new DummyEntity("qwerty", 1, new String[]{"A", "B", "C"}, Collections.singletonList("test")); | ||
|
||
merger.mergeEntities(persistence, patch, Collections.singletonList("text")); | ||
assertEquals("ytrewq", persistence.getText()); | ||
assertEquals(1, persistence.getNumber()); | ||
assertArrayEquals(new String[]{"A", "B", "C"}, persistence.getArray()); | ||
assertEquals(Collections.singletonList("test"), persistence.getList()); | ||
} | ||
|
||
@Test | ||
public void testMergeInt() { | ||
DummyEntity patch = new DummyEntity(null, 0, null, null); | ||
DummyEntity persistence = new DummyEntity("qwerty", 1, new String[]{"A", "B", "C"}, Collections.singletonList("test")); | ||
|
||
merger.mergeEntities(persistence, patch, Collections.singletonList("number")); | ||
assertEquals("qwerty", persistence.getText()); | ||
assertEquals(0, persistence.getNumber()); | ||
assertArrayEquals(new String[]{"A", "B", "C"}, persistence.getArray()); | ||
assertEquals(Collections.singletonList("test"), persistence.getList()); | ||
} | ||
|
||
@Test | ||
public void testMergeArray() { | ||
DummyEntity patch = new DummyEntity(null, 0, new String[]{"C", "B", "A"}, null); | ||
DummyEntity persistence = new DummyEntity("qwerty", 1, new String[]{"A", "B", "C"}, Collections.singletonList("test")); | ||
|
||
merger.mergeEntities(persistence, patch, Collections.singletonList("array")); | ||
assertEquals("qwerty", persistence.getText()); | ||
assertEquals(1, persistence.getNumber()); | ||
assertArrayEquals(new String[]{"C", "B", "A"}, persistence.getArray()); | ||
assertEquals(Collections.singletonList("test"), persistence.getList()); | ||
} | ||
|
||
@Test | ||
public void testMergeArraySpecificElement() { | ||
DummyEntity patch = new DummyEntity(null, 0, new String[]{"C", "B", "A"}, null); | ||
DummyEntity persistence = new DummyEntity("qwerty", 1, new String[]{"A", "B", "C"}, Collections.singletonList("test")); | ||
|
||
merger.mergeEntities(persistence, patch, Collections.singletonList("array[0]")); | ||
assertEquals("qwerty", persistence.getText()); | ||
assertEquals(1, persistence.getNumber()); | ||
assertArrayEquals(new String[]{"C", "B", "C"}, persistence.getArray()); | ||
assertEquals(Collections.singletonList("test"), persistence.getList()); | ||
} | ||
|
||
@Test | ||
public void testMergeList() { | ||
DummyEntity patch = new DummyEntity(null, 0, null, Collections.singletonList("qwerty")); | ||
DummyEntity persistence = new DummyEntity("qwerty", 1, new String[]{"A", "B", "C"}, Collections.singletonList("test")); | ||
|
||
merger.mergeEntities(persistence, patch, Collections.singletonList("list")); | ||
assertEquals("qwerty", persistence.getText()); | ||
assertEquals(1, persistence.getNumber()); | ||
assertArrayEquals(new String[]{"A", "B", "C"}, persistence.getArray()); | ||
assertEquals(Collections.singletonList("qwerty"), persistence.getList()); | ||
} | ||
|
||
@Test | ||
public void testMergeChildString() { | ||
DummyEntity patch = new DummyEntity(null, 0, null, null); | ||
patch.setChild(new DummyEntity("sibling", 0, null, null)); | ||
DummyEntity persistence = new DummyEntity("qwerty", 1, new String[]{"A", "B", "C"}, Collections.singletonList("test")); | ||
persistence.setChild(new DummyEntity("child", 1, null, null)); | ||
|
||
merger.mergeEntities(persistence, patch, Collections.singletonList("child.text")); | ||
assertEquals("sibling", persistence.getChild().getText()); | ||
} | ||
|
||
@Test | ||
public void testMergeChildInt() { | ||
DummyEntity patch = new DummyEntity(null, 0, null, null); | ||
patch.setChild(new DummyEntity(null, 0, null, null)); | ||
DummyEntity persistence = new DummyEntity("qwerty", 1, new String[]{"A", "B", "C"}, Collections.singletonList("test")); | ||
persistence.setChild(new DummyEntity("child", 1, null, null)); | ||
|
||
merger.mergeEntities(persistence, patch, Collections.singletonList("child.number")); | ||
assertEquals(0, persistence.getChild().getNumber()); | ||
} | ||
|
||
@Test | ||
public void testMergeChildArray() { | ||
DummyEntity patch = new DummyEntity(null, 0, null, null); | ||
patch.setChild(new DummyEntity(null, 0, new String[]{"C", "B", "A"}, null)); | ||
DummyEntity persistence = new DummyEntity("qwerty", 1, null, Collections.singletonList("test")); | ||
persistence.setChild(new DummyEntity("child", 1, new String[]{"A", "B", "C"}, null)); | ||
|
||
merger.mergeEntities(persistence, patch, Collections.singletonList("child.array")); | ||
assertArrayEquals(new String[]{"C", "B", "A"}, persistence.getChild().getArray()); | ||
} | ||
|
||
@Test | ||
public void testMergeChildArraySpecific() { | ||
DummyEntity patch = new DummyEntity(null, 0, null, null); | ||
patch.setChild(new DummyEntity(null, 0, new String[]{"C", "B", "A"}, null)); | ||
DummyEntity persistence = new DummyEntity("qwerty", 1, null, Collections.singletonList("test")); | ||
persistence.setChild(new DummyEntity("child", 1, new String[]{"A", "B", "C"}, null)); | ||
|
||
merger.mergeEntities(persistence, patch, Collections.singletonList("child.array[2]")); | ||
assertArrayEquals(new String[]{"A", "B", "A"}, persistence.getChild().getArray()); | ||
} | ||
} |
Oops, something went wrong.