Skip to content

Commit

Permalink
Refactores packages
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkrog committed Apr 16, 2018
1 parent 3ab256c commit 094b820
Show file tree
Hide file tree
Showing 9 changed files with 412 additions and 0 deletions.
51 changes: 51 additions & 0 deletions src/main/java/dk/apaq/rest/EntityMerger.java
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;
}
}

9 changes: 9 additions & 0 deletions src/main/java/dk/apaq/rest/PropertyReferenceConverter.java
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);
}
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 src/main/java/dk/apaq/rest/jackson/JacksonTreeNodeMapper.java
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);
}
}
20 changes: 20 additions & 0 deletions src/main/java/dk/apaq/rest/jackson/TreeNodeHolder.java
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);
}
}
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);
}

}
61 changes: 61 additions & 0 deletions src/test/java/dk/apaq/rest/DummyEntity.java
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;
}
}
116 changes: 116 additions & 0 deletions src/test/java/dk/apaq/rest/EntityMergerTest.java
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());
}
}
Loading

0 comments on commit 094b820

Please sign in to comment.