INT-1049 Added CycleDetector as transformer package-only visible class for now, added tests
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.transformer;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 2.0
|
||||
*/
|
||||
class CycleDetector {
|
||||
private final ExpressionParser parser = new SpelExpressionParser();
|
||||
private final String[] defaultIgnorePakages = new String[]{
|
||||
"com.apple",
|
||||
"com.sun",
|
||||
"java.awt",
|
||||
"java",
|
||||
"javax",
|
||||
"org.jcp",
|
||||
"org.omg",
|
||||
"sun"
|
||||
};
|
||||
private boolean ignoreClassAttribute = true;
|
||||
private boolean ignoreDefaultPackages = true;
|
||||
/**
|
||||
*
|
||||
* @param target
|
||||
* @param ignorePakages
|
||||
* @return
|
||||
*/
|
||||
public void detectCycle(Object target, String... ignorePakages){
|
||||
Map<Object, Set<Object>> objectReferenceMap = new HashMap<Object, Set<Object>>();
|
||||
this.doDetect(target, objectReferenceMap, ignorePakages);
|
||||
}
|
||||
/*
|
||||
*
|
||||
*/
|
||||
private void doDetect(Object target, Map<Object, Set<Object>> objectReferenceMap, String[] ignorePakages){
|
||||
if (propertyInIgnoredPackage(target, ignorePakages)){
|
||||
return;
|
||||
}
|
||||
if (collectionMapOrArray(target)){
|
||||
Iterable<?> iterable = null;
|
||||
if (target instanceof Collection<?>) {
|
||||
iterable = (Iterable<?>) target;
|
||||
} else if (target instanceof Map<?, ?>){
|
||||
iterable = ((Map<?, ?>) target).values();
|
||||
} else if (target.getClass().isArray()){
|
||||
iterable = CollectionUtils.arrayToList(target);
|
||||
}
|
||||
for (Object value : iterable) {
|
||||
this.doDetect(value, objectReferenceMap, ignorePakages);
|
||||
}
|
||||
} else {
|
||||
if (!objectReferenceMap.containsKey(target)){
|
||||
objectReferenceMap.put(target, new HashSet<Object>());
|
||||
}
|
||||
EvaluationContext context = new StandardEvaluationContext(target);
|
||||
BeanWrapperImpl bw = new BeanWrapperImpl(target);
|
||||
PropertyDescriptor[] descriptors = bw.getPropertyDescriptors();
|
||||
for (PropertyDescriptor propertyDescriptor : descriptors) {
|
||||
String propertyName = propertyDescriptor.getName();
|
||||
if (propertyName.equals("class") && ignoreClassAttribute){
|
||||
continue; // no need to process
|
||||
}
|
||||
Expression expression = parser.parseExpression(propertyName);
|
||||
Object propertyValue = null;
|
||||
try {
|
||||
propertyValue = expression.getValue(context);
|
||||
} catch (Exception e) {/*nothing to do, might only happen when 'ignoreClassAttribute' is set to false ('true' by default)*/}
|
||||
|
||||
if (propertyValue != null){
|
||||
if (!collectionMapOrArray(propertyValue)){
|
||||
Set<Object> references = objectReferenceMap.get(target);
|
||||
if (!references.contains(propertyValue)){
|
||||
references.add(propertyValue);
|
||||
}
|
||||
if (objectReferenceMap.containsKey(propertyValue)){
|
||||
references = objectReferenceMap.get(propertyValue);
|
||||
if (references.contains(target)){
|
||||
throw new MessageTransformationException("Cyclic reference detected between: " +
|
||||
propertyValue.getClass().getSimpleName() + " - " + target.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
this.doDetect(propertyValue, objectReferenceMap, ignorePakages);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
*
|
||||
*/
|
||||
private boolean collectionMapOrArray(Object elementValue){
|
||||
return (elementValue instanceof Map<?,?> ||
|
||||
elementValue instanceof Collection<?> ||
|
||||
elementValue.getClass().isArray());
|
||||
}
|
||||
/*
|
||||
*
|
||||
*/
|
||||
private boolean propertyInIgnoredPackage(Object elementValue, String[] ignorePakagess){
|
||||
if (this.collectionMapOrArray(elementValue)){
|
||||
return false;
|
||||
}
|
||||
for (String packagePattern : ignorePakagess) {
|
||||
if (elementValue.getClass().getPackage().getName().startsWith(packagePattern)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (ignoreDefaultPackages){
|
||||
for (String packagePattern : defaultIgnorePakages) {
|
||||
if (elementValue.getClass().getPackage().getName().startsWith(packagePattern)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public boolean isIgnoreClassAttribute() {
|
||||
return ignoreClassAttribute;
|
||||
}
|
||||
|
||||
public void setIgnoreClassAttribute(boolean ignoreClassAttribute) {
|
||||
this.ignoreClassAttribute = ignoreClassAttribute;
|
||||
}
|
||||
|
||||
public boolean isIgnoreDefaultPackages() {
|
||||
return ignoreDefaultPackages;
|
||||
}
|
||||
|
||||
public void setIgnoreDefaultPackages(boolean ignoreDefaultPackages) {
|
||||
this.ignoreDefaultPackages = ignoreDefaultPackages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.transformer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
public class CycleDetectorTests {
|
||||
@Test
|
||||
public void testWithNoIgnoredPackages(){
|
||||
Parent parent = this.prepare(false);
|
||||
CycleDetector builder = new CycleDetector();
|
||||
builder.setIgnoreDefaultPackages(false);
|
||||
builder.detectCycle(parent);
|
||||
// should not throw an exception
|
||||
}
|
||||
@Test
|
||||
public void testWithNoIgnoredPackagesAndClassProperty(){
|
||||
Parent parent = this.prepare(false);
|
||||
CycleDetector builder = new CycleDetector();
|
||||
builder.setIgnoreDefaultPackages(false);
|
||||
builder.setIgnoreClassAttribute(false);
|
||||
builder.detectCycle(parent);
|
||||
// should not throw an exception
|
||||
}
|
||||
@Test
|
||||
public void testWithAditionalIgnoredPackages(){
|
||||
Parent parent = this.prepare(true);
|
||||
CycleDetector builder = new CycleDetector();
|
||||
builder.detectCycle(parent, "org.springframework.integration.transformer");
|
||||
// should not throw an exception, however if you remove additional package from
|
||||
// the above method there is a cycle in the domain model
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObjectReferenceMapWithoutCycle(){
|
||||
Parent parent = this.prepare(false);
|
||||
CycleDetector builder = new CycleDetector();
|
||||
builder.detectCycle(parent);
|
||||
// should not throw an exception
|
||||
}
|
||||
|
||||
@Test(expected=MessageTransformationException.class)
|
||||
public void testObjectReferenceMapWithCycle(){
|
||||
Parent parent = this.prepare(true);
|
||||
CycleDetector builder = new CycleDetector();
|
||||
builder.detectCycle(parent);
|
||||
}
|
||||
@Test
|
||||
public void testWithNoCyclesInArray(){
|
||||
Parent parent = this.prepare(false);
|
||||
Foo[] foos = new Foo[]{new Foo(), new Foo()};
|
||||
parent.getAddress().setFoos(foos);
|
||||
CycleDetector builder = new CycleDetector();
|
||||
builder.detectCycle(parent);
|
||||
}
|
||||
@Test(expected=MessageTransformationException.class)
|
||||
public void testWithCyclesInArray(){
|
||||
Parent parent = this.prepare(false);
|
||||
Foo foo = new Foo();
|
||||
Bar bar = new Bar();
|
||||
foo.setBar(bar);
|
||||
bar.setFoo(foo);
|
||||
Foo[] foos = new Foo[]{foo, new Foo()};
|
||||
parent.getAddress().setFoos(foos);
|
||||
CycleDetector builder = new CycleDetector();
|
||||
builder.detectCycle(parent);
|
||||
}
|
||||
@Test
|
||||
public void testWithNoCyclesInMapWithList(){
|
||||
Parent parent = this.prepare(false);
|
||||
Foo fooA = new Foo();
|
||||
Foo fooB = new Foo();
|
||||
Foo[] foos = new Foo[]{fooA, fooB};
|
||||
parent.getAddress().setFoos(foos);
|
||||
List<Foo> fooList = new ArrayList<Foo>();
|
||||
fooList.add(fooA);
|
||||
fooList.add(fooB);
|
||||
Map<Object, List<Foo>> mapOfFoos = new HashMap<Object, List<Foo>>();
|
||||
mapOfFoos.put("listOfFoos", fooList);
|
||||
parent.getChild().setMapOfFoos(mapOfFoos);
|
||||
CycleDetector builder = new CycleDetector();
|
||||
builder.detectCycle(parent);
|
||||
}
|
||||
@Test(expected=MessageTransformationException.class)
|
||||
public void testWithCyclesInMapWithList(){
|
||||
Parent parent = this.prepare(false);
|
||||
Foo fooA = new Foo();
|
||||
Bar bar = new Bar();
|
||||
bar.setFoo(fooA);
|
||||
fooA.setBar(bar);
|
||||
Foo fooB = new Foo();
|
||||
Foo[] foos = new Foo[]{new Foo(), new Foo()};
|
||||
parent.getAddress().setFoos(foos);
|
||||
List<Foo> fooList = new ArrayList<Foo>();
|
||||
fooList.add(fooA);
|
||||
fooList.add(fooB);
|
||||
Map<Object, List<Foo>> mapOfFoos = new HashMap<Object, List<Foo>>();
|
||||
mapOfFoos.put("listOfFoos", fooList);
|
||||
parent.getChild().setMapOfFoos(mapOfFoos);
|
||||
CycleDetector builder = new CycleDetector();
|
||||
builder.detectCycle(parent);
|
||||
}
|
||||
|
||||
//################# Test Classes ###################
|
||||
public Parent prepare(boolean cycle){
|
||||
Parent parent = new Parent();
|
||||
Child child = new Child();
|
||||
Address address = new Address();
|
||||
address.setStreet("123 Main st");
|
||||
child.setAddress(address);
|
||||
List<String> nickNames = new ArrayList<String>();
|
||||
nickNames.add("spanky");
|
||||
nickNames.add("goofy");
|
||||
child.setNickNames(nickNames);
|
||||
child.setName("Seva");
|
||||
if (cycle){
|
||||
child.setParent(parent);
|
||||
}
|
||||
|
||||
parent.setAddress(address);
|
||||
parent.setChild(child);
|
||||
parent.setName("Oleg");
|
||||
return parent;
|
||||
}
|
||||
/*
|
||||
*
|
||||
*/
|
||||
public static class Parent{
|
||||
private Address address;
|
||||
private Child child;
|
||||
private String name;
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
public void setAddress(Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
public Child getChild() {
|
||||
return child;
|
||||
}
|
||||
public void setChild(Child child) {
|
||||
this.child = child;
|
||||
}
|
||||
}
|
||||
/*
|
||||
*
|
||||
*/
|
||||
public static class Child{
|
||||
private String name;
|
||||
private List<String> nickNames;
|
||||
private Map<Object, List<Foo>> mapOfFoos;
|
||||
private Address address;
|
||||
private Parent parent;
|
||||
public Parent getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(Parent parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public List<String> getNickNames() {
|
||||
return nickNames;
|
||||
}
|
||||
|
||||
public void setNickNames(List<String> nickNames) {
|
||||
this.nickNames = nickNames;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public Map<Object, List<Foo>> getMapOfFoos() {
|
||||
return mapOfFoos;
|
||||
}
|
||||
|
||||
public void setMapOfFoos(Map<Object, List<Foo>> mapOfFoos) {
|
||||
this.mapOfFoos = mapOfFoos;
|
||||
}
|
||||
}
|
||||
/*
|
||||
*
|
||||
*/
|
||||
public static class Address{
|
||||
private String street;
|
||||
private Foo[] foos;
|
||||
|
||||
public Foo[] getFoos() {
|
||||
return foos;
|
||||
}
|
||||
|
||||
public void setFoos(Foo[] foos) {
|
||||
this.foos = foos;
|
||||
}
|
||||
|
||||
public String getStreet() {
|
||||
return street;
|
||||
}
|
||||
|
||||
public void setStreet(String street) {
|
||||
this.street = street;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Foo{
|
||||
private Bar bar;
|
||||
|
||||
public Bar getBar() {
|
||||
return bar;
|
||||
}
|
||||
|
||||
public void setBar(Bar bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
}
|
||||
public static class Bar{
|
||||
private Foo foo;
|
||||
|
||||
public Foo getFoo() {
|
||||
return foo;
|
||||
}
|
||||
|
||||
public void setFoo(Foo foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user