This is an automated email from the ASF dual-hosted git repository.

shuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to refs/heads/master by this push:
     new f2fb628  UNOMI-365 Create new Unomi Topic object & queries & mutations 
(#189)
f2fb628 is described below

commit f2fb62886648fffa32469fc1016a5d65a289865a
Author: anatol-sialitski <[email protected]>
AuthorDate: Tue Sep 22 11:46:39 2020 +0300

    UNOMI-365 Create new Unomi Topic object & queries & mutations (#189)
    
    * UNOMI-365 Create new Unomi Topic object
    
    * UNOMI-365 Create new Unomi Topic object
    
    * resolved conflicts
    
    * UNOMI-365 Create new Unomi Topic object
---
 .../src/main/java/org/apache/unomi/api/Topic.java  | 34 ++++++---
 .../apache/unomi/api/services/TopicService.java    | 57 +++++++++++++++
 .../commands/CreateOrUpdateTopicCommand.java       | 25 ++++++-
 .../unomi/graphql/commands/DeleteTopicCommand.java |  7 +-
 .../condition/factories/TopicConditionFactory.java | 78 +++++++++++++++++++++
 .../fetchers/FindTopicsConnectionDataFetcher.java  | 25 ++++++-
 .../unomi/graphql/fetchers/TopicDataFetcher.java   | 14 +++-
 .../unomi/graphql/types/output/CDPTopic.java       | 17 ++++-
 .../graphql/types/output/CDPTopicConnection.java   | 25 +++++--
 .../unomi/graphql/types/output/CDPTopicEdge.java   | 11 ++-
 .../unomi/itests/graphql/GraphQLTopicIT.java       | 78 +++++++++++++++++++++
 .../test/resources/graphql/topic/create-topic.json | 11 +++
 .../test/resources/graphql/topic/delete-topic.json |  7 ++
 .../test/resources/graphql/topic/find-topics.json  | 25 +++++++
 .../test/resources/graphql/topic/get-topic.json    |  7 ++
 .../test/resources/graphql/topic/update-topic.json | 11 +++
 .../resources/META-INF/cxs/mappings/topic.json     | 20 ++++++
 .../unomi/persistence/spi/CustomObjectMapper.java  |  1 +
 .../cxs/conditions/topicPropertyCondition.json     | 31 +++++++++
 .../services/impl/topics/TopicServiceImpl.java     | 81 ++++++++++++++++++++++
 .../resources/OSGI-INF/blueprint/blueprint.xml     | 12 ++++
 21 files changed, 551 insertions(+), 26 deletions(-)

diff --git 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/TopicDataFetcher.java
 b/api/src/main/java/org/apache/unomi/api/Topic.java
similarity index 60%
copy from 
graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/TopicDataFetcher.java
copy to api/src/main/java/org/apache/unomi/api/Topic.java
index 05ba1af..03d789d 100644
--- 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/TopicDataFetcher.java
+++ b/api/src/main/java/org/apache/unomi/api/Topic.java
@@ -14,24 +14,38 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.graphql.fetchers;
+package org.apache.unomi.api;
 
-import graphql.schema.DataFetcher;
-import graphql.schema.DataFetchingEnvironment;
-import org.apache.unomi.graphql.types.output.CDPTopic;
+public class Topic extends Item {
 
-public class TopicDataFetcher implements DataFetcher<CDPTopic> {
+    public static final String ITEM_TYPE = "topic";
 
-    private final String topicId;
+    private String topicId;
 
-    public TopicDataFetcher(final String topicId) {
+    private String name;
+
+    public String getTopicId() {
+        return topicId;
+    }
+
+    public void setTopicId(String topicId) {
         this.topicId = topicId;
     }
 
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
     @Override
-    public CDPTopic get(DataFetchingEnvironment environment) throws Exception {
-        // Unomi doesn't have an API for that yet, so return a stub
-        return null;
+    public String toString() {
+        return "Topic{" +
+                "topicId='" + topicId + '\'' +
+                ", name='" + name + '\'' +
+                '}';
     }
 
 }
diff --git a/api/src/main/java/org/apache/unomi/api/services/TopicService.java 
b/api/src/main/java/org/apache/unomi/api/services/TopicService.java
new file mode 100644
index 0000000..6cb5e0b
--- /dev/null
+++ b/api/src/main/java/org/apache/unomi/api/services/TopicService.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.api.services;
+
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.Topic;
+import org.apache.unomi.api.query.Query;
+
+public interface TopicService {
+
+    /**
+     * Retrieves the topic identified by the specified identifier.
+     *
+     * @param topicId the identifier of the topic to retrieve
+     * @return the topic identified by the specified identifier or {@code 
null} if no such topic exists
+     */
+    Topic load(final String topicId);
+
+    /**
+     * Saves the specified topic in the context server.
+     *
+     * @param topic the topic to be saved
+     * @return the newly saved topic if the creation or update was successful, 
{@code null} otherwise
+     */
+    Topic save(final Topic topic);
+
+    /**
+     * Retrieves topic matching the specified query.
+     *
+     * @param query a {@link Query} specifying which elements to retrieve
+     * @return a {@link PartialList} of {@link Topic} metadata
+     */
+    PartialList<Topic> search(final Query query);
+
+    /**
+     * Removes the topic identified by the specified identifier.
+     *
+     * @param topicId the identifier of the profile or persona to delete
+     * @return {@code true} if the deletion was successful, {@code false} 
otherwise
+     */
+    boolean delete(final String topicId);
+
+}
diff --git 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateTopicCommand.java
 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateTopicCommand.java
index 22c41c2..835b613 100644
--- 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateTopicCommand.java
+++ 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateTopicCommand.java
@@ -16,6 +16,9 @@
  */
 package org.apache.unomi.graphql.commands;
 
+import com.google.common.base.Strings;
+import org.apache.unomi.api.Topic;
+import org.apache.unomi.api.services.TopicService;
 import org.apache.unomi.graphql.types.input.CDPTopicInput;
 import org.apache.unomi.graphql.types.output.CDPTopic;
 
@@ -33,8 +36,26 @@ public class CreateOrUpdateTopicCommand extends 
BaseCommand<CDPTopic> {
 
     @Override
     public CDPTopic execute() {
-        // Unomi doesn't have an API for that yet, so return a stub
-        return new CDPTopic();
+        final TopicService topicService = 
serviceManager.getService(TopicService.class);
+
+        Topic topic = topicService.load(topicInput.getId());
+
+        if (topic == null) {
+            topic = new Topic();
+        }
+
+        final String topicId = Strings.isNullOrEmpty(topicInput.getId())
+                ? topicInput.getName()
+                : topicInput.getId();
+
+        topic.setTopicId(topicId);
+        topic.setItemId(topicId);
+        topic.setName(topicInput.getName());
+        topic.setScope(topicInput.getView());
+
+        Topic storedTopic = topicService.save(topic);
+
+        return new CDPTopic(storedTopic);
     }
 
     public static Builder create(final CDPTopicInput topicInput) {
diff --git 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/DeleteTopicCommand.java
 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/DeleteTopicCommand.java
index c8fa304..33fc7cd 100644
--- 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/DeleteTopicCommand.java
+++ 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/DeleteTopicCommand.java
@@ -16,6 +16,8 @@
  */
 package org.apache.unomi.graphql.commands;
 
+import org.apache.unomi.api.services.TopicService;
+
 import java.util.Objects;
 
 public class DeleteTopicCommand extends BaseCommand<Boolean> {
@@ -30,8 +32,9 @@ public class DeleteTopicCommand extends BaseCommand<Boolean> {
 
     @Override
     public Boolean execute() {
-        // Unomi doesn't have an API for that yet, so return a stub
-        return false;
+        final TopicService topicService = 
serviceManager.getService(TopicService.class);
+
+        return topicService.delete(topicId);
     }
 
     public static Builder create(final String topicId) {
diff --git 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/condition/factories/TopicConditionFactory.java
 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/condition/factories/TopicConditionFactory.java
new file mode 100644
index 0000000..9c68361
--- /dev/null
+++ 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/condition/factories/TopicConditionFactory.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.graphql.condition.factories;
+
+import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.graphql.types.input.CDPTopicFilterInput;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class TopicConditionFactory extends ConditionFactory {
+
+    private static TopicConditionFactory instance;
+
+    public static synchronized TopicConditionFactory get(final 
DataFetchingEnvironment environment) {
+        if (instance == null) {
+            instance = new TopicConditionFactory(environment);
+        }
+
+        return instance;
+    }
+
+    private TopicConditionFactory(final DataFetchingEnvironment environment) {
+        super("topicPropertyCondition", environment);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Condition filterInputCondition(final CDPTopicFilterInput 
filterInput, final Map<String, Object> filterInputAsMap) {
+        if (filterInput == null) {
+            return matchAllCondition();
+        }
+
+        final List<Condition> rootSubConditions = new ArrayList<>();
+
+        if (filterInput.getId_equals() != null) {
+            rootSubConditions.add(propertyCondition("itemId", 
filterInput.getId_equals()));
+        }
+
+        if (filterInput.getName_equals() != null) {
+            rootSubConditions.add(propertyCondition("name", 
filterInput.getName_equals()));
+        }
+
+        if (filterInput.getView_equals() != null) {
+            rootSubConditions.add(propertyCondition("scope", 
filterInput.getView_equals()));
+        }
+
+        if (filterInputAsMap.get("and") != null) {
+            final List<Map<String, Object>> andFilterInputAsMap = 
(List<Map<String, Object>>) filterInputAsMap.get("and");
+
+            rootSubConditions.add(filtersToCondition(filterInput.getAnd(), 
andFilterInputAsMap, this::filterInputCondition, "and"));
+        }
+
+        if (filterInputAsMap.get("or") != null) {
+            final List<Map<String, Object>> orFilterInputAsMap = 
(List<Map<String, Object>>) filterInputAsMap.get("or");
+
+            rootSubConditions.add(filtersToCondition(filterInput.getOr(), 
orFilterInputAsMap, this::filterInputCondition, "or"));
+        }
+
+        return booleanCondition("and", rootSubConditions);
+    }
+
+}
diff --git 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/FindTopicsConnectionDataFetcher.java
 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/FindTopicsConnectionDataFetcher.java
index 80aad1f..5aa0ab9 100644
--- 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/FindTopicsConnectionDataFetcher.java
+++ 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/FindTopicsConnectionDataFetcher.java
@@ -17,12 +17,21 @@
 package org.apache.unomi.graphql.fetchers;
 
 import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.Topic;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.services.TopicService;
+import org.apache.unomi.graphql.condition.factories.TopicConditionFactory;
 import org.apache.unomi.graphql.services.ServiceManager;
 import org.apache.unomi.graphql.types.input.CDPOrderByInput;
 import org.apache.unomi.graphql.types.input.CDPTopicFilterInput;
+import org.apache.unomi.graphql.types.output.CDPPageInfo;
 import org.apache.unomi.graphql.types.output.CDPTopicConnection;
+import org.apache.unomi.graphql.types.output.CDPTopicEdge;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class FindTopicsConnectionDataFetcher extends 
BaseConnectionDataFetcher<CDPTopicConnection> {
 
@@ -39,10 +48,22 @@ public class FindTopicsConnectionDataFetcher extends 
BaseConnectionDataFetcher<C
     @Override
     public CDPTopicConnection get(DataFetchingEnvironment environment) throws 
Exception {
         final ServiceManager serviceManager = environment.getContext();
+
+        final TopicService topicService = 
serviceManager.getService(TopicService.class);
+
         final ConnectionParams params = parseConnectionParams(environment);
 
-        // Unomi doesn't have an API for that yet, so return a stub
-        return null;
+        final Query query = buildQuery(createCondition(environment), 
orderByInput, params);
+
+        final PartialList<Topic> topicPartialList = topicService.search(query);
+
+        final List<CDPTopicEdge> edges = 
topicPartialList.getList().stream().map(CDPTopicEdge::new).collect(Collectors.toList());
+
+        return new CDPTopicConnection(topicPartialList.getTotalSize(), edges, 
new CDPPageInfo());
+    }
+
+    private Condition createCondition(final DataFetchingEnvironment 
environment) {
+        return 
TopicConditionFactory.get(environment).filterInputCondition(filterInput, 
environment.getArgument("filter"));
     }
 
 }
diff --git 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/TopicDataFetcher.java
 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/TopicDataFetcher.java
index 05ba1af..2a4a733 100644
--- 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/TopicDataFetcher.java
+++ 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/TopicDataFetcher.java
@@ -18,6 +18,9 @@ package org.apache.unomi.graphql.fetchers;
 
 import graphql.schema.DataFetcher;
 import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.api.Topic;
+import org.apache.unomi.api.services.TopicService;
+import org.apache.unomi.graphql.services.ServiceManager;
 import org.apache.unomi.graphql.types.output.CDPTopic;
 
 public class TopicDataFetcher implements DataFetcher<CDPTopic> {
@@ -30,7 +33,16 @@ public class TopicDataFetcher implements 
DataFetcher<CDPTopic> {
 
     @Override
     public CDPTopic get(DataFetchingEnvironment environment) throws Exception {
-        // Unomi doesn't have an API for that yet, so return a stub
+        final ServiceManager serviceManager = environment.getContext();
+
+        final TopicService topicService = 
serviceManager.getService(TopicService.class);
+
+        final Topic topic = topicService.load(topicId);
+
+        if (topic != null) {
+            return new CDPTopic(topic);
+        }
+
         return null;
     }
 
diff --git 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopic.java
 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopic.java
index 1dda33d..eb4271c 100644
--- 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopic.java
+++ 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopic.java
@@ -22,6 +22,7 @@ import graphql.annotations.annotationTypes.GraphQLID;
 import graphql.annotations.annotationTypes.GraphQLName;
 import graphql.annotations.annotationTypes.GraphQLNonNull;
 import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.api.Topic;
 
 import static org.apache.unomi.graphql.types.output.CDPTopic.TYPE_NAME;
 
@@ -31,23 +32,33 @@ public class CDPTopic {
 
     public static final String TYPE_NAME = "CDP_Topic";
 
+    private final Topic topic;
+
+    public CDPTopic() {
+        this(null);
+    }
+
+    public CDPTopic(Topic topic) {
+        this.topic = topic;
+    }
+
     @GraphQLID
     @GraphQLField
     @GraphQLNonNull
     public String id(final DataFetchingEnvironment environment) {
-        return null;
+        return topic != null ? topic.getTopicId() : null;
     }
 
     @GraphQLField
     @GraphQLNonNull
     public String name(final DataFetchingEnvironment environment) {
-        return null;
+        return topic != null ? topic.getName() : null;
     }
 
     @GraphQLField
     @GraphQLNonNull
     public CDPView view(final DataFetchingEnvironment environment) {
-        return null;
+        return topic != null ? new CDPView(topic.getScope()) : null;
     }
 
 }
diff --git 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopicConnection.java
 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopicConnection.java
index 8bfec66..897dce0 100644
--- 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopicConnection.java
+++ 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopicConnection.java
@@ -20,6 +20,7 @@ import graphql.annotations.annotationTypes.GraphQLField;
 import graphql.annotations.annotationTypes.GraphQLName;
 import graphql.schema.DataFetchingEnvironment;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import static 
org.apache.unomi.graphql.types.output.CDPTopicConnection.TYPE_NAME;
@@ -29,19 +30,35 @@ public class CDPTopicConnection {
 
     public static final String TYPE_NAME = "CDP_TopicConnection";
 
+    private Long totalCount;
+
+    private List<CDPTopicEdge> edges;
+
+    private CDPPageInfo pageInfo;
+
+    public CDPTopicConnection() {
+        this(0L, new ArrayList<>(), new CDPPageInfo());
+    }
+
+    public CDPTopicConnection(final Long totalCount, final List<CDPTopicEdge> 
edges, final CDPPageInfo pageInfo) {
+        this.totalCount = totalCount;
+        this.edges = edges;
+        this.pageInfo = pageInfo;
+    }
+
     @GraphQLField
-    public Integer totalCount(final DataFetchingEnvironment environment) {
-        return 0;
+    public Long totalCount(final DataFetchingEnvironment environment) {
+        return totalCount;
     }
 
     @GraphQLField
     public List<CDPTopicEdge> edges(final DataFetchingEnvironment environment) 
{
-        return null;
+        return edges;
     }
 
     @GraphQLField
     public CDPPageInfo pageInfo(final DataFetchingEnvironment environment) {
-        return null;
+        return pageInfo;
     }
 
 }
diff --git 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopicEdge.java
 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopicEdge.java
index 3c75519..91194c0 100644
--- 
a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopicEdge.java
+++ 
b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPTopicEdge.java
@@ -19,6 +19,7 @@ package org.apache.unomi.graphql.types.output;
 import graphql.annotations.annotationTypes.GraphQLField;
 import graphql.annotations.annotationTypes.GraphQLName;
 import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.api.Topic;
 
 import static org.apache.unomi.graphql.types.output.CDPTopicEdge.TYPE_NAME;
 
@@ -27,14 +28,20 @@ public class CDPTopicEdge {
 
     public static final String TYPE_NAME = "CDP_TopicEdge";
 
+    private final Topic topic;
+
+    public CDPTopicEdge(final Topic topic) {
+        this.topic = topic;
+    }
+
     @GraphQLField
     public CDPTopic node(final DataFetchingEnvironment environment) {
-        return null;
+        return new CDPTopic(topic);
     }
 
     @GraphQLField
     public String cursor(final DataFetchingEnvironment environment) {
-        return null;
+        return topic.getTopicId();
     }
 
 }
diff --git 
a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLTopicIT.java 
b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLTopicIT.java
new file mode 100644
index 0000000..482c38f
--- /dev/null
+++ b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLTopicIT.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.itests.graphql;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.unomi.api.services.TopicService;
+import org.junit.Assert;
+import org.junit.Test;
+import org.ops4j.pax.exam.util.Filter;
+
+import javax.inject.Inject;
+import java.io.IOException;
+
+public class GraphQLTopicIT extends BaseGraphQLIT {
+
+    @Inject
+    @Filter(timeout = 600000)
+    protected TopicService topicService;
+
+    @Test
+    public void testCRUD() throws IOException {
+        try (CloseableHttpResponse response = 
post("graphql/topic/create-topic.json")) {
+            final ResponseContext context = 
ResponseContext.parse(response.getEntity());
+
+            Assert.assertEquals("testTopic", 
context.getValue("data.cdp.createOrUpdateTopic.id"));
+        }
+
+        try (CloseableHttpResponse response = 
post("graphql/topic/update-topic.json")) {
+            final ResponseContext context = 
ResponseContext.parse(response.getEntity());
+
+            Assert.assertEquals("testTopic", 
context.getValue("data.cdp.createOrUpdateTopic.id"));
+            Assert.assertEquals("testTopicName Updated", 
context.getValue("data.cdp.createOrUpdateTopic.name"));
+            Assert.assertEquals("testTopicView", 
context.getValue("data.cdp.createOrUpdateTopic.view.name"));
+        }
+
+        try (CloseableHttpResponse response = 
post("graphql/topic/get-topic.json")) {
+            final ResponseContext context = 
ResponseContext.parse(response.getEntity());
+
+            Assert.assertEquals("testTopic", 
context.getValue("data.cdp.getTopic.id"));
+            Assert.assertEquals("testTopicName Updated", 
context.getValue("data.cdp.getTopic.name"));
+            Assert.assertEquals("testTopicView", 
context.getValue("data.cdp.getTopic.view.name"));
+        }
+
+        try (CloseableHttpResponse response = 
post("graphql/topic/find-topics.json")) {
+            final ResponseContext context = 
ResponseContext.parse(response.getEntity());
+
+            Assert.assertEquals(1, (int) 
context.getValue("data.cdp.findTopics.totalCount"));
+            
Assert.assertNotNull(context.getValue("data.cdp.findTopics.edges"));
+        }
+
+        try (CloseableHttpResponse response = 
post("graphql/topic/delete-topic.json")) {
+            final ResponseContext context = 
ResponseContext.parse(response.getEntity());
+
+            Assert.assertTrue(context.getValue("data.cdp.deleteTopic"));
+        }
+
+        try (CloseableHttpResponse response = 
post("graphql/topic/get-topic.json")) {
+            final ResponseContext context = 
ResponseContext.parse(response.getEntity());
+
+            Assert.assertNull(context.getValue("data.cdp.getTopic"));
+        }
+    }
+
+}
diff --git a/itests/src/test/resources/graphql/topic/create-topic.json 
b/itests/src/test/resources/graphql/topic/create-topic.json
new file mode 100644
index 0000000..59e46cc
--- /dev/null
+++ b/itests/src/test/resources/graphql/topic/create-topic.json
@@ -0,0 +1,11 @@
+{
+  "operationName": "createOrUpdateTopic",
+  "variables": {
+    "topic": {
+      "id": "testTopic",
+      "name": "testTopicName",
+      "view": "testTopicView"
+    }
+  },
+  "query": "mutation createOrUpdateTopic($topic: CDP_TopicInput) {\n  cdp {\n  
  createOrUpdateTopic(topic: $topic) {\n      id\n    }\n  }\n}\n"
+}
diff --git a/itests/src/test/resources/graphql/topic/delete-topic.json 
b/itests/src/test/resources/graphql/topic/delete-topic.json
new file mode 100644
index 0000000..8c18f44
--- /dev/null
+++ b/itests/src/test/resources/graphql/topic/delete-topic.json
@@ -0,0 +1,7 @@
+{
+  "operationName": "deleteTopic",
+  "variables": {
+    "topicId": "testTopic"
+  },
+  "query": "mutation deleteTopic($topicId: ID!) {\n  cdp {\n    
deleteTopic(topicID: $topicId)\n  }\n}\n"
+}
diff --git a/itests/src/test/resources/graphql/topic/find-topics.json 
b/itests/src/test/resources/graphql/topic/find-topics.json
new file mode 100644
index 0000000..bd4b4c0
--- /dev/null
+++ b/itests/src/test/resources/graphql/topic/find-topics.json
@@ -0,0 +1,25 @@
+{
+  "operationName": "findTopics",
+  "variables": {
+    "filterInput": {
+      "or": [
+        {
+          "id_equals": "testTopic"
+        },
+        {
+          "id_equals": "testTopic2"
+        },
+        {
+          "id_equals": "testTopic3"
+        },
+        {
+          "id_equals": "testTopic4"
+        },
+        {
+          "id_equals": "testTopic5"
+        }
+      ]
+    }
+  },
+  "query": "query findTopics($filterInput: CDP_TopicFilterInput) {\n  cdp {\n  
  findTopics(filter: $filterInput) {\n      totalCount\n      edges {\n        
node {\n          id\n        }\n      }\n    }\n  }\n}\n"
+}
diff --git a/itests/src/test/resources/graphql/topic/get-topic.json 
b/itests/src/test/resources/graphql/topic/get-topic.json
new file mode 100644
index 0000000..0bf4144
--- /dev/null
+++ b/itests/src/test/resources/graphql/topic/get-topic.json
@@ -0,0 +1,7 @@
+{
+  "operationName": "getTopic",
+  "variables": {
+    "topicId": "testTopic"
+  },
+  "query": "query getTopic($topicId: ID!) {\n  cdp {\n    getTopic(topicID: 
$topicId) {\n      id\n      name\n      view {\n        name\n      }\n    }\n 
 }\n}\n"
+}
diff --git a/itests/src/test/resources/graphql/topic/update-topic.json 
b/itests/src/test/resources/graphql/topic/update-topic.json
new file mode 100644
index 0000000..8efd5e6
--- /dev/null
+++ b/itests/src/test/resources/graphql/topic/update-topic.json
@@ -0,0 +1,11 @@
+{
+  "operationName": "createOrUpdateTopic",
+  "variables": {
+    "topic": {
+      "id": "testTopic",
+      "name": "testTopicName Updated",
+      "view": "testTopicView"
+    }
+  },
+  "query": "mutation createOrUpdateTopic($topic: CDP_TopicInput) {\n  cdp {\n  
  createOrUpdateTopic(topic: $topic) {\n      id\n      name\n      view {\n    
    name\n      }\n    }\n  }\n}\n"
+}
diff --git 
a/persistence-elasticsearch/core/src/main/resources/META-INF/cxs/mappings/topic.json
 
b/persistence-elasticsearch/core/src/main/resources/META-INF/cxs/mappings/topic.json
new file mode 100644
index 0000000..d2d90cb
--- /dev/null
+++ 
b/persistence-elasticsearch/core/src/main/resources/META-INF/cxs/mappings/topic.json
@@ -0,0 +1,20 @@
+{
+  "dynamic_templates": [
+    {
+      "all": {
+        "match": "*",
+        "match_mapping_type": "string",
+        "mapping": {
+          "type": "text",
+          "analyzer": "folding",
+          "fields": {
+            "keyword": {
+              "type": "keyword",
+              "ignore_above": 256
+            }
+          }
+        }
+      }
+    }
+  ]
+}
diff --git 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
index a641e2b..c2af0d5 100644
--- 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
+++ 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
@@ -77,6 +77,7 @@ public class CustomObjectMapper extends ObjectMapper {
         classes.put(Session.ITEM_TYPE, Session.class);
         classes.put(ConditionType.ITEM_TYPE, ConditionType.class);
         classes.put(ActionType.ITEM_TYPE, ActionType.class);
+        classes.put(Topic.ITEM_TYPE, Topic.class);
         for (Map.Entry<String, Class<? extends Item>> entry : 
classes.entrySet()) {
             propertyTypedObjectDeserializer.registerMapping("itemType=" + 
entry.getKey(), entry.getValue());
             itemDeserializer.registerMapping(entry.getKey(), entry.getValue());
diff --git 
a/plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/topicPropertyCondition.json
 
b/plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/topicPropertyCondition.json
new file mode 100644
index 0000000..f6ee28d
--- /dev/null
+++ 
b/plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/topicPropertyCondition.json
@@ -0,0 +1,31 @@
+{
+  "metadata": {
+    "id": "topicPropertyCondition",
+    "name": "topicPropertyCondition",
+    "description": "",
+    "systemTags": [
+      "condition",
+      "topicCondition"
+    ],
+    "readOnly": true
+  },
+  "conditionEvaluator": "propertyConditionEvaluator",
+  "queryBuilder": "propertyConditionESQueryBuilder",
+  "parameters": [
+    {
+      "id": "propertyName",
+      "type": "string",
+      "multivalued": false
+    },
+    {
+      "id": "comparisonOperator",
+      "type": "comparisonOperator",
+      "multivalued": false
+    },
+    {
+      "id": "propertyValue",
+      "type": "string",
+      "multivalued": false
+    }
+  ]
+}
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/topics/TopicServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/impl/topics/TopicServiceImpl.java
new file mode 100644
index 0000000..7058447
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/impl/topics/TopicServiceImpl.java
@@ -0,0 +1,81 @@
+/*
+ * 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.unomi.services.impl.topics;
+
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.Topic;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.services.TopicService;
+import org.apache.unomi.persistence.spi.PersistenceService;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.SynchronousBundleListener;
+
+public class TopicServiceImpl implements TopicService, 
SynchronousBundleListener {
+
+    private PersistenceService persistenceService;
+
+    private BundleContext bundleContext;
+
+    public void setPersistenceService(PersistenceService persistenceService) {
+        this.persistenceService = persistenceService;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    @Override
+    public Topic load(final String topicId) {
+        return persistenceService.load(topicId, Topic.class);
+    }
+
+    @Override
+    public Topic save(final Topic topic) {
+        if (persistenceService.save(topic)) {
+            persistenceService.refreshIndex(Topic.class, null);
+
+            return topic;
+        }
+
+        return null;
+    }
+
+    @Override
+    public PartialList<Topic> search(final Query query) {
+        return persistenceService.query(query.getCondition(), 
query.getSortby(), Topic.class, query.getOffset(), query.getLimit());
+    }
+
+    @Override
+    public boolean delete(String topicId) {
+        return persistenceService.remove(topicId, Topic.class);
+    }
+
+    @Override
+    public void bundleChanged(BundleEvent bundleEvent) {
+        // do nothing
+    }
+
+    public void postConstruct() {
+        bundleContext.addBundleListener(this);
+    }
+
+    public void preDestroy() {
+        bundleContext.removeBundleListener(this);
+    }
+
+}
diff --git a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml 
b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 68edc8a..1a5876e 100644
--- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -262,6 +262,18 @@
         </interfaces>
     </service>
 
+    <bean id="topicServiceImpl" 
class="org.apache.unomi.services.impl.topics.TopicServiceImpl"
+          init-method="postConstruct" destroy-method="preDestroy">
+        <property name="persistenceService" ref="persistenceService"/>
+        <property name="bundleContext" ref="blueprintBundleContext"/>
+    </bean>
+    <service id="topicService" ref="topicServiceImpl">
+        <interfaces>
+            <value>org.apache.unomi.api.services.TopicService</value>
+            <value>org.osgi.framework.SynchronousBundleListener</value>
+        </interfaces>
+    </service>
+
 
     <!-- We use a listener here because using the list directly for listening 
to proxies coming from the same bundle didn't seem to work -->
     <reference-list id="eventListenerServices"

Reply via email to