Return-Path: X-Original-To: apmail-zest-commits-archive@minotaur.apache.org Delivered-To: apmail-zest-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id D4211186A3 for ; Thu, 30 Jul 2015 19:48:00 +0000 (UTC) Received: (qmail 59700 invoked by uid 500); 30 Jul 2015 19:48:00 -0000 Delivered-To: apmail-zest-commits-archive@zest.apache.org Received: (qmail 59648 invoked by uid 500); 30 Jul 2015 19:48:00 -0000 Mailing-List: contact commits-help@zest.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@zest.apache.org Delivered-To: mailing list commits@zest.apache.org Received: (qmail 59535 invoked by uid 99); 30 Jul 2015 19:48:00 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 30 Jul 2015 19:48:00 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 462DAE714D; Thu, 30 Jul 2015 19:48:00 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: niclas@apache.org To: commits@zest.apache.org Date: Thu, 30 Jul 2015 19:48:06 -0000 Message-Id: <66e1d62166ba4f1db60c6281819949f9@git.apache.org> In-Reply-To: <4cbd2683fa884aa6bf1c8b2865185ebf@git.apache.org> References: <4cbd2683fa884aa6bf1c8b2865185ebf@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [07/80] [partial] zest-java git commit: First round of changes to move to org.apache.zest namespace. http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookup.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookup.java b/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookup.java new file mode 100644 index 0000000..0ebfbbb --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/structure/TypeLookup.java @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2008-2012, Rickard Öberg. + * Copyright (c) 2008-2012, Niclas Hedhman. + * Copyright (c) 2012, Paul Merlin. + * + * 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.apache.zest.runtime.structure; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.zest.api.composite.AmbiguousTypeException; +import org.apache.zest.api.composite.ModelDescriptor; +import org.apache.zest.api.service.NoSuchServiceException; +import org.apache.zest.api.service.ServiceReference; +import org.apache.zest.functional.Function; +import org.apache.zest.functional.Specification; +import org.apache.zest.functional.Specifications; +import org.apache.zest.runtime.composite.TransientModel; +import org.apache.zest.runtime.entity.EntityModel; +import org.apache.zest.runtime.object.ObjectModel; +import org.apache.zest.runtime.value.ValueModel; +import org.apache.zest.spi.module.ModelModule; + +import static org.apache.zest.api.common.Visibility.application; +import static org.apache.zest.api.common.Visibility.layer; +import static org.apache.zest.api.common.Visibility.module; +import static org.apache.zest.api.util.Classes.RAW_CLASS; +import static org.apache.zest.api.util.Classes.interfacesOf; +import static org.apache.zest.functional.Iterables.cast; +import static org.apache.zest.functional.Iterables.filter; +import static org.apache.zest.functional.Iterables.first; +import static org.apache.zest.functional.Iterables.flatten; +import static org.apache.zest.functional.Iterables.flattenIterables; +import static org.apache.zest.functional.Iterables.iterable; +import static org.apache.zest.functional.Iterables.toList; +import static org.apache.zest.functional.Iterables.unique; + +/** + * Central place for Composite Type lookups. + */ +public class TypeLookup +{ + + // Constructor parameters + private final ModuleInstance moduleInstance; + // Eager instance objects + private final Map, ModelModule> objectModels; + private final Map, ModelModule> transientModels; + private final Map, ModelModule> valueModels; + private final Map, Iterable>> allEntityModels; + private final Map, ModelModule> unambiguousEntityModels; + private final Map> serviceReferences; + private final Map>> servicesReferences; + + /** + * Create a new TypeLookup bound to the given ModuleInstance. + * + * @param moduleInstance ModuleInstance bound to this TypeLookup + */ + /* package */ TypeLookup( ModuleInstance moduleInstance ) + { + // Constructor parameters + this.moduleInstance = moduleInstance; + + // Eager instance objects + objectModels = new ConcurrentHashMap<>(); + transientModels = new ConcurrentHashMap<>(); + valueModels = new ConcurrentHashMap<>(); + allEntityModels = new ConcurrentHashMap<>(); + unambiguousEntityModels = new ConcurrentHashMap<>(); + serviceReferences = new ConcurrentHashMap<>(); + servicesReferences = new ConcurrentHashMap<>(); + } + + /** + * Lookup first Object Model matching the given Type. + * + *

First, if Object Models exactly match the given type, the closest one (Visibility then Assembly order) is returned. + * Multiple exact matches with the same Visibility are forbidden and result in an AmbiguousTypeException.

+ * + *

Second, if Object Models match a type assignable to the given type, the closest one (Visibility then Assembly order) is returned. + * Multiple assignable matches with the same Visibility are forbidden and result in an AmbiguousTypeException.

+ * + *

Type lookup is done lazily and cached.

+ * + * @param type Looked up Type + * + * @return First matching Object Model + */ + @SuppressWarnings( { "raw", "unchecked" } ) + /* package */ ModelModule lookupObjectModel( final Class type ) + { + ModelModule model = objectModels.get( type ); + + if( model == null ) + { + // Unambiguously and lazily resolve ObjectModel + Iterable> flatten = flatten( + ambiguousTypeCheck( type, + findModels( new ExactTypeLookupSpecification( type ), + moduleInstance.visibleObjects( module ), + moduleInstance.layerInstance().visibleObjects( layer ), + moduleInstance.layerInstance().visibleObjects( application ), + moduleInstance.layerInstance() + .usedLayersInstance() + .visibleObjects() ) ), + ambiguousTypeCheck( type, + findModels( new AssignableTypeLookupSpecification( type ), + moduleInstance.visibleObjects( module ), + moduleInstance.layerInstance().visibleObjects( layer ), + moduleInstance.layerInstance().visibleObjects( application ), + moduleInstance.layerInstance() + .usedLayersInstance() + .visibleObjects() ) ) ); + + model = first( flatten ); + + if( model != null ) + { + objectModels.put( type, model ); + } + } + + return model; + } + + /** + * Lookup first Transient Model matching the given Type. + * + *

First, if Transient Models exactly match the given type, the closest one (Visibility then Assembly order) is returned. + * Multiple exact matches with the same Visibility are forbidden and result in an AmbiguousTypeException.

+ * + *

Second, if Transient Models match a type assignable to the given type, the closest one (Visibility then Assembly order) is returned. + * Multiple assignable matches with the same Visibility are forbidden and result in an AmbiguousTypeException.

+ * + *

Type lookup is done lazily and cached.

+ * + * @param type Looked up Type + * + * @return First matching Transient Model + */ + @SuppressWarnings( { "raw", "unchecked" } ) + /* package */ ModelModule lookupTransientModel( final Class type ) + { + ModelModule model = transientModels.get( type ); + + if( model == null ) + { + // Unambiguously and lazily resolve TransientModel + Iterable> allModels = flatten( + ambiguousTypeCheck( type, + findModels( new ExactTypeLookupSpecification( type ), + moduleInstance.visibleTransients( module ), + moduleInstance.layerInstance().visibleTransients( layer ), + moduleInstance.layerInstance().visibleTransients( application ), + moduleInstance.layerInstance().usedLayersInstance().visibleTransients() + ) + ), + + ambiguousTypeCheck( type, + findModels( new AssignableTypeLookupSpecification( type ), + moduleInstance.visibleTransients( module ), + moduleInstance.layerInstance().visibleTransients( layer ), + moduleInstance.layerInstance().visibleTransients( application ), + moduleInstance.layerInstance().usedLayersInstance().visibleTransients() + ) + ) + ); + model = first( allModels ); + + if( model != null ) + { + transientModels.put( type, model ); + } + } + + return model; + } + + /** + * Lookup first Value Model matching the given Type. + * + *

First, if Value Models exactly match the given type, the closest one (Visibility then Assembly order) is returned. + * Multiple exact matches with the same Visibility are forbidden and result in an AmbiguousTypeException.

+ * + *

Second, if Value Models match a type assignable to the given type, the closest one (Visibility then Assembly order) is returned. + * Multiple assignable matches with the same Visibility are forbidden and result in an AmbiguousTypeException.

+ * + *

Type lookup is done lazily and cached.

+ * + * @param type Looked up Type + * + * @return First matching Value Model + */ + @SuppressWarnings( { "raw", "unchecked" } ) + public ModelModule lookupValueModel( final Class type ) + { + ModelModule model = valueModels.get( type ); + + if( model == null ) + { + // Unambiguously and lazily resolve ValueModel + Iterable> flatten = flatten( + ambiguousTypeCheck( type, + findModels( new ExactTypeLookupSpecification( type ), + moduleInstance.visibleValues( module ), + moduleInstance.layerInstance().visibleValues( layer ), + moduleInstance.layerInstance().visibleValues( application ), + moduleInstance.layerInstance().usedLayersInstance().visibleValues() ) ), + ambiguousTypeCheck( type, + findModels( new AssignableTypeLookupSpecification( type ), + moduleInstance.visibleValues( module ), + moduleInstance.layerInstance().visibleValues( layer ), + moduleInstance.layerInstance().visibleValues( application ), + moduleInstance.layerInstance().usedLayersInstance().visibleValues() + ) + ) + ); + + model = first( flatten ); + + if( model != null ) + { + valueModels.put( type, model ); + } + } + + return model; + } + + /** + * Lookup first Entity Model matching the given Type. + * + *

First, if Entity Models exactly match the given type, the closest one (Visibility then Assembly order) is returned. + * Multiple exact matches with the same Visibility are forbidden and result in an AmbiguousTypeException.

+ * + *

Second, if Entity Models match a type assignable to the given type, the closest one (Visibility then Assembly order) is returned. + * Multiple assignable matches with the same Visibility are forbidden and result in an AmbiguousTypeException.

+ * + *

Type lookup is done lazily and cached.

+ * + *

Should be used for creational use cases only. For non-creational use cases see + * {@link #lookupEntityModels(java.lang.Class)}.

+ * + * @param type Looked up Type + * + * @return First matching Entity Model + */ + @SuppressWarnings( { "raw", "unchecked" } ) + /* package */ ModelModule lookupEntityModel( final Class type ) + { + ModelModule model = unambiguousEntityModels.get( type ); + + if( model == null ) + { + // Unambiguously and lazily resolve EntityModels + Iterable> allModels = flatten( + ambiguousTypeCheck( type, + findModels( new ExactTypeLookupSpecification( type ), + moduleInstance.visibleEntities( module ), + moduleInstance.layerInstance().visibleEntities( layer ), + moduleInstance.layerInstance().visibleEntities( application ), + moduleInstance.layerInstance() + .usedLayersInstance() + .visibleEntities() ) ), + ambiguousTypeCheck( type, + findModels( new AssignableTypeLookupSpecification( type ), + moduleInstance.visibleEntities( module ), + moduleInstance.layerInstance().visibleEntities( layer ), + moduleInstance.layerInstance().visibleEntities( application ), + moduleInstance.layerInstance().usedLayersInstance().visibleEntities() + ) + ) + ); + + model = first( allModels ); + + if( model != null ) + { + unambiguousEntityModels.put( type, model ); + } + } + + return model; + } + + /** + * Lookup all Entity Models matching the given Type. + * + *

Returned Iterable contains, in order, Entity Models that:

+ * + *
    + *
  • exactly match the given type, in Visibility then Assembly order ;
  • + *
  • match a type assignable to the given type, in Visibility then Assembly order.
  • + *
+ * + *

Multiple exact matches with the same Visibility are forbidden and result in an AmbiguousTypeException.

+ *

Multiple assignable matches are allowed to enable polymorphic fetches and queries.

+ * + *

Type lookup is done lazily and cached.

+ * + *

Should be used for non-creational use cases only. For creational use cases see + * {@link #lookupEntityModel(java.lang.Class)}.

+ * + * @param type Looked up Type + * + * @return All matching Entity Models + */ + @SuppressWarnings( { "raw", "unchecked" } ) + /* package */ Iterable> lookupEntityModels( final Class type ) + { + Iterable> models = allEntityModels.get( type ); + if( models == null ) + { + // Ambiguously and lasily resolve EntityModels + Iterable> matchingEntityModels = flatten( + ambiguousTypeCheck( type, + findModels( new ExactTypeLookupSpecification( type ), + moduleInstance.visibleEntities( module ), + moduleInstance.layerInstance().visibleEntities( layer ), + moduleInstance.layerInstance().visibleEntities( application ), + moduleInstance.layerInstance().usedLayersInstance().visibleEntities() + ) + ), + findModels( new AssignableTypeLookupSpecification( type ), + moduleInstance.visibleEntities( module ), + moduleInstance.layerInstance().visibleEntities( layer ), + moduleInstance.layerInstance().visibleEntities( application ), + moduleInstance.layerInstance().usedLayersInstance().visibleEntities() ) ); + + // Don't return the same EntityModel multiple times + matchingEntityModels = unique( matchingEntityModels ); + + models = toList( matchingEntityModels ); + allEntityModels.put( type, models ); + } + return models; + } + + /** + * Lookup first ServiceReference matching the given Type. + * + *

Type lookup is done lazily and cached.

+ * + *

See {@link #lookupServiceReferences(java.lang.reflect.Type)}.

+ * + * @param Service Type + * @param serviceType Looked up Type + * + * @return First matching ServiceReference + */ + /* package */ + @SuppressWarnings( "unchecked" ) + ServiceReference lookupServiceReference( Type serviceType ) + { + ServiceReference serviceReference = serviceReferences.get( serviceType ); + if( serviceReference == null ) + { + // Lazily resolve ServiceReference + serviceReference = first( lookupServiceReferences( serviceType ) ); + if( serviceReference != null ) + { + serviceReferences.put( serviceType, serviceReference ); + } + } + + if( serviceReference == null ) + { + throw new NoSuchServiceException( RAW_CLASS.map( serviceType ).getName(), moduleInstance.name() ); + } + + return (ServiceReference) serviceReference; + } + + /** + * Lookup all ServiceReferences matching the given Type. + * + *

Returned Iterable contains, in order, ServiceReferences that:

+ * + *
    + *
  • exactly match the given type, in Visibility then Assembly order ;
  • + *
  • match a type assignable to the given type, in Visibility then Assembly order.
  • + *
+ * + *

Multiple exact matches with the same Visibility are allowed to enable polymorphic lookup/injection.

+ *

Multiple assignable matches with the same Visibility are allowed for the very same reason.

+ * + *

Type lookup is done lazily and cached.

+ * + * @param Service Type + * @param serviceType Looked up Type + * + * @return All matching ServiceReferences + */ + @SuppressWarnings( "unchecked" ) + /* package */ Iterable> lookupServiceReferences( final Type serviceType ) + { + Iterable> serviceRefs = servicesReferences.get( serviceType ); + if( serviceRefs == null ) + { + // Lazily resolve ServicesReferences + Iterable> matchingServices = flatten( + findServiceReferences( new ExactTypeLookupSpecification( serviceType ), + moduleInstance.visibleServices( module ), + moduleInstance.layerInstance().visibleServices( layer ), + moduleInstance.layerInstance().visibleServices( application ), + moduleInstance.layerInstance().usedLayersInstance().visibleServices() ), + findServiceReferences( new AssignableTypeLookupSpecification( serviceType ), + moduleInstance.visibleServices( module ), + moduleInstance.layerInstance().visibleServices( layer ), + moduleInstance.layerInstance().visibleServices( application ), + moduleInstance.layerInstance().usedLayersInstance().visibleServices() ) ); + + // Don't return the same ServiceReference multiple times + matchingServices = unique( matchingServices ); + + serviceRefs = toList( matchingServices ); + servicesReferences.put( serviceType, serviceRefs ); + } + + return cast( serviceRefs ); + } + + @SuppressWarnings( { "raw", "unchecked" } ) + private static Iterable> findModels( Specification>> specification, + Iterable>... models + ) + { + Specification> spec = Specifications.translate( new ModelModuleTypesFunction(), specification ); + Iterable> flattened = flattenIterables( iterable( models ) ); + return filter( spec, flattened ); + } + + @SuppressWarnings( { "raw", "unchecked" } ) + private static Iterable> findServiceReferences( Specification>> specification, + Iterable>... references + ) + { + Specification> spec = Specifications.translate( new ServiceReferenceTypesFunction(), specification ); + Iterable> flattened = flattenIterables( iterable( references ) ); + return filter( spec, flattened ); + } + + /** + * Check if the list of models contains several ones with the same visibility. If yes, then + * throw an AmbiguousTypeException + */ + @SuppressWarnings( "raw" ) + private static Iterable> ambiguousTypeCheck( final Class type, + final Iterable> models + ) + { + return new Iterable>() + { + + @Override + public Iterator> iterator() + { + ModelModule current = null; + List> ambiguous = null; + List> results = new ArrayList<>(); + for( ModelModule model : models ) + { + if( current != null && !model.equals( current ) ) + { + if( model.model().visibility() == current.model().visibility() ) + { + if( ambiguous == null ) + { + ambiguous = new ArrayList<>(); + } + ambiguous.add( model ); + } + } + else + { + current = model; + } + results.add( model ); + } + if( ambiguous != null ) + { + // Check if we had any ambiguities + ambiguous.add( current ); + throw new AmbiguousTypeException( "More than one type matches " + type.getName() + ":" + ambiguous ); + } + // Ambiguity check done, and no ambiguities found. Return results + return results.iterator(); + } + }; + } + + private static class ModelModuleTypesFunction + implements Function, Iterable>> + { + + @Override + public Iterable> map( ModelModule modelModule ) + { + return modelModule.model().types(); + } + } + + private static class ServiceReferenceTypesFunction + implements Function, Iterable>> + { + + @Override + public Iterable> map( ServiceReference serviceReference ) + { + return serviceReference.types(); + } + } + + private static abstract class AbstractTypeLookupSpecification + implements Specification>> + { + + protected final Type lookedUpType; + + private AbstractTypeLookupSpecification( Type lookedUpType ) + { + this.lookedUpType = lookedUpType; + } + + @Override + public final boolean satisfiedBy( Iterable> types ) + { + if( lookedUpType instanceof Class ) + { + // Straight class assignability check + return checkClassMatch( types, (Class) lookedUpType ); + } + else + { + if( lookedUpType instanceof ParameterizedType ) + { + // Foo check + // First check Foo + ParameterizedType parameterizedType = (ParameterizedType) lookedUpType; + if( !checkClassMatch( types, (Class) parameterizedType.getRawType() ) ) + { + return false; + } + // Then check Bar + for( Type intf : interfacesOf( types ) ) + { + if( intf.equals( lookedUpType ) ) + { + // All parameters are the same - ok! + return true; + } + } + return false; + } + else if( lookedUpType instanceof WildcardType ) + { + return true; + } + return false; + } + } + + private boolean checkClassMatch( Iterable> candidates, Class lookedUpType ) + { + for( Class candidate : candidates ) + { + if( checkClassMatch( candidate, lookedUpType ) ) + { + return true; + } + } + return false; + } + + protected abstract boolean checkClassMatch( Class candidate, Class lookedUpType ); + } + + private static final class ExactTypeLookupSpecification + extends AbstractTypeLookupSpecification + { + + private ExactTypeLookupSpecification( Type lookedupType ) + { + super( lookedupType ); + } + + @Override + protected boolean checkClassMatch( Class candidate, Class lookedUpType ) + { + return candidate.equals( lookedUpType ); + } + } + + private static final class AssignableTypeLookupSpecification + extends AbstractTypeLookupSpecification + { + + private AssignableTypeLookupSpecification( Type lookedupType ) + { + super( lookedupType ); + } + + @Override + protected boolean checkClassMatch( Class candidate, Class lookedUpType ) + { + return !candidate.equals( lookedUpType ) && lookedUpType.isAssignableFrom( candidate ); + } + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/structure/UsedLayersInstance.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/structure/UsedLayersInstance.java b/core/runtime/src/main/java/org/apache/zest/runtime/structure/UsedLayersInstance.java new file mode 100644 index 0000000..12893cf --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/structure/UsedLayersInstance.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2008, Rickard Öberg. All Rights Reserved. + * + * 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.apache.zest.runtime.structure; + +import java.util.List; +import org.apache.zest.api.common.Visibility; +import org.apache.zest.api.composite.TransientDescriptor; +import org.apache.zest.api.entity.EntityDescriptor; +import org.apache.zest.api.object.ObjectDescriptor; +import org.apache.zest.api.service.ServiceReference; +import org.apache.zest.api.value.ValueDescriptor; +import org.apache.zest.functional.Function; +import org.apache.zest.spi.module.ModelModule; + +import static org.apache.zest.functional.Iterables.*; + +/** + * JAVADOC + */ +public final class UsedLayersInstance +{ + private final List usedLayerInstances; + + public UsedLayersInstance( List usedLayerInstances ) + { + this.usedLayerInstances = usedLayerInstances; + } + + /* package */ Iterable> visibleObjects() + { + return flattenIterables( map( new Function>>() + { + @Override + public Iterable> map( LayerInstance layerInstance ) + { + return layerInstance.visibleObjects( Visibility.application ); + } + }, usedLayerInstances ) ); + } + + /* package */ Iterable> visibleTransients() + { + return flattenIterables( map( new Function>>() + { + @Override + public Iterable> map( LayerInstance layerInstance ) + { + return layerInstance.visibleTransients( Visibility.application ); + } + }, usedLayerInstances ) ); + } + + /* package */ Iterable> visibleEntities() + { + return flattenIterables( map( new Function>>() + { + @Override + public Iterable> map( LayerInstance layerInstance ) + { + return layerInstance.visibleEntities( Visibility.application ); + } + }, usedLayerInstances ) ); + } + + /* package */ Iterable> visibleValues() + { + return flattenIterables( map( new Function>>() + { + @Override + public Iterable> map( LayerInstance layerInstance ) + { + return layerInstance.visibleValues( Visibility.application ); + } + }, usedLayerInstances ) ); + } + + /* package */ Iterable> visibleServices() + { + return flattenIterables( map( new Function>>() + { + @Override + public Iterable> map( LayerInstance layerInstance ) + { + return layerInstance.visibleServices( Visibility.application ); + } + }, usedLayerInstances ) ); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/structure/UsedLayersModel.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/structure/UsedLayersModel.java b/core/runtime/src/main/java/org/apache/zest/runtime/structure/UsedLayersModel.java new file mode 100644 index 0000000..1f098a9 --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/structure/UsedLayersModel.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2008, Rickard Öberg. All Rights Reserved. + * + * 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.apache.zest.runtime.structure; + +import java.util.List; +import org.apache.zest.api.structure.UsedLayersDescriptor; +import org.apache.zest.functional.HierarchicalVisitor; +import org.apache.zest.functional.VisitableHierarchy; + +/** + * JAVADOC + */ +public final class UsedLayersModel + implements UsedLayersDescriptor, VisitableHierarchy +{ + private final List usedLayers; + + public UsedLayersModel( List usedLayers ) + { + this.usedLayers = usedLayers; + } + + @Override + public Iterable layers() + { + return usedLayers; + } + + @Override + public boolean accept( HierarchicalVisitor visitor ) + throws ThrowableType + { + if( visitor.visitEnter( this ) ) + { + for( LayerModel usedLayer : usedLayers ) + { + if( !usedLayer.accept( visitor ) ) + { + break; + } + } + } + + return visitor.visitLeave( this ); + } + + public UsedLayersInstance newInstance( List usedLayerInstances ) + { + return new UsedLayersInstance( usedLayerInstances ); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/structure/VisibilitySpecification.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/structure/VisibilitySpecification.java b/core/runtime/src/main/java/org/apache/zest/runtime/structure/VisibilitySpecification.java new file mode 100644 index 0000000..bb646eb --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/structure/VisibilitySpecification.java @@ -0,0 +1,47 @@ +/* + * 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.zest.runtime.structure; + +import org.apache.zest.api.common.Visibility; +import org.apache.zest.api.composite.ModelDescriptor; +import org.apache.zest.functional.Specification; + +/** + * TODO + */ +public class VisibilitySpecification + implements Specification +{ + public static final Specification MODULE = new VisibilitySpecification( Visibility.module ); + public static final Specification LAYER = new VisibilitySpecification( Visibility.layer ); + public static final Specification APPLICATION = new VisibilitySpecification( Visibility.application ); + + private final Visibility visibility; + + public VisibilitySpecification( Visibility visibility ) + { + this.visibility = visibility; + } + + @Override + public boolean satisfiedBy( ModelDescriptor item ) + { + return item.visibility().ordinal() >= visibility.ordinal(); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/types/ValueTypeFactory.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/types/ValueTypeFactory.java b/core/runtime/src/main/java/org/apache/zest/runtime/types/ValueTypeFactory.java new file mode 100644 index 0000000..2c7951c --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/types/ValueTypeFactory.java @@ -0,0 +1,230 @@ +/* + * Copyright 2009 Niclas Hedhman. + * + * 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.apache.zest.runtime.types; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import org.apache.zest.api.common.InvalidApplicationException; +import org.apache.zest.api.common.MetaInfo; +import org.apache.zest.api.common.Visibility; +import org.apache.zest.api.type.CollectionType; +import org.apache.zest.api.type.EnumType; +import org.apache.zest.api.type.MapType; +import org.apache.zest.api.type.Serialization; +import org.apache.zest.api.type.ValueCompositeType; +import org.apache.zest.api.type.ValueType; +import org.apache.zest.api.util.Classes; +import org.apache.zest.api.value.ValueComposite; +import org.apache.zest.functional.HierarchicalVisitorAdapter; +import org.apache.zest.functional.Iterables; +import org.apache.zest.functional.Specifications; +import org.apache.zest.runtime.association.AssociationsModel; +import org.apache.zest.runtime.association.ManyAssociationsModel; +import org.apache.zest.runtime.association.NamedAssociationsModel; +import org.apache.zest.runtime.composite.CompositeMethodsModel; +import org.apache.zest.runtime.composite.MixinsModel; +import org.apache.zest.runtime.property.PropertiesModel; +import org.apache.zest.runtime.structure.LayerModel; +import org.apache.zest.runtime.structure.ModuleModel; +import org.apache.zest.runtime.structure.UsedLayersModel; +import org.apache.zest.runtime.value.ValueModel; +import org.apache.zest.runtime.value.ValueStateModel; +import org.apache.zest.runtime.value.ValuesModel; + +public class ValueTypeFactory +{ + private static final ValueTypeFactory instance = new ValueTypeFactory(); + + public static ValueTypeFactory instance() + { + return instance; + } + + @SuppressWarnings( {"raw", "unchecked"} ) + public ValueType newValueType( Type type, + Class declaringClass, + Class compositeType, + LayerModel layer, + ModuleModel module, + Serialization.Variant variant + ) + { + ValueType valueType = null; + if( CollectionType.isCollection( type ) ) + { + if( type instanceof ParameterizedType ) + { + ParameterizedType pt = (ParameterizedType) type; + Type collectionType = pt.getActualTypeArguments()[ 0 ]; + if( collectionType instanceof TypeVariable ) + { + TypeVariable collectionTypeVariable = (TypeVariable) collectionType; + collectionType = Classes.resolveTypeVariable( collectionTypeVariable, declaringClass, compositeType ); + } + ValueType collectedType = newValueType( collectionType, declaringClass, compositeType, layer, module, variant ); + valueType = new CollectionType( Classes.RAW_CLASS.map( type ), collectedType ); + } + else + { + ValueType collectedType = newValueType( Object.class, declaringClass, compositeType, layer, module, variant ); + valueType = new CollectionType( Classes.RAW_CLASS.map( type ), collectedType ); + } + } + else if( MapType.isMap( type ) ) + { + if( type instanceof ParameterizedType ) + { + ParameterizedType pt = (ParameterizedType) type; + Type keyType = pt.getActualTypeArguments()[ 0 ]; + if( keyType instanceof TypeVariable ) + { + TypeVariable keyTypeVariable = (TypeVariable) keyType; + keyType = Classes.resolveTypeVariable( keyTypeVariable, declaringClass, compositeType ); + } + ValueType keyedType = newValueType( keyType, declaringClass, compositeType, layer, module, variant ); + Type valType = pt.getActualTypeArguments()[ 1 ]; + if( valType instanceof TypeVariable ) + { + TypeVariable valueTypeVariable = (TypeVariable) valType; + valType = Classes.resolveTypeVariable( valueTypeVariable, declaringClass, compositeType ); + } + ValueType valuedType = newValueType( valType, declaringClass, compositeType, layer, module, variant ); + valueType = new MapType( Classes.RAW_CLASS.map( type ), keyedType, valuedType, variant ); + } + else + { + ValueType keyType = newValueType( Object.class, declaringClass, compositeType, layer, module, variant ); + ValueType valuesType = newValueType( Object.class, declaringClass, compositeType, layer, module, variant ); + valueType = new MapType( Classes.RAW_CLASS.map( type ), keyType, valuesType, variant ); + } + } + else if( ValueCompositeType.isValueComposite( type ) ) + { + // Find ValueModel in module/layer/used layers + ValueModel model = new ValueFinder( layer, module, Classes.RAW_CLASS.map( type ) ).getFoundModel(); + + if( model == null ) + { + if( type.equals( ValueComposite.class ) ) + { + // Create default model + MixinsModel mixinsModel = new MixinsModel(); + Iterable valueComposite = (Iterable) Iterables.iterable( ValueComposite.class ); + ValueStateModel valueStateModel = new ValueStateModel( new PropertiesModel(), + new AssociationsModel(), + new ManyAssociationsModel(), + new NamedAssociationsModel() ); + model = new ValueModel( valueComposite, Visibility.application, new MetaInfo(), + mixinsModel, valueStateModel, new CompositeMethodsModel( mixinsModel ) ); + } + else + { + throw new InvalidApplicationException( "[" + module.name() + "] Could not find ValueComposite of type " + type ); + } + } + + return model.valueType(); + } + else if( EnumType.isEnum( type ) ) + { + valueType = new EnumType( Classes.RAW_CLASS.map( type ) ); + } + else + { + valueType = new ValueType( Classes.RAW_CLASS.map( type ) ); + } + + return valueType; + } + + @SuppressWarnings( "raw" ) + private static class ValueFinder + extends HierarchicalVisitorAdapter + { + private Class type; + private ValueModel foundModel; + private Visibility visibility; + + private ValueFinder( LayerModel layer, ModuleModel module, Class type ) + { + this.type = type; + + visibility = Visibility.module; + module.accept( this ); + + if( foundModel == null ) + { + visibility = Visibility.layer; + layer.accept( this ); + + if( foundModel == null ) + { + visibility = Visibility.application; + layer.usedLayers().accept( this ); + } + } + } + + public ValueModel getFoundModel() + { + return foundModel; + } + + @Override + public boolean visitEnter( Object visited ) + throws RuntimeException + { + if( visited instanceof ValuesModel ) + { + return true; + } + else if( visited instanceof ModuleModel ) + { + return true; + } + else if (visited instanceof LayerModel ) + { + return true; + } + else if (visited instanceof UsedLayersModel ) + { + return true; + } + else if( visited instanceof ValueModel ) + { + ValueModel valueModel = (ValueModel) visited; + boolean typeEquality = Specifications.in( valueModel.types() ).satisfiedBy( type ); + if( typeEquality && valueModel.visibility().ordinal() >= visibility.ordinal() ) + { + foundModel = valueModel; + } + } + + return false; + } + + @Override + public boolean visitLeave( Object visited ) + throws RuntimeException + { + return foundModel == null; + } + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderEntityState.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderEntityState.java b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderEntityState.java new file mode 100644 index 0000000..a390874 --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderEntityState.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2009-2011, Rickard Öberg. All Rights Reserved. + * Copyright (c) 2009-2013, Niclas Hedhman. All Rights Reserved. + * Copyright (c) 2014, Paul Merlin. All Rights Reserved. + * + * 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.apache.zest.runtime.unitofwork; + +import java.util.HashMap; +import java.util.Map; +import org.apache.zest.api.common.QualifiedName; +import org.apache.zest.api.entity.EntityDescriptor; +import org.apache.zest.api.entity.EntityReference; +import org.apache.zest.api.util.Classes; +import org.apache.zest.spi.entity.EntityState; +import org.apache.zest.spi.entity.EntityStatus; +import org.apache.zest.spi.entity.ManyAssociationState; +import org.apache.zest.spi.entity.NamedAssociationState; + +/** + * Implementation of EntityState for use through EntityBuilder. + */ +public final class BuilderEntityState + implements EntityState +{ + private final EntityDescriptor entityType; + private final EntityReference reference; + private final Map properties = new HashMap<>(); + private final Map associations = new HashMap<>(); + private final Map manyAssociations = new HashMap<>(); + private final Map namedAssociations = new HashMap<>(); + + public BuilderEntityState( EntityDescriptor type, EntityReference reference ) + { + this.entityType = type; + this.reference = reference; + } + + @Override + public EntityReference identity() + { + return reference; + } + + @Override + public String version() + { + return ""; + } + + @Override + public long lastModified() + { + return 0; + } + + @Override + public void remove() + { + } + + @Override + public EntityStatus status() + { + return EntityStatus.NEW; + } + + @Override + public boolean isAssignableTo( Class type ) + { + return Classes.exactTypeSpecification( type ).satisfiedBy( entityType ); + } + + @Override + public EntityDescriptor entityDescriptor() + { + return entityType; + } + + @Override + public Object propertyValueOf( QualifiedName stateName ) + { + return properties.get( stateName ); + } + + @Override + public EntityReference associationValueOf( QualifiedName stateName ) + { + return associations.get( stateName ); + } + + @Override + public void setPropertyValue( QualifiedName stateName, Object newValue ) + { + properties.put( stateName, newValue ); + } + + @Override + public void setAssociationValue( QualifiedName stateName, EntityReference newEntity ) + { + associations.put( stateName, newEntity ); + } + + @Override + public ManyAssociationState manyAssociationValueOf( QualifiedName stateName ) + { + ManyAssociationState state = manyAssociations.get( stateName ); + if( state == null ) + { + state = new BuilderManyAssociationState(); + manyAssociations.put( stateName, state ); + } + return state; + } + + @Override + public NamedAssociationState namedAssociationValueOf( QualifiedName stateName ) + { + NamedAssociationState state = namedAssociations.get( stateName ); + if( state == null ) + { + state = new BuilderNamedAssociationState(); + namedAssociations.put( stateName, state ); + } + return state; + } + + public void copyTo( EntityState newEntityState ) + { + for( Map.Entry fromPropertyEntry : properties.entrySet() ) + { + newEntityState.setPropertyValue( fromPropertyEntry.getKey(), fromPropertyEntry.getValue() ); + } + for( Map.Entry fromAssociationEntry : associations.entrySet() ) + { + newEntityState.setAssociationValue( fromAssociationEntry.getKey(), fromAssociationEntry.getValue() ); + } + for( Map.Entry fromManyAssociationEntry : manyAssociations.entrySet() ) + { + QualifiedName qName = fromManyAssociationEntry.getKey(); + ManyAssociationState fromManyAssoc = fromManyAssociationEntry.getValue(); + ManyAssociationState toManyAssoc = newEntityState.manyAssociationValueOf( qName ); + for( EntityReference entityReference : fromManyAssoc ) + { + toManyAssoc.add( 0, entityReference ); + } + } + for( Map.Entry fromNamedAssociationEntry : namedAssociations.entrySet() ) + { + QualifiedName qName = fromNamedAssociationEntry.getKey(); + NamedAssociationState fromNamedAssoc = fromNamedAssociationEntry.getValue(); + NamedAssociationState toNamedAssoc = newEntityState.namedAssociationValueOf( qName ); + for( String name : fromNamedAssoc ) + { + toNamedAssoc.put( name, fromNamedAssoc.get( name ) ); + } + } + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderManyAssociationState.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderManyAssociationState.java b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderManyAssociationState.java new file mode 100644 index 0000000..d549c2a --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderManyAssociationState.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2009, Rickard Öberg. All Rights Reserved. + * + * 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.apache.zest.runtime.unitofwork; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.apache.zest.api.entity.EntityReference; +import org.apache.zest.spi.entity.ManyAssociationState; + +/** + * Default implementation of ManyAssociationState that also + * keeps a list of changes that can be extracted at any time. + */ +public final class BuilderManyAssociationState + implements ManyAssociationState +{ + private List references; + + public BuilderManyAssociationState() + { + references = new ArrayList(); + } + + @Override + public int count() + { + return references.size(); + } + + @Override + public boolean contains( EntityReference entityReference ) + { + return references.contains( entityReference ); + } + + @Override + public boolean add( int i, EntityReference entityReference ) + { + if( references.contains( entityReference ) ) + { + return false; + } + + references.add( i, entityReference ); + return true; + } + + @Override + public boolean remove( EntityReference entityReference ) + { + return references.remove( entityReference ); + } + + @Override + public EntityReference get( int i ) + { + return references.get( i ); + } + + @Override + public Iterator iterator() + { + return references.iterator(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderNamedAssociationState.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderNamedAssociationState.java b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderNamedAssociationState.java new file mode 100644 index 0000000..8ec7a4b --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/BuilderNamedAssociationState.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2011-2013, Niclas Hedhman. All Rights Reserved. + * Copyright (c) 2014, Paul Merlin. All Rights Reserved. + * + * 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.apache.zest.runtime.unitofwork; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.apache.zest.api.entity.EntityReference; +import org.apache.zest.spi.entity.NamedAssociationState; + +/** + * Default implementation of NamedAssociationState that also + * keeps a list of changes that can be extracted at any time. + */ +public final class BuilderNamedAssociationState + implements NamedAssociationState +{ + private final Map references; + + public BuilderNamedAssociationState() + { + references = new HashMap<>(); + } + + @Override + public int count() + { + return references.size(); + } + + @Override + public boolean containsName( String name ) + { + return references.containsKey( name ); + } + + @Override + public boolean put( String name, EntityReference entityReference ) + { + return references.put( name, entityReference ) != null; + } + + @Override + public boolean remove( String name ) + { + return references.remove( name ) != null; + } + + @Override + public EntityReference get( String name ) + { + return references.get( name ); + } + + @Override + public String nameOf( EntityReference entityReference ) + { + for( Map.Entry entry : references.entrySet() ) + { + if( entry.getValue().equals( entityReference ) ) + { + return entry.getKey(); + } + } + return null; + } + + @Override + public Iterator iterator() + { + return references.keySet().iterator(); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/EntityBuilderInstance.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/EntityBuilderInstance.java b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/EntityBuilderInstance.java new file mode 100644 index 0000000..014c28c --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/EntityBuilderInstance.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2007-2009, Rickard Öberg. All Rights Reserved. + * Copyright (c) 2008, Alin Dreghiciu. All Rights Reserved. + * Copyright (c) 2008, Edward Yakop. All Rights Reserved. + * Copyright (c) 2014-2015, Paul Merlin. All Rights Reserved. + * + * 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.apache.zest.runtime.unitofwork; + +import org.apache.zest.api.common.QualifiedName; +import org.apache.zest.api.entity.EntityBuilder; +import org.apache.zest.api.entity.EntityReference; +import org.apache.zest.api.entity.Identity; +import org.apache.zest.api.entity.LifecycleException; +import org.apache.zest.runtime.composite.FunctionStateResolver; +import org.apache.zest.runtime.entity.EntityInstance; +import org.apache.zest.runtime.entity.EntityModel; +import org.apache.zest.spi.module.ModelModule; +import org.apache.zest.runtime.structure.ModuleUnitOfWork; +import org.apache.zest.spi.entity.EntityState; +import org.apache.zest.spi.entitystore.EntityStoreUnitOfWork; + +/** + * Implementation of EntityBuilder. Maintains an instance of the entity which + * will not have its state validated until it is created by calling newInstance(). + */ +public final class EntityBuilderInstance + implements EntityBuilder +{ + private static final QualifiedName IDENTITY_STATE_NAME; + + private final ModelModule model; + private final ModuleUnitOfWork uow; + private final EntityStoreUnitOfWork store; + private String identity; + + private final BuilderEntityState entityState; + private final EntityInstance prototypeInstance; + + static + { + try + { + IDENTITY_STATE_NAME = QualifiedName.fromAccessor( Identity.class.getMethod( "identity" ) ); + } + catch( NoSuchMethodException e ) + { + throw new InternalError( "Zest Core Runtime codebase is corrupted. Contact Zest team: EntityBuilderInstance" ); + } + } + + public EntityBuilderInstance( + ModelModule model, + ModuleUnitOfWork uow, + EntityStoreUnitOfWork store, + String identity + ) + { + this( model, uow, store, identity, null ); + } + + public EntityBuilderInstance( + ModelModule model, + ModuleUnitOfWork uow, + EntityStoreUnitOfWork store, + String identity, + FunctionStateResolver stateResolver + ) + { + this.model = model; + this.uow = uow; + this.store = store; + this.identity = identity; + EntityReference reference = new EntityReference( identity ); + entityState = new BuilderEntityState( model.model(), reference ); + model.model().initState( model.module(), entityState ); + if( stateResolver != null ) + { + stateResolver.populateState( model.model(), entityState ); + } + entityState.setPropertyValue( IDENTITY_STATE_NAME, identity ); + prototypeInstance = model.model().newInstance( uow, model.module(), entityState ); + } + + @SuppressWarnings( "unchecked" ) + @Override + public T instance() + { + checkValid(); + return prototypeInstance.proxy(); + } + + @Override + public K instanceFor( Class mixinType ) + { + checkValid(); + return prototypeInstance.newProxy( mixinType ); + } + + @Override + @SuppressWarnings( "unchecked" ) + public T newInstance() + throws LifecycleException + { + checkValid(); + + String identity; + + // Figure out whether to use given or generated identity + identity = (String) entityState.propertyValueOf( IDENTITY_STATE_NAME ); + EntityState newEntityState = model.model().newEntityState( store, uow.module(), + EntityReference.parseEntityReference( identity ) ); + + prototypeInstance.invokeCreate(); + + // Check constraints + prototypeInstance.checkConstraints(); + + entityState.copyTo( newEntityState ); + + EntityInstance instance = model.model().newInstance( uow, model.module(), newEntityState ); + + Object proxy = instance.proxy(); + + // Add entity in UOW + uow.addEntity( instance ); + + // Invalidate builder + this.identity = null; + + return (T) proxy; + } + + private void checkValid() + throws IllegalStateException + { + if( identity == null ) + { + throw new IllegalStateException( "EntityBuilder is not valid after call to newInstance()" ); + } + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/EntityStateStore.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/EntityStateStore.java b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/EntityStateStore.java new file mode 100644 index 0000000..6afe68e --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/EntityStateStore.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2009, Rickard Öberg. All Rights Reserved. + * + * 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.apache.zest.runtime.unitofwork; + +import org.apache.zest.api.association.AssociationStateHolder; +import org.apache.zest.spi.entity.EntityState; + +/** + * JAVADOC + */ +final class EntityStateStore +{ + AssociationStateHolder stateHolder; + EntityState state; + + @Override + public String toString() + { + return state.identity().toString(); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/UnitOfWorkInstance.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/UnitOfWorkInstance.java b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/UnitOfWorkInstance.java new file mode 100644 index 0000000..2136528 --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/unitofwork/UnitOfWorkInstance.java @@ -0,0 +1,545 @@ +/* + * Copyright (c) 2007-2013, Niclas Hedhman. All Rights Reserved. + * + * 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.apache.zest.runtime.unitofwork; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Stack; +import java.util.concurrent.TimeUnit; +import org.apache.zest.api.common.MetaInfo; +import org.apache.zest.api.entity.EntityComposite; +import org.apache.zest.api.entity.EntityReference; +import org.apache.zest.api.metrics.MetricsCounter; +import org.apache.zest.api.metrics.MetricsCounterFactory; +import org.apache.zest.api.metrics.MetricsProvider; +import org.apache.zest.api.metrics.MetricsTimer; +import org.apache.zest.api.metrics.MetricsTimerFactory; +import org.apache.zest.api.unitofwork.ConcurrentEntityModificationException; +import org.apache.zest.api.unitofwork.EntityTypeNotFoundException; +import org.apache.zest.api.unitofwork.NoSuchEntityException; +import org.apache.zest.api.unitofwork.UnitOfWorkCallback; +import org.apache.zest.api.unitofwork.UnitOfWorkCompletionException; +import org.apache.zest.api.unitofwork.UnitOfWorkException; +import org.apache.zest.api.unitofwork.UnitOfWorkOptions; +import org.apache.zest.api.usecase.Usecase; +import org.apache.zest.runtime.entity.EntityInstance; +import org.apache.zest.runtime.entity.EntityModel; +import org.apache.zest.runtime.structure.ModuleUnitOfWork; +import org.apache.zest.spi.entity.EntityState; +import org.apache.zest.spi.entity.EntityStatus; +import org.apache.zest.spi.entitystore.ConcurrentEntityStateModificationException; +import org.apache.zest.spi.entitystore.EntityNotFoundException; +import org.apache.zest.spi.entitystore.EntityStore; +import org.apache.zest.spi.entitystore.EntityStoreUnitOfWork; +import org.apache.zest.spi.entitystore.StateCommitter; +import org.apache.zest.spi.metrics.DefaultMetric; +import org.apache.zest.spi.module.ModelModule; +import org.apache.zest.spi.module.ModuleSpi; + +import static org.apache.zest.api.unitofwork.UnitOfWorkCallback.UnitOfWorkStatus.COMPLETED; +import static org.apache.zest.api.unitofwork.UnitOfWorkCallback.UnitOfWorkStatus.DISCARDED; +import static org.apache.zest.functional.Iterables.map; + +public final class UnitOfWorkInstance +{ + private static final ThreadLocal> current = new ThreadLocal>() + { + @Override + protected Stack initialValue() + { + return new Stack<>(); + } + }; + private MetricsTimer.Context metricsTimer; + + public static Stack getCurrent() + { + return current.get(); + } + + private long currentTime; + private MetricsProvider metrics; + final HashMap instanceCache; + final HashMap storeUnitOfWork; + + private boolean open; + + private boolean paused; + + /** + * Lazy query builder factory. + */ + private Usecase usecase; + + private MetaInfo metaInfo; + + private List callbacks; + + public UnitOfWorkInstance( Usecase usecase, long currentTime, MetricsProvider metrics ) + { + this.currentTime = currentTime; + this.open = true; + instanceCache = new HashMap<>(); + storeUnitOfWork = new HashMap<>(); + getCurrent().push( this ); + paused = false; + this.usecase = usecase; + startCapture( metrics ); + } + + public long currentTime() + { + return currentTime; + } + + public EntityStoreUnitOfWork getEntityStoreUnitOfWork( EntityStore store, ModuleSpi module ) + { + EntityStoreUnitOfWork uow = storeUnitOfWork.get( store ); + if( uow == null ) + { + uow = store.newUnitOfWork( usecase, module, currentTime ); + storeUnitOfWork.put( store, uow ); + } + return uow; + } + + public T get( EntityReference identity, + ModuleUnitOfWork uow, + Iterable> potentialModels, + Class mixinType + ) + throws EntityTypeNotFoundException, NoSuchEntityException + { + checkOpen(); + + EntityInstance entityInstance = instanceCache.get( identity ); + if( entityInstance == null ) + { // Not yet in cache + + // Check if this is a root UoW, or if no parent UoW knows about this entity + EntityState entityState = null; + EntityModel model = null; + ModuleSpi module = null; + // Figure out what EntityStore to use + for( ModelModule potentialModel : potentialModels ) + { + EntityStore store = potentialModel.module().entityStore(); + EntityStoreUnitOfWork storeUow = getEntityStoreUnitOfWork( store, potentialModel.module() ); + try + { + entityState = storeUow.entityStateOf( potentialModel.module(), identity ); + } + catch( EntityNotFoundException e ) + { + continue; + } + + // Get the selected model + model = (EntityModel) entityState.entityDescriptor(); + module = potentialModel.module(); + } + + // Check if model was found + if( model == null ) + { + // Check if state was found + if( entityState == null ) + { + throw new NoSuchEntityException( identity, mixinType, usecase ); + } + else + { + throw new EntityTypeNotFoundException( mixinType.getName(), + module.name(), + map( ModelModule.toStringFunction, + module.findVisibleEntityTypes() + ) ); + } + } + + // Create instance + entityInstance = new EntityInstance( uow, module, model, entityState ); + + instanceCache.put( identity, entityInstance ); + } + else + { + // Check if it has been removed + if( entityInstance.status() == EntityStatus.REMOVED ) + { + throw new NoSuchEntityException( identity, mixinType, usecase ); + } + } + + return entityInstance.proxy(); + } + + public Usecase usecase() + { + return usecase; + } + + public MetaInfo metaInfo() + { + if( metaInfo == null ) + { + metaInfo = new MetaInfo(); + } + + return metaInfo; + } + + public void pause() + { + if( !paused ) + { + paused = true; + getCurrent().pop(); + + UnitOfWorkOptions unitOfWorkOptions = metaInfo().get( UnitOfWorkOptions.class ); + if( unitOfWorkOptions == null ) + { + unitOfWorkOptions = usecase().metaInfo( UnitOfWorkOptions.class ); + } + + if( unitOfWorkOptions != null ) + { + if( unitOfWorkOptions.isPruneOnPause() ) + { + List prunedInstances = null; + for( EntityInstance entityInstance : instanceCache.values() ) + { + if( entityInstance.status() == EntityStatus.LOADED ) + { + if( prunedInstances == null ) + { + prunedInstances = new ArrayList<>(); + } + prunedInstances.add( entityInstance.identity() ); + } + } + if( prunedInstances != null ) + { + for( EntityReference prunedInstance : prunedInstances ) + { + instanceCache.remove( prunedInstance ); + } + } + } + } + } + else + { + throw new UnitOfWorkException( "Unit of work is not active" ); + } + } + + public void resume() + { + if( paused ) + { + paused = false; + getCurrent().push( this ); + } + else + { + throw new UnitOfWorkException( "Unit of work has not been paused" ); + } + } + + public void complete() + throws UnitOfWorkCompletionException + { + checkOpen(); + + // Copy list so that it cannot be modified during completion + List currentCallbacks = callbacks == null ? null : new ArrayList<>( callbacks ); + + // Commit state to EntityStores + List committers = applyChanges(); + + // Check callbacks + notifyBeforeCompletion( currentCallbacks ); + + // Commit all changes + for( StateCommitter committer : committers ) + { + committer.commit(); + } + + close(); + + // Call callbacks + notifyAfterCompletion( currentCallbacks, COMPLETED ); + + callbacks = currentCallbacks; + } + + public void discard() + { + if( !isOpen() ) + { + return; + } + close(); + + // Copy list so that it cannot be modified during completion + List currentCallbacks = callbacks == null ? null : new ArrayList<>( callbacks ); + + // Call callbacks + notifyAfterCompletion( currentCallbacks, DISCARDED ); + + for( EntityStoreUnitOfWork entityStoreUnitOfWork : storeUnitOfWork.values() ) + { + entityStoreUnitOfWork.discard(); + } + + callbacks = currentCallbacks; + } + + private void close() + { + checkOpen(); + + if( !isPaused() ) + { + getCurrent().pop(); + } + endCapture(); + open = false; + } + + public boolean isOpen() + { + return open; + } + + public void addUnitOfWorkCallback( UnitOfWorkCallback callback ) + { + if( callbacks == null ) + { + callbacks = new ArrayList<>(); + } + + callbacks.add( callback ); + } + + public void removeUnitOfWorkCallback( UnitOfWorkCallback callback ) + { + if( callbacks != null ) + { + callbacks.remove( callback ); + } + } + + public void addEntity( EntityInstance instance ) + { + instanceCache.put( instance.identity(), instance ); + } + + private List applyChanges() + throws UnitOfWorkCompletionException + { + List committers = new ArrayList<>(); + for( EntityStoreUnitOfWork entityStoreUnitOfWork : storeUnitOfWork.values() ) + { + try + { + StateCommitter committer = entityStoreUnitOfWork.applyChanges(); + committers.add( committer ); + } + catch( Exception e ) + { + // Cancel all previously prepared stores + for( StateCommitter committer : committers ) + { + committer.cancel(); + } + + if( e instanceof ConcurrentEntityStateModificationException ) + { + // If we cancelled due to concurrent modification, then create the proper exception for it! + ConcurrentEntityStateModificationException mee = (ConcurrentEntityStateModificationException) e; + Collection modifiedEntityIdentities = mee.modifiedEntities(); + Collection modifiedEntities = new ArrayList<>(); + for( EntityReference modifiedEntityIdentity : modifiedEntityIdentities ) + { + Collection instances = instanceCache.values(); + for( EntityInstance instance : instances ) + { + if( instance.identity().equals( modifiedEntityIdentity ) ) + { + modifiedEntities.add( instance.proxy() ); + } + } + } + throw new ConcurrentEntityModificationException( modifiedEntities ); + } + else + { + throw new UnitOfWorkCompletionException( e ); + } + } + } + return committers; + } + + private void notifyBeforeCompletion( List callbacks ) + throws UnitOfWorkCompletionException + { + // Notify explicitly registered callbacks + if( callbacks != null ) + { + for( UnitOfWorkCallback callback : callbacks ) + { + callback.beforeCompletion(); + } + } + + // Notify entities + try + { + for( EntityInstance instance : instanceCache.values() ) + { + boolean isCallback = instance.proxy() instanceof UnitOfWorkCallback; + boolean isNotRemoved = !instance.status().equals( EntityStatus.REMOVED ); + if( isCallback && isNotRemoved ) + { + UnitOfWorkCallback callback = UnitOfWorkCallback.class.cast( instance.proxy() ); + callback.beforeCompletion(); + } + } + } + catch( UnitOfWorkCompletionException e ) + { + throw e; + } + catch( Exception e ) + { + throw new UnitOfWorkCompletionException( e ); + } + } + + private void notifyAfterCompletion( List callbacks, + final UnitOfWorkCallback.UnitOfWorkStatus status + ) + { + if( callbacks != null ) + { + for( UnitOfWorkCallback callback : callbacks ) + { + try + { + callback.afterCompletion( status ); + } + catch( Exception e ) + { + // Ignore + } + } + } + + // Notify entities + try + { + for( EntityInstance instance : instanceCache.values() ) + { + boolean isCallback = instance.proxy() instanceof UnitOfWorkCallback; + boolean isNotRemoved = !instance.status().equals( EntityStatus.REMOVED ); + if( isCallback && isNotRemoved ) + { + UnitOfWorkCallback callback = UnitOfWorkCallback.class.cast( instance.proxy() ); + callback.afterCompletion( status ); + } + } + } + catch( Exception e ) + { + // Ignore + } + } + + public void checkOpen() + { + if( !isOpen() ) + { + throw new UnitOfWorkException( "Unit of work has been closed" ); + } + } + + public boolean isPaused() + { + return paused; + } + + @Override + public String toString() + { + return "UnitOfWork " + hashCode() + "(" + usecase + "): entities:" + instanceCache.size(); + } + + public void remove( EntityReference entityReference ) + { + instanceCache.remove( entityReference ); + } + + private void incrementCount() + { + MetricsCounter counter = getCounter(); + counter.increment(); + } + + private void decrementCount() + { + MetricsCounter counter = getCounter(); + counter.decrement(); + } + + private MetricsCounter getCounter() + { + if( metrics != null ) + { + MetricsCounterFactory metricsFactory = metrics.createFactory( MetricsCounterFactory.class ); + return metricsFactory.createCounter( getClass(), "UnitOfWork Counter" ); + } + return new DefaultMetric(); + } + + private void endCapture() + { + decrementCount(); + metricsTimer.stop(); + } + + private void startCapture( MetricsProvider metrics ) + { + this.metrics = metrics; + incrementCount(); + startTimer( metrics ); + } + + private void startTimer( MetricsProvider metrics ) + { + MetricsTimerFactory metricsFactory = metrics.createFactory( MetricsTimerFactory.class ); + String name = "UnitOfWork Timer"; + MetricsTimer timer = metricsFactory.createTimer( getClass(), name, TimeUnit.MILLISECONDS, TimeUnit.SECONDS ); + metricsTimer = timer.start(); + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/8744a67f/core/runtime/src/main/java/org/apache/zest/runtime/value/ManyAssociationValueState.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/value/ManyAssociationValueState.java b/core/runtime/src/main/java/org/apache/zest/runtime/value/ManyAssociationValueState.java new file mode 100644 index 0000000..6056d9b --- /dev/null +++ b/core/runtime/src/main/java/org/apache/zest/runtime/value/ManyAssociationValueState.java @@ -0,0 +1,105 @@ +/* + * 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.zest.runtime.value; + +import java.util.Iterator; +import java.util.List; +import org.apache.zest.api.entity.EntityReference; +import org.apache.zest.spi.entity.ManyAssociationState; + +/** + * ManyAssociationState implementation for Value composites. + */ +public class ManyAssociationValueState + implements ManyAssociationState +{ + private List references; + + public ManyAssociationValueState( List references ) + { + this.references = references; + } + + @Override + public int count() + { + return references.size(); + } + + @Override + public boolean contains( EntityReference entityReference ) + { + return references.contains( entityReference ); + } + + @Override + public boolean add( int i, EntityReference entityReference ) + { + if( references.contains( entityReference ) ) + { + return false; + } + + references.add( i, entityReference ); + return true; + } + + @Override + public boolean remove( EntityReference entity ) + { + boolean removed = references.remove( entity ); + return removed; + } + + @Override + public EntityReference get( int i ) + { + return references.get( i ); + } + + @Override + public Iterator iterator() + { + final Iterator iter = references.iterator(); + + return new Iterator() + { + EntityReference current; + + @Override + public boolean hasNext() + { + return iter.hasNext(); + } + + @Override + public EntityReference next() + { + current = iter.next(); + return current; + } + + @Override + public void remove() + { + iter.remove(); + } + }; + } +}