http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/structure/UsedLayersDescriptor.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/structure/UsedLayersDescriptor.java b/core/api/src/main/java/org/apache/polygene/api/structure/UsedLayersDescriptor.java new file mode 100644 index 0000000..37ad6b2 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/structure/UsedLayersDescriptor.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.structure; + +import java.util.stream.Stream; + +/** + * Used Layers Descriptor. + */ +public interface UsedLayersDescriptor +{ + Stream<? extends LayerDescriptor> layers(); +}
http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/structure/package.html ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/structure/package.html b/core/api/src/main/java/org/apache/polygene/api/structure/package.html new file mode 100644 index 0000000..c44c4ad --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/structure/package.html @@ -0,0 +1,24 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you 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. + ~ + ~ + --> +<html> + <body> + <h2>Application Structure API.</h2> + </body> +</html> http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/time/SystemTime.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/time/SystemTime.java b/core/api/src/main/java/org/apache/polygene/api/time/SystemTime.java new file mode 100644 index 0000000..a469001 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/time/SystemTime.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.time; + +import java.time.Clock; +import java.time.Instant; + +public class SystemTime +{ + private static Clock defaultClock = Clock.systemUTC(); + + public static Clock getDefaultClock() + { + return defaultClock; + } + + public static void setDefaultClock(Clock defaultClock) + { + SystemTime.defaultClock = defaultClock; + } + + public static Instant now() + { + return Instant.now(defaultClock); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/type/CollectionType.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/type/CollectionType.java b/core/api/src/main/java/org/apache/polygene/api/type/CollectionType.java new file mode 100644 index 0000000..faf19c5 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/type/CollectionType.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.type; + +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.apache.polygene.api.util.Classes; + +/** + * Collection ValueType. + * <p>This handles Collection, List and Set types.</p> + */ +public final class CollectionType + extends ValueType +{ + + public static boolean isCollection( Type type ) + { + Class<?> cl = Classes.RAW_CLASS.apply( type ); + return cl.equals( Collection.class ) || cl.equals( List.class ) || cl.equals( Set.class ); + } + + public static CollectionType collectionOf( Class<?> collectedType ) + { + return new CollectionType( Collection.class, ValueType.of( collectedType ) ); + } + + public static CollectionType listOf( Class<?> collectedType ) + { + return new CollectionType( List.class, ValueType.of( collectedType ) ); + } + + public static CollectionType setOf( Class<?> collectedType ) + { + return new CollectionType( Set.class, ValueType.of( collectedType ) ); + } + private ValueType collectedType; + + public CollectionType( Class<?> type, ValueType collectedType ) + { + super( type ); + this.collectedType = collectedType; + if( !isCollection( type ) ) + { + throw new IllegalArgumentException( type + " is not a Collection, List or Set." ); + } + } + + public ValueType collectedType() + { + return collectedType; + } + + @Override + public String toString() + { + return super.toString() + "<" + collectedType + ">"; + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/type/EnumType.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/type/EnumType.java b/core/api/src/main/java/org/apache/polygene/api/type/EnumType.java new file mode 100644 index 0000000..ba7dc5a --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/type/EnumType.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.type; + +import java.lang.reflect.Type; + +/** + * Enum ValueType. + */ +public final class EnumType + extends ValueType +{ + + public static boolean isEnum( Type type ) + { + if( type instanceof Class ) + { + Class<?> typeClass = (Class) type; + return ( typeClass.isEnum() ); + } + return false; + } + + public static EnumType of( Class<?> type ) + { + return new EnumType( type ); + } + + public EnumType( Class<?> type ) + { + super( type ); + if( !isEnum( type ) ) + { + throw new IllegalArgumentException( type + " is not an Enum." ); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/type/HasTypes.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/type/HasTypes.java b/core/api/src/main/java/org/apache/polygene/api/type/HasTypes.java new file mode 100644 index 0000000..ae1f3c9 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/type/HasTypes.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.type; + +import java.lang.reflect.Type; +import java.util.stream.Stream; +import org.apache.polygene.api.util.Classes; + +/** + * Has types. + */ +public interface HasTypes +{ + Stream<Class<?>> types(); + + default boolean hasType( Type type ) + { + Class<?> rawType = Classes.RAW_CLASS.apply( type ); + return types().map( Classes.RAW_CLASS ).anyMatch( rawType::isAssignableFrom ); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/type/MapType.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/type/MapType.java b/core/api/src/main/java/org/apache/polygene/api/type/MapType.java new file mode 100644 index 0000000..4046628 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/type/MapType.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.type; + +import java.lang.reflect.Type; +import java.util.Map; +import org.apache.polygene.api.util.Classes; + +/** + * Map ValueType. + * <p>This handles instances of Map.</p> + */ +public final class MapType + extends ValueType +{ + + private ValueType keyType; + private ValueType valueType; + private final Serialization.Variant variant; + + public static boolean isMap( Type type ) + { + Class<?> cl = Classes.RAW_CLASS.apply( type ); + return Map.class.isAssignableFrom( cl ); + } + + public static MapType of( Class<?> keyType, Class<?> valueType ) + { + return new MapType( Map.class, ValueType.of( keyType ), ValueType.of( valueType ) ); + } + + public static MapType of( Class<?> keyType, Class<?> valueType, Serialization.Variant variant ) + { + return new MapType( Map.class, ValueType.of( keyType ), ValueType.of( valueType ), variant ); + } + + public MapType( Class<?> type, ValueType keyType, ValueType valueType ) + { + this( type, keyType, valueType, Serialization.Variant.entry ); + } + + public MapType( Class<?> type, ValueType keyType, ValueType valueType, Serialization.Variant variant ) + { + super( type ); + this.keyType = keyType; + this.valueType = valueType; + this.variant = variant; + if( !isMap( type ) ) + { + throw new IllegalArgumentException( type + " is not a Map." ); + } + } + + public ValueType keyType() + { + return keyType; + } + + public ValueType valueType() + { + return valueType; + } + + public Serialization.Variant variant() + { + return variant; + } + + @Override + public String toString() + { + return super.toString() + "<" + keyType + "," + valueType + ">"; + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/type/MatchTypeSpecification.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/type/MatchTypeSpecification.java b/core/api/src/main/java/org/apache/polygene/api/type/MatchTypeSpecification.java new file mode 100644 index 0000000..8d8ff92 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/type/MatchTypeSpecification.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.type; + +import java.util.function.Predicate; + +/** + * Match Type Specification for HasTypes. + */ +public class MatchTypeSpecification + implements Predicate<HasTypes> +{ + private final Class<?> matchType; + + public MatchTypeSpecification( Class<?> matchType ) + { + this.matchType = matchType; + } + + @Override + public boolean test( HasTypes item ) + { + return item.types().anyMatch( matchType::isAssignableFrom ); +// for( Class<?> type : item.types() ) +// { +// if( matchType.isAssignableFrom( type ) ) +// { +// return true; +// } +// } +// return false; + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/type/Serialization.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/type/Serialization.java b/core/api/src/main/java/org/apache/polygene/api/type/Serialization.java new file mode 100644 index 0000000..981ab00 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/type/Serialization.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.type; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Serialization options for Property intstances. + * <p> + * The {@code entry} type represents the explicit key=keyValue, value=valueValue. For JSON serialization; + * </p> + * <pre> + * [ + * { "key1" : "value1" }, + * { "key2" : "value2" } + * ] + * </pre> + * <p> + * For XML serialization; + * </p> + * <pre> + * <object> + * < + * </object> + * </pre> + * <p> + * The {@code object} type represents the explicit keyValue=valueValue. + * </p> + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target( { ElementType.TYPE, ElementType.METHOD } ) +@Documented +public @interface Serialization +{ + Variant value(); + + enum Variant + { + entry, object + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/type/ValueCompositeType.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/type/ValueCompositeType.java b/core/api/src/main/java/org/apache/polygene/api/type/ValueCompositeType.java new file mode 100644 index 0000000..4cf86a6 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/type/ValueCompositeType.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.type; + +import java.lang.reflect.Type; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.polygene.api.association.AssociationDescriptor; +import org.apache.polygene.api.property.PropertyDescriptor; +import org.apache.polygene.api.util.Classes; +import org.apache.polygene.api.value.ValueComposite; +import org.apache.polygene.api.value.ValueDescriptor; + +/** + * ValueComposite ValueType. + */ +public final class ValueCompositeType + extends ValueType +{ + private final ValueDescriptor model; + + public static boolean isValueComposite( Type type ) + { + return ValueComposite.class.isAssignableFrom( Classes.RAW_CLASS.apply( type ) ); + } + + public ValueCompositeType( ValueDescriptor model ) + { + super( model.types().collect( Collectors.toList() ) ); + this.model = model; + } + + public Stream<? extends PropertyDescriptor> properties() + { + return model.state().properties(); + } + + public Stream<? extends AssociationDescriptor> associations() + { + return model.state().associations(); + } + + public Stream<? extends AssociationDescriptor> manyAssociations() + { + return model.state().manyAssociations(); + } + + public Stream<? extends AssociationDescriptor> namedAssociations() + { + return model.state().namedAssociations(); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/type/ValueType.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/type/ValueType.java b/core/api/src/main/java/org/apache/polygene/api/type/ValueType.java new file mode 100644 index 0000000..13277d7 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/type/ValueType.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.type; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import org.apache.polygene.api.identity.Identity; +import org.apache.polygene.api.util.NullArgumentException; + +import static java.util.stream.Collectors.joining; + +/** + * Base class for types of values in ValueComposites and Properties. + */ +public class ValueType + implements HasTypes +{ + + public static ValueType of( Class<?> type ) + { + return new ValueType( type ); + } + + /** + * Check if a non-null object is of any of the Primitive Value Types or an array of them. + * <p> + * String, Boolean, Integer, Double, Float, Long, Byte, Short and Character and their Java primitive types + * counterparts are considered as Primitive Value Types. + * </p> + * <p> + * Date, BigInteger, BigDecimal and JodaTime types are not considered as Primitive Value Types. + * </p> + * + * @return true if object is a primitive value or an array of primitive values + * @throws IllegalArgumentException if object is null + */ + public static boolean isPrimitiveValue( Object object ) + { + NullArgumentException.validateNotNull( "object", object ); + if( object instanceof String + || object instanceof Character + || object instanceof Boolean + || object instanceof Integer + || object instanceof Double + || object instanceof Float + || object instanceof Long + || object instanceof Byte + || object instanceof Short ) + { + return true; + } + if( object.getClass().isArray() ) + { + return isArrayOfPrimitiveValues( object ); + } + return false; + } + + public static boolean isIdentity( Object object ) + { + return object instanceof Identity; + } + + private static boolean isArrayOfPrimitiveValues( Object array ) + { + if( array instanceof String[] + || array instanceof char[] || array instanceof Character[] + || array instanceof boolean[] || array instanceof Boolean[] + || array instanceof int[] || array instanceof Integer[] + || array instanceof double[] || array instanceof Double[] + || array instanceof float[] || array instanceof Float[] + || array instanceof long[] || array instanceof Long[] + || array instanceof byte[] || array instanceof Byte[] + || array instanceof short[] || array instanceof Short[] ) + { + return true; + } + return false; + } + + public static boolean isPrimitiveValueType( ValueType valueType ) + { + return isPrimitiveValueType( valueType.mainType() ); + } + + /** + * @see ValueType#isPrimitiveValue(java.lang.Object) + */ + public static boolean isPrimitiveValueType( Class<?> type ) + { + NullArgumentException.validateNotNull( "type", type ); + if( String.class.isAssignableFrom( type ) ) + { + return true; + } + if( type.isArray() ) + { + return isPrimitiveValueType( type.getComponentType() ); + } + return false; + } + protected final List<Class<?>> types; + + public ValueType( Class<?> type ) + { + this( Collections.singletonList( type ) ); + } + + @SuppressWarnings( "unchecked" ) + public ValueType( List<Class<?>> types ) + { + this.types = types; + } + + public Class<?> mainType() + { + return types.stream().findFirst().orElse( null ); + } + + @Override + public Stream<Class<?>> types() + { + return types.stream(); + } + + @Override + public String toString() + { + String name = types.stream().map( Class::getName ).collect( joining( "," ) ); + if( name.contains( "," ) ) + { + name = "{" + name + "}"; + } + return name; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/type/package.html ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/type/package.html b/core/api/src/main/java/org/apache/polygene/api/type/package.html new file mode 100644 index 0000000..c0c3eca --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/type/package.html @@ -0,0 +1,24 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you 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. + ~ + ~ + --> +<html> + <body> + <h2>Type API.</h2> + </body> +</html> http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/ConcurrentEntityModificationException.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/ConcurrentEntityModificationException.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/ConcurrentEntityModificationException.java new file mode 100644 index 0000000..7d439b0 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/ConcurrentEntityModificationException.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork; + +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.polygene.api.entity.EntityComposite; +import org.apache.polygene.api.type.HasTypes; +import org.apache.polygene.api.usecase.Usecase; + +/** + * This exception is thrown by UnitOfWork.complete() if any entities that are being committed + * had been changed while the UnitOfWork was being executed. + */ +public class ConcurrentEntityModificationException + extends UnitOfWorkCompletionException +{ + private static final long serialVersionUID = 3872723845064767689L; + + private final Map<EntityComposite, HasTypes> concurrentlyModifiedEntities; + + public ConcurrentEntityModificationException( Map<EntityComposite, HasTypes> concurrentlyModifiedEntities, + Usecase usecase + ) + { + super( "Entities changed concurrently, and detected in usecase '" + usecase + "'\nModified entities : " + format( concurrentlyModifiedEntities ) ); + this.concurrentlyModifiedEntities = concurrentlyModifiedEntities; + } + + private static String format( Map<EntityComposite, HasTypes> concurrentlyModifiedEntities ) + { + return concurrentlyModifiedEntities.entrySet().stream() + .map( entry -> + entry.getKey() + + " : " + + entry.getValue().types().map( Class::getSimpleName ) + .collect( Collectors.joining( "," ) ) + ) + .collect( Collectors.joining( "\n" ) ); + } + + public Map<EntityComposite, HasTypes> concurrentlyModifiedEntities() + { + return concurrentlyModifiedEntities; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/EntityCompositeAlreadyExistsException.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/EntityCompositeAlreadyExistsException.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/EntityCompositeAlreadyExistsException.java new file mode 100644 index 0000000..dec6965 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/EntityCompositeAlreadyExistsException.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork; + +import org.apache.polygene.api.entity.EntityReference; + +/** + * If you try to create an EntityComposite whose reference already exists, + * then this exception will be thrown. + */ +public class EntityCompositeAlreadyExistsException + extends UnitOfWorkException +{ + private static final long serialVersionUID = -7297710939536508481L; + + private final EntityReference reference; + + public EntityCompositeAlreadyExistsException( EntityReference reference) + { + super( "EntityComposite (" + reference + ") already exists." ); + this.reference = reference; + } + + public EntityReference reference() + { + return reference; + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/NoSuchEntityException.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/NoSuchEntityException.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/NoSuchEntityException.java new file mode 100644 index 0000000..7b31427 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/NoSuchEntityException.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork; + +import java.util.stream.Stream; +import org.apache.polygene.api.entity.EntityReference; +import org.apache.polygene.api.usecase.Usecase; + +import static java.util.Arrays.stream; + +/** + * This exception indicates that the requested Entity with the given + * reference does not exist. + */ +public class NoSuchEntityException + extends UnitOfWorkException +{ + private final EntityReference reference; + private final Usecase usecase; + private final Class<?>[] mixinTypes; + + public NoSuchEntityException(EntityReference reference, Class<?> mixinType, Usecase usecase ) + { + super( "Could not find entity (" + reference + ") of type " + mixinType.getName() + " in usecase '" + usecase.name() + "'" ); + this.reference = reference; + this.usecase = usecase; + this.mixinTypes = new Class<?>[]{ mixinType }; + } + + public NoSuchEntityException(EntityReference reference, Class<?>[] mixinTypes, Usecase usecase ) + { + super( "Could not find entity (" + reference + ") of type " + toString( mixinTypes ) + " in usecase '" + usecase.name() + "'" ); + this.reference = reference; + this.mixinTypes = mixinTypes; + this.usecase = usecase; + } + + public NoSuchEntityException(EntityReference reference, Stream<Class<?>> types, Usecase usecase ) + { + this(reference, types.toArray( Class[]::new ), usecase ); + } + + public EntityReference reference() + { + return reference; + } + + public Class<?>[] mixinTypes() + { + return mixinTypes; + } + + public Usecase usecase() + { + return usecase; + } + + private static String toString( Class<?>[] mixinTypes ) + { + String reduced = stream( mixinTypes ).map( Class::getName ).reduce( "", ( ret, name ) -> ret + "," + name ); + return "[" + reduced.substring( 1 ) + "]"; + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/NoSuchEntityTypeException.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/NoSuchEntityTypeException.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/NoSuchEntityTypeException.java new file mode 100644 index 0000000..bd038f2 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/NoSuchEntityTypeException.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork; + +import java.util.stream.Collectors; +import org.apache.polygene.api.composite.NoSuchCompositeException; +import org.apache.polygene.api.structure.TypeLookup; + +/** + * Polygene exception to be thrown in case that an entity composite + * was not found during a lookup call. + */ +public class NoSuchEntityTypeException + extends NoSuchCompositeException +{ + public NoSuchEntityTypeException( String typeName, String moduleName, TypeLookup typeLookup ) + { + super( "EntityComposite", typeName, moduleName, formatVisibleTypes( typeLookup ) ); + } + + private static String formatVisibleTypes( TypeLookup typeLookup ) + { + return typeLookup.allEntities() + .map( descriptor -> descriptor.primaryType().getName() ) + .collect( Collectors.joining( "\n", "Visible entity types are:\n", "" ) ); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWork.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWork.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWork.java new file mode 100644 index 0000000..10e4216 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWork.java @@ -0,0 +1,528 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; +import org.apache.polygene.api.association.AssociationDescriptor; +import org.apache.polygene.api.association.ManyAssociation; +import org.apache.polygene.api.association.NamedAssociation; +import org.apache.polygene.api.common.Optional; +import org.apache.polygene.api.composite.AmbiguousTypeException; +import org.apache.polygene.api.entity.EntityBuilder; +import org.apache.polygene.api.entity.EntityReference; +import org.apache.polygene.api.identity.HasIdentity; +import org.apache.polygene.api.entity.LifecycleException; +import org.apache.polygene.api.identity.Identity; +import org.apache.polygene.api.property.PropertyDescriptor; +import org.apache.polygene.api.query.Query; +import org.apache.polygene.api.query.QueryBuilder; +import org.apache.polygene.api.structure.MetaInfoHolder; +import org.apache.polygene.api.structure.ModuleDescriptor; +import org.apache.polygene.api.usecase.Usecase; + +/** + * All operations on entities goes through an UnitOfWork. + * <p> + * A UnitOfWork allows you to access + * Entities and work with them. All modifications to Entities are recorded by the UnitOfWork, + * and at the end they may be sent to the underlying EntityStore by calling complete(). If the + * UoW was read-only you may instead simply discard() it. + * </p> + * <p> + * A UoW differs from a traditional Transaction in the sense that it is not tied at all to the underlying + * storage resource. Because of this there is no timeout on a UoW. It can be very short or very long. + * Another difference is that if a call to complete() fails, and the cause is validation errors in the + * Entities of the UoW, then these can be corrected and the UoW retried. By contrast, when a Transaction + * commit fails, then the whole transaction has to be done from the beginning again. + * </p> + * <p> + * A UoW can be associated with a Usecase. A Usecase describes the metainformation about the process + * to be performed by the UoW. + * </p> + * <p> + * If a code block that uses a UoW throws an exception you need to ensure that this is handled properly, + * and that the UoW is closed before returning. Because discard() is a no-op if the UoW is closed, we therefore + * recommend the following template to be used: + * </p> + * <pre> + * UnitOfWork uow = module.newUnitOfWork(); + * try + * { + * ... + * uow.complete(); + * } + * finally + * { + * uow.discard(); + * } + * </pre> + * <p> + * This ensures that in the happy case the UoW is completed, and if any exception is thrown the UoW is discarded. After + * the UoW has completed the discard() method doesn't do anything, and so has no effect. You can choose to either add + * catch blocks for any exceptions, including exceptions from complete(), or skip them. + * </p> + * <p> + * Since 2.1 you can leverage Java 7 Automatic Resource Management (ie. Try With Resources) and use the following + * template instead: + * </p> + * <pre> + * try( UnitOfWork uow = module.newUnitOfWork() ) + * { + * ... + * uow.complete(); + * } + * </pre> + * <p> + * It has the very same effect than the template above but is shorter.</p> + */ +public interface UnitOfWork extends MetaInfoHolder, AutoCloseable +{ + + /** + * Get the UnitOfWorkFactory that this UnitOfWork was created from. + * + * @return The UnitOfWorkFactory instance that was used to create this UnitOfWork. + */ + UnitOfWorkFactory unitOfWorkFactory(); + + Instant currentTime(); + + /** + * Get the Usecase for this UnitOfWork + * + * @return the Usecase + */ + Usecase usecase(); + + void setMetaInfo( Object metaInfo ); + + <T> Query<T> newQuery( QueryBuilder<T> queryBuilder ); + +// DataSet newDataSetBuilder(Specification<?>... constraints); + + /** + * Create a new Entity which implements the given mixin type. + * <p> + * An EntityComposite + * will be chosen according to what has been registered and the visibility rules + * for Modules and Layers will be considered. If several + * EntityComposites implement the type then an AmbiguousTypeException will be thrown. + * </p> + * <p> + * The reference of the Entity will be generated by the IdentityGenerator of the Module of the EntityComposite. + * </p> + * + * @param type the mixin type that the EntityComposite must implement + * + * @return a new Entity + * + * @throws NoSuchEntityTypeException if no EntityComposite type of the given mixin type has been registered + * @throws AmbiguousTypeException If several mixins implement the given type + * @throws LifecycleException if the entity cannot be created + */ + <T> T newEntity( Class<T> type ) + throws NoSuchEntityTypeException, AmbiguousTypeException, LifecycleException; + + /** + * Create a new Entity which implements the given mixin type. An EntityComposite + * will be chosen according to what has been registered and the visibility rules + * for Modules and Layers will be considered. If several + * EntityComposites implement the type then an AmbiguousTypeException will be thrown. + * + * @param type the mixin type that the EntityComposite must implement + * @param identity the reference of the new Entity + * + * @return a new Entity + * + * @throws NoSuchEntityTypeException if no EntityComposite type of the given mixin type has been registered + * @throws AmbiguousTypeException If several mixins implement the given type + * @throws LifecycleException if the entity cannot be created + */ + <T> T newEntity( Class<T> type, @Optional Identity identity ) + throws NoSuchEntityTypeException, AmbiguousTypeException, LifecycleException; + + /** + * Create a new EntityBuilder for an EntityComposite which implements the given mixin type. An EntityComposite + * will be chosen according to what has been registered and the visibility rules + * for Modules and Layers will be considered. If several + * EntityComposites implement the type then an AmbiguousTypeException will be thrown. + * + * @param type the mixin type that the EntityComposite must implement + * + * @return a new EntityBuilder + * + * @throws NoSuchEntityTypeException if no EntityComposite type of the given mixin type has been registered + * @throws AmbiguousTypeException If several mixins implement the given type + */ + <T> EntityBuilder<T> newEntityBuilder( Class<T> type ) + throws NoSuchEntityTypeException, AmbiguousTypeException; + + /** + * Create a new EntityBuilder for an EntityComposite which implements the given mixin type. An EntityComposite + * will be chosen according to what has been registered and the visibility rules + * for Modules and Layers will be considered. If several + * mixins implement the type then an AmbiguousTypeException will be thrown. + * + * @param type the mixin type that the EntityComposite must implement + * @param identity the reference of the new Entity + * + * @return a new EntityBuilder + * + * @throws NoSuchEntityTypeException if no EntityComposite type of the given mixin type has been registered + * @throws AmbiguousTypeException If several mixins implement the given type + */ + <T> EntityBuilder<T> newEntityBuilder( Class<T> type, @Optional Identity identity ) + throws NoSuchEntityTypeException, AmbiguousTypeException; + + /** + * Create a new EntityBuilder for an EntityComposite wich implements the given mixin type starting with the given + * state. + * <p> + * An EntityComposite will be chosen according to what has been registered and the visibility rules for Modules and + * Layers will be considered. + * + * @param <T> Entity type + * @param type Entity type + * @param propertyFunction a function providing the state of properties + * @param associationFunction a function providing the state of associations + * @param manyAssociationFunction a function providing the state of many associations + * @param namedAssociationFunction a function providing the state of named associations + * + * @return a new EntityBuilder starting with the given state + * + * @throws NoSuchEntityTypeException if no EntityComposite type of the given mixin type has been registered + * @throws AmbiguousTypeException If several mixins implement the given type + */ + <T> EntityBuilder<T> newEntityBuilderWithState( Class<T> type, + Function<PropertyDescriptor, Object> propertyFunction, + Function<AssociationDescriptor, EntityReference> associationFunction, + Function<AssociationDescriptor, Stream<EntityReference>> manyAssociationFunction, + Function<AssociationDescriptor, Stream<Map.Entry<String, EntityReference>>> namedAssociationFunction + ) + throws NoSuchEntityTypeException, AmbiguousTypeException; + + /** + * Create a new EntityBuilder for an EntityComposite wich implements the given mixin type starting with the given + * state. + * <p> + * An EntityComposite will be chosen according to what has been registered and the visibility rules for Modules and + * Layers will be considered. + * + * @param <T> Entity type + * @param type Entity type + * @param identity the reference of the new Entity + * @param propertyFunction a function providing the state of properties + * @param associationFunction a function providing the state of associations + * @param manyAssociationFunction a function providing the state of many associations + * @param namedAssociationFunction a function providing the state of named associations + * + * @return a new EntityBuilder starting with the given state + * + * @throws NoSuchEntityTypeException If no mixins implements the given type + * @throws AmbiguousTypeException If several mixins implement the given type + */ + <T> EntityBuilder<T> newEntityBuilderWithState( Class<T> type, @Optional Identity identity, + Function<PropertyDescriptor, Object> propertyFunction, + Function<AssociationDescriptor, EntityReference> associationFunction, + Function<AssociationDescriptor, Stream<EntityReference>> manyAssociationFunction, + Function<AssociationDescriptor, Stream<Map.Entry<String, EntityReference>>> namedAssociationFunction + ) + throws NoSuchEntityTypeException, AmbiguousTypeException; + + /** + * Find an Entity of the given mixin type with the give reference. This + * method verifies that it exists by asking the underlying EntityStore. + * + * @param type of the entity + * @param identity of the entity + * + * @return the entity + * + * @throws NoSuchEntityTypeException if no entity type could be found + * @throws NoSuchEntityException if the entity could not be found + */ + <T> T get( Class<T> type, Identity identity ) + throws NoSuchEntityTypeException, NoSuchEntityException; + + /** + * If you have a reference to an Entity from another + * UnitOfWork and want to create a reference to it in this + * UnitOfWork, then call this method. + * + * @param entity the Entity to be dereferenced + * + * @return an Entity from this UnitOfWork + * + * @throws NoSuchEntityTypeException if no entity type could be found + */ + <T> T get( T entity ) + throws NoSuchEntityTypeException; + + /** + * Remove the given Entity. + * + * @param entity the Entity to be removed. + * + * @throws LifecycleException if the entity could not be removed + */ + void remove( Object entity ) + throws LifecycleException; + + /** + * Complete this UnitOfWork. This will send all the changes down to the underlying + * EntityStore's. + * + * @throws UnitOfWorkCompletionException if the UnitOfWork could not be completed + * @throws ConcurrentEntityModificationException if entities have been modified by others + */ + void complete() + throws UnitOfWorkCompletionException, ConcurrentEntityModificationException; + + /** + * Discard this UnitOfWork. Use this if a failure occurs that you cannot handle, + * or if the usecase was of a read-only character. This is a no-op of the UnitOfWork + * is already closed. + */ + void discard(); + + /** + * Discard this UnitOfWork. Use this if a failure occurs that you cannot handle, + * or if the usecase was of a read-only character. This is a no-op of the UnitOfWork + * is already closed. This simply call the {@link #discard()} method and is an + * implementation of the {@link AutoCloseable} interface providing Try With Resources + * support for UnitOfWork. + */ + @Override + public void close(); + + /** + * Check if the UnitOfWork is open. It is closed after either complete() or discard() + * methods have been called successfully. + * + * @return true if the UnitOfWork is open. + */ + boolean isOpen(); + + /** + * Check if the UnitOfWork is paused. It is not paused after it has been create through the + * UnitOfWorkFactory, and it can be paused by calling {@link #pause()} and then resumed by calling + * {@link #resume()}. + * + * @return true if this UnitOfWork has been paused. + */ + boolean isPaused(); + + /** + * Pauses this UnitOfWork. + * <p> + * Calling this method will cause the underlying UnitOfWork to become the current UnitOfWork until the + * the resume() method is called. It is the client's responsibility not to drop the reference to this + * UnitOfWork while being paused. + * </p> + */ + void pause(); + + /** + * Resumes this UnitOfWork to again become the current UnitOfWork. + */ + void resume(); + + /** + * Register a callback. Callbacks are invoked when the UnitOfWork + * is completed or discarded. + * + * @param callback a callback to be registered with this UnitOfWork + */ + void addUnitOfWorkCallback( UnitOfWorkCallback callback ); + + /** + * Unregister a callback. Callbacks are invoked when the UnitOfWork + * is completed or discarded. + * + * @param callback a callback to be unregistered with this UnitOfWork + */ + void removeUnitOfWorkCallback( UnitOfWorkCallback callback ); + + /** + * Converts the provided Entity to a Value of the same type. + * This is a convenience method to convert an EntityComposite to a ValueComposite. + * <p> + * All Property values are transferred across as-is, and the Association, ManyAssociation + * and NamedAssociatino values are kept in the ValueComposite as EntityReferences + * until they are dereferenced (get() and other methods), and IF a UnitOfWork is + * present at dereferencing the corresponding EntityCompoiste is retrieved from the + * EntityStore. If there is not an UnitOfWork present, an exception is thrown. + * </p> + * <p> + * For this to work, the Composites (both Entity and Value) must not declare the + * EntityComposite and ValueComposite super types, but rely on the declaration in + * the assembly, and also extend the Identity supertype. + * </p> + * Example; + * <pre><code> + * public interface Person extends Identity { ... }; + * public class MyAssembler + * { + * public void assemble( ModuleAssembly module ) + * { + * module.values( Person.class ); + * module.entities( Person.class ); + * } + * } + * </code></pre> + * + * @param <T> The generic shared type + * @param primaryType The shared type for which the properties and associations will + * be converted. Properties outside this type will be ignored. + * @param entityComposite The entity to be convered. + * + * @return The Value + */ + <T extends HasIdentity> T toValue(Class<T> primaryType, T entityComposite ); + + /** + * Converts all the entities referenced in the ManyAssociation into a List of values of the same type. + * + * <p> + * All the referenced entities inside the association will be fetched from the underlying entity store, + * which is potentially very expensive operation. Each of the fetched entities will be passed to + * {@link #toValue(Class, HasIdentity)}, and its associations will NOT be converted into values, but remain + * {@link EntityReference} values. Hence there is no problem with circular references. + * </p> + * + * <p> + * For this to work, the type <T> must be registered at bootstrap as both an Entity and a Value, and + * as seen in the method signature, also be sub-type of {@link HasIdentity}. + * </p> + * + * @param association The association of entities to be converted into values. + * @param <T> The primary type of the association. + * + * @return A List of ValueComposites that has been converted from EntityComposites referenced by the Associations. + * + * @see #toValue(Class, HasIdentity) + */ + <T extends HasIdentity> List<T> toValueList(ManyAssociation<T> association ); + + /** + * Converts all the entities referenced in the ManyAssociation into a Set of values of the same type. + * + * <p> + * All the referenced entities inside the association will be fetched from the underlying entity store, + * which is potentially very expensive operation. However, any duplicate EntityReferences in the association + * will be dropped before the fetch occurs. Each of the fetched entities will be passed to + * {@link #toValue(Class, HasIdentity)}, and its associations will NOT be converted into values, but remain + * {@link EntityReference} values. Hence there is no problem with circular references. + * </p> + * + * <p> + * For this to work, the type <T> must be registered at bootstrap as both an Entity and a Value, and + * as seen in the method signature, also be sub-type of {@link HasIdentity}. + * </p> + * + * @param association The association of entities to be converted into values. + * @param <T> The primary type of the association. + * + * @return A List of ValueComposites that has been converted from EntityComposites referenced by the Associations. + * + * @see #toValue(Class, HasIdentity) + */ + <T extends HasIdentity> Set<T> toValueSet(ManyAssociation<T> association ); + + /** + * Converts the {@link NamedAssociation} into a Map with a String key and a ValueComposite as the value. + * + * <p> + * A {@link NamedAssociation} is effectively a Map with a String key and an EntityReference as the value. The + * EntityReference is fetched from the entity store and converted into a value of the same type.Each of the fetched + * entities will be passed to {@link #toValue(Class, HasIdentity)}, and its associations will NOT be converted into + * values, but remain {@link EntityReference} values. Hence there is no problem with circular references. + * </p> + * <p> + * For this to work, the type <T> must be registered at bootstrap as both an Entity and a Value, and + * as seen in the method signature, also be sub-type of {@link HasIdentity}. + * </p> + * + * @param association The association of entities to be converted into values. + * @param <T> The primary type of the association. + * + * @return A List of ValueComposites that has been converted from EntityComposites referenced by the Associations. + * + * @see #toValue(Class, HasIdentity) + */ + <T extends HasIdentity> Map<String, T> toValueMap(NamedAssociation<T> association ); + + /** + * Converts the provided Value to an Entity of the same type. + * This is a convenience method to convert a ValueComposite to an EntityComposite. + * <p> + * All Property values are transferred across as-is (no deep copy in case mutable + * types (DISCOURAGED!) are used), and the Association, ManyAssociation + * and NamedAssociatino that were in the ValueComposite as EntityReferences are + * transferred into the EntityComposite correctly, and can be dereferenced. + * </p> + * <p> + * This method MUST be called within a UnitOfWork. + * </p> + * <p> + * If an Entity with the Identity in the ValueComposite already exists, then that + * Entity is updated with the values from the ValueComposite. If an Entity of + * that Identity doesn't exist a new one is created. + * </p> + * <p> + * For this to work, the Composites (both Entity and Value) must not declare the + * EntityComposite and ValueComposite super types, but rely on the declaration in + * the assembly, and also extend the Identity supertype. + * </p> + * Example; + * <pre><code> + * public interface Person extends Identity { ... }; + * public class MyAssembler + * { + * public void assemble( ModuleAssembly module ) + * { + * module.values( Person.class ); + * module.entities( Person.class ); + * } + * } + * </code></pre> + * + * @param <T> The generic shared type + * @param primaryType The shared type for which the properties and associations will + * be converted. Properties outside this type will be ignored. + * @param valueComposite The Value to be convered into an Entity. + * + * @return The new or updated Entity + */ + <T extends HasIdentity> T toEntity(Class<T> primaryType, T valueComposite ); + + /** + * The Module of the UnitOfWork is defined as the Module the UnitOfWorkFactory belonged to from where the + * UnitOfWork was created. + * + * @return the Module where this UnitOfWork was initialized. + */ + ModuleDescriptor module(); +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkCallback.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkCallback.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkCallback.java new file mode 100644 index 0000000..f8e467b --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkCallback.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork; + +/** + * Callback interface for UnitOfWork completion or discard. Implementations + * of this interface can be registered through {@link UnitOfWork#addUnitOfWorkCallback(UnitOfWorkCallback)}. + * + * If Entities implement this interface they will also receive invocations of this callback interface. + */ +public interface UnitOfWorkCallback +{ + /** + * This is called before the completion of the UnitOfWork. + * The callback may do any validation checks and throw + * UnitOfWorkCompletionException if there is any reason + * why the UnitOfWork is not in a valid state to be completed. + * + * @throws UnitOfWorkCompletionException + */ + void beforeCompletion() + throws UnitOfWorkCompletionException; + + /** + * This is called after the completion or discarding + * of the UnitOfWork. The callback may do any cleanup + * necessary related to the UnitOfWork. Note that the + * UnitOfWork is no longer active when this method is + * called, so no methods on it may be invoked. + * + * @param status + */ + void afterCompletion( UnitOfWorkStatus status ); + + enum UnitOfWorkStatus + { + COMPLETED, DISCARDED + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkCompletionException.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkCompletionException.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkCompletionException.java new file mode 100644 index 0000000..0fd8335 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkCompletionException.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork; + +/** + * When an attempt to {@link UnitOfWork#complete()} an UnitOfWork + * fails, this exception will be thrown. + */ +public class UnitOfWorkCompletionException + extends RuntimeException +{ + private static final long serialVersionUID = 6531642131384516904L; + + public UnitOfWorkCompletionException() + { + } + + public UnitOfWorkCompletionException( String string ) + { + super( string ); + } + + public UnitOfWorkCompletionException( String string, Throwable throwable ) + { + super( string, throwable ); + } + + public UnitOfWorkCompletionException( Throwable throwable ) + { + super( throwable ); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkException.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkException.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkException.java new file mode 100644 index 0000000..fdf6903 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkException.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork; + +/** + * Base Exception for UnitOfWork related concerns. + */ +public class UnitOfWorkException + extends RuntimeException +{ + private static final long serialVersionUID = -8544178439804058558L; + + public UnitOfWorkException() + { + } + + public UnitOfWorkException( String message ) + { + super( message ); + } + + public UnitOfWorkException( String message, Throwable cause ) + { + super( message, cause ); + } + + public UnitOfWorkException( Throwable cause ) + { + super( cause ); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkFactory.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkFactory.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkFactory.java new file mode 100644 index 0000000..98d23ba --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkFactory.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork; + +import java.time.Instant; +import org.apache.polygene.api.entity.EntityComposite; +import org.apache.polygene.api.time.SystemTime; +import org.apache.polygene.api.usecase.Usecase; + +/** + * Factory for UnitOfWork. + */ +public interface UnitOfWorkFactory +{ + /** + * Create a new UnitOfWork and associate it with the current thread. + * <p> + * The UnitOfWork will use the default Usecase settings. + * </p> + * <p> + * Current time will be set to {@link SystemTime#now()} + * </p> + * @return a new UnitOfWork + */ + UnitOfWork newUnitOfWork(); + + /** + * Create a new UnitOfWork and associate it with the current thread. + * <p> + * The UnitOfWork will use the default Usecase settings. + * </p> + * @return a new UnitOfWork + */ + UnitOfWork newUnitOfWork( Instant currentTime ); + + /** + * Create a new UnitOfWork for the given Usecase and associate it with the current thread. + * <p> + * Current time will be set to {@link SystemTime#now()} + * </p> + * @param usecase the Usecase for this UnitOfWork + * + * @return a new UnitOfWork + */ + UnitOfWork newUnitOfWork( Usecase usecase ); + + /** + * Create a new UnitOfWork for the given Usecase and associate it with the current thread. + * + * @param usecase the Usecase for this UnitOfWork + * + * @return a new UnitOfWork + */ + UnitOfWork newUnitOfWork( Usecase usecase, Instant currentTime ); + + /** + * @return true if there is an active UnitOfWork associated with the executing thread + */ + boolean isUnitOfWorkActive(); + + /** + * Returns the UnitOfWork that is currently associated with the executing thread. + * + * @return The current UnitOfWork associated with the executing thread + * + * @throws IllegalStateException if no current UnitOfWork is active + */ + UnitOfWork currentUnitOfWork() + throws IllegalStateException; + + /** + * Returns the UnitOfWork that the EntityComposite is bound to. + * + * @param entity the entity to be checked. + * + * @return The UnitOfWork instance that the Entity is bound to, or null if the entity is not associated with + * any UnitOfWork. + */ + UnitOfWork getUnitOfWork( EntityComposite entity ); +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkOptions.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkOptions.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkOptions.java new file mode 100644 index 0000000..02c6d5a --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkOptions.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork; + +/** + * Set instances of this in MetaInfo on UnitOfWork or the associated Usecase. + * <p> + * Options: + * </p> + * <p> + * "pruneOnPause": if true, then clear out all instances that have been loaded in the UoW but not modified + * </p> + */ +public class UnitOfWorkOptions +{ + private boolean pruneOnPause = false; + + public UnitOfWorkOptions( boolean pruneOnPause ) + { + this.pruneOnPause = pruneOnPause; + } + + public boolean isPruneOnPause() + { + return pruneOnPause; + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkTemplate.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkTemplate.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkTemplate.java new file mode 100644 index 0000000..1c821c3 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/UnitOfWorkTemplate.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork; + +import org.apache.polygene.api.structure.Module; +import org.apache.polygene.api.usecase.Usecase; + +/** + * UnitOfWork Template. + */ +public abstract class UnitOfWorkTemplate<RESULT, ThrowableType extends Throwable> +{ + private Usecase usecase = Usecase.DEFAULT; + private int retries = 10; + private boolean complete = true; + + protected UnitOfWorkTemplate() + { + } + + protected UnitOfWorkTemplate( int retries, boolean complete ) + { + this.retries = retries; + this.complete = complete; + } + + protected UnitOfWorkTemplate( Usecase usecase, int retries, boolean complete ) + { + this.usecase = usecase; + this.retries = retries; + this.complete = complete; + } + + protected abstract RESULT withUnitOfWork( UnitOfWork uow ) + throws ThrowableType; + + @SuppressWarnings( "unchecked" ) + public RESULT withModule( Module module ) + throws ThrowableType, UnitOfWorkCompletionException + { + int loop = 0; + ThrowableType ex = null; + do + { + UnitOfWork uow = module.unitOfWorkFactory().newUnitOfWork( usecase ); + + try + { + RESULT result = withUnitOfWork( uow ); + if( complete ) + { + try + { + uow.complete(); + return result; + } + catch( ConcurrentEntityModificationException e ) + { + // Retry? + ex = (ThrowableType) e; + } + } + } + catch( Throwable e ) + { + ex = (ThrowableType) e; + } + finally + { + uow.discard(); + } + } + while( loop++ < retries ); + + throw ex; + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/1c722f44/core/api/src/main/java/org/apache/polygene/api/unitofwork/concern/UnitOfWorkConcern.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/polygene/api/unitofwork/concern/UnitOfWorkConcern.java b/core/api/src/main/java/org/apache/polygene/api/unitofwork/concern/UnitOfWorkConcern.java new file mode 100644 index 0000000..f982c45 --- /dev/null +++ b/core/api/src/main/java/org/apache/polygene/api/unitofwork/concern/UnitOfWorkConcern.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.api.unitofwork.concern; + +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; +import org.apache.polygene.api.common.AppliesTo; +import org.apache.polygene.api.concern.GenericConcern; +import org.apache.polygene.api.injection.scope.Invocation; +import org.apache.polygene.api.injection.scope.Structure; +import org.apache.polygene.api.unitofwork.ConcurrentEntityModificationException; +import org.apache.polygene.api.unitofwork.UnitOfWork; +import org.apache.polygene.api.unitofwork.UnitOfWorkFactory; +import org.apache.polygene.api.usecase.Usecase; +import org.apache.polygene.api.usecase.UsecaseBuilder; + +/** + * {@code UnitOfWorkConcern} manages the unit of work complete, discard and retry policy. + * + * @see UnitOfWorkPropagation + * @see UnitOfWorkDiscardOn + */ +@AppliesTo( UnitOfWorkPropagation.class ) +public class UnitOfWorkConcern + extends GenericConcern +{ + private static final Class<?>[] DEFAULT_DISCARD_CLASSES = new Class[]{ Throwable.class }; + + @Structure + private UnitOfWorkFactory uowf; + + @Invocation + private UnitOfWorkPropagation propagation; + + /** + * Handles method with {@code UnitOfWorkPropagation} annotation. + * + * @param proxy The object. + * @param method The invoked method. + * @param args The method arguments. + * + * @return The returned value of method invocation. + * + * @throws Throwable Thrown if the method invocation throw exception. + */ + @Override + public Object invoke( Object proxy, Method method, Object[] args ) + throws Throwable + { + UnitOfWorkPropagation.Propagation propagationPolicy = propagation.value(); + if( propagationPolicy == UnitOfWorkPropagation.Propagation.REQUIRED ) + { + if( uowf.isUnitOfWorkActive() ) + { + //noinspection ConstantConditions + return next.invoke( proxy, method, args ); + } + else + { + Usecase usecase = usecase(); + return invokeWithCommit( proxy, method, args, uowf.newUnitOfWork( usecase ) ); + } + } + else if( propagationPolicy == UnitOfWorkPropagation.Propagation.MANDATORY ) + { + if( !uowf.isUnitOfWorkActive() ) + { + throw new IllegalStateException( "UnitOfWork was required but there is no available unit of work." ); + } + } + else if( propagationPolicy == UnitOfWorkPropagation.Propagation.REQUIRES_NEW ) + { + Usecase usecase = usecase(); + return invokeWithCommit( proxy, method, args, uowf.newUnitOfWork( usecase ) ); + } + //noinspection ConstantConditions + return next.invoke( proxy, method, args ); + } + + private Usecase usecase() + { + String usecaseName = propagation.usecase(); + Usecase usecase; + if( usecaseName == null ) + { + usecase = Usecase.DEFAULT; + } + else + { + usecase = UsecaseBuilder.newUsecase( usecaseName ); + } + return usecase; + } + + protected Object invokeWithCommit( Object proxy, Method method, Object[] args, UnitOfWork currentUnitOfWork ) + throws Throwable + { + try + { + UnitOfWorkRetry retryAnnot = method.getAnnotation( UnitOfWorkRetry.class ); + int maxTries = 0; + long delayFactor = 0; + long initialDelay = 0; + if( retryAnnot != null ) + { + maxTries = retryAnnot.retries(); + initialDelay = retryAnnot.initialDelay(); + delayFactor = retryAnnot.delayFactor(); + } + int retry = 0; + while( true ) + { + //noinspection ConstantConditions + Object result = next.invoke( proxy, method, args ); + try + { + currentUnitOfWork.complete(); + return result; + } + catch( UndeclaredThrowableException e ) + { + Throwable undeclared = e.getUndeclaredThrowable(); + if( undeclared instanceof ConcurrentEntityModificationException ) + { + ConcurrentEntityModificationException ceme = (ConcurrentEntityModificationException) undeclared; + currentUnitOfWork = checkRetry( maxTries, delayFactor, initialDelay, retry, ceme ); + retry++; + } + else + { + throw e; + } + } + catch( ConcurrentEntityModificationException e ) + { + currentUnitOfWork = checkRetry( maxTries, delayFactor, initialDelay, retry, e ); + retry++; + } + } + } + catch( Throwable throwable ) + { + // Discard only if this concern create a unit of work + discardIfRequired( method, currentUnitOfWork, throwable ); + throw throwable; + } + } + + private UnitOfWork checkRetry( int maxTries, + long delayFactor, + long initialDelay, + int retry, + ConcurrentEntityModificationException e + ) + throws ConcurrentEntityModificationException, InterruptedException + { + if( retry >= maxTries ) + { + throw e; + } + uowf.currentUnitOfWork().discard(); + Thread.sleep( initialDelay + retry * delayFactor ); + return uowf.newUnitOfWork( usecase() ); + } + + /** + * Discard unit of work if the discard policy match. + * + * @param aMethod The invoked method. This argument must not be {@code null}. + * @param aUnitOfWork The current unit of work. This argument must not be {@code null}. + * @param aThrowable The exception thrown. This argument must not be {@code null}. + */ + protected void discardIfRequired( Method aMethod, UnitOfWork aUnitOfWork, Throwable aThrowable ) + { + UnitOfWorkDiscardOn discardPolicy = aMethod.getAnnotation( UnitOfWorkDiscardOn.class ); + Class<?>[] discardClasses; + if( discardPolicy != null ) + { + discardClasses = discardPolicy.value(); + } + else + { + discardClasses = DEFAULT_DISCARD_CLASSES; + } + + Class<? extends Throwable> aThrowableClass = aThrowable.getClass(); + for( Class<?> discardClass : discardClasses ) + { + if( discardClass.isAssignableFrom( aThrowableClass ) ) + { + aUnitOfWork.discard(); + } + } + } +} \ No newline at end of file
