Initial Zookeeper Entity Store done.

Project: http://git-wip-us.apache.org/repos/asf/polygene-java/repo
Commit: http://git-wip-us.apache.org/repos/asf/polygene-java/commit/68b55fa9
Tree: http://git-wip-us.apache.org/repos/asf/polygene-java/tree/68b55fa9
Diff: http://git-wip-us.apache.org/repos/asf/polygene-java/diff/68b55fa9

Branch: refs/heads/develop
Commit: 68b55fa9b6950275a7ec2397cd89c40414526014
Parents: c68ad9a
Author: niclas <nic...@hedhman.org>
Authored: Thu Apr 26 08:30:44 2018 +0800
Committer: niclas <nic...@hedhman.org>
Committed: Thu Apr 26 08:30:44 2018 +0800

----------------------------------------------------------------------
 dependencies.gradle                             |   3 +
 .../entitystore/riak/RiakEntityStoreMixin.java  |   3 +-
 extensions/entitystore-zookeeper/build.gradle   |  41 +++
 extensions/entitystore-zookeeper/dev-status.xml |  38 +++
 .../src/docs/es-zookeeper.txt                   |  53 +++
 .../ZookeeperEntityStoreConfiguration.java      |  89 +++++
 .../zookeeper/ZookeeperEntityStoreMixin.java    | 336 +++++++++++++++++++
 .../zookeeper/ZookeeperEntityStoreService.java  |  52 +++
 .../assembly/ZookeeperEntityStoreAssembler.java |  50 +++
 .../polygene/entitystore/zookeeper/package.html |  24 ++
 .../polygene/entitystore/zookeeper/ZkUtil.java  |  33 ++
 .../zookeeper/ZookeeperEntityStoreTest.java     |  67 ++++
 .../ZookeeperEntityStoreTestSuite.java          |  62 ++++
 .../ZookeeperEntityStoreWithCacheTest.java      |  60 ++++
 settings.gradle                                 |   1 +
 15 files changed, 911 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/dependencies.gradle
----------------------------------------------------------------------
diff --git a/dependencies.gradle b/dependencies.gradle
index e2aad56..6bf9405 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -83,6 +83,8 @@ def solrVersion = "1.4.1" // 4.x Fails to compile!
 def springVersion = '5.0.5.RELEASE'
 def spymemcachedVersion = '2.12.3'
 def velocityVersion = '1.7'
+def zookeeperVersion = '3.4.10'
+
 dependencies.libraries << [
         bdb_je            : "com.sleepycat:je:$bdbjeVersion",
         bonecp            : "com.jolbox:bonecp:$bonecpVersion",
@@ -171,6 +173,7 @@ dependencies.libraries << [
                              
"org.springframework:spring-context:$springVersion"],
         spymemcached      : "net.spy:spymemcached:$spymemcachedVersion",
         velocity          : "org.apache.velocity:velocity:$velocityVersion",
+        zookeeper         : "org.apache.zookeeper:zookeeper:$zookeeperVersion"
 ]
 
 // Tools dependencies

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-riak/src/main/java/org/apache/polygene/entitystore/riak/RiakEntityStoreMixin.java
----------------------------------------------------------------------
diff --git 
a/extensions/entitystore-riak/src/main/java/org/apache/polygene/entitystore/riak/RiakEntityStoreMixin.java
 
