diff --git a/api/src/main/java/org/apache/unomi/api/services/EventService.java b/api/src/main/java/org/apache/unomi/api/services/EventService.java
index fb6d60d70b..64ca1beebd 100644
--- a/api/src/main/java/org/apache/unomi/api/services/EventService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/EventService.java
@@ -153,4 +153,11 @@ public interface EventService {
* @param profileId identifier of the profile that we want to remove it's events
*/
void removeProfileEvents(String profileId);
+
+ /**
+ * Deletes the event identified by the given identifier from persistence.
+ *
+ * @param eventIdentifier the unique identifier for the event
+ */
+ void deleteEvent(String eventIdentifier);
}
diff --git a/api/src/main/java/org/apache/unomi/api/services/ProfileService.java b/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
index 03da6ce9be..bd4c537068 100644
--- a/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
@@ -217,6 +217,13 @@ default Session loadSession(String sessionId) {
*/
void removeProfileSessions(String profileId);
+ /**
+ * Deletes the session identified by the given identifier from persistence.
+ *
+ * @param sessionIdentifier the unique identifier for the session
+ */
+ void deleteSession(String sessionIdentifier);
+
/**
* Checks whether the specified profile and/or session satisfy the specified condition.
*
diff --git a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
index f345466c7d..60680924e6 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
@@ -336,4 +336,9 @@ public void removeProfileEvents(String profileId){
persistenceService.removeByQuery(profileCondition,Event.class);
}
+
+ @Override
+ public void deleteEvent(String eventIdentifier) {
+ persistenceService.remove(eventIdentifier, Event.class);
+ }
}
diff --git a/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
index 9673a707cd..e6b74c9172 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
@@ -1015,6 +1015,11 @@ public void removeProfileSessions(String profileId) {
persistenceService.removeByQuery(profileCondition, Session.class);
}
+ @Override
+ public void deleteSession(String sessionIdentifier) {
+ persistenceService.remove(sessionIdentifier, Session.class);
+ }
+
@Override
public boolean matchCondition(Condition condition, Profile profile, Session session) {
ParserHelper.resolveConditionType(definitionsService, condition, "profile " + profile.getItemId() + " matching");
diff --git a/tools/shell-dev-commands/pom.xml b/tools/shell-dev-commands/pom.xml
index 27b0b641f3..807677a638 100644
--- a/tools/shell-dev-commands/pom.xml
+++ b/tools/shell-dev-commands/pom.xml
@@ -92,6 +92,8 @@
org.apache.httpcomponents
httpclient-osgi
+ ${httpclient-osgi.version}
+ bundle
provided
@@ -99,6 +101,10 @@
commons-lang3
provided
+
+ org.apache.commons
+ commons-csv
+
junit
@@ -115,6 +121,14 @@
*
+
+ org.apache.unomi.shell.dev.services,
+ org.apache.unomi.shell.dev.commands
+
+ <_dsannotations>*
+ <_dsannotations-options>inherit
+ <_metatypeannotations>*
+ <_metatypeannotations-options>version;nested
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ActionList.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ActionList.java
deleted file mode 100644
index 7839cb7c80..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ActionList.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.actions.ActionType;
-import org.apache.unomi.api.services.DefinitionsService;
-import org.apache.unomi.common.DataTable;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-@Command(scope = "unomi", name = "action-list", description = "This will list all the actions deployed in the Apache Unomi Context Server")
-@Service
-public class ActionList extends ListCommandSupport {
-
- @Reference
- DefinitionsService definitionsService;
-
- @Override
- protected String[] getHeaders() {
- return new String[] {
- "Id",
- "Name",
- "System tags"
- };
- }
-
- @Override
- protected DataTable buildDataTable() {
- Collection allActions = definitionsService.getAllActionTypes();
-
- DataTable dataTable = new DataTable();
-
- for (ActionType actionType : allActions) {
- ArrayList rowData = new ArrayList<>();
- rowData.add(actionType.getItemId());
- rowData.add(actionType.getMetadata().getName());
- rowData.add(StringUtils.join(actionType.getMetadata().getSystemTags(), ","));
- dataTable.addRow(rowData.toArray(new Comparable[rowData.size()]));
- }
-
- dataTable.sort(new DataTable.SortCriteria(1, DataTable.SortOrder.ASCENDING));
- return dataTable;
- }
-
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ActionView.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ActionView.java
deleted file mode 100644
index 939e7f9d1d..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ActionView.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.karaf.shell.api.action.Action;
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.actions.ActionType;
-import org.apache.unomi.api.services.DefinitionsService;
-import org.apache.unomi.persistence.spi.CustomObjectMapper;
-
-@Command(scope = "unomi", name = "action-view", description = "This will display a single action deployed in the Apache Unomi Context Server")
-@Service
-public class ActionView implements Action {
-
- @Reference
- DefinitionsService definitionsService;
-
- @Argument(index = 0, name = "actionId", description = "The identifier for the action", required = true, multiValued = false)
- String actionTypeIdentifier;
-
- public Object execute() throws Exception {
- ActionType actionType = definitionsService.getActionType(actionTypeIdentifier);
- if (actionType == null) {
- System.out.println("Couldn't find an action with id=" + actionTypeIdentifier);
- return null;
- }
- String jsonRule = CustomObjectMapper.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(actionType);
- System.out.println(jsonRule);
- return null;
- }
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ConditionList.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ConditionList.java
deleted file mode 100644
index 83f6debc5a..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ConditionList.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.conditions.ConditionType;
-import org.apache.unomi.api.services.DefinitionsService;
-import org.apache.unomi.common.DataTable;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-@Command(scope = "unomi", name = "condition-list", description = "This will list all the conditions deployed in the Apache Unomi Context Server")
-@Service
-public class ConditionList extends ListCommandSupport {
-
- @Reference
- DefinitionsService definitionsService;
-
- @Override
- protected String[] getHeaders() {
- return new String[] {
- "Id",
- "Name",
- "System tags"
- };
- }
-
- @Override
- protected DataTable buildDataTable() {
- Collection allConditionTypes = definitionsService.getAllConditionTypes();
-
- DataTable dataTable = new DataTable();
-
- for (ConditionType conditionType : allConditionTypes) {
- ArrayList rowData = new ArrayList<>();
- rowData.add(conditionType.getItemId());
- rowData.add(conditionType.getMetadata().getName());
- rowData.add(StringUtils.join(conditionType.getMetadata().getSystemTags(), ","));
- dataTable.addRow(rowData.toArray(new Comparable[rowData.size()]));
- }
-
- dataTable.sort(new DataTable.SortCriteria(1, DataTable.SortOrder.ASCENDING));
- return dataTable;
- }
-
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ConditionView.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ConditionView.java
deleted file mode 100644
index 03efe9d48a..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ConditionView.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.karaf.shell.api.action.Action;
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.conditions.ConditionType;
-import org.apache.unomi.api.services.DefinitionsService;
-import org.apache.unomi.persistence.spi.CustomObjectMapper;
-
-@Command(scope = "unomi", name = "condition-view", description = "This will display a single condition deployed in the Apache Unomi Context Server")
-@Service
-public class ConditionView implements Action {
-
- @Reference
- DefinitionsService definitionsService;
-
- @Argument(index = 0, name = "conditionId", description = "The identifier for the condition", required = true, multiValued = false)
- String conditionTypeIdentifier;
-
- public Object execute() throws Exception {
- ConditionType conditionType = definitionsService.getConditionType(conditionTypeIdentifier);
- if (conditionType == null) {
- System.out.println("Couldn't find an action with id=" + conditionTypeIdentifier);
- return null;
- }
- String jsonRule = CustomObjectMapper.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(conditionType);
- System.out.println(jsonRule);
- return null;
- }
-
-
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeployDefinition.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeployDefinition.java
deleted file mode 100644
index 9365de8a98..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeployDefinition.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.Patch;
-import org.apache.unomi.api.PersonaWithSessions;
-import org.apache.unomi.api.PropertyType;
-import org.apache.unomi.api.actions.ActionType;
-import org.apache.unomi.api.campaigns.Campaign;
-import org.apache.unomi.api.conditions.ConditionType;
-import org.apache.unomi.api.goals.Goal;
-import org.apache.unomi.api.rules.Rule;
-import org.apache.unomi.api.segments.Scoring;
-import org.apache.unomi.api.segments.Segment;
-import org.apache.unomi.persistence.spi.CustomObjectMapper;
-
-import java.io.IOException;
-import java.net.URL;
-
-@Command(scope = "unomi", name = "deploy-definition", description = "This will deploy Unomi definitions contained in bundles")
-@Service
-public class DeployDefinition extends DeploymentCommandSupport {
-
- public void processDefinition(String definitionType, URL definitionURL) {
- try {
- if (ALL_OPTION_LABEL.equals(definitionType)) {
- String definitionURLString = definitionURL.toString();
- for (String possibleDefinitionType : definitionTypes) {
- if (definitionURLString.contains(getDefinitionTypePath(possibleDefinitionType))) {
- definitionType = possibleDefinitionType;
- break;
- }
- }
- if (ALL_OPTION_LABEL.equals(definitionType)) {
- System.out.println("Couldn't resolve definition type for definition URL " + definitionURL);
- return;
- }
- }
- boolean successful = true;
- switch (definitionType) {
- case CONDITION_DEFINITION_TYPE:
- ConditionType conditionType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, ConditionType.class);
- definitionsService.setConditionType(conditionType);
- break;
- case ACTION_DEFINITION_TYPE:
- ActionType actionType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, ActionType.class);
- definitionsService.setActionType(actionType);
- break;
- case GOAL_DEFINITION_TYPE:
- Goal goal = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Goal.class);
- goalsService.setGoal(goal);
- break;
- case CAMPAIGN_DEFINITION_TYPE:
- Campaign campaign = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Campaign.class);
- goalsService.setCampaign(campaign);
- break;
- case PERSONA_DEFINITION_TYPE:
- PersonaWithSessions persona = CustomObjectMapper.getObjectMapper().readValue(definitionURL, PersonaWithSessions.class);
- profileService.savePersonaWithSessions(persona);
- break;
- case PROPERTY_DEFINITION_TYPE:
- PropertyType propertyType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, PropertyType.class);
- profileService.setPropertyTypeTarget(definitionURL, propertyType);
- profileService.setPropertyType(propertyType);
- break;
- case RULE_DEFINITION_TYPE:
- Rule rule = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Rule.class);
- rulesService.setRule(rule);
- break;
- case SEGMENT_DEFINITION_TYPE:
- Segment segment = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Segment.class);
- segmentService.setSegmentDefinition(segment);
- break;
- case SCORING_DEFINITION_TYPE:
- Scoring scoring = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Scoring.class);
- segmentService.setScoringDefinition(scoring);
- break;
- case PATCH_DEFINITION_TYPE:
- Patch patch = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Patch.class);
- patchService.patch(patch);
- break;
- default:
- System.out.println("Unrecognized definition type:" + definitionType);
- successful = false;
- break;
- }
- if (successful) {
- System.out.println("Predefined definition registered : " + definitionURL.getFile());
- }
- } catch (IOException e) {
- System.out.println("Error while saving definition " + definitionURL);
- System.out.println(e.getMessage());
- }
- }
-
-
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventList.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventList.java
deleted file mode 100644
index b996a7085a..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventList.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.Event;
-import org.apache.unomi.api.PartialList;
-import org.apache.unomi.api.conditions.Condition;
-import org.apache.unomi.api.query.Query;
-import org.apache.unomi.api.services.DefinitionsService;
-import org.apache.unomi.api.services.EventService;
-import org.apache.unomi.common.DataTable;
-
-import java.util.ArrayList;
-
-@Command(scope = "unomi", name = "event-list", description = "This commands lists the latest events updated in the Apache Unomi Context Server")
-@Service
-public class EventList extends ListCommandSupport {
-
- @Reference
- private EventService eventService;
-
- @Reference
- DefinitionsService definitionsService;
-
- @Argument(index = 0, name = "maxEntries", description = "The maximum number of entries to retrieve (defaults to 100)", required = false, multiValued = false)
- int maxEntries = 100;
-
- @Argument(index = 1, name = "eventType", description = "If specified, will filter the event list by the given event type", required = false, multiValued = false)
- String eventType = null;
-
- String[] columnHeaders = new String[] {
- "ID",
- "Type",
- "Session",
- "Profile",
- "Timestamp",
- "Scope",
- "Persistent"
- };
-
- @Override
- protected String[] getHeaders() {
- return columnHeaders;
- }
-
- @Override
- protected DataTable buildDataTable() {
- Condition condition = new Condition(definitionsService.getConditionType("matchAllCondition"));
- if (eventType != null) {
- condition = new Condition(definitionsService.getConditionType("eventTypeCondition"));
- condition.setParameter("eventTypeId", eventType);
- }
- Query query = new Query();
- query.setLimit(maxEntries);
- query.setCondition(condition);
- query.setSortby("timeStamp:desc");
- PartialList lastEvents = eventService.search(query);
- DataTable dataTable = new DataTable();
- for (Event event : lastEvents.getList()) {
- ArrayList rowData = new ArrayList<>();
- rowData.add(event.getItemId());
- rowData.add(event.getEventType());
- rowData.add(event.getSessionId());
- rowData.add(event.getProfileId());
- rowData.add(event.getTimeStamp().toString());
- rowData.add(event.getScope());
- rowData.add(Boolean.toString(event.isPersistent()));
- dataTable.addRow(rowData.toArray(new Comparable[rowData.size()]));
- }
- return dataTable;
- }
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventSearch.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventSearch.java
deleted file mode 100644
index 082f1d3d30..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventSearch.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.Event;
-import org.apache.unomi.api.PartialList;
-import org.apache.unomi.api.conditions.Condition;
-import org.apache.unomi.api.services.DefinitionsService;
-import org.apache.unomi.api.services.EventService;
-import org.apache.unomi.common.DataTable;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Command(scope = "unomi", name = "event-search", description = "This commands search for profile events of a certain type by last timestamp in the Apache Unomi Context Server")
-@Service
-public class EventSearch extends ListCommandSupport {
- @Reference
- private EventService eventService;
-
- @Reference
- DefinitionsService definitionsService;
-
- @Argument(index = 0, name = "profile", description = "The identifier for the profile", required = true, multiValued = false)
- String profileIdentifier;
-
- @Argument(index = 1, name = "eventType", description = "The type of the event", required = false, multiValued = false)
- String eventTypeId;
-
- @Argument(index = 2, name = "maxEntries", description = "The maximum number of entries to retrieve (defaults to 100)", required = false, multiValued = false)
- int maxEntries = 100;
-
- String[] columnHeaders = new String[] {
- "ID",
- "Type",
- "Session",
- "Profile",
- "Timestamp",
- "Scope",
- "Persistent"
- };
-
- @Override
- protected String[] getHeaders() {
- return columnHeaders;
- }
-
- @Override
- protected DataTable buildDataTable() {
- Condition booleanCondition = new Condition(definitionsService.getConditionType("booleanCondition"));
- booleanCondition.setParameter("operator", "and");
- List subConditions = new ArrayList<>();
- if (profileIdentifier != null) {
- Condition eventProfileIdCondition = new Condition(definitionsService.getConditionType("eventPropertyCondition"));
- eventProfileIdCondition.setParameter("propertyName", "profileId");
- eventProfileIdCondition.setParameter("comparisonOperator", "equals");
- eventProfileIdCondition.setParameter("propertyValue", profileIdentifier);
- subConditions.add(eventProfileIdCondition);
- }
- if (eventTypeId != null) {
- Condition eventTypeIdCondition = new Condition(definitionsService.getConditionType("eventTypeCondition"));
- eventTypeIdCondition.setParameter("eventTypeId", eventTypeId);
- subConditions.add(eventTypeIdCondition);
- }
- booleanCondition.setParameter("subConditions", subConditions);
- PartialList lastEvents = eventService.searchEvents(booleanCondition, 0, maxEntries);
- DataTable dataTable = new DataTable();
- for (Event event : lastEvents.getList()) {
- ArrayList rowData = new ArrayList<>();
- rowData.add(event.getItemId());
- rowData.add(event.getEventType());
- rowData.add(event.getSessionId());
- rowData.add(event.getProfileId());
- rowData.add(event.getTimeStamp().toString());
- rowData.add(event.getScope());
- rowData.add(Boolean.toString(event.isPersistent()));
- dataTable.addRow(rowData.toArray(new Comparable[rowData.size()]));
- }
- return dataTable;
- }
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventView.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventView.java
deleted file mode 100644
index 10054ddd46..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventView.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.karaf.shell.api.action.Action;
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.Event;
-import org.apache.unomi.api.PartialList;
-import org.apache.unomi.api.conditions.Condition;
-import org.apache.unomi.api.services.DefinitionsService;
-import org.apache.unomi.api.services.EventService;
-import org.apache.unomi.persistence.spi.CustomObjectMapper;
-
-@Command(scope = "unomi", name = "event-view", description = "This command will dump an Event as a JSON object")
-@Service
-public class EventView implements Action {
-
- @Reference
- EventService eventService;
-
- @Reference
- DefinitionsService definitionsService;
-
- @Argument(index = 0, name = "event", description = "The identifier for the event", required = true, multiValued = false)
- String eventIdentifier;
-
- public Object execute() throws Exception {
-
- Condition eventCondition = new Condition(definitionsService.getConditionType("eventPropertyCondition"));
- eventCondition.setParameter("propertyName", "itemId");
- eventCondition.setParameter("comparisonOperator", "equals");
- eventCondition.setParameter("propertyValue", eventIdentifier);
-
- PartialList matchingEvents = eventService.searchEvents(eventCondition, 0, 10);
- if (matchingEvents == null || matchingEvents.getTotalSize() != 1) {
- System.out.println("Couldn't find a single event with id=" + eventIdentifier + ". Maybe it wasn't a persistent event ?");
- return null;
- }
- String jsonEvent = CustomObjectMapper.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(matchingEvents.get(0));
- System.out.println(jsonEvent);
- return null;
- }
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ProfileList.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ProfileList.java
deleted file mode 100644
index 24f3a2c97b..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ProfileList.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.PartialList;
-import org.apache.unomi.api.Profile;
-import org.apache.unomi.api.conditions.Condition;
-import org.apache.unomi.api.query.Query;
-import org.apache.unomi.api.services.DefinitionsService;
-import org.apache.unomi.api.services.ProfileService;
-import org.apache.unomi.common.DataTable;
-
-import java.util.ArrayList;
-
-@Command(scope = "unomi", name = "profile-list", description = "This commands lists the latest profiles updated in the Apache Unomi Context Server")
-@Service
-public class ProfileList extends ListCommandSupport {
-
- @Reference
- ProfileService profileService;
-
- @Reference
- DefinitionsService definitionsService;
-
- @Argument(index = 0, name = "maxEntries", description = "The maximum number of entries to retrieve (defaults to 100)", required = false, multiValued = false)
- int maxEntries = 100;
-
- @java.lang.Override
- protected String[] getHeaders() {
- return new String[] {
- "ID",
- "Scope",
- "Segments",
- "Consents",
- "Last visit",
- "Last update"
- };
- }
-
- @java.lang.Override
- protected DataTable buildDataTable() {
- Query query = new Query();
- query.setSortby("systemProperties.lastUpdated:desc,properties.lastVisit:desc");
- query.setLimit(maxEntries);
- Condition matchAllCondition = new Condition(definitionsService.getConditionType("matchAllCondition"));
- query.setCondition(matchAllCondition);
- PartialList lastModifiedProfiles = profileService.search(query, Profile.class);
- DataTable dataTable = new DataTable();
- for (Profile profile : lastModifiedProfiles.getList()) {
- ArrayList rowData = new ArrayList<>();
- rowData.add(profile.getItemId());
- rowData.add(profile.getScope());
- rowData.add(StringUtils.join(profile.getSegments(), ","));
- rowData.add(StringUtils.join(profile.getConsents().keySet(), ","));
- rowData.add((String) profile.getProperty("lastVisit"));
- if (profile.getSystemProperties() != null && profile.getSystemProperties().get("lastUpdated") != null) {
- rowData.add((String) profile.getSystemProperties().get("lastUpdated"));
- } else {
- rowData.add("");
- }
- dataTable.addRow(rowData.toArray(new Comparable[rowData.size()]));
- }
- return dataTable;
- }
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ProfileView.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ProfileView.java
deleted file mode 100644
index 89705790f1..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ProfileView.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.karaf.shell.api.action.Action;
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.Profile;
-import org.apache.unomi.api.services.ProfileService;
-import org.apache.unomi.persistence.spi.CustomObjectMapper;
-
-@Command(scope = "unomi", name = "profile-view", description = "This command will dump a profile as a JSON string")
-@Service
-public class ProfileView implements Action {
-
- @Reference
- ProfileService profileService;
-
- @Argument(index = 0, name = "profile", description = "The identifier for the profile", required = true, multiValued = false)
- String profileIdentifier;
-
- public Object execute() throws Exception {
- Profile profile = profileService.load(profileIdentifier);
- if (profile == null) {
- System.out.println("Couldn't find a profile with id=" + profileIdentifier);
- return null;
- }
- String jsonProfile = CustomObjectMapper.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(profile);
- System.out.println(jsonProfile);
- return null;
- }
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleList.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleList.java
deleted file mode 100644
index de5a4d8c62..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleList.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.Metadata;
-import org.apache.unomi.api.PartialList;
-import org.apache.unomi.api.conditions.Condition;
-import org.apache.unomi.api.query.Query;
-import org.apache.unomi.api.rules.RuleStatistics;
-import org.apache.unomi.api.services.DefinitionsService;
-import org.apache.unomi.api.services.RulesService;
-import org.apache.unomi.common.DataTable;
-
-import java.util.ArrayList;
-import java.util.Map;
-
-@Command(scope = "unomi", name = "rule-list", description = "This will list all the rules deployed in the Apache Unomi Context Server")
-@Service
-public class RuleList extends ListCommandSupport {
-
- @Reference
- RulesService rulesService;
-
- @Reference
- DefinitionsService definitionsService;
-
- @Argument(index = 0, name = "maxEntries", description = "The maximum number of entries to retrieve (defaults to 100)", required = false, multiValued = false)
- int maxEntries = 100;
-
- @Override
- protected String[] getHeaders() {
- return new String[] {
- "Activated",
- "Hidden",
- "Read-only",
- "Identifier",
- "Scope",
- "Name",
- "Tags",
- "System tags",
- "Executions",
- "Conditions [ms]",
- "Actions [ms]"
- };
- }
-
- @Override
- protected DataTable buildDataTable() {
- Query query = new Query();
- Condition matchAllCondition = new Condition(definitionsService.getConditionType("matchAllCondition"));
- query.setCondition(matchAllCondition);
- query.setLimit(maxEntries);
- PartialList ruleMetadatas = rulesService.getRuleMetadatas(query);
- if (ruleMetadatas.getList().size() != ruleMetadatas.getTotalSize()) {
- System.out.println("WARNING : Only the first " + ruleMetadatas.getPageSize() + " rules have been retrieved, there are " + ruleMetadatas.getTotalSize() + " rules registered in total. Use the maxEntries parameter to retrieve more rules");
- }
- Map allRuleStatistics = rulesService.getAllRuleStatistics();
-
- DataTable dataTable = new DataTable();
- for (Metadata ruleMetadata : ruleMetadatas.getList()) {
- ArrayList rowData = new ArrayList<>();
- String ruleId = ruleMetadata.getId();
- rowData.add(ruleMetadata.isEnabled() ? "x" : "");
- rowData.add(ruleMetadata.isHidden() ? "x" : "");
- rowData.add(ruleMetadata.isReadOnly() ? "x" : "");
- rowData.add(ruleId);
- rowData.add(ruleMetadata.getScope());
- rowData.add(ruleMetadata.getName());
- rowData.add(StringUtils.join(ruleMetadata.getTags(), ","));
- rowData.add(StringUtils.join(ruleMetadata.getSystemTags(), ","));
- RuleStatistics ruleStatistics = allRuleStatistics.get(ruleId);
- if (ruleStatistics != null) {
- rowData.add(ruleStatistics.getExecutionCount());
- rowData.add(ruleStatistics.getConditionsTime());
- rowData.add(ruleStatistics.getActionsTime());
- } else {
- rowData.add(0L);
- rowData.add(0L);
- rowData.add(0L);
- }
- dataTable.addRow(rowData.toArray(new Comparable[rowData.size()]));
- }
- dataTable.sort(new DataTable.SortCriteria(9, DataTable.SortOrder.DESCENDING),
- new DataTable.SortCriteria(10, DataTable.SortOrder.DESCENDING),
- new DataTable.SortCriteria(5, DataTable.SortOrder.ASCENDING));
- return dataTable;
- }
-
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SegmentList.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SegmentList.java
deleted file mode 100644
index 5f8395137a..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SegmentList.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.Metadata;
-import org.apache.unomi.api.PartialList;
-import org.apache.unomi.api.services.SegmentService;
-import org.apache.unomi.common.DataTable;
-
-import java.util.ArrayList;
-
-@Command(scope = "unomi", name = "segment-list", description = "This will list all the segments present in the Apache Unomi Context Server")
-@Service
-public class SegmentList extends ListCommandSupport {
-
- @Reference
- SegmentService segmentService;
-
- @Argument(index = 0, name = "maxEntries", description = "The maximum number of entries to retrieve (defaults to 100)", required = false, multiValued = false)
- int maxEntries = 100;
-
- @Override
- protected String[] getHeaders() {
- return new String[] {
- "Enabled",
- "Hidden",
- "Id",
- "Scope",
- "Name",
- "System tags"
- };
- }
-
- @Override
- protected DataTable buildDataTable() {
- PartialList segmentMetadatas = segmentService.getSegmentMetadatas(0, maxEntries, null);
-
- DataTable dataTable = new DataTable();
- for (Metadata metadata : segmentMetadatas.getList()) {
- ArrayList rowData = new ArrayList<>();
- rowData.add(metadata.isEnabled() ? "x" : "");
- rowData.add(metadata.isHidden() ? "x" : "");
- rowData.add(metadata.getId());
- rowData.add(metadata.getScope());
- rowData.add(metadata.getName());
- rowData.add(StringUtils.join(metadata.getSystemTags(), ","));
- dataTable.addRow(rowData.toArray(new Comparable[rowData.size()]));
- }
-
- dataTable.sort(new DataTable.SortCriteria(4, DataTable.SortOrder.ASCENDING));
- return dataTable;
- }
-
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SegmentRemove.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SegmentRemove.java
deleted file mode 100644
index 14b4efdf1c..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SegmentRemove.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.karaf.shell.api.action.Action;
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.segments.DependentMetadata;
-import org.apache.unomi.api.services.SegmentService;
-
-@Command(scope = "unomi", name = "segment-remove", description = "Remove segments in the Apache Unomi Context Server")
-@Service
-public class SegmentRemove implements Action {
-
- @Reference
- SegmentService segmentService;
-
- @Argument(index = 0, name = "segmentId", description = "The identifier for the segment", required = true, multiValued = false)
- String segmentIdentifier;
-
- @Argument(index = 1, name = "validate", description = "Check if the segment is used in goals or other segments", required = false, multiValued = false)
- Boolean validate = true;
-
-
- public Object execute() throws Exception {
- DependentMetadata dependantMetadata = segmentService.removeSegmentDefinition(segmentIdentifier, validate);
- if (!validate || (dependantMetadata.getSegments().isEmpty() && dependantMetadata.getScorings().isEmpty())) {
- System.out.println("Segment " + segmentIdentifier + " successfully deleted");
- } else if (validate) {
- System.out.print("Segment " + segmentIdentifier + " could not be deleted because of the following dependents:");
- if (!dependantMetadata.getScorings().isEmpty()) {
- System.out.print(" scoring:" + dependantMetadata.getScorings());
- }
- if (!dependantMetadata.getSegments().isEmpty()) {
- System.out.println(" segments:" + dependantMetadata.getSegments());
- }
- }
- return null;
- }
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SegmentView.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SegmentView.java
deleted file mode 100644
index 9016aa9ed0..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SegmentView.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.karaf.shell.api.action.Action;
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.segments.Segment;
-import org.apache.unomi.api.services.SegmentService;
-import org.apache.unomi.persistence.spi.CustomObjectMapper;
-
-@Command(scope = "unomi", name = "segment-view", description = "This will allows to view a segment in the Apache Unomi Context Server")
-@Service
-public class SegmentView implements Action {
-
- @Reference
- SegmentService segmentService;
-
- @Argument(index = 0, name = "segmentId", description = "The identifier for the segment", required = true, multiValued = false)
- String segmentIdentifier;
-
- public Object execute() throws Exception {
- Segment segment = segmentService.getSegmentDefinition(segmentIdentifier);
- if (segment == null) {
- System.out.println("Couldn't find a segment with id=" + segmentIdentifier);
- return null;
- }
- String jsonRule = CustomObjectMapper.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(segment);
- System.out.println(jsonRule);
- return null;
- }
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SessionList.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SessionList.java
deleted file mode 100644
index a3d0fda989..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/SessionList.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.karaf.shell.api.action.Argument;
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.PartialList;
-import org.apache.unomi.api.Session;
-import org.apache.unomi.api.conditions.Condition;
-import org.apache.unomi.api.query.Query;
-import org.apache.unomi.api.services.DefinitionsService;
-import org.apache.unomi.api.services.ProfileService;
-import org.apache.unomi.common.DataTable;
-
-import java.util.ArrayList;
-
-@Command(scope = "unomi", name = "session-list", description = "This commands lists the latest sessions updated in the Apache Unomi Context Server")
-@Service
-public class SessionList extends ListCommandSupport {
-
- @Reference
- ProfileService profileService;
-
- @Reference
- DefinitionsService definitionsService;
-
- @Argument(index = 0, name = "maxEntries", description = "The maximum number of entries to retrieve (defaults to 100)", required = false, multiValued = false)
- int maxEntries = 100;
-
- @java.lang.Override
- protected String[] getHeaders() {
- return new String[] {
- "ID",
- "Scope",
- "Last event",
- "Duration",
- "Profile",
- "Timestamp"
- };
- }
-
- @java.lang.Override
- protected DataTable buildDataTable() {
- Query query = new Query();
- query.setSortby("lastEventDate:desc");
- query.setLimit(maxEntries);
- Condition matchAllCondition = new Condition(definitionsService.getConditionType("matchAllCondition"));
- query.setCondition(matchAllCondition);
- PartialList lastModifiedProfiles = profileService.searchSessions(query);
- DataTable dataTable = new DataTable();
- for (Session session : lastModifiedProfiles.getList()) {
- ArrayList rowData = new ArrayList<>();
- rowData.add(session.getItemId());
- rowData.add(session.getScope());
- rowData.add(session.getLastEventDate());
- rowData.add(session.getDuration());
- rowData.add(session.getProfileId());
- rowData.add(session.getTimeStamp());
- dataTable.addRow(rowData.toArray(new Comparable[rowData.size()]));
- }
- return dataTable;
- }
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/UndeployDefinition.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/UndeployDefinition.java
deleted file mode 100644
index e672bff149..0000000000
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/UndeployDefinition.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.shell.commands;
-
-import org.apache.karaf.shell.api.action.Command;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.unomi.api.Patch;
-import org.apache.unomi.api.PersonaWithSessions;
-import org.apache.unomi.api.PropertyType;
-import org.apache.unomi.api.actions.ActionType;
-import org.apache.unomi.api.campaigns.Campaign;
-import org.apache.unomi.api.conditions.ConditionType;
-import org.apache.unomi.api.goals.Goal;
-import org.apache.unomi.api.rules.Rule;
-import org.apache.unomi.api.segments.Scoring;
-import org.apache.unomi.api.segments.Segment;
-import org.apache.unomi.persistence.spi.CustomObjectMapper;
-
-import java.io.IOException;
-import java.net.URL;
-
-@Command(scope = "unomi", name = "undeploy-definition", description = "This will undeploy definitions contained in bundles")
-@Service
-public class UndeployDefinition extends DeploymentCommandSupport {
-
- public void processDefinition(String definitionType, URL definitionURL) {
- try {
- if (ALL_OPTION_LABEL.equals(definitionType)) {
- String definitionURLString = definitionURL.toString();
- for (String possibleDefinitionType : definitionTypes) {
- if (definitionURLString.contains(getDefinitionTypePath(possibleDefinitionType))) {
- definitionType = possibleDefinitionType;
- break;
- }
- }
- if (ALL_OPTION_LABEL.equals(definitionType)) {
- System.out.println("Couldn't resolve definition type for definition URL " + definitionURL);
- return;
- }
- }
- boolean successful = true;
- switch (definitionType) {
- case CONDITION_DEFINITION_TYPE:
- ConditionType conditionType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, ConditionType.class);
- definitionsService.removeActionType(conditionType.getItemId());
- break;
- case ACTION_DEFINITION_TYPE:
- ActionType actionType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, ActionType.class);
- definitionsService.removeActionType(actionType.getItemId());
- break;
- case GOAL_DEFINITION_TYPE:
- Goal goal = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Goal.class);
- goalsService.removeGoal(goal.getItemId());
- break;
- case CAMPAIGN_DEFINITION_TYPE:
- Campaign campaign = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Campaign.class);
- goalsService.removeCampaign(campaign.getItemId());
- break;
- case PERSONA_DEFINITION_TYPE:
- PersonaWithSessions persona = CustomObjectMapper.getObjectMapper().readValue(definitionURL, PersonaWithSessions.class);
- profileService.delete(persona.getPersona().getItemId(), true);
- break;
- case PROPERTY_DEFINITION_TYPE:
- PropertyType propertyType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, PropertyType.class);
- profileService.deletePropertyType(propertyType.getItemId());
- break;
- case RULE_DEFINITION_TYPE:
- Rule rule = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Rule.class);
- rulesService.removeRule(rule.getItemId());
- break;
- case SEGMENT_DEFINITION_TYPE:
- Segment segment = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Segment.class);
- segmentService.removeSegmentDefinition(segment.getItemId(), false);
- break;
- case SCORING_DEFINITION_TYPE:
- Scoring scoring = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Scoring.class);
- segmentService.removeScoringDefinition(scoring.getItemId(), false);
- break;
- case PATCH_DEFINITION_TYPE:
- Patch patch = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Patch.class);
- // patchService.patch(patch);
- break;
- default:
- System.out.println("Unrecognized definition type: " + definitionType);
- successful = false;
- break;
- }
- if (successful) {
- System.out.println("Predefined definition unregistered : " + definitionURL.getFile());
- }
- } catch (IOException e) {
- System.out.println("Error while removing definition " + definitionURL);
- System.out.println(e.getMessage());
- }
- }
-
-}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/actions/UnomiCrudCommand.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/actions/UnomiCrudCommand.java
new file mode 100644
index 0000000000..2ca8433f15
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/actions/UnomiCrudCommand.java
@@ -0,0 +1,608 @@
+/*
+ * 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.shell.dev.actions;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.apache.karaf.shell.api.action.*;
+import org.apache.karaf.shell.api.action.lifecycle.Init;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+import java.io.PrintStream;
+import org.apache.unomi.shell.dev.completers.IdCompleter;
+import org.apache.unomi.shell.dev.completers.OperationCompleter;
+import org.apache.unomi.shell.dev.completers.TypeCompleter;
+import org.apache.unomi.shell.dev.services.CrudCommand;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+
+@Command(scope = "unomi", name = "crud", description = "Perform CRUD operations on Unomi objects")
+@Service
+public class UnomiCrudCommand implements Action {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(UnomiCrudCommand.class.getName());
+
+ private static final ObjectMapper OBJECT_MAPPER = CustomObjectMapper.getObjectMapper();
+
+ @Reference
+ private BundleContext bundleContext;
+
+ @Reference
+ private Session session;
+
+ @Argument(index = 0, name = "operation", description = "Operation to perform (create/read/update/delete/list/help)", required = true)
+ @Completion(OperationCompleter.class)
+ private String operation;
+
+ @Argument(index = 1, name = "type", description = "Object type", required = true)
+ @Completion(TypeCompleter.class)
+ private String type;
+
+ // Multi-valued argument that captures all remaining tokens after type
+ // ⚠️ IMPORTANT: Only the last argument (highest index) can be multi-valued in Karaf
+ // Since this is at index 2 (last argument), it can safely be multi-valued
+ // For create: remaining[0] = JSON/URL
+ // For read/delete: remaining[0] = ID
+ // For update: remaining[0] = ID, remaining[1] = JSON/URL
+ // For list: remaining contains all remaining tokens (--csv, -n, 50, etc.) for manual parsing
+ @Argument(index = 2, name = "remaining", description = "ID/JSON/URL (for create/read/update/delete) or remaining tokens (for list)", required = false, multiValued = true)
+ @Completion(IdCompleter.class) // Could be enhanced to detect context
+ private List remaining;
+
+ // Option fields for list operation (populated via manual parsing from remaining)
+ @Option(name = "--csv", description = "Output list in CSV format", required = false, multiValued = false)
+ private boolean csv;
+
+ @Option(name = "-n", aliases = "--max-entries", description = "Maximum number of entries to list", required = false)
+ private Integer maxEntries;
+
+ @Init
+ public void init() {
+ LOGGER.debug("UnomiCrudCommand init");
+ }
+
+ /**
+ * Check if a token is a max-entries option flag.
+ *
+ * @param token the token to check
+ * @return true if the token is -n or --max-entries
+ */
+ private boolean isMaxEntriesOption(String token) {
+ return "-n".equals(token) || "--max-entries".equals(token);
+ }
+
+ /**
+ * Parse max-entries option value from the remaining list.
+ * Validates that the value is a positive integer.
+ *
+ * @param remaining the remaining argument list
+ * @param index the index of the option flag
+ * @return the parsed integer value (must be > 0), or null if invalid/missing
+ */
+ private Integer parseMaxEntriesValue(List remaining, int index) {
+ if (index + 1 >= remaining.size()) {
+ return null;
+ }
+ try {
+ int value = Integer.parseInt(remaining.get(index + 1));
+ // Only accept positive values
+ if (value <= 0) {
+ LOGGER.warn("Invalid max-entries value (must be positive): " + value);
+ return null;
+ }
+ return value;
+ } catch (NumberFormatException e) {
+ LOGGER.warn("Invalid number for max-entries option: " + remaining.get(index + 1));
+ return null;
+ }
+ }
+
+ /**
+ * Parse list-specific options from the remaining argument list.
+ * This implements Option 1: Simple Manual Parsing from the redesign proposal.
+ *
+ * Note: If --csv was already set by Karaf's option parser (when placed before arguments),
+ * we preserve that value. Otherwise, we parse it from the remaining list.
+ *
+ * @param remaining List of remaining tokens after type (e.g., ["--csv", "-n", "50"])
+ */
+ private void parseListOptions(List remaining) {
+ // Preserve csv value if already set by Karaf's option parser (when --csv comes before arguments)
+ boolean csv = this.csv;
+ Integer maxEntries = this.maxEntries;
+
+ if (remaining == null || remaining.isEmpty()) {
+ // Keep existing values if already set by Karaf
+ return;
+ }
+
+ for (int i = 0; i < remaining.size(); i++) {
+ String token = remaining.get(i);
+
+ if ("--csv".equals(token)) {
+ csv = true;
+ } else if (isMaxEntriesOption(token)) {
+ Integer value = parseMaxEntriesValue(remaining, i);
+ if (value != null) {
+ maxEntries = value;
+ i++; // Skip the next token as it's the value
+ }
+ }
+ // Ignore unknown tokens (could log warning)
+ }
+
+ // Populate option fields
+ this.csv = csv;
+ this.maxEntries = maxEntries;
+ }
+
+ /**
+ * Check if remaining argument list has at least the specified number of non-empty elements.
+ *
+ * @param remaining the remaining argument list
+ * @param minSize minimum number of elements required
+ * @return true if valid, false otherwise
+ */
+ private boolean hasMinimumRemainingArgs(List remaining, int minSize) {
+ if (remaining == null || remaining.size() < minSize) {
+ return false;
+ }
+ for (int i = 0; i < minSize; i++) {
+ if (StringUtils.isBlank(remaining.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Parse JSON properties from remaining argument or URL with error handling.
+ * This method wraps parseProperties() and handles exceptions, providing consistent error messages.
+ *
+ * @param jsonOrUrl JSON string or URL from remaining argument
+ * @param console Console for error output
+ * @return Map of properties, or null if parsing failed or error occurred
+ */
+ private Map parsePropertiesWithErrorHandling(String jsonOrUrl, PrintStream console) {
+ final String errorMsg = "Error: Failed to parse JSON or URL: " + jsonOrUrl;
+ try {
+ Map props = parseProperties(jsonOrUrl);
+ if (props == null) {
+ console.println(errorMsg);
+ }
+ return props;
+ } catch (Exception e) {
+ console.println(errorMsg);
+ console.println("Error details: " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Strip surrounding quotes from a string if present.
+ *
+ * @param str the string to process
+ * @return the string with quotes removed, or original if no quotes
+ */
+ private String stripQuotes(String str) {
+ if (StringUtils.isEmpty(str) || str.length() < 2) {
+ return str;
+ }
+ // Check for single quotes
+ if (str.charAt(0) == '\'' && str.charAt(str.length() - 1) == '\'') {
+ return str.substring(1, str.length() - 1);
+ }
+ // Check for double quotes
+ if (str.charAt(0) == '"' && str.charAt(str.length() - 1) == '"') {
+ return str.substring(1, str.length() - 1);
+ }
+ return str;
+ }
+
+ /**
+ * Check if a string is a valid URL by attempting to parse it as a URI.
+ * This method supports all URL schemes that Pax URL supports:
+ * - file:// (file protocol)
+ * - http://, https:// (HTTP/HTTPS protocols)
+ * - mvn: (Maven protocol)
+ * - war: (War protocol)
+ * - Any other valid URI scheme
+ *
+ * The method uses Java's URI class to validate the scheme, which is more
+ * robust than simple string matching and supports all standard and custom schemes.
+ *
+ * @param str the string to check
+ * @return true if the string is a valid URI with a scheme, false otherwise
+ */
+ private boolean isUrl(String str) {
+ if (StringUtils.isBlank(str)) {
+ return false;
+ }
+
+ // JSON strings typically start with { or [, so they're not URLs
+ String trimmed = str.trim();
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
+ return false;
+ }
+
+ try {
+ URI uri = new URI(trimmed);
+ // A valid URI with a scheme is considered a URL
+ // getScheme() returns null for relative URIs, which are not URLs
+ return uri.getScheme() != null;
+ } catch (URISyntaxException e) {
+ // Not a valid URI, so not a URL
+ return false;
+ }
+ }
+
+ /**
+ * Parse JSON from a file URL.
+ *
+ * @param fileUrl the file:// URL
+ * @return the parsed JSON as a Map
+ * @throws Exception if there's an error reading or parsing the file
+ */
+ private Map parseFileUrl(String fileUrl) throws Exception {
+ URI uri = new URI(fileUrl);
+ String scheme = uri.getScheme();
+
+ if (!"file".equals(scheme)) {
+ throw new IllegalArgumentException("Expected file:// URL, got: " + fileUrl);
+ }
+
+ // Handle file:// URLs - getPath() handles both file:///path and file://path
+ String filePath = uri.getPath();
+ if (filePath == null || filePath.isEmpty()) {
+ throw new IllegalArgumentException("Invalid file URL: " + fileUrl);
+ }
+
+ @SuppressWarnings("unchecked")
+ Map result = OBJECT_MAPPER.readValue(Files.readString(Paths.get(filePath)), Map.class);
+ return result;
+ }
+
+ /**
+ * Parse JSON properties from remaining argument or URL.
+ * Supports:
+ * - Inline JSON string: {"itemId":"test"} (quoted or unquoted)
+ * - File URL: file:///path/to/file.json
+ * - HTTP/HTTPS URL: http://example.com/data.json (not yet implemented)
+ * - Maven URL: mvn:groupId/artifactId/version (not yet implemented)
+ * - War URL: war:file://path/to.war (not yet implemented)
+ * - Any other Pax URL supported scheme (not yet implemented)
+ *
+ * Note: If JSON is quoted in the command (e.g., '{"itemId":"test"}'),
+ * the Gogo parser will strip the quotes before passing to this method.
+ * This method handles both quoted and unquoted JSON strings.
+ *
+ * @param jsonOrUrl JSON string or URL from remaining argument
+ * @return Map of properties, or null if invalid
+ * @throws Exception if there's an error parsing the JSON or reading the URL
+ */
+ private Map parseProperties(String jsonOrUrl) throws Exception {
+ if (StringUtils.isBlank(jsonOrUrl)) {
+ return null;
+ }
+
+ String trimmed = stripQuotes(StringUtils.trim(jsonOrUrl));
+
+ if (isUrl(trimmed)) {
+ URI uri = new URI(trimmed);
+ String scheme = uri.getScheme();
+
+ if ("file".equals(scheme)) {
+ return parseFileUrl(trimmed);
+ } else {
+ // Other URL schemes (http, https, mvn, war, etc.) are not yet supported
+ // In the future, we could use Pax URL's URLStreamHandler to resolve these
+ throw new UnsupportedOperationException(
+ "URL scheme '" + scheme + "' is not yet supported. " +
+ "Currently only file:// URLs are supported. Use file:// or inline JSON.");
+ }
+ }
+
+ // Treat as inline JSON
+ @SuppressWarnings("unchecked")
+ Map result = OBJECT_MAPPER.readValue(trimmed, Map.class);
+ return result;
+ }
+
+ /**
+ * Validate that operation and type are provided.
+ *
+ * @param console console for error output
+ * @return true if valid, false otherwise
+ */
+ private boolean validateOperationAndType(PrintStream console) {
+ if (StringUtils.isBlank(operation)) {
+ console.println("Error: Operation is required");
+ console.println("Usage: unomi:crud [remaining...]");
+ console.println("Available operations: create, read, update, delete, list, help");
+ return false;
+ }
+
+ if (StringUtils.isBlank(type)) {
+ console.println("Error: Type is required");
+ console.println("Usage: unomi:crud [remaining...]");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Execute the appropriate handler for the given operation.
+ *
+ * @param cmd the CrudCommand instance
+ * @param operationLower the lowercase operation name
+ * @param console console for output
+ * @return the result of the operation
+ * @throws Exception if the operation fails
+ */
+ private Object executeOperation(CrudCommand cmd, String operationLower, PrintStream console) throws Exception {
+ switch (operationLower) {
+ case "create":
+ return handleCreate(cmd, console);
+
+ case "read":
+ return handleRead(cmd, console);
+
+ case "update":
+ return handleUpdate(cmd, console);
+
+ case "delete":
+ return handleDelete(cmd, console);
+
+ case "list":
+ return handleList(cmd, console);
+
+ case "help":
+ console.println("Properties for " + type + ":");
+ console.println(cmd.getPropertiesHelp());
+ return null;
+
+ default:
+ console.println("Unknown operation: " + operation);
+ console.println("Available operations: create, read, update, delete, list, help");
+ return null;
+ }
+ }
+
+ /**
+ * Find and execute the CrudCommand for the given type.
+ *
+ * @param console console for output
+ * @return true if a handler was found and executed, false otherwise
+ * @throws Exception if the operation fails
+ */
+ private boolean findAndExecuteCommand(PrintStream console) throws Exception {
+ ServiceReference>[] refs = bundleContext.getAllServiceReferences(CrudCommand.class.getName(), null);
+ if (refs == null) {
+ return false;
+ }
+
+ String operationLower = operation.toLowerCase();
+ for (ServiceReference> ref : refs) {
+ CrudCommand cmd = (CrudCommand) bundleContext.getService(ref);
+ if (cmd.getObjectType().equals(type)) {
+ try {
+ executeOperation(cmd, operationLower, console);
+ return true; // Handler found and executed
+ } finally {
+ bundleContext.ungetService(ref);
+ }
+ }
+ }
+ return false; // No handler found
+ }
+
+ @Override
+ public Object execute() throws Exception {
+ PrintStream console = session.getConsole();
+
+ if (!validateOperationAndType(console)) {
+ return null;
+ }
+
+ boolean handlerFound = findAndExecuteCommand(console);
+ if (!handlerFound) {
+ console.println("No handler found for object type: " + type);
+ }
+ return null;
+ }
+
+ /**
+ * Validate that remaining argument list has at least one non-empty element for create operation.
+ *
+ * @param remaining the remaining argument list
+ * @param console console for error output
+ * @return true if valid, false otherwise
+ */
+ private boolean validateCreateRemaining(List remaining, PrintStream console) {
+ if (!hasMinimumRemainingArgs(remaining, 1)) {
+ console.println("Error: JSON string or URL is required for create operation");
+ console.println("Usage: unomi:crud create ");
+ console.println("Example: unomi:crud create goal '{\"itemId\":\"test\",\"enabled\":true}'");
+ console.println("Example: unomi:crud create goal file:///path/to/file.json");
+ console.println("Note: Quote JSON strings to ensure they're treated as a single argument");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Handle create operation.
+ * Syntax: unomi:crud create
+ * remaining[0] = JSON string or URL
+ */
+ private Object handleCreate(CrudCommand cmd, PrintStream console) throws Exception {
+ if (!validateCreateRemaining(remaining, console)) {
+ return null;
+ }
+
+ String jsonOrUrl = remaining.get(0);
+ Map createProps = parsePropertiesWithErrorHandling(jsonOrUrl, console);
+ if (createProps == null) {
+ return null;
+ }
+
+ // Validate that we have at least some properties (empty JSON {} is not valid)
+ if (createProps.isEmpty()) {
+ console.println("Error: Empty JSON object is not valid. Please provide required properties.");
+ console.println("Usage: unomi:crud create ");
+ return null;
+ }
+
+ String newId = cmd.create(createProps);
+ if (newId == null) {
+ console.println("Error: Failed to create " + type + ". The create operation returned null.");
+ return null;
+ }
+ console.println("Created " + type + " with ID: " + newId);
+ return null;
+ }
+
+ /**
+ * Validate that remaining argument list has at least one non-empty element.
+ *
+ * @param remaining the remaining argument list
+ * @param operation the operation name (for error messages)
+ * @param console console for error output
+ * @return true if valid, false otherwise
+ */
+ private boolean validateRemainingNotEmpty(List remaining, String operation, PrintStream console) {
+ if (!hasMinimumRemainingArgs(remaining, 1)) {
+ console.println("Error: ID is required for " + operation + " operation");
+ console.println("Usage: unomi:crud " + operation + " ");
+ console.println("Example: unomi:crud " + operation + " goal test-goal-123");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Handle read operation.
+ * Syntax: unomi:crud read
+ * remaining[0] = Object ID
+ */
+ private Object handleRead(CrudCommand cmd, PrintStream console) throws Exception {
+ if (!validateRemainingNotEmpty(remaining, "read", console)) {
+ return null;
+ }
+
+ String id = remaining.get(0);
+ Map obj = cmd.read(id);
+ if (obj != null) {
+ console.println(OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj));
+ } else {
+ console.println(type + " not found with ID: " + id);
+ }
+ return null;
+ }
+
+ /**
+ * Handle update operation.
+ * Syntax: unomi:crud update
+ * remaining[0] = Object ID
+ * remaining[1] = JSON string or URL
+ */
+ private Object handleUpdate(CrudCommand cmd, PrintStream console) throws Exception {
+ if (!hasMinimumRemainingArgs(remaining, 2)) {
+ console.println("Error: ID and JSON/URL are required for update operation");
+ console.println("Usage: unomi:crud update ");
+ console.println("Example: unomi:crud update goal test-goal-123 '{\"itemId\":\"test-goal-123\",\"enabled\":false}'");
+ console.println("Note: Quote JSON strings to ensure they're treated as a single argument");
+ return null;
+ }
+
+ String id = remaining.get(0);
+ String jsonOrUrl = remaining.get(1);
+
+ // hasMinimumRemainingArgs already ensures both id and jsonOrUrl are non-blank
+ Map updateProps = parsePropertiesWithErrorHandling(jsonOrUrl, console);
+ if (updateProps == null) {
+ return null;
+ }
+
+ cmd.update(id, updateProps);
+ console.println("Updated " + type + " with ID: " + id);
+ return null;
+ }
+
+ /**
+ * Handle delete operation.
+ * Syntax: unomi:crud delete
+ * remaining[0] = Object ID
+ */
+ private Object handleDelete(CrudCommand cmd, PrintStream console) throws Exception {
+ if (!validateRemainingNotEmpty(remaining, "delete", console)) {
+ return null;
+ }
+
+ String id = remaining.get(0);
+ cmd.delete(id);
+ console.println("Deleted " + type + " with ID: " + id);
+ return null;
+ }
+
+ /**
+ * Handle list operation.
+ * Syntax: unomi:crud list [--csv] [-n ]
+ * remaining contains all remaining tokens (--csv, -n, 50, etc.) for manual parsing
+ */
+ private Object handleList(CrudCommand cmd, PrintStream console) throws Exception {
+ // Parse list-specific options from remaining argument
+ parseListOptions(remaining);
+
+ String[] headers = cmd.getHeaders();
+ if (headers == null || headers.length == 0) {
+ console.println("Error: No headers available for " + type);
+ return null;
+ }
+
+ // Ensure limit is positive (default to 100 if null or invalid)
+ int limit = (maxEntries != null && maxEntries > 0) ? maxEntries : 100;
+
+ if (csv) {
+ // Generate proper CSV output using Apache Commons CSV
+ cmd.buildCsvOutput(console, headers, limit);
+ } else {
+ // Generate table output
+ ShellTable table = new ShellTable();
+ for (String header : headers) {
+ table.column(header);
+ }
+ cmd.buildRows(table, limit);
+ table.print(console, true);
+ }
+ return null;
+ }
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/BaseCommand.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/BaseCommand.java
new file mode 100644
index 0000000000..a968aaff1b
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/BaseCommand.java
@@ -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.
+ */
+package org.apache.unomi.shell.dev.commands;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.table.ShellTable;
+import org.apache.unomi.persistence.spi.PersistenceService;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ * Base class for Unomi shell commands
+ */
+public abstract class BaseCommand implements Action {
+ protected static final int DEFAULT_ENTRIES = 100;
+
+ @Reference
+ protected PersistenceService persistenceService;
+
+ protected ShellTable buildTable() {
+ ShellTable table = new ShellTable();
+ table.column("ID");
+ table.column("Name");
+ table.column("Description");
+ return table;
+ }
+
+ protected void printTable(ShellTable table, Session session) {
+ table.print(session.getConsole());
+ }
+
+ protected boolean confirm(Session session, String message) throws IOException {
+ String response = session.readLine(message + " (y/n): ", null);
+ return response != null && response.toLowerCase().startsWith("y");
+ }
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/BaseListCommand.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/BaseListCommand.java
new file mode 100644
index 0000000000..f40b51a359
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/BaseListCommand.java
@@ -0,0 +1,74 @@
+/*
+ * 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.shell.dev.commands;
+
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.table.ShellTable;
+import org.apache.unomi.api.Item;
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.services.DefinitionsService;
+
+/**
+ * Base class for list commands
+ */
+public abstract class BaseListCommand extends BaseCommand {
+
+ @Reference
+ protected DefinitionsService definitionsService;
+
+ @Reference
+ protected Session session;
+
+ @Option(name = "--max-entries", description = "Maximum number of entries to display", required = false)
+ protected int maxEntries = DEFAULT_ENTRIES;
+
+ @Option(name = "--sort-by", description = "Sort by field name", required = false)
+ protected String sortBy;
+
+ protected abstract Class getItemType();
+
+ protected abstract void printItem(ShellTable table, T item);
+
+ @Override
+ public Object execute() throws Exception {
+ Query query = new Query();
+ query.setLimit(maxEntries);
+ query.setSortby(sortBy);
+
+ Condition condition = new Condition();
+ condition.setConditionType(definitionsService.getConditionType("matchAllCondition"));
+ query.setCondition(condition);
+
+ PartialList items = persistenceService.query(query.getCondition(), query.getSortby(), getItemType(), query.getOffset(), query.getLimit());
+
+ ShellTable table = buildTable();
+ for (T item : items.getList()) {
+ printItem(table, item);
+ }
+ printTable(table, session);
+
+ return null;
+ }
+
+ public void setDefinitionsService(DefinitionsService definitionsService) {
+ this.definitionsService = definitionsService;
+ }
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/BaseSimpleCommand.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/BaseSimpleCommand.java
new file mode 100644
index 0000000000..1ef8ee99bc
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/BaseSimpleCommand.java
@@ -0,0 +1,61 @@
+/*
+ * 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.shell.dev.commands;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.console.Session;
+
+import java.io.PrintStream;
+
+/**
+ * Base class for simple shell commands that provides common functionality
+ * for accessing Session and console output.
+ */
+public abstract class BaseSimpleCommand implements Action {
+
+ @Reference
+ protected Session session;
+
+ /**
+ * Get the console PrintStream from the session.
+ *
+ * @return the console PrintStream
+ */
+ protected PrintStream getConsole() {
+ return session.getConsole();
+ }
+
+ /**
+ * Print a message to the console.
+ *
+ * @param message the message to print
+ */
+ protected void println(String message) {
+ getConsole().println(message);
+ }
+
+ /**
+ * Print a formatted message to the console.
+ *
+ * @param format the format string
+ * @param args the arguments
+ */
+ protected void printf(String format, Object... args) {
+ getConsole().printf(format, args);
+ }
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/CommandUtils.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/CommandUtils.java
new file mode 100644
index 0000000000..44dfcbf821
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/CommandUtils.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.shell.dev.commands;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Utility class for common command functionality.
+ */
+public final class CommandUtils {
+
+ /**
+ * Standard date format used across commands: "yyyy-MM-dd HH:mm:ss"
+ */
+ public static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+ /**
+ * Thread-local SimpleDateFormat instance for date formatting.
+ * SimpleDateFormat is not thread-safe, so we use ThreadLocal to ensure thread safety.
+ */
+ private static final ThreadLocal DATE_FORMAT =
+ ThreadLocal.withInitial(() -> new SimpleDateFormat(DATE_FORMAT_PATTERN));
+
+ private CommandUtils() {
+ // Utility class - prevent instantiation
+ }
+
+ /**
+ * Format a date using the standard date format pattern.
+ *
+ * @param date the date to format
+ * @return the formatted date string, or "-" if date is null
+ */
+ public static String formatDate(Date date) {
+ if (date == null) {
+ return "-";
+ }
+ return DATE_FORMAT.get().format(date);
+ }
+
+ /**
+ * Format a date using the standard date format pattern.
+ *
+ * @param date the date to format
+ * @param nullValue the value to return if date is null
+ * @return the formatted date string, or nullValue if date is null
+ */
+ public static String formatDate(Date date, String nullValue) {
+ if (date == null) {
+ return nullValue;
+ }
+ return DATE_FORMAT.get().format(date);
+ }
+
+ /**
+ * Get a SimpleDateFormat instance for the standard pattern.
+ * Note: This returns a new instance each time. For thread-safe usage,
+ * prefer using formatDate() methods.
+ *
+ * @return a SimpleDateFormat instance
+ */
+ public static SimpleDateFormat getDateFormat() {
+ return new SimpleDateFormat(DATE_FORMAT_PATTERN);
+ }
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/DeployDefinition.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/DeployDefinition.java
new file mode 100644
index 0000000000..243e04e518
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/DeployDefinition.java
@@ -0,0 +1,137 @@
+/*
+ * 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.shell.dev.commands;
+
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.unomi.api.Patch;
+import org.apache.unomi.api.PersonaWithSessions;
+import org.apache.unomi.api.PropertyType;
+import org.apache.unomi.api.actions.ActionType;
+import org.apache.unomi.api.campaigns.Campaign;
+import org.apache.unomi.api.conditions.ConditionType;
+import org.apache.unomi.api.goals.Goal;
+import org.apache.unomi.api.rules.Rule;
+import org.apache.unomi.api.segments.Scoring;
+import org.apache.unomi.api.segments.Segment;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.URL;
+
+@Command(scope = "unomi", name = "deploy-definition", description = "This will deploy Unomi definitions contained in bundles")
+@Service
+public class DeployDefinition extends DeploymentCommandSupport {
+
+ public void processDefinition(String definitionType, URL definitionURL) {
+ try {
+ processDefinitionInternal(definitionType, definitionURL, getConsole(), "Predefined definition registered");
+ } catch (IOException e) {
+ handleDefinitionError(definitionURL, "saving", e);
+ }
+ }
+
+ protected void deployConditionType(URL definitionURL) throws IOException {
+ ConditionType conditionType = readDefinition(definitionURL, ConditionType.class);
+ definitionsService.setConditionType(conditionType);
+ }
+
+ protected void deployActionType(URL definitionURL) throws IOException {
+ ActionType actionType = readDefinition(definitionURL, ActionType.class);
+ definitionsService.setActionType(actionType);
+ }
+
+ protected void deployGoal(URL definitionURL) throws IOException {
+ Goal goal = readDefinition(definitionURL, Goal.class);
+ goalsService.setGoal(goal);
+ }
+
+ protected void deployCampaign(URL definitionURL) throws IOException {
+ Campaign campaign = readDefinition(definitionURL, Campaign.class);
+ goalsService.setCampaign(campaign);
+ }
+
+ protected void deployPersona(URL definitionURL) throws IOException {
+ PersonaWithSessions persona = readDefinition(definitionURL, PersonaWithSessions.class);
+ profileService.savePersonaWithSessions(persona);
+ }
+
+ protected void deployPropertyType(URL definitionURL) throws IOException {
+ PropertyType propertyType = readDefinition(definitionURL, PropertyType.class);
+ profileService.setPropertyTypeTarget(definitionURL, propertyType);
+ profileService.setPropertyType(propertyType);
+ }
+
+ protected void deployRule(URL definitionURL) throws IOException {
+ Rule rule = readDefinition(definitionURL, Rule.class);
+ rulesService.setRule(rule);
+ }
+
+ protected void deploySegment(URL definitionURL) throws IOException {
+ Segment segment = readDefinition(definitionURL, Segment.class);
+ segmentService.setSegmentDefinition(segment);
+ }
+
+ protected void deployScoring(URL definitionURL) throws IOException {
+ Scoring scoring = readDefinition(definitionURL, Scoring.class);
+ segmentService.setScoringDefinition(scoring);
+ }
+
+ protected void deployPatch(URL definitionURL) throws IOException {
+ Patch patch = readDefinition(definitionURL, Patch.class);
+ patchService.patch(patch);
+ }
+
+ @Override
+ protected boolean processDefinitionByType(String definitionType, URL definitionURL, PrintStream console) throws IOException {
+ switch (definitionType) {
+ case CONDITION_DEFINITION_TYPE:
+ deployConditionType(definitionURL);
+ return true;
+ case ACTION_DEFINITION_TYPE:
+ deployActionType(definitionURL);
+ return true;
+ case GOAL_DEFINITION_TYPE:
+ deployGoal(definitionURL);
+ return true;
+ case CAMPAIGN_DEFINITION_TYPE:
+ deployCampaign(definitionURL);
+ return true;
+ case PERSONA_DEFINITION_TYPE:
+ deployPersona(definitionURL);
+ return true;
+ case PROPERTY_DEFINITION_TYPE:
+ deployPropertyType(definitionURL);
+ return true;
+ case RULE_DEFINITION_TYPE:
+ deployRule(definitionURL);
+ return true;
+ case SEGMENT_DEFINITION_TYPE:
+ deploySegment(definitionURL);
+ return true;
+ case SCORING_DEFINITION_TYPE:
+ deployScoring(definitionURL);
+ return true;
+ case PATCH_DEFINITION_TYPE:
+ deployPatch(definitionURL);
+ return true;
+ default:
+ console.println("Unrecognized definition type:" + definitionType);
+ return false;
+ }
+ }
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeploymentCommandSupport.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/DeploymentCommandSupport.java
similarity index 72%
rename from tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeploymentCommandSupport.java
rename to tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/DeploymentCommandSupport.java
index 56db909abf..7bfdf79af7 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeploymentCommandSupport.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/DeploymentCommandSupport.java
@@ -14,14 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.unomi.shell.commands;
+package org.apache.unomi.shell.dev.commands;
import org.apache.commons.lang3.StringUtils;
import org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.Argument;
import org.apache.karaf.shell.api.action.lifecycle.Reference;
import org.apache.karaf.shell.api.console.Session;
+
+import java.io.PrintStream;
import org.apache.unomi.api.services.*;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
import org.jline.reader.LineReader;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@@ -33,7 +36,7 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;
-public abstract class DeploymentCommandSupport implements Action {
+public abstract class DeploymentCommandSupport extends BaseSimpleCommand {
public static final String ALL_OPTION_LABEL = "* (All)";
@Reference
@@ -57,8 +60,6 @@ public abstract class DeploymentCommandSupport implements Action {
@Reference
BundleContext bundleContext;
- @Reference
- Session session;
public static final String CONDITION_DEFINITION_TYPE = "conditions";
public static final String ACTION_DEFINITION_TYPE = "actions";
@@ -102,6 +103,74 @@ public abstract class DeploymentCommandSupport implements Action {
public abstract void processDefinition(String definitionType, URL definitionURL);
+ /**
+ * Process definition by type. Override in subclasses to provide specific implementation.
+ *
+ * @param definitionType the type of definition
+ * @param definitionURL the URL of the definition
+ * @param console the console for output
+ * @return true if successful, false otherwise
+ * @throws IOException if there's an error reading the definition
+ */
+ protected boolean processDefinitionByType(String definitionType, URL definitionURL, PrintStream console) throws IOException {
+ return false;
+ }
+
+ /**
+ * Read a definition object from a URL using CustomObjectMapper.
+ *
+ * @param definitionURL the URL to read from
+ * @param clazz the class of the object to read
+ * @param the type of the object
+ * @return the read object
+ * @throws IOException if there's an error reading the definition
+ */
+ protected T readDefinition(URL definitionURL, Class clazz) throws IOException {
+ return CustomObjectMapper.getObjectMapper().readValue(definitionURL, clazz);
+ }
+
+ /**
+ * Handle errors that occur during definition processing.
+ *
+ * @param definitionURL the URL of the definition that caused the error
+ * @param operation the operation being performed (e.g., "saving", "removing")
+ * @param e the exception that occurred
+ */
+ protected void handleDefinitionError(URL definitionURL, String operation, IOException e) {
+ PrintStream console = getConsole();
+ console.println("Error while " + operation + " definition " + definitionURL);
+ console.println(e.getMessage());
+ }
+
+ /**
+ * Internal method to process a definition. Handles common logic like resolving definition type.
+ *
+ * @param definitionType the type of definition
+ * @param definitionURL the URL of the definition
+ * @param console the console for output
+ * @param successMessage the message to display on success
+ * @throws IOException if there's an error processing the definition
+ */
+ protected void processDefinitionInternal(String definitionType, URL definitionURL, PrintStream console, String successMessage) throws IOException {
+ if (ALL_OPTION_LABEL.equals(definitionType)) {
+ String definitionURLString = definitionURL.toString();
+ for (String possibleDefinitionType : definitionTypes) {
+ if (definitionURLString.contains(getDefinitionTypePath(possibleDefinitionType))) {
+ definitionType = possibleDefinitionType;
+ break;
+ }
+ }
+ if (ALL_OPTION_LABEL.equals(definitionType)) {
+ console.println("Couldn't resolve definition type for definition URL " + definitionURL);
+ return;
+ }
+ }
+ boolean successful = processDefinitionByType(definitionType, definitionURL, console);
+ if (successful) {
+ console.println(successMessage + " : " + definitionURL.getFile());
+ }
+ }
+
public Object execute() throws Exception {
List bundlesToUpdate;
if ("*".equals(definitionType)) {
@@ -137,7 +206,7 @@ public Object execute() throws Exception {
Bundle bundle = bundleContext.getBundle(bundleIdentifier);
if (bundle == null) {
- System.out.println("Couldn't find a bundle with id: " + bundleIdentifier);
+ println("Couldn't find a bundle with id: " + bundleIdentifier);
return null;
}
@@ -149,7 +218,7 @@ public Object execute() throws Exception {
possibleDefinitionNames.add(ALL_OPTION_LABEL);
if (possibleDefinitionNames.isEmpty()) {
- System.out.println("Couldn't find definitions in bundle : " + bundlesToUpdate);
+ println("Couldn't find definitions in bundle : " + bundlesToUpdate);
return null;
}
@@ -159,14 +228,14 @@ public Object execute() throws Exception {
}
if (!definitionTypes.contains(definitionType) && !ALL_OPTION_LABEL.equals(definitionType)) {
- System.out.println("Invalid type '" + definitionType + "' , allowed values : " +definitionTypes);
+ println("Invalid type '" + definitionType + "' , allowed values : " +definitionTypes);
return null;
}
String definitionTypePath = getDefinitionTypePath(definitionType);
List definitionTypeURLs = bundlesToUpdate.stream().flatMap(b->b.findEntries(definitionTypePath, "*.json", true) != null ? Collections.list(b.findEntries(definitionTypePath, "*.json", true)).stream() : Stream.empty()).collect(Collectors.toList());
if (definitionTypeURLs.isEmpty()) {
- System.out.println("Couldn't find definitions in bundle with id: " + bundleIdentifier + " and definition path: " + definitionTypePath);
+ println("Couldn't find definitions in bundle with id: " + bundleIdentifier + " and definition path: " + definitionTypePath);
return null;
}
@@ -194,7 +263,7 @@ public Object execute() throws Exception {
URL url = optionalURL.get();
processDefinition(definitionType, url);
} else {
- System.out.println("Couldn't find file " + fileName);
+ println("Couldn't find file " + fileName);
return null;
}
}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventTail.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/EventTail.java
similarity index 81%
rename from tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventTail.java
rename to tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/EventTail.java
index 36ecdeceba..c4fd462c95 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventTail.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/EventTail.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.unomi.shell.commands;
+package org.apache.unomi.shell.dev.commands;
import org.apache.karaf.shell.api.action.Argument;
import org.apache.karaf.shell.api.action.Command;
@@ -24,7 +24,6 @@
import org.apache.unomi.api.services.EventService;
import java.io.PrintStream;
-import java.util.ArrayList;
import java.util.List;
@Command(scope = "unomi", name = "event-tail", description = "This will tail all the events coming into the Apache Unomi Context Server")
@@ -57,12 +56,12 @@ public String[] getColumnHeaders() {
@Override
public Object getListener() {
- return new TailEventListener(session.getConsole());
+ return new TailEventListener(getConsole());
}
class TailEventListener implements EventListenerService {
- PrintStream out;
+ private final PrintStream out;
public TailEventListener(PrintStream out) {
this.out = out;
@@ -78,14 +77,7 @@ public int onEvent(Event event) {
if (!event.isPersistent() && !withInternal) {
return EventService.NO_CHANGE;
}
- List eventInfo = new ArrayList<>();
- eventInfo.add(event.getItemId());
- eventInfo.add(event.getEventType());
- eventInfo.add(event.getSessionId());
- eventInfo.add(event.getProfileId());
- eventInfo.add(event.getTimeStamp().toString());
- eventInfo.add(event.getScope());
- eventInfo.add(Boolean.toString(event.isPersistent()));
+ List eventInfo = TailCommandUtils.extractEventInfo(event);
outputLine(out, eventInfo);
return EventService.NO_CHANGE;
}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ListCommandSupport.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/ListCommandSupport.java
similarity index 87%
rename from tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ListCommandSupport.java
rename to tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/ListCommandSupport.java
index dad45f10b7..154c3c96d5 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/ListCommandSupport.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/ListCommandSupport.java
@@ -14,12 +14,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.unomi.shell.commands;
+package org.apache.unomi.shell.dev.commands;
import org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.console.Session;
import org.apache.karaf.shell.support.table.Row;
import org.apache.karaf.shell.support.table.ShellTable;
+
+import java.io.PrintStream;
import org.apache.unomi.common.DataTable;
import java.util.ArrayList;
@@ -29,6 +33,9 @@
*/
public abstract class ListCommandSupport implements Action {
+ @Reference
+ protected Session session;
+
@Option(name = "--csv", description = "Output table in CSV format", required = false, multiValued = false)
boolean csv;
@@ -47,13 +54,13 @@ public abstract class ListCommandSupport implements Action {
protected abstract DataTable buildDataTable();
public Object execute() throws Exception {
-
DataTable dataTable = buildDataTable();
String[] headers = getHeaders();
+ PrintStream console = session.getConsole();
if (csv) {
- System.out.println(dataTable.toCSV(headers));
+ console.println(dataTable.toCSV(headers));
return null;
}
@@ -70,7 +77,7 @@ public Object execute() throws Exception {
row.addContent(rowData);
}
- shellTable.print(System.out);
+ shellTable.print(console);
return null;
}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RemoveCommandSupport.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RemoveCommandSupport.java
new file mode 100644
index 0000000000..58009cca66
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RemoveCommandSupport.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.shell.dev.commands;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Option;
+
+import java.io.IOException;
+
+public abstract class RemoveCommandSupport extends BaseSimpleCommand {
+
+ @Option(name = "--force", description = "Force deletion without confirmation", required = false, multiValued = false)
+ boolean force;
+
+ public abstract Object doRemove() throws Exception;
+
+ public abstract String getResourceDescription();
+
+ @Override
+ public Object execute() throws Exception {
+ Object result = null;
+ // Prompt for confirmation
+ if (force || askForConfirmation("Are you sure you want to delete "+getResourceDescription()+" ? (yes/no): ")) {
+ result = doRemove();
+ println("Resource deleted successfully.");
+ } else {
+ println("Operation cancelled.");
+ }
+ return result;
+ }
+
+ private boolean askForConfirmation(String prompt) throws IOException {
+ String input = session.readLine(prompt, null);
+ return "yes".equals(input);
+ }
+
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleResetStats.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RuleResetStats.java
similarity index 86%
rename from tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleResetStats.java
rename to tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RuleResetStats.java
index 7a228be9fa..ad5c11aba2 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleResetStats.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RuleResetStats.java
@@ -14,9 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.unomi.shell.commands;
+package org.apache.unomi.shell.dev.commands;
-import org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.lifecycle.Reference;
import org.apache.karaf.shell.api.action.lifecycle.Service;
@@ -24,7 +23,7 @@
@Command(scope = "unomi", name = "rule-reset-stats", description = "This command will reset the rule statistics")
@Service
-public class RuleResetStats implements Action {
+public class RuleResetStats extends BaseSimpleCommand {
@Reference
RulesService rulesService;
@@ -32,7 +31,7 @@ public class RuleResetStats implements Action {
@Override
public Object execute() throws Exception {
rulesService.resetAllRuleStatistics();
- System.out.println("Rule statistics successfully reset.");
+ println("Rule statistics successfully reset.");
return null;
}
}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleTail.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RuleTail.java
similarity index 80%
rename from tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleTail.java
rename to tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RuleTail.java
index 6cf66b2192..e6de275a94 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleTail.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RuleTail.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.unomi.shell.commands;
+package org.apache.unomi.shell.dev.commands;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.lifecycle.Service;
@@ -23,7 +23,6 @@
import org.apache.unomi.api.services.RuleListenerService;
import java.io.PrintStream;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -56,12 +55,12 @@ public String[] getColumnHeaders() {
@Override
public Object getListener() {
- return new TailRuleListener(session.getConsole());
+ return new TailRuleListener(getConsole());
}
class TailRuleListener implements RuleListenerService {
- PrintStream out;
+ private final PrintStream out;
public TailRuleListener(PrintStream out) {
this.out = out;
@@ -79,14 +78,7 @@ public void onAlreadyRaised(AlreadyRaisedFor alreadyRaisedFor, Rule rule, Event
@Override
public void onExecuteActions(Rule rule, Event event) {
- List ruleExecutionInfo = new ArrayList<>();
- ruleExecutionInfo.add(rule.getItemId());
- ruleExecutionInfo.add(rule.getMetadata().getName());
- ruleExecutionInfo.add(event.getEventType());
- ruleExecutionInfo.add(event.getSessionId());
- ruleExecutionInfo.add(event.getProfileId());
- ruleExecutionInfo.add(event.getTimeStamp().toString());
- ruleExecutionInfo.add(event.getScope());
+ List ruleExecutionInfo = TailCommandUtils.extractRuleExecutionInfo(rule, event);
outputLine(out, ruleExecutionInfo);
}
}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleWatch.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RuleWatch.java
similarity index 82%
rename from tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleWatch.java
rename to tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RuleWatch.java
index c94394455c..63ba069e59 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleWatch.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/RuleWatch.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.unomi.shell.commands;
+package org.apache.unomi.shell.dev.commands;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.karaf.shell.api.action.Argument;
@@ -25,7 +25,6 @@
import org.apache.unomi.api.services.RuleListenerService;
import java.io.PrintStream;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -62,12 +61,12 @@ public String[] getColumnHeaders() {
@Override
public Object getListener() {
- return new RuleWatchListener(session.getConsole());
+ return new RuleWatchListener(getConsole());
}
class RuleWatchListener implements RuleListenerService {
- PrintStream out;
+ private final PrintStream out;
public RuleWatchListener(PrintStream out) {
this.out = out;
@@ -78,7 +77,6 @@ public void onEvaluate(Rule rule, Event event) {
populateRuleInfo(rule, event, "EVALUATE");
}
-
@Override
public void onAlreadyRaised(AlreadyRaisedFor alreadyRaisedFor, Rule rule, Event event) {
populateRuleInfo(rule, event, "AR " + alreadyRaisedFor.toString());
@@ -93,15 +91,7 @@ public void populateRuleInfo(Rule rule, Event event, String status) {
if (!ArrayUtils.contains(ruleIds, rule.getItemId())) {
return;
}
- List ruleExecutionInfo = new ArrayList<>();
- ruleExecutionInfo.add(status);
- ruleExecutionInfo.add(rule.getItemId());
- ruleExecutionInfo.add(rule.getMetadata().getName());
- ruleExecutionInfo.add(event.getEventType());
- ruleExecutionInfo.add(event.getSessionId());
- ruleExecutionInfo.add(event.getProfileId());
- ruleExecutionInfo.add(event.getTimeStamp().toString());
- ruleExecutionInfo.add(event.getScope());
+ List ruleExecutionInfo = TailCommandUtils.extractRuleExecutionInfoWithStatus(rule, event, status);
outputLine(out, ruleExecutionInfo);
}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/TailCommandSupport.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/TailCommandSupport.java
similarity index 93%
rename from tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/TailCommandSupport.java
rename to tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/TailCommandSupport.java
index f6af0328a4..7de0d44512 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/TailCommandSupport.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/TailCommandSupport.java
@@ -14,12 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.unomi.shell.commands;
+package org.apache.unomi.shell.dev.commands;
import org.apache.commons.lang3.StringUtils;
import org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.console.Session;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
@@ -28,15 +27,12 @@
import java.util.Hashtable;
import java.util.List;
-public abstract class TailCommandSupport implements Action {
+public abstract class TailCommandSupport extends BaseSimpleCommand {
public abstract int[] getColumnSizes();
public abstract String[] getColumnHeaders();
- @Reference
- Session session;
-
@Reference
BundleContext bundleContext;
@@ -74,12 +70,12 @@ public void outputLine(PrintStream out, List eventInfo) {
}
out.println(eventLine.toString());
}
-
+
public abstract Object getListener();
public Object execute() throws Exception {
// Do not use System.out as it may write to the wrong console depending on the thread that calls our log handler
- PrintStream out = session.getConsole();
+ PrintStream out = getConsole();
out.flush();
outputHeaders(out);
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/TailCommandUtils.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/TailCommandUtils.java
new file mode 100644
index 0000000000..bb6fa50160
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/TailCommandUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.shell.dev.commands;
+
+import org.apache.unomi.api.Event;
+import org.apache.unomi.api.rules.Rule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for common tail command functionality.
+ */
+public final class TailCommandUtils {
+
+ private TailCommandUtils() {
+ // Utility class - prevent instantiation
+ }
+
+ /**
+ * Extract event information into a list of strings for display.
+ *
+ * @param event the event to extract information from
+ * @return list of event information strings
+ */
+ public static List extractEventInfo(Event event) {
+ List eventInfo = new ArrayList<>();
+ eventInfo.add(event.getItemId());
+ eventInfo.add(event.getEventType());
+ eventInfo.add(event.getSessionId());
+ eventInfo.add(event.getProfileId());
+ eventInfo.add(event.getTimeStamp().toString());
+ eventInfo.add(event.getScope());
+ eventInfo.add(Boolean.toString(event.isPersistent()));
+ return eventInfo;
+ }
+
+ /**
+ * Extract rule execution information into a list of strings for display.
+ *
+ * @param rule the rule to extract information from
+ * @param event the event associated with the rule execution
+ * @return list of rule execution information strings
+ */
+ public static List extractRuleExecutionInfo(Rule rule, Event event) {
+ List ruleExecutionInfo = new ArrayList<>();
+ ruleExecutionInfo.add(rule.getItemId());
+ ruleExecutionInfo.add(rule.getMetadata().getName());
+ ruleExecutionInfo.add(event.getEventType());
+ ruleExecutionInfo.add(event.getSessionId());
+ ruleExecutionInfo.add(event.getProfileId());
+ ruleExecutionInfo.add(event.getTimeStamp().toString());
+ ruleExecutionInfo.add(event.getScope());
+ return ruleExecutionInfo;
+ }
+
+ /**
+ * Extract rule execution information with status into a list of strings for display.
+ *
+ * @param rule the rule to extract information from
+ * @param event the event associated with the rule execution
+ * @param status the status of the rule execution (e.g., "EVALUATE", "EXECUTE", "AR ...")
+ * @return list of rule execution information strings with status as first element
+ */
+ public static List extractRuleExecutionInfoWithStatus(Rule rule, Event event, String status) {
+ List ruleExecutionInfo = new ArrayList<>();
+ ruleExecutionInfo.add(status);
+ ruleExecutionInfo.add(rule.getItemId());
+ ruleExecutionInfo.add(rule.getMetadata().getName());
+ ruleExecutionInfo.add(event.getEventType());
+ ruleExecutionInfo.add(event.getSessionId());
+ ruleExecutionInfo.add(event.getProfileId());
+ ruleExecutionInfo.add(event.getTimeStamp().toString());
+ ruleExecutionInfo.add(event.getScope());
+ return ruleExecutionInfo;
+ }
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/UndeployDefinition.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/UndeployDefinition.java
new file mode 100644
index 0000000000..70e8ccd7d8
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/UndeployDefinition.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.shell.dev.commands;
+
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.unomi.api.Patch;
+import org.apache.unomi.api.PersonaWithSessions;
+import org.apache.unomi.api.PropertyType;
+import org.apache.unomi.api.actions.ActionType;
+import org.apache.unomi.api.campaigns.Campaign;
+import org.apache.unomi.api.conditions.ConditionType;
+import org.apache.unomi.api.goals.Goal;
+import org.apache.unomi.api.rules.Rule;
+import org.apache.unomi.api.segments.Scoring;
+import org.apache.unomi.api.segments.Segment;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.URL;
+
+@Command(scope = "unomi", name = "undeploy-definition", description = "This will undeploy definitions contained in bundles")
+@Service
+public class UndeployDefinition extends DeploymentCommandSupport {
+
+ public void processDefinition(String definitionType, URL definitionURL) {
+ try {
+ processDefinitionInternal(definitionType, definitionURL, getConsole(), "Predefined definition unregistered");
+ } catch (IOException e) {
+ handleDefinitionError(definitionURL, "removing", e);
+ }
+ }
+
+ @Override
+ protected boolean processDefinitionByType(String definitionType, URL definitionURL, PrintStream console) throws IOException {
+ switch (definitionType) {
+ case CONDITION_DEFINITION_TYPE:
+ ConditionType conditionType = readDefinition(definitionURL, ConditionType.class);
+ definitionsService.removeConditionType(conditionType.getItemId());
+ return true;
+ case ACTION_DEFINITION_TYPE:
+ ActionType actionType = readDefinition(definitionURL, ActionType.class);
+ definitionsService.removeActionType(actionType.getItemId());
+ return true;
+ case GOAL_DEFINITION_TYPE:
+ Goal goal = readDefinition(definitionURL, Goal.class);
+ goalsService.removeGoal(goal.getItemId());
+ return true;
+ case CAMPAIGN_DEFINITION_TYPE:
+ Campaign campaign = readDefinition(definitionURL, Campaign.class);
+ goalsService.removeCampaign(campaign.getItemId());
+ return true;
+ case PERSONA_DEFINITION_TYPE:
+ PersonaWithSessions persona = readDefinition(definitionURL, PersonaWithSessions.class);
+ profileService.delete(persona.getPersona().getItemId(), true);
+ return true;
+ case PROPERTY_DEFINITION_TYPE:
+ PropertyType propertyType = readDefinition(definitionURL, PropertyType.class);
+ profileService.deletePropertyType(propertyType.getItemId());
+ return true;
+ case RULE_DEFINITION_TYPE:
+ Rule rule = readDefinition(definitionURL, Rule.class);
+ rulesService.removeRule(rule.getItemId());
+ return true;
+ case SEGMENT_DEFINITION_TYPE:
+ Segment segment = readDefinition(definitionURL, Segment.class);
+ segmentService.removeSegmentDefinition(segment.getItemId(), false);
+ return true;
+ case SCORING_DEFINITION_TYPE:
+ Scoring scoring = readDefinition(definitionURL, Scoring.class);
+ segmentService.removeScoringDefinition(scoring.getItemId(), false);
+ return true;
+ case PATCH_DEFINITION_TYPE:
+ Patch patch = readDefinition(definitionURL, Patch.class);
+ // patchService.patch(patch);
+ return true;
+ default:
+ console.println("Unrecognized definition type: " + definitionType);
+ return false;
+ }
+ }
+
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/actions/ActionTypeCrudCommand.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/actions/ActionTypeCrudCommand.java
new file mode 100644
index 0000000000..319464aef2
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/actions/ActionTypeCrudCommand.java
@@ -0,0 +1,157 @@
+/*
+ * 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.shell.dev.commands.actions;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.unomi.api.Parameter;
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.Scope;
+import org.apache.unomi.api.actions.ActionType;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.services.ScopeService;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.apache.unomi.shell.dev.services.BaseCrudCommand;
+import org.apache.unomi.shell.dev.services.CrudCommand;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Component(service = CrudCommand.class, immediate = true)
+public class ActionTypeCrudCommand extends BaseCrudCommand {
+
+ private static final ObjectMapper OBJECT_MAPPER = new CustomObjectMapper();
+ private static final List PROPERTY_NAMES = List.of(
+ "itemId", "name", "description", "scope", "tags", "systemTags", "parameters"
+ );
+
+ @Reference
+ private ScopeService scopeService;
+
+ @Override
+ public String getObjectType() {
+ return "actiontype";
+ }
+
+ @Override
+ protected String[] getHeadersWithoutTenant() {
+ return new String[] {
+ "Identifier",
+ "Name",
+ "Description",
+ "Scope",
+ "Tags",
+ "System Tags",
+ "Parameters",
+ "Action Executor"
+ };
+ }
+
+ @Override
+ protected PartialList> getItems(Query query) {
+ List actionTypes = new ArrayList<>(definitionsService.getAllActionTypes());
+ return paginateList(actionTypes, query);
+ }
+
+ @Override
+ protected String[] buildRow(Object item) {
+ ActionType actionType = (ActionType) item;
+ return new String[] {
+ actionType.getItemId(),
+ actionType.getMetadata().getName(),
+ actionType.getMetadata().getDescription(),
+ actionType.getMetadata().getScope(),
+ String.join(",", actionType.getMetadata().getTags()),
+ String.join(",", actionType.getMetadata().getSystemTags()),
+ String.join(",", actionType.getParameters().stream().map(Parameter::getId).collect(Collectors.toList())),
+ actionType.getActionExecutor()
+ };
+ }
+
+ @Override
+ public String create(Map properties) {
+ ActionType actionType = OBJECT_MAPPER.convertValue(properties, ActionType.class);
+ definitionsService.setActionType(actionType);
+ return actionType.getItemId();
+ }
+
+ @Override
+ public Map read(String id) {
+ ActionType actionType = definitionsService.getActionType(id);
+ if (actionType != null) {
+ return OBJECT_MAPPER.convertValue(actionType, Map.class);
+ }
+ return null;
+ }
+
+ @Override
+ public void update(String id, Map properties) {
+ ActionType existing = definitionsService.getActionType(id);
+ if (existing == null) {
+ throw new IllegalArgumentException("Action type not found: " + id);
+ }
+
+ ActionType updated = OBJECT_MAPPER.convertValue(properties, ActionType.class);
+ updated.setItemId(id);
+ definitionsService.setActionType(updated);
+ }
+
+ @Override
+ public void delete(String id) {
+ definitionsService.removeActionType(id);
+ }
+
+ @Override
+ public String getPropertiesHelp() {
+ return String.join("\n",
+ "Required properties:",
+ "- itemId: Identifier for the action type",
+ "- name: Name of the action type",
+ "",
+ "Optional properties:",
+ "- description: Description of the action type",
+ "- scope: Scope of the action type",
+ "- tags: List of tags",
+ "- systemTags: List of system tags",
+ "- parameters: Map of parameter definitions"
+ );
+ }
+
+ @Override
+ public List completePropertyNames(String prefix) {
+ return filterPropertyNames(PROPERTY_NAMES, prefix);
+ }
+
+ @Override
+ public List completePropertyValue(String propertyName, String prefix) {
+ if ("scope".equals(propertyName)) {
+ return scopeService.getScopes().stream()
+ .map(Scope::getItemId)
+ .filter(id -> id.startsWith(prefix))
+ .collect(Collectors.toList());
+ }
+ return Collections.emptyList();
+ }
+
+ public void setScopeService(ScopeService scopeService) {
+ this.scopeService = scopeService;
+ }
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/campaigns/CampaignCrudCommand.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/campaigns/CampaignCrudCommand.java
new file mode 100644
index 0000000000..caa609f474
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/campaigns/CampaignCrudCommand.java
@@ -0,0 +1,151 @@
+/*
+ * 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.shell.dev.commands.campaigns;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.campaigns.Campaign;
+import org.apache.unomi.api.campaigns.CampaignDetail;
+import org.apache.unomi.api.goals.Goal;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.services.GoalsService;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.apache.unomi.shell.dev.services.BaseCrudCommand;
+import org.apache.unomi.shell.dev.services.CrudCommand;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * A command to perform CRUD operations on campaigns
+ */
+@Component(service = CrudCommand.class, immediate = true)
+public class CampaignCrudCommand extends BaseCrudCommand {
+
+ private static final ObjectMapper OBJECT_MAPPER = new CustomObjectMapper();
+ private static final List PROPERTY_NAMES = List.of(
+ "itemId", "name", "description", "scope", "startDate", "endDate", "cost", "currency", "primaryGoal", "goals", "entryCondition", "enabled"
+ );
+
+ @Reference
+ private GoalsService goalsService;
+
+ @Override
+ public String getObjectType() {
+ return "campaign";
+ }
+
+ @Override
+ protected String[] getHeadersWithoutTenant() {
+ return new String[]{"ID", "Name", "Description", "Start Date", "End Date", "Cost", "Currency", "Primary Goal", "Enabled"};
+ }
+
+ @Override
+ protected PartialList> getItems(Query query) {
+ return goalsService.getCampaignDetails(query);
+ }
+
+ @Override
+ protected Comparable[] buildRow(Object item) {
+ CampaignDetail detail = (CampaignDetail) item;
+ Campaign campaign = detail.getCampaign();
+ String primaryGoalName = "";
+ if (campaign.getPrimaryGoal() != null) {
+ // Get the goal details to get its name
+ Goal primaryGoal = goalsService.getGoal(campaign.getPrimaryGoal());
+ if (primaryGoal != null) {
+ primaryGoalName = primaryGoal.getMetadata().getName();
+ }
+ }
+ return new Comparable[]{
+ campaign.getItemId(),
+ campaign.getMetadata().getName(),
+ campaign.getMetadata().getDescription(),
+ campaign.getStartDate() != null ? campaign.getStartDate().toString() : "",
+ campaign.getEndDate() != null ? campaign.getEndDate().toString() : "",
+ campaign.getCost() != null ? campaign.getCost().toString() : "",
+ campaign.getCurrency(),
+ primaryGoalName,
+ campaign.getMetadata().isEnabled()
+ };
+ }
+
+ @Override
+ public Map read(String id) {
+ Campaign campaign = goalsService.getCampaign(id);
+ if (campaign == null) {
+ return null;
+ }
+ return OBJECT_MAPPER.convertValue(campaign, Map.class);
+ }
+
+ @Override
+ public String create(Map properties) {
+ Campaign campaign = OBJECT_MAPPER.convertValue(properties, Campaign.class);
+ goalsService.setCampaign(campaign);
+ return campaign.getItemId();
+ }
+
+ @Override
+ public void update(String id, Map properties) {
+ Campaign existingCampaign = goalsService.getCampaign(id);
+ if (existingCampaign == null) {
+ return;
+ }
+
+ Campaign updatedCampaign = OBJECT_MAPPER.convertValue(properties, Campaign.class);
+ updatedCampaign.setItemId(id);
+ goalsService.setCampaign(updatedCampaign);
+ }
+
+ @Override
+ public void delete(String id) {
+ Campaign campaign = goalsService.getCampaign(id);
+ if (campaign != null) {
+ goalsService.removeCampaign(id);
+ }
+ }
+
+ @Override
+ public List completePropertyNames(String prefix) {
+ return filterPropertyNames(PROPERTY_NAMES, prefix);
+ }
+
+ @Override
+ public String getPropertiesHelp() {
+ return String.join("\n",
+ "Required properties:",
+ "- itemId: The unique identifier of the campaign",
+ "- name: The name of the campaign",
+ "- description: The description of the campaign",
+ "",
+ "Optional properties:",
+ "- scope: The scope of the campaign (defaults to systemscope)",
+ "- startDate: The start date of the campaign (ISO-8601 format)",
+ "- endDate: The end date of the campaign (ISO-8601 format)",
+ "- cost: The cost of the campaign",
+ "- currency: The currency for the campaign cost",
+ "- primaryGoal: The primary goal of the campaign",
+ "- goals: List of goals associated with the campaign",
+ "- entryCondition: The condition that determines when a visitor enters the campaign",
+ "- enabled: Whether the campaign is enabled (true/false)"
+ );
+ }
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/campaigns/CampaignEventCrudCommand.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/campaigns/CampaignEventCrudCommand.java
new file mode 100644
index 0000000000..8096e9dcb9
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/campaigns/CampaignEventCrudCommand.java
@@ -0,0 +1,143 @@
+/*
+ * 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.shell.dev.commands.campaigns;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.campaigns.events.CampaignEvent;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.services.GoalsService;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.apache.unomi.shell.dev.services.BaseCrudCommand;
+import org.apache.unomi.shell.dev.services.CrudCommand;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * A command to perform CRUD operations on campaign events
+ */
+@Component(service = CrudCommand.class, immediate = true)
+public class CampaignEventCrudCommand extends BaseCrudCommand {
+
+ private static final ObjectMapper OBJECT_MAPPER = new CustomObjectMapper();
+ private static final List PROPERTY_NAMES = List.of(
+ "itemId", "name", "description", "scope", "campaignId", "eventDate", "cost", "currency", "timezone"
+ );
+
+ @Reference
+ private GoalsService goalsService;
+
+ @Override
+ public String getObjectType() {
+ return "campaignevent";
+ }
+
+ @Override
+ protected String[] getHeadersWithoutTenant() {
+ return new String[]{"ID", "Name", "Description", "Campaign ID", "Event Date", "Cost", "Currency", "Timezone"};
+ }
+
+ @Override
+ protected PartialList> getItems(Query query) {
+ return goalsService.getEvents(query);
+ }
+
+ @Override
+ protected Comparable[] buildRow(Object item) {
+ CampaignEvent event = (CampaignEvent) item;
+ return new Comparable[]{
+ event.getItemId(),
+ event.getMetadata().getName(),
+ event.getMetadata().getDescription(),
+ event.getCampaignId(),
+ event.getEventDate() != null ? event.getEventDate().toString() : "",
+ event.getCost() != null ? event.getCost().toString() : "",
+ event.getCurrency(),
+ event.getTimezone()
+ };
+ }
+
+ @Override
+ public Map read(String id) {
+ // There's no direct method to get a single campaign event, so we need to query for it
+ Query query = new Query();
+ Condition condition = new Condition();
+ condition.setConditionType(definitionsService.getConditionType("matchAllCondition"));
+ condition.setParameter("operator", "and");
+ condition.setParameter("subConditions", new ArrayList<>());
+ query.setCondition(condition);
+
+ PartialList events = goalsService.getEvents(query);
+ CampaignEvent event = events.getList().stream()
+ .filter(e -> e.getItemId().equals(id))
+ .findFirst()
+ .orElse(null);
+
+ if (event == null) {
+ return null;
+ }
+ return OBJECT_MAPPER.convertValue(event, Map.class);
+ }
+
+ @Override
+ public String create(Map properties) {
+ CampaignEvent event = OBJECT_MAPPER.convertValue(properties, CampaignEvent.class);
+ goalsService.setCampaignEvent(event);
+ return event.getItemId();
+ }
+
+ @Override
+ public void update(String id, Map properties) {
+ CampaignEvent updatedEvent = OBJECT_MAPPER.convertValue(properties, CampaignEvent.class);
+ updatedEvent.setItemId(id);
+ goalsService.setCampaignEvent(updatedEvent);
+ }
+
+ @Override
+ public void delete(String id) {
+ goalsService.removeCampaignEvent(id);
+ }
+
+ @Override
+ public List completePropertyNames(String prefix) {
+ return filterPropertyNames(PROPERTY_NAMES, prefix);
+ }
+
+ @Override
+ public String getPropertiesHelp() {
+ return String.join("\n",
+ "Required properties:",
+ "- itemId: The unique identifier of the campaign event",
+ "- name: The name of the campaign event",
+ "- description: The description of the campaign event",
+ "- campaignId: The ID of the campaign this event belongs to",
+ "",
+ "Optional properties:",
+ "- scope: The scope of the campaign event (defaults to systemscope)",
+ "- eventDate: The date of the event (ISO-8601 format)",
+ "- cost: The cost associated with this event",
+ "- currency: The currency for the event cost",
+ "- timezone: The timezone for the event"
+ );
+ }
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/conditions/ConditionTypeCrudCommand.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/conditions/ConditionTypeCrudCommand.java
new file mode 100644
index 0000000000..b98560290a
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/conditions/ConditionTypeCrudCommand.java
@@ -0,0 +1,153 @@
+/*
+ * 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.shell.dev.commands.conditions;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.unomi.api.Metadata;
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.conditions.ConditionType;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.apache.unomi.shell.dev.services.BaseCrudCommand;
+import org.apache.unomi.shell.dev.services.CrudCommand;
+import org.osgi.service.component.annotations.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Component(service = CrudCommand.class, immediate = true)
+public class ConditionTypeCrudCommand extends BaseCrudCommand {
+
+ private static final ObjectMapper OBJECT_MAPPER = new CustomObjectMapper();
+ private static final List PROPERTY_NAMES = List.of(
+ "itemId", "scope", "name", "description", "conditionEvaluator", "queryBuilder", "parameters", "parentCondition"
+ );
+
+ @Override
+ public String getObjectType() {
+ return "conditiontype";
+ }
+
+ @Override
+ protected String[] getHeadersWithoutTenant() {
+ return new String[] {
+ "Activated",
+ "Hidden",
+ "Read-only",
+ "Identifier",
+ "Scope",
+ "Name",
+ "Tags",
+ "System tags"
+ };
+ }
+
+ @Override
+ protected PartialList> getItems(Query query) {
+ List allTypes = new ArrayList<>(definitionsService.getAllConditionTypes());
+ int offset = query.getOffset();
+ int pageSize = query.getLimit();
+ int totalSize = allTypes.size();
+
+ List pageTypes = allTypes.subList(
+ Math.min(offset, totalSize),
+ Math.min(offset + pageSize, totalSize)
+ );
+
+ return new PartialList(pageTypes, offset, pageSize, totalSize, PartialList.Relation.EQUAL);
+ }
+
+ @Override
+ protected Comparable[] buildRow(Object item) {
+ ConditionType type = (ConditionType) item;
+ Metadata metadata = type.getMetadata();
+ ArrayList rowData = new ArrayList<>();
+ rowData.add(metadata.isEnabled() ? "x" : "");
+ rowData.add(metadata.isHidden() ? "x" : "");
+ rowData.add(metadata.isReadOnly() ? "x" : "");
+ rowData.add(metadata.getId());
+ rowData.add(metadata.getScope());
+ rowData.add(metadata.getName());
+ rowData.add(StringUtils.join(metadata.getTags(), ","));
+ rowData.add(StringUtils.join(metadata.getSystemTags(), ","));
+ return rowData.toArray(new Comparable[0]);
+ }
+
+ @Override
+ public String create(Map properties) {
+ ConditionType conditionType = OBJECT_MAPPER.convertValue(properties, ConditionType.class);
+ definitionsService.setConditionType(conditionType);
+ return conditionType.getItemId();
+ }
+
+ @Override
+ public Map read(String id) {
+ ConditionType conditionType = definitionsService.getConditionType(id);
+ if (conditionType == null) {
+ return null;
+ }
+ return OBJECT_MAPPER.convertValue(conditionType, Map.class);
+ }
+
+ @Override
+ public void update(String id, Map properties) {
+ ConditionType conditionType = definitionsService.getConditionType(id);
+ if (conditionType == null) {
+ throw new IllegalArgumentException("Condition type with id '" + id + "' not found");
+ }
+ ConditionType updatedConditionType = OBJECT_MAPPER.convertValue(properties, ConditionType.class);
+ updatedConditionType.setItemId(id);
+ definitionsService.setConditionType(updatedConditionType);
+ }
+
+ @Override
+ public void delete(String id) {
+ definitionsService.removeConditionType(id);
+ }
+
+ @Override
+ public List completePropertyNames(String prefix) {
+ return filterPropertyNames(PROPERTY_NAMES, prefix);
+ }
+
+ @Override
+ public String getPropertiesHelp() {
+ return "Required properties:\n" +
+ "- itemId (string): Unique identifier for the condition type\n" +
+ "- scope (string): Scope of the condition type\n" +
+ "- name (string): Human-readable name\n" +
+ "\n" +
+ "Optional properties:\n" +
+ "- description (string): Description of the condition type\n" +
+ "- conditionEvaluator (string): Name of the condition evaluator implementation\n" +
+ "- queryBuilder (string): Name of the query builder implementation\n" +
+ "- parameters (array): List of parameters, each containing:\n" +
+ " - id (string): Parameter identifier\n" +
+ " - type (string): Parameter type\n" +
+ " - multivalued (boolean): Whether the parameter accepts multiple values\n" +
+ " - defaultValue (any): Default value for the parameter\n" +
+ "- parentCondition (object): Parent condition definition\n" +
+ "- enabled (boolean): Whether the condition type is enabled (default: true)\n" +
+ "- hidden (boolean): Whether the condition type is hidden (default: false)\n" +
+ "- readOnly (boolean): Whether the condition type is read-only (default: false)\n" +
+ "- tags (array): List of tags for the condition type\n" +
+ "- systemTags (array): List of system tags for the condition type";
+ }
+}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/consents/ConsentCrudCommand.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/consents/ConsentCrudCommand.java
new file mode 100644
index 0000000000..0590c0c2f3
--- /dev/null
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/dev/commands/consents/ConsentCrudCommand.java
@@ -0,0 +1,248 @@
+/*
+ * 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.shell.dev.commands.consents;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.unomi.api.Consent;
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.apache.unomi.shell.dev.services.BaseCrudCommand;
+import org.apache.unomi.shell.dev.services.CrudCommand;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * A command to perform CRUD operations on consents
+ */
+@Component(service = CrudCommand.class, immediate = true)
+public class ConsentCrudCommand extends BaseCrudCommand {
+
+ private static final ObjectMapper OBJECT_MAPPER = new CustomObjectMapper();
+ private static final String CONSENT_DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
+ private static final ThreadLocal DATE_FORMAT = ThreadLocal.withInitial(
+ () -> new SimpleDateFormat(CONSENT_DATE_FORMAT_PATTERN));
+ private static final List PROPERTY_NAMES = List.of(
+ "profileId", "scope", "typeIdentifier", "status", "statusDate", "revokeDate"
+ );
+
+ @Reference
+ private ProfileService profileService;
+
+ @Override
+ public String getObjectType() {
+ return "consent";
+ }
+
+ @Override
+ protected String[] getHeadersWithoutTenant() {
+ return new String[]{"Profile ID", "Scope", "Type", "Status", "Status Date", "Revoke Date"};
+ }
+
+ @Override
+ protected PartialList> getItems(Query query) {
+ // Since consents are stored within profiles, we need to get all profiles and extract their consents
+ PartialList profiles = profileService.search(query, Profile.class);
+ List