b/extensions/entitystore-riak/src/main/java/org/apache/polygene/entitystore/riak/RiakEntityStoreMixin.java
index 39fd13a..7981318 100644
--- 
a/extensions/entitystore-riak/src/main/java/org/apache/polygene/entitystore/riak/RiakEntityStoreMixin.java
+++ 
b/extensions/entitystore-riak/src/main/java/org/apache/polygene/entitystore/riak/RiakEntityStoreMixin.java
@@ -282,7 +282,8 @@ public class RiakEntityStoreMixin implements 
ServiceActivation, MapEntityStore,
                             {
                                 super.close();
                                 EntityReference reference = 
mapChange.reference();
-                                Location location = new Location( namespace, 
reference.identity().toString() );
+                                String identity = 
reference.identity().toString();
+                                Location location = new Location( namespace, 
identity );
                                 FetchValue fetch = new FetchValue.Builder( 
location ).build();
                                 FetchValue.Response response = 
riakClient.execute( fetch );
                                 if( response.isNotFound() )

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/build.gradle
----------------------------------------------------------------------
diff --git a/extensions/entitystore-zookeeper/build.gradle 
b/extensions/entitystore-zookeeper/build.gradle
new file mode 100644
index 0000000..b8c6430
--- /dev/null
+++ b/extensions/entitystore-zookeeper/build.gradle
@@ -0,0 +1,41 @@
+/*
+ *  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.
+ *
+ *
+ */
+
+apply plugin: 'polygene-extension'
+
+description = "Apache Polygene™ Zookeeper EntityStore Extension. NOTE: Meant 
for configuration and other very light loads."
+
+jar { manifest { name = "Apache Polygene™ Extension - EntityStore - 
Zookeeper" } }
+
+dependencies {
+  api polygene.core.bootstrap
+
+  implementation polygene.library( 'locking' )
+  implementation polygene.library( 'constraints' )
+  implementation libraries.zookeeper
+
+  runtimeOnly polygene.core.runtime
+
+  testImplementation polygene.core.testsupport
+  testImplementation libraries.awaitility
+  testImplementation libraries.docker_junit
+
+  testRuntimeOnly libraries.logback
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/dev-status.xml
----------------------------------------------------------------------
diff --git a/extensions/entitystore-zookeeper/dev-status.xml 
b/extensions/entitystore-zookeeper/dev-status.xml
new file mode 100644
index 0000000..c79727f
--- /dev/null
+++ b/extensions/entitystore-zookeeper/dev-status.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~  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.
+  ~
+  ~
+  -->
+<module xmlns="http://polygene.apache.org/schemas/2008/dev-status/1";
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+        
xsi:schemaLocation="http://polygene.apache.org/schemas/2008/dev-status/1
+        http://polygene.apache.org/schemas/2008/dev-status/1/dev-status.xsd";>
+  <status>
+        <!--none,early,beta,stable,mature-->
+        <codebase>beta</codebase>
+
+        <!-- none, brief, good, complete -->
+        <documentation>brief</documentation>
+
+        <!-- none, some, good, complete -->
+        <unittests>good</unittests>
+    </status>
+    <licenses>
+        <license>ALv2</license>
+    </licenses>
+</module>

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/src/docs/es-zookeeper.txt
----------------------------------------------------------------------
diff --git a/extensions/entitystore-zookeeper/src/docs/es-zookeeper.txt 
b/extensions/entitystore-zookeeper/src/docs/es-zookeeper.txt
new file mode 100644
index 0000000..18253b9
--- /dev/null
+++ b/extensions/entitystore-zookeeper/src/docs/es-zookeeper.txt
@@ -0,0 +1,53 @@
+///////////////////////////////////////////////////////////////
+ * 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.
+///////////////////////////////////////////////////////////////
+
+[[extension-es-zookeeper, Zookeeper EntityStore]]
+= Zookeeper EntityStore =
+
+[devstatus]
+--------------
+source=extensions/entitystore-sqlkv/dev-status.xml
+--------------
+
+EntityStore service backed by a Zookeeper cluster. All entities are stored 
under a configurable ZNode, by default
+/polygene/store.
+
+Note that Zookeeper is not intended for heavy loads, and this implementation 
is primarily for Configurations and
+reference data that is primarily read-only in nature.
+
+include::../../build/docs/buildinfo/artifact.txt[]
+
+== Configuration ==
+
+Here are the available configuration properties:
+
+[snippet,java]
+----
+source=extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreConfiguration.java
+tag=config
+----
+
+Assembly is done using the provided Assembler:
+
+[snippet,java]
+----
+source=extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreTest.java
+tag=assembly
+----
+

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreConfiguration.java
----------------------------------------------------------------------
diff --git 
a/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreConfiguration.java
 
b/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreConfiguration.java
new file mode 100644
index 0000000..f7ffdbf
--- /dev/null
+++ 
b/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreConfiguration.java
@@ -0,0 +1,89 @@
+/*
+ *  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.entitystore.zookeeper;
+
+import java.util.List;
+import org.apache.polygene.api.common.Optional;
+import org.apache.polygene.api.common.UseDefaults;
+import org.apache.polygene.api.property.Property;
+
+/**
+ * Configuration for ZookeeperEntityStoreService.
+ */
+// START SNIPPET: config
+public interface ZookeeperEntityStoreConfiguration
+{
+    /**
+     * List of Zookeeper hosts
+     * <p>
+     * Each entry must contain an IP address / hostname followed by a column 
and the host's port.
+     * <p>
+     * Defaulted to 127.0.0.1:2181 if empty.
+     *
+     * @return List of Zookeeper hosts
+     */
+    @UseDefaults
+    Property<List<String>> hosts();
+
+    /**
+     * Path/Node in Zookeeper's namespace where Entities state will be stored.
+     * <p>
+     * Defaulted to "/polygene/store".
+     *
+     * @return The path of the the node where the entities will be stored.
+     */
+    @UseDefaults( "/polygene/store" )
+    Property<String> storageNode();
+
+    /** Timeout of Session in milliseconds
+     */
+    @UseDefaults( "10000" )
+    Property<Integer> sessionTimeout();
+
+    /**
+     * ACL to be used for new znodes.
+     * <p>
+     * Each String is in the format of;  PERM, SCHEME, ID
+     * </p>
+     * <p>
+     * where <strong>PERM</strong> is an integer by adding together
+     * <ul>
+     * <li>1 = <code>READ</code> </li>
+     * <li>2 = <code>WRITE</code> </li>
+     * <li>4 = <code>CREATE</code> </li>
+     * <li>8 = <code>DELETE</code> </li>
+     * <li>16 = <code>ADMIN</code> </li>
+     * </ul>
+     * or 31 for <code>ALL</code>, which is also the default value.
+     *
+     * </p>
+     *<p>
+     * <strong>SCHEME</strong> is the zookeeper ACL scheme, one of "world", 
"auth", ...(?)...
+     * <br/>
+     * Default: "world"
+     *</p>
+     * <p>
+     *     ID is the identity within the SCHEME. For "world" SCHEME, "anyone" 
is wildcard as can be expected.
+     *
+     *     Default: "anyone"
+     * </p>
+     */
+    @Optional
+    Property<List<String>> acls();
+}
+// END SNIPPET: config

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreMixin.java
----------------------------------------------------------------------
diff --git 
a/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreMixin.java
 
b/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreMixin.java
new file mode 100644
index 0000000..59a0b5a
--- /dev/null
+++ 
b/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreMixin.java
@@ -0,0 +1,336 @@
+/*
+ *  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.entitystore.zookeeper;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Spliterators;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import org.apache.polygene.api.common.Optional;
+import org.apache.polygene.api.configuration.Configuration;
+import org.apache.polygene.api.entity.EntityDescriptor;
+import org.apache.polygene.api.entity.EntityReference;
+import org.apache.polygene.api.injection.scope.Service;
+import org.apache.polygene.api.injection.scope.This;
+import org.apache.polygene.api.service.ServiceActivation;
+import org.apache.polygene.spi.entitystore.EntityNotFoundException;
+import org.apache.polygene.spi.entitystore.EntityStoreException;
+import org.apache.polygene.spi.entitystore.helpers.MapEntityStore;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.WatchedEvent;
+import org.apache.zookeeper.Watcher;
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Id;
+import org.apache.zookeeper.data.Stat;
+
+import static java.util.Spliterator.DISTINCT;
+import static java.util.Spliterator.IMMUTABLE;
+import static java.util.Spliterator.NONNULL;
+import static org.apache.zookeeper.CreateMode.PERSISTENT;
+import static org.apache.zookeeper.ZooDefs.Ids.ANYONE_ID_UNSAFE;
+import static org.apache.zookeeper.ZooDefs.Perms.ALL;
+
+/**
+ * Zookeeper implementation of MapEntityStore.
+ */
+public class ZookeeperEntityStoreMixin
+    implements ServiceActivation, MapEntityStore
+{
+    private static final String DEFAULT_HOSTPORT = "localhost:2181";
+    private static final byte[] EMPTY_DATA = new byte[ 0 ];
+
+    @Service
+    @Optional
+    private Watcher watcher;
+
+    @This
+    private Configuration<ZookeeperEntityStoreConfiguration> configuration;
+    private ZooKeeper zkClient;
+    private String storageNode;
+    private List<ACL> acl;
+
+    @Override
+    public void activateService()
+        throws Exception
+    {
+        // Load configuration
+        configuration.refresh();
+        ZookeeperEntityStoreConfiguration config = configuration.get();
+        setupStorageNode( config );
+
+        List<String> hosts = config.hosts().get();
+        if( hosts == null || hosts.size() == 0 )
+        {
+            hosts = Collections.singletonList( DEFAULT_HOSTPORT );
+        }
+        String hostPort = hosts.get( (int) ( Math.random() * hosts.size() ) );
+
+        int sessionTimeout = config.sessionTimeout().get();
+
+        zkClient = new ZooKeeper( hostPort, sessionTimeout, watcher == null ? 
new DummyWatcher() : watcher );
+        createStorageNodeIfNotExists( config );
+    }
+
+    private void setupStorageNode( ZookeeperEntityStoreConfiguration config )
+    {
+        storageNode = config.storageNode().get();
+        while( storageNode.startsWith( "//" ) )
+        {
+            storageNode = storageNode.substring( 1 );
+        }
+        while( storageNode.endsWith( "/" ) )
+        {
+            storageNode = storageNode.substring( 0, storageNode.length() - 1 );
+        }
+        if( !storageNode.startsWith( "/" ) )
+        {
+            storageNode = "/" + storageNode;
+        }
+    }
+
+    private void createStorageNodeIfNotExists( 
ZookeeperEntityStoreConfiguration config )
+        throws KeeperException, InterruptedException
+    {
+        acl = parseAcls( config.acls().get() );
+        String nodeName = config.storageNode().get();
+        String[] parts = nodeName.split( "/" );
+        String current = "";
+        for( String part : parts )
+        {
+            if( part.length() > 0 )
+            {
+                current = current + "/" + part;
+                Stat stat = zkClient.exists( current, false );
+                if( stat == null )
+                {
+                    zkClient.create( current, EMPTY_DATA, acl, PERSISTENT );
+                }
+            }
+        }
+    }
+
+    private List<ACL> parseAcls( List<String> acls )
+    {
+        List<ACL> result = new ArrayList<>();
+        if( acls == null || acls.size() == 0 )
+        {
+            result.add( new ACL( ALL, ANYONE_ID_UNSAFE ) );
+            return result;
+        }
+        acls.forEach( s -> {
+            String[] parts = s.split( "," );
+            int perms = Integer.valueOf( parts[ 0 ] );
+            String id = parts[ 2 ];
+            String scheme = parts[ 1 ];
+            result.add( new ACL( perms, new Id( scheme, id ) ) );
+        } );
+        return result;
+    }
+
+    @Override
+    public void passivateService()
+        throws Exception
+    {
+        zkClient.close();
+    }
+
+    @Override
+    public Reader get( EntityReference reference )
+    {
+        try
+        {
+            Stat stat = new Stat();
+            byte[] data = zkClient.getData( znode( reference ), false, stat );
+            if( data == null || stat.getDataLength() == 0 )
+            {
+                throw new EntityNotFoundException( reference );
+            }
+            return new StringReader( new String( data ) );
+        }
+        catch( KeeperException.NoNodeException e )
+        {
+            throw new EntityNotFoundException( reference );
+        }
+        catch( InterruptedException | KeeperException e )
+        {
+            throw new EntityStoreException( "Unable to get Entity " + 
reference.identity(), e );
+        }
+    }
+
+    @Override
+    public void applyChanges( MapChanges changes )
+    {
+        try
+        {
+            changes.visitMap( new MapChanger()
+            {
+                @Override
+                public Writer newEntity( EntityReference ref, EntityDescriptor 
entityDescriptor )
+                {
+                    return new StringWriter( 1000 )
+                    {
+                        @Override
+                        public void close()
+                            throws IOException
+                        {
+                            try
+                            {
+                                super.close();
+                                String znode = znode( ref );
+                                byte[] data = toString().getBytes();
+                                zkClient.create( znode, data, acl, PERSISTENT 
);
+                            }
+                            catch( InterruptedException | KeeperException e )
+                            {
+                                throw new EntityStoreException( "Unable to 
apply entity change: newEntity", e );
+                            }
+                        }
+                    };
+                }
+
+                @Override
+                public Writer updateEntity( MapChange mapChange )
+                {
+                    return new StringWriter( 1000 )
+                    {
+                        @Override
+                        public void close()
+                            throws IOException
+                        {
+                            try
+                            {
+                                super.close();
+                                EntityReference ref = mapChange.reference();
+                                String znode = znode( ref );
+                                Stat stat = zkClient.exists( znode, false );
+                                if( stat == null )
+                                {
+                                    throw new EntityNotFoundException( ref );
+                                }
+                                int version = stat.getVersion();
+                                zkClient.setData( znode, 
toString().getBytes(), version );
+                            }
+                            catch( InterruptedException | KeeperException e )
+                            {
+                                throw new EntityStoreException( "Unable to 
apply entity change: updateEntity", e );
+                            }
+                        }
+                    };
+                }
+
+                @Override
+                public void removeEntity( EntityReference ref, 
EntityDescriptor entityDescriptor )
+                {
+                    try
+                    {
+                        String znode = znode( ref );
+                        Stat stat = zkClient.exists( znode, false );
+                        int version = stat.getVersion();
+                        zkClient.delete( znode, version );
+                    }
+                    catch( InterruptedException | KeeperException e )
+                    {
+                        throw new EntityStoreException( "Unable to apply 
entity change: removeEntity", e );
+                    }
+                }
+            } );
+        }
+        catch( Exception ex )
+        {
+            throw new EntityStoreException( "Unable to apply entity changes.", 
ex );
+        }
+    }
+
+    private String znode( EntityReference ref )
+    {
+        return storageNode + '/' + ref.identity().toString();
+    }
+
+    @Override
+    public Stream<Reader> entityStates()
+    {
+        try
+        {
+            List<String> children = zkClient.getChildren( storageNode, false );
+            final ItemIterator iterator = new ItemIterator();
+            for( String child : children )
+            {
+                Stat stat = new Stat();
+                String path = storageNode + "/" + child;
+                byte[] data = zkClient.getData( path, false, stat );
+                if( data.length > 0 )
+                {
+                    String json = new String( data );
+                    iterator.queue.offer( new StringReader( json ) );
+                }
+            }
+            return StreamSupport.stream( Spliterators.spliterator( iterator, 
DISTINCT | NONNULL | IMMUTABLE, 1 ), false );
+        }
+        catch( KeeperException | InterruptedException e )
+        {
+            throw new EntityStoreException( "Unable to get entity states.", e 
);
+        }
+    }
+
+    private class ItemIterator
+        implements Iterator<Reader>
+    {
+        private final BlockingQueue<Reader> queue = new 
LinkedBlockingQueue<>();
+
+        @Override
+        public boolean hasNext()
+        {
+            return queue.size() > 0;
+        }
+
+        @Override
+        public Reader next()
+        {
+            try
+            {
+                return queue.take();
+            }
+            catch( InterruptedException e )
+            {
+                throw new UndeclaredThrowableException( e );
+            }
+        }
+    }
+
+    private class DummyWatcher
+        implements Watcher
+    {
+        @Override
+        public void process( WatchedEvent event )
+        {
+            // ignore all
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreService.java
----------------------------------------------------------------------
diff --git 
a/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreService.java
 
b/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreService.java
new file mode 100644
index 0000000..80cb2a5
--- /dev/null
+++ 
b/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreService.java
@@ -0,0 +1,52 @@
+/*
+ *  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.entitystore.zookeeper;
+
+import org.apache.polygene.api.concern.Concerns;
+import org.apache.polygene.api.configuration.Configuration;
+import org.apache.polygene.api.mixin.Mixins;
+import org.apache.polygene.api.service.ServiceActivation;
+import org.apache.polygene.library.locking.LockingAbstractComposite;
+import org.apache.polygene.spi.entitystore.ConcurrentModificationCheckConcern;
+import org.apache.polygene.spi.entitystore.EntityStateVersions;
+import org.apache.polygene.spi.entitystore.EntityStore;
+import org.apache.polygene.spi.entitystore.StateChangeNotificationConcern;
+import 
org.apache.polygene.spi.entitystore.helpers.JSONMapEntityStoreActivation;
+import org.apache.polygene.spi.entitystore.helpers.JSONMapEntityStoreMixin;
+import org.apache.polygene.spi.entitystore.helpers.MapEntityStoreActivation;
+import org.apache.polygene.spi.entitystore.helpers.MapEntityStoreMixin;
+
+/**
+ * Riak EntityStore service.
+ * <p>Can be used with Riak implementations of MapEntityStore.</p>
+ * <p>Based on {@link JSONMapEntityStoreMixin}</p>
+ */
+@Concerns( { StateChangeNotificationConcern.class, 
ConcurrentModificationCheckConcern.class } )
+@Mixins( { JSONMapEntityStoreMixin.class, MapEntityStoreMixin.class } )
+public interface ZookeeperEntityStoreService
+    extends EntityStore,
+            EntityStateVersions,
+            MapEntityStoreActivation,
+            JSONMapEntityStoreActivation,
+            ServiceActivation,
+            LockingAbstractComposite,
+            Configuration
+{
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/assembly/ZookeeperEntityStoreAssembler.java
----------------------------------------------------------------------
diff --git 
a/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/assembly/ZookeeperEntityStoreAssembler.java
 
b/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/assembly/ZookeeperEntityStoreAssembler.java
new file mode 100644
index 0000000..664513c
--- /dev/null
+++ 
b/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/assembly/ZookeeperEntityStoreAssembler.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.entitystore.zookeeper.assembly;
+
+import org.apache.polygene.bootstrap.Assemblers;
+import org.apache.polygene.bootstrap.ModuleAssembly;
+import org.apache.polygene.bootstrap.ServiceDeclaration;
+import 
org.apache.polygene.entitystore.zookeeper.ZookeeperEntityStoreConfiguration;
+import org.apache.polygene.entitystore.zookeeper.ZookeeperEntityStoreMixin;
+import org.apache.polygene.entitystore.zookeeper.ZookeeperEntityStoreService;
+
+/**
+ * Zookeeper EntityStore assembly.
+ */
+public class ZookeeperEntityStoreAssembler
+    extends Assemblers.VisibilityIdentityConfig<ZookeeperEntityStoreAssembler>
+{
+    @Override
+    public void assemble( ModuleAssembly module )
+    {
+        super.assemble( module );
+        ServiceDeclaration service = module.services( 
ZookeeperEntityStoreService.class )
+                                           .withMixins( 
ZookeeperEntityStoreMixin.class )
+                                           .visibleIn( visibility() );
+        if( hasIdentity() )
+        {
+            service.identifiedBy( identity() );
+        }
+        if( hasConfig() )
+        {
+            configModule().entities( ZookeeperEntityStoreConfiguration.class )
+                          .visibleIn( configVisibility() );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/package.html
----------------------------------------------------------------------
diff --git 
a/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/package.html
 
b/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/package.html
new file mode 100644
index 0000000..142a770
--- /dev/null
+++ 
b/extensions/entitystore-zookeeper/src/main/java/org/apache/polygene/entitystore/zookeeper/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>Zookeeper EntityStore.</h2>
+    </body>
+</html>

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZkUtil.java
----------------------------------------------------------------------
diff --git 
a/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZkUtil.java
 
b/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZkUtil.java
new file mode 100644
index 0000000..8b08892
--- /dev/null
+++ 
b/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZkUtil.java
@@ -0,0 +1,33 @@
+package org.apache.polygene.entitystore.zookeeper;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.data.Stat;
+
+public class ZkUtil
+{
+    static void cleanUp( String host, String znodeName )
+        throws KeeperException, InterruptedException, IOException
+    {
+        ZooKeeper zk = new ZooKeeper( host, 10000, event -> {
+        } );
+        delete( zk, znodeName );
+    }
+
+    private static void delete( ZooKeeper zk, String znodeName )
+        throws KeeperException, InterruptedException
+    {
+        Stat stat = zk.exists( znodeName, false );
+        if( stat != null )
+        {
+            List<String> children = zk.getChildren( znodeName, false );
+            for( String child : children )
+            {
+                delete( zk, znodeName + "/" + child );
+            }
+            zk.delete( znodeName, stat.getVersion() );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreTest.java
----------------------------------------------------------------------
diff --git 
a/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreTest.java
 
b/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreTest.java
new file mode 100644
index 0000000..bf5bc99
--- /dev/null
+++ 
b/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreTest.java
@@ -0,0 +1,67 @@
+/*
+ *  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.entitystore.zookeeper;
+
+import org.apache.polygene.api.common.Visibility;
+import org.apache.polygene.bootstrap.AssemblyException;
+import org.apache.polygene.bootstrap.ModuleAssembly;
+import 
org.apache.polygene.entitystore.zookeeper.assembly.ZookeeperEntityStoreAssembler;
+import org.apache.polygene.test.EntityTestAssembler;
+import org.apache.polygene.test.TemporaryFolder;
+import org.apache.polygene.test.entity.AbstractEntityStoreTest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static java.util.Collections.singletonList;
+
+@ExtendWith( TemporaryFolder.class )
+public class ZookeeperEntityStoreTest
+    extends AbstractEntityStoreTest
+{
+
+    static final String TEST_ZNODE_NAME = "/polygene/entitystore-test";
+
+    @Override
+    // START SNIPPET: assembly
+    public void assemble( ModuleAssembly module )
+        throws AssemblyException
+    {
+        // END SNIPPET: assembly
+        super.assemble( module );
+        ModuleAssembly config = module.layer().module( "config" );
+        new EntityTestAssembler().defaultServicesVisibleIn( Visibility.layer 
).assemble( config );
+        // START SNIPPET: assembly
+        ZookeeperEntityStoreAssembler zkAssembler = new 
ZookeeperEntityStoreAssembler();
+        zkAssembler.withConfig( config, Visibility.layer ).assemble( module );
+        // END SNIPPET: assembly
+        ZookeeperEntityStoreConfiguration defaults = 
zkAssembler.configModule().forMixin( ZookeeperEntityStoreConfiguration.class 
).declareDefaults();
+        defaults.hosts().set( singletonList( "localhost:2181" ) );
+        defaults.storageNode().set( TEST_ZNODE_NAME );
+        // START SNIPPET: assembly
+    }
+    // END SNIPPET: assembly
+
+    @AfterEach
+    void cleanUp()
+        throws Exception
+    {
+        ZkUtil.cleanUp( "localhost:2181", TEST_ZNODE_NAME );
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreTestSuite.java
----------------------------------------------------------------------
diff --git 
a/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreTestSuite.java
 
b/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreTestSuite.java
new file mode 100644
index 0000000..bf83f26
--- /dev/null
+++ 
b/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreTestSuite.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.entitystore.zookeeper;
+
+import org.apache.polygene.api.common.Visibility;
+import org.apache.polygene.bootstrap.ModuleAssembly;
+import 
org.apache.polygene.entitystore.zookeeper.assembly.ZookeeperEntityStoreAssembler;
+import org.apache.polygene.test.TemporaryFolder;
+import org.apache.polygene.test.entity.model.EntityStoreTestSuite;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static java.util.Collections.singletonList;
+import static 
org.apache.polygene.entitystore.zookeeper.ZookeeperEntityStoreTest.TEST_ZNODE_NAME;
+
+@ExtendWith( TemporaryFolder.class )
+public class ZookeeperEntityStoreTestSuite
+    extends EntityStoreTestSuite
+{
+    @Override
+    protected void defineStorageModule( ModuleAssembly module )
+    {
+        module.defaultServices();
+        new ZookeeperEntityStoreAssembler()
+            .withConfig( configModule, Visibility.application )
+            .visibleIn( Visibility.application )
+            .assemble( module );
+    }
+
+    @Override
+    protected void defineConfigModule( ModuleAssembly module )
+    {
+        super.defineConfigModule( module );
+        ZookeeperEntityStoreConfiguration defaults = module.forMixin( 
ZookeeperEntityStoreConfiguration.class ).declareDefaults();
+        defaults.hosts().set( singletonList( "localhost:2181" ) );
+        defaults.storageNode().set( TEST_ZNODE_NAME );
+    }
+
+    @AfterEach
+    void cleanUp()
+        throws Exception
+    {
+        ZkUtil.cleanUp( "localhost:2181", TEST_ZNODE_NAME );
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreWithCacheTest.java
----------------------------------------------------------------------
diff --git 
a/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreWithCacheTest.java
 
b/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreWithCacheTest.java
new file mode 100644
index 0000000..1efaf05
--- /dev/null
+++ 
b/extensions/entitystore-zookeeper/src/test/java/org/apache/polygene/entitystore/zookeeper/ZookeeperEntityStoreWithCacheTest.java
@@ -0,0 +1,60 @@
+/*
+ *  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.entitystore.zookeeper;
+
+import org.apache.polygene.api.common.Visibility;
+import org.apache.polygene.bootstrap.AssemblyException;
+import org.apache.polygene.bootstrap.ModuleAssembly;
+import 
org.apache.polygene.entitystore.zookeeper.assembly.ZookeeperEntityStoreAssembler;
+import org.apache.polygene.test.EntityTestAssembler;
+import org.apache.polygene.test.TemporaryFolder;
+import org.apache.polygene.test.cache.AbstractEntityStoreWithCacheTest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static java.util.Collections.singletonList;
+import static 
org.apache.polygene.entitystore.zookeeper.ZookeeperEntityStoreTest.TEST_ZNODE_NAME;
+
+public class ZookeeperEntityStoreWithCacheTest
+    extends AbstractEntityStoreWithCacheTest
+{
+    @Override
+    public void assemble( ModuleAssembly module )
+        throws AssemblyException
+    {
+        super.assemble( module );
+        ModuleAssembly config = module.layer().module( "config" );
+        new EntityTestAssembler().defaultServicesVisibleIn( Visibility.layer 
).assemble( config );
+        ZookeeperEntityStoreAssembler zkAssembler = new 
ZookeeperEntityStoreAssembler();
+        zkAssembler.withConfig( config, Visibility.layer ).assemble( module );
+
+        ZookeeperEntityStoreConfiguration defaults = 
zkAssembler.configModule().forMixin( ZookeeperEntityStoreConfiguration.class 
).declareDefaults();
+        defaults.hosts().set( singletonList( "localhost:2181" ) );
+        defaults.storageNode().set( TEST_ZNODE_NAME );
+    }
+
+    @AfterEach
+    void cleanUp()
+        throws Exception
+    {
+        ZkUtil.cleanUp( "localhost:2181", TEST_ZNODE_NAME );
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/68b55fa9/settings.gradle
----------------------------------------------------------------------
diff --git a/settings.gradle b/settings.gradle
index e410c6a..0f503e1 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -69,6 +69,7 @@ include 'core:api',
         'extensions:entitystore-riak',
         'extensions:entitystore-sql',
         'extensions:entitystore-sqlkv',
+        'extensions:entitystore-zookeeper',
         'extensions:indexing-elasticsearch',
         'extensions:indexing-rdf',
         'extensions:indexing-solr',

Reply via email to