http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/plugins/request/src/main/java/org/oasis_open/contextserver/plugins/request/actions/SetRemoteHostInfoAction.java
----------------------------------------------------------------------
diff --git 
a/plugins/request/src/main/java/org/oasis_open/contextserver/plugins/request/actions/SetRemoteHostInfoAction.java
 
b/plugins/request/src/main/java/org/oasis_open/contextserver/plugins/request/actions/SetRemoteHostInfoAction.java
deleted file mode 100644
index 4bdcdb4..0000000
--- 
a/plugins/request/src/main/java/org/oasis_open/contextserver/plugins/request/actions/SetRemoteHostInfoAction.java
+++ /dev/null
@@ -1,183 +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.oasis_open.contextserver.plugins.request.actions;
-
-import com.maxmind.geoip2.DatabaseReader;
-import com.maxmind.geoip2.exception.GeoIp2Exception;
-import com.maxmind.geoip2.model.CityResponse;
-import net.sf.uadetector.ReadableUserAgent;
-import net.sf.uadetector.UserAgentStringParser;
-import net.sf.uadetector.service.UADetectorServiceFactory;
-import org.oasis_open.contextserver.api.Event;
-import org.oasis_open.contextserver.api.Session;
-import org.oasis_open.contextserver.api.actions.Action;
-import org.oasis_open.contextserver.api.actions.ActionExecutor;
-import org.oasis_open.contextserver.api.services.EventService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.PostConstruct;
-import javax.servlet.http.HttpServletRequest;
-import java.io.File;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-public class SetRemoteHostInfoAction implements ActionExecutor {
-    private static final Logger logger = 
LoggerFactory.getLogger(SetRemoteHostInfoAction.class.getName());
-
-    public static final Pattern IPV4 = 
Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
-
-    private DatabaseReader databaseReader;
-    private String pathToGeoLocationDatabase;
-
-    public void setPathToGeoLocationDatabase(String pathToGeoLocationDatabase) 
{
-        this.pathToGeoLocationDatabase = pathToGeoLocationDatabase;
-    }
-
-    @Override
-    public int execute(Action action, Event event) {
-        HttpServletRequest httpServletRequest = (HttpServletRequest) 
event.getAttributes().get(Event.HTTP_REQUEST_ATTRIBUTE);
-        if (httpServletRequest == null) {
-            return EventService.NO_CHANGE;
-        }
-        Session session = event.getSession();
-        if (session == null) {
-            return EventService.NO_CHANGE;
-        }
-
-        String remoteAddr = httpServletRequest.getRemoteAddr();
-        String remoteAddrParameter = 
httpServletRequest.getParameter("remoteAddr");
-        String xff = httpServletRequest.getHeader("X-Forwarded-For");
-        if (remoteAddrParameter != null && remoteAddrParameter.length() > 0) {
-            remoteAddr = remoteAddrParameter;
-        } else if (xff != null && !xff.equals("")) {
-            if (xff.indexOf(',') > -1) {
-                xff = xff.substring(0, xff.indexOf(','));
-            }
-            remoteAddr = xff;
-        }
-
-        session.setProperty("remoteAddr", remoteAddr);
-        session.setProperty("remoteHost", httpServletRequest.getRemoteHost());
-        try {
-            if (!remoteAddr.equals("127.0.0.1") && 
IPV4.matcher(remoteAddr).matches()) {
-                ipLookup(remoteAddr, session);
-            } else {
-                session.setProperty("sessionCountryCode", "CH");
-                session.setProperty("sessionCountryName", "Switzerland");
-                session.setProperty("sessionCity", "Geneva");
-                session.setProperty("sessionAdminSubDiv1", "GE");
-                session.setProperty("sessionAdminSubDiv2", "2500");
-                session.setProperty("sessionIsp", "Cablecom");
-                Map<String, Double> location = new HashMap<String, Double>();
-                location.put("lat", 46.1884341);
-                location.put("lon", 6.1282508);
-                session.setProperty("location", location);
-            }
-            session.setProperty("countryAndCity", 
session.getProperty("sessionCountryName") + "@@" + 
session.getProperty("sessionCity"));
-        } catch (Exception e) {
-            logger.error("Cannot lookup IP", e);
-        }
-
-        UserAgentStringParser parser = 
UADetectorServiceFactory.getResourceModuleParser();
-        ReadableUserAgent agent = 
parser.parse(httpServletRequest.getHeader("User-Agent"));
-        session.setProperty("operatingSystemFamily", 
agent.getOperatingSystem().getFamilyName());
-        session.setProperty("operatingSystemName", 
agent.getOperatingSystem().getName());
-        session.setProperty("userAgentName", agent.getName());
-        session.setProperty("userAgentVersion", 
agent.getVersionNumber().toVersionString());
-        session.setProperty("userAgentNameAndVersion", 
session.getProperty("userAgentName") + "@@" + 
session.getProperty("userAgentVersion"));
-        session.setProperty("deviceCategory", 
agent.getDeviceCategory().getName());
-
-        return EventService.SESSION_UPDATED;
-    }
-
-    private boolean ipLookup(String remoteAddr, Session session) {
-        if (databaseReader != null) {
-            return ipLookupInDatabase(remoteAddr, session);
-        }
-        return false;
-    }
-
-    @PostConstruct
-    public void postConstruct() {
-        // A File object pointing to your GeoIP2 or GeoLite2 database
-        if (pathToGeoLocationDatabase == null) {
-            return;
-        }
-        File database = new File(pathToGeoLocationDatabase);
-        if (!database.exists()) {
-            return;
-        }
-
-        // This creates the DatabaseReader object, which should be reused 
across
-        // lookups.
-        try {
-            this.databaseReader = new DatabaseReader.Builder(database).build();
-        } catch (IOException e) {
-            logger.error("Cannot read IP database", e);
-        }
-
-    }
-
-    public boolean ipLookupInDatabase(String remoteAddr, Session session) {
-        if (databaseReader == null) {
-            return false;
-        }
-
-        // Replace "city" with the appropriate method for your database, e.g.,
-        // "country".
-        CityResponse cityResponse = null;
-        try {
-            cityResponse = 
databaseReader.city(InetAddress.getByName(remoteAddr));
-
-            if (cityResponse.getCountry().getName() != null) {
-                session.setProperty("sessionCountryCode", 
cityResponse.getCountry().getIsoCode());
-                session.setProperty("sessionCountryName", 
cityResponse.getCountry().getName());
-            }
-            if (cityResponse.getCity().getName() != null) {
-                session.setProperty("sessionCity", 
cityResponse.getCity().getName());
-                session.setProperty("sessionCityId", 
cityResponse.getCity().getGeoNameId());
-            }
-
-            if (cityResponse.getSubdivisions().size() > 0) {
-                session.setProperty("sessionAdminSubDiv1", 
cityResponse.getSubdivisions().get(0).getGeoNameId());
-            }
-            if (cityResponse.getSubdivisions().size() > 1) {
-                session.setProperty("sessionAdminSubDiv2", 
cityResponse.getSubdivisions().get(1).getGeoNameId());
-            }
-            String isp = 
databaseReader.isp(InetAddress.getByName(remoteAddr)).getIsp();
-            if (isp != null) {
-                session.setProperty("sessionIsp", isp);
-            }
-
-            Map<String, Double> locationMap = new HashMap<String, Double>();
-            if (cityResponse.getLocation().getLatitude() != null && 
cityResponse.getLocation().getLongitude() != null) {
-                locationMap.put("lat", 
cityResponse.getLocation().getLatitude());
-                locationMap.put("lon", 
cityResponse.getLocation().getLongitude());
-                session.setProperty("location", locationMap);
-            }
-            return true;
-        } catch (IOException | GeoIp2Exception e) {
-            logger.debug("Cannot resolve IP", e);
-        }
-        return false;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/plugins/request/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --git 
a/plugins/request/src/main/resources/OSGI-INF/blueprint/blueprint.xml 
b/plugins/request/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 371777d..3f20637 100644
--- a/plugins/request/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/plugins/request/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -32,7 +32,7 @@
     <!-- Action executors -->
 
     <bean id="requestHeaderToProfilePropertyActionImpl"
-          
class="org.oasis_open.contextserver.plugins.request.actions.RequestHeaderToProfilePropertyAction">
+          
class="org.apache.unomi.plugins.request.actions.RequestHeaderToProfilePropertyAction">
     </bean>
     <service id="requestHeaderToProfilePropertyAction" 
ref="requestHeaderToProfilePropertyActionImpl"
              auto-export="interfaces">
@@ -42,7 +42,7 @@
     </service>
 
     <bean id="requestParameterToProfilePropertyActionImpl"
-          
class="org.oasis_open.contextserver.plugins.request.actions.RequestParameterToProfilePropertyAction">
+          
class="org.apache.unomi.plugins.request.actions.RequestParameterToProfilePropertyAction">
     </bean>
     <service id="RequestParameterToProfilePropertyAction" 
ref="requestParameterToProfilePropertyActionImpl"
              auto-export="interfaces">
@@ -55,7 +55,7 @@
         <service-properties>
             <entry key="actionExecutorId" value="setRemoteHostInfo"/>
         </service-properties>
-        <bean 
class="org.oasis_open.contextserver.plugins.request.actions.SetRemoteHostInfoAction"
+        <bean 
class="org.apache.unomi.plugins.request.actions.SetRemoteHostInfoAction"
               init-method="postConstruct">
             <property name="pathToGeoLocationDatabase" 
value="${request.ipDatabase.location}"/>
         </bean>

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/plugins/tracked-event/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --git 
a/plugins/tracked-event/src/main/resources/OSGI-INF/blueprint/blueprint.xml 
b/plugins/tracked-event/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index e937172..e2a66b9 100644
--- a/plugins/tracked-event/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/plugins/tracked-event/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -16,10 +16,10 @@
   ~ limitations under the License.
   -->
 
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0";
            xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 
http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd";>
 
-    <reference id="definitionsService" 
interface="org.oasis_open.contextserver.api.services.DefinitionsService"/>
-    <reference id="persistenceService" 
interface="org.oasis_open.contextserver.persistence.spi.PersistenceService"/>
+  <reference id="definitionsService" 
interface="org.apache.unomi.api.services.DefinitionsService"/>
+  <reference id="persistenceService" interface="PersistenceService"/>
 
 </blueprint>

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/rest/pom.xml
----------------------------------------------------------------------
diff --git a/rest/pom.xml b/rest/pom.xml
index 307aa6e..1e05c82 100644
--- a/rest/pom.xml
+++ b/rest/pom.xml
@@ -15,7 +15,8 @@
   ~ limitations under the License.
   -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns="http://maven.apache.org/POM/4.0.0";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
     <modelVersion>4.0.0</modelVersion>
 
     <parent>
@@ -141,7 +142,7 @@
                     <apiSources>
                         <apiSource>
                             <springmvc>false</springmvc>
-                            
<locations>org.oasis_open.contextserver.rest</locations>
+                            <locations>org.apache.unomi.rest</locations>
                             
<templatePath>${basedir}/api-doc-template/markdown.hbs</templatePath>
                             
<outputPath>${basedir}/generated/document.html</outputPath>
                             
<swaggerDirectory>generated/swagger-ui</swaggerDirectory>

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/rest/src/main/java/org/apache/unomi/rest/CampaignsServiceEndPoint.java
----------------------------------------------------------------------
diff --git 
a/rest/src/main/java/org/apache/unomi/rest/CampaignsServiceEndPoint.java 
b/rest/src/main/java/org/apache/unomi/rest/CampaignsServiceEndPoint.java
new file mode 100644
index 0000000..3383b76
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/CampaignsServiceEndPoint.java
@@ -0,0 +1,172 @@
+/*
+ * 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.rest;
+
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.api.Metadata;
+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.campaigns.events.CampaignEvent;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.rules.Rule;
+import org.apache.unomi.api.services.GoalsService;
+
+import javax.jws.WebMethod;
+import javax.jws.WebService;
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.Set;
+
+/**
+ * A JAX-RS endpoint to manage {@link Campaign}s and related information.
+ */
+@WebService
+@Produces(MediaType.APPLICATION_JSON)
+@CrossOriginResourceSharing(
+        allowAllOrigins = true,
+        allowCredentials = true
+)
+public class CampaignsServiceEndPoint {
+
+    private GoalsService goalsService;
+
+    public CampaignsServiceEndPoint() {
+        System.out.println("Initializing campaigns service endpoint...");
+    }
+
+    @WebMethod(exclude=true)
+    public void setGoalsService(GoalsService goalsService) {
+        this.goalsService = goalsService;
+    }
+
+    /**
+     * Retrieves the set of Metadata associated with existing campaigns.
+     *
+     * @return the set of Metadata associated with existing campaigns
+     */
+    @GET
+    @Path("/")
+    public Set<Metadata> getCampaignMetadatas() {
+        return goalsService.getCampaignMetadatas();
+    }
+
+    /**
+     * Saves the specified campaign in the context server and creates 
associated {@link Rule}s if the campaign is enabled.
+     *
+     * @param campaign the Campaign to be saved
+     */
+    @POST
+    @Path("/")
+    public void setCampaignDefinition(Campaign campaign) {
+        goalsService.setCampaign(campaign);
+    }
+
+    /**
+     * Retrieves the set of Metadata associated with existing campaign 
matching the specified {@link Query}
+     *
+     * @param query the Query used to filter the campagins which metadata we 
want to retrieve
+     * @return the set of Metadata associated with existing campaigns matching 
the specified {@link Query}
+     */
+    @POST
+    @Path("/query")
+    public Set<Metadata> getCampaignMetadatas(Query query) {
+        return goalsService.getCampaignMetadatas(query);
+    }
+
+    /**
+     * Retrieves campaign details for campaigns matching the specified query.
+     *
+     * @param query the query specifying which campaigns to retrieve
+     * @return a {@link PartialList} of campaign details for the campaigns 
matching the specified query
+     */
+    @POST
+    @Path("/query/detailed")
+    public PartialList<CampaignDetail> getCampaignDetails(Query query) {
+        return goalsService.getCampaignDetails(query);
+    }
+
+    /**
+     * Retrieves the {@link CampaignDetail} associated with the campaign 
identified with the specified identifier
+     *
+     * @param campaignID the identifier of the campaign for which we want to 
retrieve the details
+     * @return the CampaignDetail for the campaign identified by the specified 
identifier or {@code null} if no such campaign exists
+     */
+    @GET
+    @Path("/{campaignID}/detailed")
+    public CampaignDetail getCampaignDetail(@PathParam("campaignID") String 
campaignID) {
+        return goalsService.getCampaignDetail(campaignID);
+    }
+
+    /**
+     * Retrieves the campaign identified by the specified identifier
+     *
+     * @param campaignID the identifier of the campaign we want to retrieve
+     * @return the campaign associated with the specified identifier or {@code 
null} if no such campaign exists
+     */
+    @GET
+    @Path("/{campaignID}")
+    public Campaign getCampaignDefinition(@PathParam("campaignID") String 
campaignID) {
+        return goalsService.getCampaign(campaignID);
+    }
+
+    /**
+     * Removes the campaign associated with the specified identifier, also 
removing associated rules if needed.
+     *
+     * @param campaignID the identifier of the campaign to be removed
+     */
+    @DELETE
+    @Path("/{campaignID}")
+    public void removeCampaignDefinition(@PathParam("campaignID") String 
campaignID) {
+        goalsService.removeCampaign(campaignID);
+    }
+
+    /**
+     * Saves the specified campaign event in the context server.
+     *
+     * @param campaignEvent the CampaignEvent to be saved
+     */
+    @POST
+    @Path("/event")
+    public void setCampaignEventDefinition(CampaignEvent campaignEvent) {
+        goalsService.setCampaignEvent(campaignEvent);
+    }
+
+    /**
+     * Removes the campaign event associated with the specified identifier.
+     *
+     * @param campaignEventID the identifier of the campaign event to be 
removed
+     */
+    @DELETE
+    @Path("/event/{eventId}")
+    public void removeCampaignEventDefinition(@PathParam("eventId") String 
campaignEventID) {
+        goalsService.removeCampaignEvent(campaignEventID);
+    }
+
+    /**
+     * Retrieves {@link CampaignEvent}s matching the specified query.
+     *
+     * @param query the Query specifying which CampaignEvents to retrieve
+     * @return a {@link PartialList} of campaign events matching the specified 
query
+     */
+    @POST
+    @Path("/events/query")
+    public PartialList<CampaignEvent> getCampaignEvents(Query query) {
+        return goalsService.getEvents(query);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/rest/src/main/java/org/apache/unomi/rest/ClusterServiceEndPoint.java
----------------------------------------------------------------------
diff --git 
a/rest/src/main/java/org/apache/unomi/rest/ClusterServiceEndPoint.java 
b/rest/src/main/java/org/apache/unomi/rest/ClusterServiceEndPoint.java
new file mode 100644
index 0000000..62da187
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/ClusterServiceEndPoint.java
@@ -0,0 +1,103 @@
+/*
+ * 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.rest;
+
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.api.ClusterNode;
+import org.apache.unomi.api.services.ClusterService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jws.WebMethod;
+import javax.jws.WebService;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.List;
+
+/**
+ * A JAX-RS endpoint to access information about the context server's cluster.
+ */
+@WebService
+@Produces(MediaType.APPLICATION_JSON)
+@CrossOriginResourceSharing(
+        allowAllOrigins = true,
+        allowCredentials = true
+)
+public class ClusterServiceEndPoint {
+    private static final Logger logger = 
LoggerFactory.getLogger(ClusterServiceEndPoint.class.getName());
+
+    @Context
+    private MessageContext messageContext;
+
+    private ClusterService clusterService;
+
+    public ClusterServiceEndPoint() {
+        System.out.println("Initializing cluster service endpoint...");
+    }
+
+    @WebMethod(exclude = true)
+    public void setClusterService(ClusterService clusterService) {
+        this.clusterService = clusterService;
+    }
+
+    @WebMethod(exclude = true)
+    public void setMessageContext(MessageContext messageContext) {
+        this.messageContext = messageContext;
+    }
+
+    /**
+     * Retrieves the list of available nodes for this context server instance.
+     *
+     * @return a list of {@link ClusterNode}
+     */
+    @GET
+    @Path("/")
+    public List<ClusterNode> getClusterNodes() {
+        return clusterService.getClusterNodes();
+    }
+
+    /**
+     * Removes all data before the specified date from the context server.
+     *
+     * @param date the Date before which all data needs to be removed
+     */
+    @GET
+    @Path("/purge/{date}")
+    public void purge(@PathParam("date") String date) {
+        try {
+            clusterService.purge(new 
SimpleDateFormat("yyyy-MM-dd").parse(date));
+        } catch (ParseException e) {
+            logger.error("Cannot purge",e);
+        }
+    }
+
+    /**
+     * Removes all data associated with the provided scope.
+     *
+     * @param scope the scope for which we want to remove data
+     */
+    @DELETE
+    @Path("{scope}")
+    public void deleteScopedData(@PathParam("scope") String scope) {
+        clusterService.purge(scope);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/rest/src/main/java/org/apache/unomi/rest/DefinitionsServiceEndPoint.java
----------------------------------------------------------------------
diff --git 
a/rest/src/main/java/org/apache/unomi/rest/DefinitionsServiceEndPoint.java 
b/rest/src/main/java/org/apache/unomi/rest/DefinitionsServiceEndPoint.java
new file mode 100644
index 0000000..1d504d3
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/DefinitionsServiceEndPoint.java
@@ -0,0 +1,252 @@
+/*
+ * 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.rest;
+
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.api.PluginType;
+import org.apache.unomi.api.PropertyMergeStrategyType;
+import org.apache.unomi.api.ValueType;
+import org.apache.unomi.api.actions.ActionType;
+import org.apache.unomi.api.conditions.ConditionType;
+import org.apache.unomi.api.services.DefinitionsService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jws.WebMethod;
+import javax.jws.WebService;
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.*;
+
+/**
+ * A JAX-RS endpoint to retrieve definition information about core context 
server entities such as conditions, actions and values.
+ */
+@WebService
+@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
+@CrossOriginResourceSharing(
+        allowAllOrigins = true,
+        allowCredentials = true
+)
+public class DefinitionsServiceEndPoint {
+    private static final Logger logger = 
LoggerFactory.getLogger(DefinitionsServiceEndPoint.class.getName());
+
+    private DefinitionsService definitionsService;
+    private LocalizationHelper localizationHelper;
+
+    @WebMethod(exclude = true)
+    public void setDefinitionsService(DefinitionsService definitionsService) {
+        this.definitionsService = definitionsService;
+    }
+
+    @WebMethod(exclude = true)
+    public void setLocalizationHelper(LocalizationHelper localizationHelper) {
+        this.localizationHelper = localizationHelper;
+    }
+
+    /**
+     * Retrieves all known tags localized using the specified language.
+     *
+     * @param language the language to use to localize
+     * @return the set of all known tags
+     */
+    @GET
+    @Path("/tags")
+    public Collection<RESTTag> getAllTags(@HeaderParam("Accept-Language") 
String language) {
+        return 
localizationHelper.generateTags(definitionsService.getAllTags(), language);
+    }
+
+    /**
+     * Retrieves the set of all root tags from which all other tags are 
derived via sub-tags localized using the specified language.
+     *
+     * @param language the language to use to localize.
+     * @return the set of all root tags
+     */
+    @GET
+    @Path("/rootTags")
+    public Collection<RESTTag> getRootTags(@HeaderParam("Accept-Language") 
String language) {
+        return 
localizationHelper.generateTags(definitionsService.getRootTags(), language);
+    }
+
+    /**
+     * Retrieves the tag with the specified identifier localized using the 
specified language.
+     *
+     * @param language     the language to use to localize.
+     * @param tag          the identifier of the tag to retrieve
+     * @param filterHidden {@code true} if hidden sub-tags should be filtered 
out, {@code false} otherwise
+     * @return the tag with the specified identifier
+     */
+    @GET
+    @Path("/tags/{tagId}")
+    public RESTTag getTag(@PathParam("tagId") String tag, 
@QueryParam("filterHidden") @DefaultValue("false") boolean filterHidden, 
@HeaderParam("Accept-Language") String language) {
+        return localizationHelper.generateTag(definitionsService.getTag(tag), 
language, filterHidden);
+    }
+
+    /**
+     * Retrieves all condition types localized using the specified language.
+     *
+     * @param language the language to use to localize.
+     * @return a Collection of all collection types
+     */
+    @GET
+    @Path("/conditions")
+    public Collection<RESTConditionType> 
getAllConditionTypes(@HeaderParam("Accept-Language") String language) {
+        Collection<ConditionType> conditionTypes = 
definitionsService.getAllConditionTypes();
+        return localizationHelper.generateConditions(conditionTypes, language);
+    }
+
+    /**
+     * Retrieves the set of condition types with the specified tags also 
retrieving condition types from sub-tags if so specified localized using the 
specified language.
+     *
+     * @param language  the language to use to localize.
+     * @param tags      a comma-separated list of tag identifiers
+     * @param recursive {@code true} if we want to also include condition 
types marked by sub-tags of the specified tag
+     * @return the set of condition types with the specified tag (and its 
sub-tags, if specified)
+     */
+    @GET
+    @Path("/conditions/tags/{tagId}")
+    public Collection<RESTConditionType> 
getConditionTypesByTag(@PathParam("tagId") String tags, 
@QueryParam("recursive") @DefaultValue("false") boolean recursive, 
@HeaderParam("Accept-Language") String language) {
+        String[] tagsArray = tags.split(",");
+        Set<ConditionType> results = new LinkedHashSet<>();
+        for (String s : tagsArray) {
+            
results.addAll(definitionsService.getConditionTypesByTag(definitionsService.getTag(s),
 recursive));
+        }
+        return localizationHelper.generateConditions(results, language);
+    }
+
+    /**
+     * Retrieves the condition type associated with the specified identifier 
localized using the specified language.
+     *
+     * @param language the language to use to localize.
+     * @param id       the identifier of the condition type to retrieve
+     * @return the condition type associated with the specified identifier or 
{@code null} if no such condition type exists
+     */
+    @GET
+    @Path("/conditions/{conditionId}")
+    public RESTConditionType getConditionType(@PathParam("conditionId") String 
id, @HeaderParam("Accept-Language") String language) {
+        ConditionType conditionType = definitionsService.getConditionType(id);
+        return localizationHelper.generateCondition(conditionType, language);
+    }
+
+    /**
+     * Retrieves all known action types localized using the specified language.
+     *
+     * @param language the language to use to localize.
+     * @return all known action types
+     */
+    @GET
+    @Path("/actions")
+    public Collection<RESTActionType> 
getAllActionTypes(@HeaderParam("Accept-Language") String language) {
+        Collection<ActionType> actionTypes = 
definitionsService.getAllActionTypes();
+        return localizationHelper.generateActions(actionTypes, language);
+    }
+
+    /**
+     * Retrieves the set of action types with the specified tags also 
retrieving action types from sub-tags if so specified localized using the 
specified language.
+     *
+     * @param language  the language to use to localize.
+     * @param tags      the tag marking the action types we want to retrieve
+     * @param recursive {@code true} if we want to also include action types 
marked by sub-tags of the specified tag
+     * @return the set of action types with the specified tag (and its 
sub-tags, if specified)
+     */
+    @GET
+    @Path("/actions/tags/{tagId}")
+    public Collection<RESTActionType> getActionTypeByTag(@PathParam("tagId") 
String tags, @QueryParam("recursive") @DefaultValue("false") boolean recursive, 
@HeaderParam("Accept-Language") String language) {
+        String[] tagsArray = tags.split(",");
+        Set<ActionType> results = new LinkedHashSet<>();
+        for (String s : tagsArray) {
+            
results.addAll(definitionsService.getActionTypeByTag(definitionsService.getTag(s),
 recursive));
+        }
+        return localizationHelper.generateActions(results, language);
+    }
+
+    /**
+     * Retrieves the action type associated with the specified identifier 
localized using the specified language.
+     *
+     * @param language the language to use to localize.
+     * @param id       the identifier of the action type to retrieve
+     * @return the action type associated with the specified identifier or 
{@code null} if no such action type exists
+     */
+    @GET
+    @Path("/actions/{actionId}")
+    public RESTActionType getActionType(@PathParam("actionId") String id, 
@HeaderParam("Accept-Language") String language) {
+        ActionType actionType = definitionsService.getActionType(id);
+        return localizationHelper.generateAction(actionType, language);
+    }
+
+    /**
+     * Retrieves all known value types localized using the specified language.
+     *
+     * @param language the language to use to localize.
+     * @return all known value types
+     */
+    @GET
+    @Path("/values")
+    public Collection<RESTValueType> 
getAllValueTypes(@HeaderParam("Accept-Language") String language) {
+        return 
localizationHelper.generateValueTypes(definitionsService.getAllValueTypes(), 
language);
+    }
+
+    /**
+     * Retrieves the set of value types with the specified tags also 
retrieving value types from sub-tags if so specified localized using the 
specified language.
+     *
+     * @param language  the language to use to localize.
+     * @param tags      the tag marking the value types we want to retrieve
+     * @param recursive {@code true} if we want to also include value types 
marked by sub-tags of the specified tag
+     * @return the set of value types with the specified tag (and its 
sub-tags, if specified)
+     */
+    @GET
+    @Path("/values/tags/{tagId}")
+    public Collection<RESTValueType> getValueTypeByTag(@PathParam("tagId") 
String tags, @QueryParam("recursive") @DefaultValue("false") boolean recursive, 
@HeaderParam("Accept-Language") String language) {
+        String[] tagsArray = tags.split(",");
+        Set<ValueType> results = new LinkedHashSet<>();
+        for (String s : tagsArray) {
+            
results.addAll(definitionsService.getValueTypeByTag(definitionsService.getTag(s),
 recursive));
+        }
+        return localizationHelper.generateValueTypes(results, language);
+    }
+
+    /**
+     * Retrieves the value type associated with the specified identifier 
localized using the specified language.
+     *
+     * @param language the language to use to localize.
+     * @param id       the identifier of the value type to retrieve
+     * @return the value type associated with the specified identifier or 
{@code null} if no such value type exists
+     */
+    @GET
+    @Path("/values/{valueTypeId}")
+    public RESTValueType getValueType(@PathParam("valueTypeId") String id, 
@HeaderParam("Accept-Language") String language) {
+        ValueType valueType = definitionsService.getValueType(id);
+        return localizationHelper.generateValueType(valueType, language);
+    }
+
+    /**
+     * Retrieves a Map of plugin identifier to a list of plugin types defined 
by that particular plugin.
+     *
+     * @return a Map of plugin identifier to a list of plugin types defined by 
that particular plugin
+     */
+    @GET
+    @Path("/typesByPlugin")
+    public Map<Long, List<PluginType>> getTypesByPlugin() {
+        return definitionsService.getTypesByPlugin();
+    }
+
+    @WebMethod(exclude = true)
+    public PropertyMergeStrategyType getPropertyMergeStrategyType(String id) {
+        return definitionsService.getPropertyMergeStrategyType(id);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/rest/src/main/java/org/apache/unomi/rest/GoalsServiceEndPoint.java
----------------------------------------------------------------------
diff --git a/rest/src/main/java/org/apache/unomi/rest/GoalsServiceEndPoint.java 
b/rest/src/main/java/org/apache/unomi/rest/GoalsServiceEndPoint.java
new file mode 100644
index 0000000..0583067
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/GoalsServiceEndPoint.java
@@ -0,0 +1,135 @@
+/*
+ * 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.rest;
+
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.api.Metadata;
+import org.apache.unomi.api.goals.Goal;
+import org.apache.unomi.api.goals.GoalReport;
+import org.apache.unomi.api.query.AggregateQuery;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.rules.Rule;
+import org.apache.unomi.api.services.GoalsService;
+
+import javax.jws.WebMethod;
+import javax.jws.WebService;
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.Set;
+
+/**
+ * A JAX-RS endpoint to manage {@link Goal}s and related information.
+ */
+@WebService
+@Produces(MediaType.APPLICATION_JSON)
+@CrossOriginResourceSharing(
+        allowAllOrigins = true,
+        allowCredentials = true
+)
+public class GoalsServiceEndPoint {
+
+    private GoalsService goalsService;
+
+    @WebMethod(exclude = true)
+    public void setGoalsService(GoalsService goalsService) {
+        this.goalsService = goalsService;
+    }
+
+    /**
+     * Retrieves the set of Metadata associated with existing goals.
+     *
+     * @return the set of Metadata associated with existing goals
+     */
+    @GET
+    @Path("/")
+    public Set<Metadata> getGoalMetadatas() {
+        return goalsService.getGoalMetadatas();
+    }
+
+
+    /**
+     * Saves the specified goal in the context server and creates associated 
{@link Rule}s if the goal is enabled.
+     *
+     * @param goal the Goal to be saved
+     */
+    @POST
+    @Path("/")
+    public void setGoal(Goal goal) {
+        goalsService.setGoal(goal);
+    }
+
+    /**
+     * Retrieves the set of Metadata associated with existing goals matching 
the specified {@link Query}
+     *
+     * @param query the Query used to filter the Goals which metadata we want 
to retrieve
+     * @return the set of Metadata associated with existing goals matching the 
specified {@link Query}
+     */
+    @POST
+    @Path("/query")
+    public Set<Metadata> getGoalMetadatas(Query query) {
+        return goalsService.getGoalMetadatas(query);
+    }
+
+    /**
+     * Retrieves the goal associated with the specified identifier.
+     *
+     * @param goalId the identifier of the goal to retrieve
+     * @return the goal associated with the specified identifier or {@code 
null} if no such goal exists
+     */
+    @GET
+    @Path("/{goalId}")
+    public Goal getGoal(@PathParam("goalId") String goalId) {
+        return goalsService.getGoal(goalId);
+    }
+
+    /**
+     * Removes the goal associated with the specified identifier, also 
removing associated rules if needed.
+     *
+     * @param goalId the identifier of the goal to be removed
+     */
+    @DELETE
+    @Path("/{goalId}")
+    public void removeGoal(@PathParam("goalId") String goalId) {
+        goalsService.removeGoal(goalId);
+    }
+
+    /**
+     * Retrieves the report for the goal identified with the specified 
identifier.
+     *
+     * @param goalId the identifier of the goal which report we want to 
retrieve
+     * @return the report for the specified goal
+     */
+    @GET
+    @Path("/{goalID}/report")
+    public GoalReport getGoalReport(@PathParam("goalID") String goalId) {
+        return goalsService.getGoalReport(goalId);
+    }
+
+    /**
+     * Retrieves the report for the goal identified with the specified 
identifier, considering only elements determined by the specified {@link 
AggregateQuery}.
+     *
+     * @param goalId the identifier of the goal which report we want to 
retrieve
+     * @param query  an {@link AggregateQuery} to further specify which 
elements of the report we want
+     * @return the report for the specified goal and query
+     */
+    @POST
+    @Path("/{goalID}/report")
+    public GoalReport getGoalReport(@PathParam("goalID") String goalId, 
AggregateQuery query) {
+        return goalsService.getGoalReport(goalId, query);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/rest/src/main/java/org/apache/unomi/rest/LocalizationHelper.java
----------------------------------------------------------------------
diff --git a/rest/src/main/java/org/apache/unomi/rest/LocalizationHelper.java 
b/rest/src/main/java/org/apache/unomi/rest/LocalizationHelper.java
new file mode 100644
index 0000000..55a4337
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/LocalizationHelper.java
@@ -0,0 +1,279 @@
+/*
+ * 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.rest;
+
+import org.apache.unomi.api.Parameter;
+import org.apache.unomi.api.PluginType;
+import org.apache.unomi.api.Tag;
+import org.apache.unomi.api.ValueType;
+import org.apache.unomi.api.actions.ActionType;
+import org.apache.unomi.api.conditions.ConditionType;
+import org.apache.unomi.api.conditions.initializers.ChoiceListInitializer;
+import org.apache.unomi.api.conditions.initializers.ChoiceListValue;
+import org.apache.unomi.api.conditions.initializers.I18nSupport;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.ResourceBundle;
+
+/**
+ * A helper class to provide localized versions of context server entities.
+ */
+public class LocalizationHelper {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(LocalizationHelper.class.getName());
+
+    private BundleContext bundleContext;
+    private ResourceBundleHelper resourceBundleHelper;
+
+    /**
+     * Creates {@link RESTConditionType}s, localized using the specified 
language, based on the specified {@link ConditionType}s.
+     *
+     * @param conditionTypes the {@link ConditionType}s to be localized
+     * @param language       the language to use to localize {@link 
ConditionType}s
+     * @return a collection of {@link RESTConditionType}s based on the 
specified {@link ConditionType}s and localized using the specified language
+     */
+    public Collection<RESTConditionType> 
generateConditions(Collection<ConditionType> conditionTypes, String language) {
+        List<RESTConditionType> result = new ArrayList<RESTConditionType>();
+        if (conditionTypes == null) {
+            return result;
+        }
+        for (ConditionType conditionType : conditionTypes) {
+            result.add(generateCondition(conditionType, language));
+        }
+        return result;
+    }
+
+    /**
+     * Creates {@link RESTActionType}s, localized using the specified 
language, based on the specified {@link ActionType}s.
+     *
+     * @param actionTypes the {@link ActionType}s to be localized
+     * @param language    the language to use to localize {@link ActionType}s
+     * @return a collection of {@link RESTActionType}s based on the specified 
{@link ActionType}s and localized using the specified language
+     */
+    public Collection<RESTActionType> generateActions(Collection<ActionType> 
actionTypes, String language) {
+        List<RESTActionType> result = new ArrayList<RESTActionType>();
+        if (actionTypes == null) {
+            return result;
+        }
+        for (ActionType actionType : actionTypes) {
+            result.add(generateAction(actionType, language));
+        }
+        return result;
+    }
+
+    /**
+     * Creates a {@link RESTConditionType} based on the specified {@link 
ConditionType} and localized using the specified language.
+     *
+     * @param conditionType the {@link ConditionType} to be localized
+     * @param language      the language to use to localize {@link 
ConditionType}
+     * @return a {@link RESTConditionType} based on the specified {@link 
ConditionType} and localized using the specified language
+     */
+    public RESTConditionType generateCondition(ConditionType conditionType, 
String language) {
+        RESTConditionType result = new RESTConditionType();
+        result.setId(conditionType.getId());
+
+        ResourceBundle bundle = 
resourceBundleHelper.getResourceBundle(conditionType, language);
+        result.setName(resourceBundleHelper.getResourceBundleValue(bundle, 
conditionType.getNameKey()));
+        
result.setDescription(resourceBundleHelper.getResourceBundleValue(bundle, 
conditionType.getDescriptionKey()));
+
+        result.setTags(conditionType.getTagIDs());
+
+        for (Parameter parameter : conditionType.getParameters()) {
+            result.getParameters().add(generateParameter(parameter, bundle));
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates a {@link RESTActionType} based on the specified {@link 
ActionType} and localized using the specified language.
+     *
+     * @param actionType the {@link ActionType} to be localized
+     * @param language   the language to use to localize {@link ActionType}
+     * @return a {@link RESTActionType} based on the specified {@link 
ActionType} and localized using the specified language
+     */
+    public RESTActionType generateAction(ActionType actionType, String 
language) {
+        RESTActionType result = new RESTActionType();
+        result.setId(actionType.getId());
+
+        ResourceBundle bundle = 
resourceBundleHelper.getResourceBundle(actionType, language);
+        result.setName(resourceBundleHelper.getResourceBundleValue(bundle, 
actionType.getNameKey()));
+        
result.setDescription(resourceBundleHelper.getResourceBundleValue(bundle, 
actionType.getDescriptionKey()));
+
+        result.setTags(actionType.getTagIds());
+
+        List<RESTParameter> parameters = new ArrayList<RESTParameter>();
+        for (Parameter parameter : actionType.getParameters()) {
+            parameters.add(generateParameter(parameter, bundle));
+        }
+        result.setParameters(parameters);
+
+        return result;
+    }
+
+    /**
+     * Creates a {@link RESTParameter} based on the specified {@link 
Parameter} and localized using the specified {@link ResourceBundle}.
+     *
+     * @param parameter the {@link Parameter} to be localized
+     * @param bundle    the {@link ResourceBundle} used to localize the {@link 
Parameter}'s choice list values if needed
+     * @return a {@link RESTParameter} based on the specified {@link 
ActionType} and localized using the specified {@link ResourceBundle}
+     */
+    public RESTParameter generateParameter(Parameter parameter, ResourceBundle 
bundle) {
+        RESTParameter result = new RESTParameter();
+        result.setId(parameter.getId());
+        result.setDefaultValue(parameter.getDefaultValue());
+        result.setMultivalued(parameter.isMultivalued());
+        result.setType(parameter.getType());
+
+        localizeChoiceListValues(bundle, result.getChoiceListValues(), 
parameter.getChoiceListInitializerFilter());
+
+        return result;
+    }
+
+    public void localizeChoiceListValues(ResourceBundle bundle, 
List<ChoiceListValue> result, String choiceListInitializerFilter) {
+        if (choiceListInitializerFilter != null && 
choiceListInitializerFilter.length() > 0) {
+            try {
+                Collection<ServiceReference<ChoiceListInitializer>> 
matchingChoiceListInitializerReferences = 
bundleContext.getServiceReferences(ChoiceListInitializer.class, 
choiceListInitializerFilter);
+                for (ServiceReference<ChoiceListInitializer> 
choiceListInitializerReference : matchingChoiceListInitializerReferences) {
+                    ChoiceListInitializer choiceListInitializer = 
bundleContext.getService(choiceListInitializerReference);
+                    List<ChoiceListValue> options = 
choiceListInitializer.getValues(bundle.getLocale());
+                    if (choiceListInitializer instanceof I18nSupport) {
+                        for (ChoiceListValue value : options) {
+                            if (value instanceof PluginType) {
+                                
result.add(value.localizedCopy(resourceBundleHelper.getResourceBundleValue(resourceBundleHelper.getResourceBundle((PluginType)
 value, bundle.getLocale().getLanguage()), value.getName())));
+                            } else {
+                                
result.add(value.localizedCopy(resourceBundleHelper.getResourceBundleValue(bundle,
 value.getName())));
+                            }
+                        }
+                    } else {
+                        result.addAll(options);
+                    }
+                }
+            } catch (InvalidSyntaxException e) {
+                logger.error("Invalid filter", e);
+            }
+        }
+    }
+
+    /**
+     * Creates {@link RESTValueType}s, localized using the specified language, 
based on the specified {@link ValueType}s.
+     *
+     * @param valueTypes the {@link ValueType}s to be localized
+     * @param language   the language to use to localize {@link ValueType}s
+     * @return a collection of {@link RESTValueType}s based on the specified 
{@link ValueType}s and localized using the specified language
+     */
+    public Collection<RESTValueType> generateValueTypes(Collection<ValueType> 
valueTypes, String language) {
+        List<RESTValueType> result = new ArrayList<RESTValueType>();
+        if (valueTypes == null) {
+            return result;
+        }
+        for (ValueType valueType : valueTypes) {
+            result.add(generateValueType(valueType, language));
+        }
+        return result;
+    }
+
+    /**
+     * Creates a {@link RESTValueType} based on the specified {@link 
ValueType} and localized using the specified language.
+     *
+     * @param valueType the {@link ValueType} to be localized
+     * @param language  the language to use to localize {@link ValueType}
+     * @return a {@link RESTValueType} based on the specified {@link 
ValueType} and localized using the specified language
+     */
+    public RESTValueType generateValueType(ValueType valueType, String 
language) {
+        RESTValueType result = new RESTValueType();
+        result.setId(valueType.getId());
+
+        ResourceBundle bundle = 
resourceBundleHelper.getResourceBundle(valueType, language);
+        result.setName(resourceBundleHelper.getResourceBundleValue(bundle, 
valueType.getNameKey()));
+        
result.setDescription(resourceBundleHelper.getResourceBundleValue(bundle, 
valueType.getDescriptionKey()));
+        result.setTags(generateTags(valueType.getTags(), language));
+        return result;
+    }
+
+    /**
+     * Same as generateTages(tags, language, false).
+     */
+    public Collection<RESTTag> generateTags(Collection<Tag> tags, String 
language) {
+        return generateTags(tags, language, false);
+    }
+
+    /**
+     * Creates {@link RESTTag}s, localized using the specified language, based 
on the specified {@link Tag}s.
+     *
+     * @param tags         the {@link Tag}s to be localized
+     * @param language     the language to use to localize {@link Tag}s
+     * @param filterHidden {@code true} to filter out hidden tags, {@code 
false} otherwise
+     * @return a collection of {@link RESTTag}s based on the specified {@link 
Tag}s and localized using the specified language
+     */
+    public Collection<RESTTag> generateTags(Collection<Tag> tags, String 
language, boolean filterHidden) {
+        List<RESTTag> result = new ArrayList<RESTTag>();
+        for (Tag tag : tags) {
+            RESTTag subTag = generateTag(tag, language, filterHidden);
+            if (subTag != null) {
+                result.add(subTag);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Same as generateTag(tag, language, false).
+     */
+    public RESTTag generateTag(Tag tag, String language) {
+        return generateTag(tag, language, false);
+    }
+
+    /**
+     * Creates a {@link RESTTag}, localized using the specified language, 
based on the specified {@link Tag}.
+     *
+     * @param tag          the {@link Tag} to be localized
+     * @param language     the language to use to localize the {@link Tag}
+     * @param filterHidden {@code true} to filter out hidden sub-tags, {@code 
false} otherwise
+     * @return a {@link RESTTag} based on the specified {@link Tag} and 
localized using the specified language
+     */
+    public RESTTag generateTag(Tag tag, String language, boolean filterHidden) 
{
+        if (filterHidden && tag.isHidden()) {
+            return null;
+        }
+        RESTTag result = new RESTTag();
+        result.setId(tag.getId());
+        ResourceBundle bundle = resourceBundleHelper.getResourceBundle(tag, 
language);
+        result.setName(resourceBundleHelper.getResourceBundleValue(bundle, 
tag.getNameKey()));
+        
result.setDescription(resourceBundleHelper.getResourceBundleValue(bundle, 
tag.getDescriptionKey()));
+        result.setParentId(tag.getParentId());
+        result.setRank(tag.getRank());
+        result.setSubTags(generateTags(tag.getSubTags(), language, 
filterHidden));
+        return result;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void setResourceBundleHelper(ResourceBundleHelper 
resourceBundleHelper) {
+        this.resourceBundleHelper = resourceBundleHelper;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/rest/src/main/java/org/apache/unomi/rest/ProfileServiceEndPoint.java
----------------------------------------------------------------------
diff --git 
a/rest/src/main/java/org/apache/unomi/rest/ProfileServiceEndPoint.java 
b/rest/src/main/java/org/apache/unomi/rest/ProfileServiceEndPoint.java
new file mode 100644
index 0000000..8238139
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/ProfileServiceEndPoint.java
@@ -0,0 +1,507 @@
+/*
+ * 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.rest;
+
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.api.*;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.api.services.SegmentService;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jws.WebMethod;
+import javax.jws.WebService;
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * A JAX-RS endpoint to manage {@link Profile}s and {@link Persona}s.
+ */
+@WebService
+@Produces(MediaType.APPLICATION_JSON)
+@CrossOriginResourceSharing(
+        allowAllOrigins = true,
+        allowCredentials = true
+)
+public class ProfileServiceEndPoint {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(ProfileServiceEndPoint.class.getName());
+
+    private ProfileService profileService;
+
+    private EventService eventService;
+
+    private SegmentService segmentService;
+
+    private LocalizationHelper localizationHelper;
+
+    public ProfileServiceEndPoint() {
+        System.out.println("Initializing profile service endpoint...");
+    }
+
+    @WebMethod(exclude = true)
+    public void setProfileService(ProfileService profileService) {
+        this.profileService = profileService;
+    }
+
+    @WebMethod(exclude = true)
+    public void setEventService(EventService eventService) {
+        this.eventService = eventService;
+    }
+
+    @WebMethod(exclude = true)
+    public void setSegmentService(SegmentService segmentService) {
+        this.segmentService = segmentService;
+    }
+
+    @WebMethod(exclude = true)
+    public void setLocalizationHelper(LocalizationHelper localizationHelper) {
+        this.localizationHelper = localizationHelper;
+    }
+
+    /**
+     * Retrieves the number of unique profiles.
+     *
+     * @return the number of unique profiles.
+     */
+    @GET
+    @Path("/count")
+    public long getAllProfilesCount() {
+        return profileService.getAllProfilesCount();
+    }
+
+    /**
+     * Retrieves profiles matching the specified query.
+     *
+     * @param query a {@link Query} specifying which elements to retrieve
+     * @return a {@link PartialList} of profiles instances matching the 
specified query
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Path("/search")
+    public PartialList<Profile> getProfiles(Query query) {
+        return profileService.search(query, Profile.class);
+    }
+
+    /**
+     * Retrieves an export of profiles matching the specified query as a 
downloadable file using the comma-separated values (CSV) format.
+     *
+     * @param query a String JSON representation of the query the profiles to 
export should match
+     * @return a Response object configured to allow caller to download the 
CSV export file
+     */
+    @GET
+    @Path("/export")
+    @Produces("text/csv")
+    public Response getExportProfiles(@QueryParam("query") String query) {
+        try {
+            Query queryObject = 
CustomObjectMapper.getObjectMapper().readValue(query, Query.class);
+            Response.ResponseBuilder response = 
Response.ok(profileService.exportProfilesPropertiesToCsv(queryObject));
+            response.header("Content-Disposition",
+                    "attachment; filename=Profiles_export_" + new 
SimpleDateFormat("yyyy-MM-dd-HH-mm").format(new Date()) + ".csv");
+            return response.build();
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+            return Response.serverError().build();
+        }
+    }
+
+    /**
+     * A version of {@link #getExportProfiles(String)} suitable to be called 
from an HTML form.
+     *
+     * @param query a form-encoded representation of the query the profiles to 
export should match
+     * @return a Response object configured to allow caller to download the 
CSV export file
+     */
+    @GET
+    @Path("/export")
+    @Produces("text/csv")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response formExportProfiles(@FormParam("query") String query) {
+        try {
+            Query queryObject = 
CustomObjectMapper.getObjectMapper().readValue(query, Query.class);
+            Response.ResponseBuilder response = 
Response.ok(profileService.exportProfilesPropertiesToCsv(queryObject));
+            response.header("Content-Disposition",
+                    "attachment; filename=Profiles_export_" + new 
SimpleDateFormat("yyyy-MM-dd-HH-mm").format(new Date()) + ".csv");
+            return response.build();
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+            return Response.serverError().build();
+        }
+    }
+
+    /**
+     * Update all profiles in batch according to the specified {@link 
BatchUpdate}
+     *
+     * @param update the batch update specification
+     */
+    @POST
+    @Path("/batchProfilesUpdate")
+    public void batchProfilesUpdate(BatchUpdate update) {
+        profileService.batchProfilesUpdate(update);
+    }
+
+    /**
+     * Retrieves the profile identified by the specified identifier.
+     *
+     * @param profileId the identifier of the profile to retrieve
+     * @return the profile identified by the specified identifier or {@code 
null} if no such profile exists
+     */
+    @GET
+    @Path("/{profileId}")
+    public Profile load(@PathParam("profileId") String profileId) {
+        return profileService.load(profileId);
+    }
+
+    /**
+     * Saves the specified profile in the context server, sending a {@code 
profileUpdated} event.
+     *
+     * @param profile the profile to be saved
+     * @return the newly saved profile
+     */
+    @POST
+    @Path("/")
+    public Profile save(Profile profile) {
+        // TODO: check that the profile was actually updated correctly before 
sending the event
+        Event profileUpdated = new Event("profileUpdated", null, profile, 
null, null, profile, new Date());
+        profileUpdated.setPersistent(false);
+        eventService.send(profileUpdated);
+        return profileService.save(profile);
+    }
+
+    /**
+     * Removes the profile (or persona if the {@code persona} query parameter 
is set to {@code true}) identified by the specified identifier.
+     *
+     * @param profileId the identifier of the profile or persona to delete
+     * @param persona   {@code true} if the specified identifier is supposed 
to refer to a persona, {@code false} if it is supposed to refer to a profile
+     */
+    @DELETE
+    @Path("/{profileId}")
+    public void delete(@PathParam("profileId") String profileId, 
@QueryParam("persona") @DefaultValue("false") boolean persona) {
+        profileService.delete(profileId, false);
+    }
+
+    /**
+     * Retrieves the sessions associated with the profile identified by the 
specified identifier that match the specified query (if specified), ordered 
according to the specified
+     * {@code sortBy} String and and paged: only {@code size} of them are 
retrieved, starting with the {@code offset}-th one.
+     *
+     * TODO: use a Query object instead of distinct parameter?
+     *
+     * @param profileId the identifier of the profile we want to retrieve 
sessions from
+     * @param query     a String of text used for fulltext filtering which 
sessions we are interested in or {@code null} (or an empty String) if we want 
to retrieve all sessions
+     * @param offset    zero or a positive integer specifying the position of 
the first session in the total ordered collection of matching sessions
+     * @param size      a positive integer specifying how many matching 
sessions should be retrieved or {@code -1} if all of them should be retrieved
+     * @param sortBy    an optional ({@code null} if no sorting is required) 
String of comma ({@code ,}) separated property names on which ordering should 
be performed, ordering
+     *                  elements according to the property order in the
+     *                  String, considering each in turn and moving on to the 
next one in case of equality of all preceding ones. Each property name is 
optionally followed by
+     *                  a column ({@code :}) and an order specifier: {@code 
asc} or {@code desc}.
+     * @return a {@link PartialList} of matching sessions
+     */
+    @GET
+    @Path("/{profileId}/sessions")
+    public PartialList<Session> getProfileSessions(@PathParam("profileId") 
String profileId,
+                                                   @QueryParam("q") String 
query,
+                                                   @QueryParam("offset") 
@DefaultValue("0") int offset,
+                                                   @QueryParam("size") 
@DefaultValue("50") int size,
+                                                   @QueryParam("sort") String 
sortBy) {
+        return profileService.getProfileSessions(profileId, query, offset, 
size, sortBy);
+    }
+
+    /**
+     * Retrieves the list of segment metadata for the segments the specified 
profile is a member of.
+     *
+     * @param profileId the identifier of the profile for which we want to 
retrieve the segment metadata
+     * @return the (possibly empty) list of segment metadata for the segments 
the specified profile is a member of
+     */
+    @GET
+    @Path("/{profileId}/segments")
+    public List<Metadata> getProfileSegments(@PathParam("profileId") String 
profileId) {
+        Profile profile = profileService.load(profileId);
+        return segmentService.getSegmentMetadatasForProfile(profile);
+    }
+
+    /**
+     * TODO
+     *
+     * @param fromPropertyTypeId
+     * @return
+     */
+    @GET
+    @Path("/properties/mappings/{fromPropertyTypeId}")
+    public String getPropertyTypeMapping(@PathParam("fromPropertyTypeId") 
String fromPropertyTypeId) {
+        return profileService.getPropertyTypeMapping(fromPropertyTypeId);
+    }
+
+    /**
+     * Retrieves {@link Persona} matching the specified query.
+     *
+     * @param query a {@link Query} specifying which elements to retrieve
+     * @return a {@link PartialList} of Persona instances matching the 
specified query
+     */
+    @POST
+    @Path("/personas/search")
+    public PartialList<Persona> getPersonas(Query query) {
+        return profileService.search(query, Persona.class);
+    }
+
+    /**
+     * Retrieves the {@link Persona} identified by the specified identifier.
+     *
+     * @param personaId the identifier of the persona to retrieve
+     * @return the persona identified by the specified identifier or {@code 
null} if no such persona exists
+     */
+    @GET
+    @Path("/personas/{personaId}")
+    public Persona loadPersona(@PathParam("personaId") String personaId) {
+        return profileService.loadPersona(personaId);
+    }
+
+    /**
+     * Retrieves the persona identified by the specified identifier and all 
its associated sessions
+     *
+     * @param personaId the identifier of the persona to retrieve
+     * @return a {@link PersonaWithSessions} instance with the persona 
identified by the specified identifier and all its associated sessions
+     */
+    @GET
+    @Path("/personasWithSessions/{personaId}")
+    public PersonaWithSessions loadPersonaWithSessions(@PathParam("personaId") 
String personaId) {
+        return profileService.loadPersonaWithSessions(personaId);
+    }
+
+    /**
+     * Persists the specified {@link Persona} in the context server.
+     *
+     * @param persona the persona to persist
+     * @return the newly persisted persona
+     */
+    @POST
+    @Path("/personas")
+    public Persona savePersona(Persona persona) {
+        return profileService.savePersona(persona);
+    }
+
+    /**
+     * Removes the persona identified by the specified identifier.
+     *
+     * @param personaId the identifier of the persona to delete
+     */
+    @DELETE
+    @Path("/personas/{personaId}")
+    public void deletePersona(@PathParam("personaId") String personaId) {
+        profileService.delete(personaId, true);
+    }
+
+    /**
+     * Creates a persona with the specified identifier and automatically 
creates an associated session with it.
+     *
+     * @param personaId the identifier to use for the new persona
+     * @return the newly created persona
+     */
+    @PUT
+    @Path("/personas/{personaId}")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Persona createPersona(@PathParam("personaId") String personaId) {
+        return profileService.createPersona(personaId);
+    }
+
+    /**
+     * Retrieves the sessions associated with the persona identified by the 
specified identifier, ordered according to the specified {@code sortBy} String 
and and paged: only
+     * {@code size} of them are retrieved, starting with the {@code offset}-th 
one.
+     *
+     * @param personaId the persona id
+     * @param offset    zero or a positive integer specifying the position of 
the first session in the total ordered collection of matching sessions
+     * @param size      a positive integer specifying how many matching 
sessions should be retrieved or {@code -1} if all of them should be retrieved
+     * @param sortBy    an optional ({@code null} if no sorting is required) 
String of comma ({@code ,}) separated property names on which ordering should 
be performed, ordering
+     *                  elements according to the property order in the
+     *                  String, considering each in turn and moving on to the 
next one in case of equality of all preceding ones. Each property name is 
optionally followed by
+     *                  a column ({@code :}) and an order specifier: {@code 
asc} or {@code desc}.
+     * @return a {@link PartialList} of sessions for the persona identified by 
the specified identifier
+     */
+    @GET
+    @Path("/personas/{personaId}/sessions")
+    public PartialList<Session> getPersonaSessions(@PathParam("personaId") 
String personaId,
+                                                   @QueryParam("offset") 
@DefaultValue("0") int offset,
+                                                   @QueryParam("size") 
@DefaultValue("50") int size,
+                                                   @QueryParam("sort") String 
sortBy) {
+        return profileService.getPersonaSessions(personaId, offset, size, 
sortBy);
+    }
+
+    /**
+     * Retrieves the session identified by the specified identifier.
+     *
+     * @param sessionId the identifier of the session to be retrieved
+     * @param dateHint  a Date helping in identifying where the item is located
+     * @return the session identified by the specified identifier
+     */
+    @GET
+    @Path("/sessions/{sessionId}")
+    public Session loadSession(@PathParam("sessionId") String sessionId, 
@QueryParam("dateHint") String dateHint) throws ParseException {
+        return profileService.loadSession(sessionId, dateHint != null ? new 
SimpleDateFormat("yyyy-MM").parse(dateHint) : null);
+    }
+
+    /**
+     * Saves the specified session.
+     *
+     * @param session the session to be saved
+     * @return the newly saved session
+     */
+    @POST
+    @Path("/sessions/{sessionId}")
+    public Session saveSession(Session session) {
+        return profileService.saveSession(session);
+    }
+
+    /**
+     * Retrieves {@link Event}s for the {@link Session} identified by the 
provided session identifier, matching any of the provided event types,
+     * ordered according to the specified {@code sortBy} String and paged: 
only {@code size} of them are retrieved, starting with the {@code offset}-th 
one.
+     * If a {@code query} is provided, a full text search is performed on the 
matching events to further filter them.
+     *
+     * @param sessionId  the identifier of the user session we're considering
+     * @param eventTypes an array of event type names; the events to retrieve 
should at least match one of these
+     * @param query      a String to perform full text filtering on events 
matching the other conditions
+     * @param offset     zero or a positive integer specifying the position of 
the first event in the total ordered collection of matching events
+     * @param size       a positive integer specifying how many matching 
events should be retrieved or {@code -1} if all of them should be retrieved
+     * @param sortBy     an optional ({@code null} if no sorting is required) 
String of comma ({@code ,}) separated property names on which ordering should 
be performed, ordering
+     *                   elements according to the property order in
+     *                   the String, considering each in turn and moving on to 
the next one in case of equality of all preceding ones. Each property name is 
optionally followed by
+     *                   a column ({@code :}) and an order specifier: {@code 
asc} or {@code desc}.
+     * @return a {@link PartialList} of matching events
+     */
+    @GET
+    @Path("/sessions/{sessionId}/events")
+    public PartialList<Event> getSessionEvents(@PathParam("sessionId") String 
sessionId,
+                                               @QueryParam("eventTypes") 
String[] eventTypes,
+                                               @QueryParam("q") String query,
+                                               @QueryParam("offset") 
@DefaultValue("0") int offset,
+                                               @QueryParam("size") 
@DefaultValue("50") int size,
+                                               @QueryParam("sort") String 
sortBy) {
+        return eventService.searchEvents(sessionId, eventTypes, query, offset, 
size, sortBy);
+    }
+
+    @WebMethod(exclude = true)
+    public PartialList<Session> findProfileSessions(String profileId) {
+        return null;
+    }
+
+    @WebMethod(exclude = true)
+    public boolean matchCondition(Condition condition, Profile profile, 
Session session) {
+        return profileService.matchCondition(condition, profile, session);
+    }
+
+    /**
+     * Retrieves the existing property types for the specified type as defined 
by the Item subclass public field {@code ITEM_TYPE} and with the specified tag.
+     *
+     * TODO: move to a different class
+     *
+     * @param tagId    the tag we're interested in
+     * @param itemType the String representation of the item type we want to 
retrieve the count of, as defined by its class' {@code ITEM_TYPE} field
+     * @return all property types defined for the specified item type and with 
the specified tag
+     */
+    @GET
+    @Path("/existingProperties")
+    public Collection<PropertyType> getExistingProperties(@QueryParam("tagId") 
String tagId, @QueryParam("itemType") String itemType, 
@HeaderParam("Accept-Language") String language) {
+        Set<PropertyType> properties = 
profileService.getExistingProperties(tagId, itemType);
+        return properties;
+    }
+
+    /**
+     * Retrieves all known property types.
+     *
+     * TODO: move to a different class
+     *
+     * @param language TODO unused
+     * @return a Map associating targets as keys to related {@link 
PropertyType}s
+     */
+    @GET
+    @Path("/properties")
+    public Map<String, Collection<PropertyType>> 
getPropertyTypes(@HeaderParam("Accept-Language") String language) {
+        return profileService.getAllPropertyTypes();
+    }
+
+    /**
+     * Retrieves all the property types associated with the specified target.
+     *
+     * TODO: move to a different class
+     *
+     * @param target   the target for which we want to retrieve the associated 
property types
+     * @param language TODO unused
+     * @return a collection of all the property types associated with the 
specified target
+     */
+    @GET
+    @Path("/properties/{target}")
+    public Collection<PropertyType> 
getPropertyTypesByTarget(@PathParam("target") String target, 
@HeaderParam("Accept-Language") String language) {
+        return profileService.getAllPropertyTypes(target);
+    }
+
+    /**
+     * Retrieves all property types with the specified tags also retrieving 
property types with sub-tags of the specified tags if so specified.
+     *
+     * TODO: move to a different class
+     * TODO: passing a list of tags via a comma-separated list is not very 
RESTful
+     *
+     * @param tags      a comma-separated list of tag identifiers
+     * @param recursive {@code true} if sub-tags of the specified tag should 
also be considered, {@code false} otherwise
+     * @return a Set of the property types with the specified tag
+     */
+    @GET
+    @Path("/properties/tags/{tagId}")
+    public Collection<PropertyType> getPropertyTypeByTag(@PathParam("tagId") 
String tags, @QueryParam("recursive") @DefaultValue("false") boolean recursive, 
@HeaderParam("Accept-Language") String language) {
+        String[] tagsArray = tags.split(",");
+        Set<PropertyType> results = new LinkedHashSet<>();
+        for (String s : tagsArray) {
+            results.addAll(profileService.getPropertyTypeByTag(s, recursive));
+        }
+        return results;
+    }
+
+    /**
+     * Persists the specified property type in the context server.
+     *
+     * TODO: move to a different class
+     *
+     * @param property the property type to persist
+     * @return {@code true} if the property type was properly created, {@code 
false} otherwise (for example, if the property type already existed
+     */
+    @POST
+    @Path("/properties")
+    public boolean createPropertyType(PropertyType property) {
+        return profileService.createPropertyType(property);
+    }
+
+    /**
+     * Deletes the property type identified by the specified identifier.
+     *
+     * TODO: move to a different class
+     *
+     * @param propertyId the identifier of the property type to delete
+     * @return {@code true} if the property type was properly deleted, {@code 
false} otherwise
+     */
+    @DELETE
+    @Path("/properties/{propertyId}")
+    public boolean deleteProperty(@PathParam("propertyId") String propertyId) {
+        return profileService.deletePropertyType(propertyId);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/rest/src/main/java/org/apache/unomi/rest/QueryServiceEndPoint.java
----------------------------------------------------------------------
diff --git a/rest/src/main/java/org/apache/unomi/rest/QueryServiceEndPoint.java 
b/rest/src/main/java/org/apache/unomi/rest/QueryServiceEndPoint.java
new file mode 100644
index 0000000..c578de5
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/QueryServiceEndPoint.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.rest;
+
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.api.Item;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.api.query.AggregateQuery;
+import org.apache.unomi.api.services.QueryService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jws.WebMethod;
+import javax.jws.WebService;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.Map;
+
+/**
+ * A JAX-RS endpoint to perform queries against context-server data.
+ */
+@WebService
+@Produces(MediaType.APPLICATION_JSON)
+@CrossOriginResourceSharing(
+        allowAllOrigins = true,
+        allowCredentials = true
+)
+public class QueryServiceEndPoint {
+    private static final Logger logger = 
LoggerFactory.getLogger(QueryServiceEndPoint.class.getName());
+
+    private QueryService queryService;
+
+    private LocalizationHelper localizationHelper;
+
+    @WebMethod(exclude = true)
+    public void setQueryService(QueryService queryService) {
+        this.queryService = queryService;
+    }
+
+
+    @WebMethod(exclude = true)
+    public void setLocalizationHelper(LocalizationHelper localizationHelper) {
+        this.localizationHelper = localizationHelper;
+    }
+
+    /**
+     * Retrieves the number of items with the specified type as defined by the 
Item subclass public field {@code ITEM_TYPE} and aggregated by possible values 
of the specified
+     * property.
+     *
+     * @param type     the String representation of the item type we want to 
retrieve the count of, as defined by its class' {@code ITEM_TYPE} field
+     * @param property the property we're aggregating on, i.e. for each 
possible value of this property, we are counting how many items of the 
specified type have that value
+     * @return a Map associating a specific value of the property to the 
cardinality of items with that value
+     * @see Item Item for a discussion of {@code ITEM_TYPE}
+     */
+    @GET
+    @Path("/{type}/{property}")
+    public Map<String, Long> getAggregate(@PathParam("type") String type, 
@PathParam("property") String property) {
+        return queryService.getAggregate(type, property);
+    }
+
+    /**
+     * TODO: rework, this method is confusing since it either behaves like 
{@link #getAggregate(String, String)} if query is null but completely 
differently if it isn't
+     *
+     * Retrieves the number of items with the specified type as defined by the 
Item subclass public field {@code ITEM_TYPE} and aggregated by possible values 
of the specified
+     * property or, if the specified query is not {@code null}, perform that 
aggregate query.
+     *
+     * @param type           the String representation of the item type we 
want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field
+     * @param property       the property we're aggregating on, i.e. for each 
possible value of this property, we are counting how many items of the 
specified type have that value
+     * @param aggregateQuery the {@link AggregateQuery} specifying the 
aggregation that should be perfomed
+     * @return a Map associating a specific value of the property to the 
cardinality of items with that value
+     * @see Item Item for a discussion of {@code ITEM_TYPE}
+     */
+    @POST
+    @Path("/{type}/{property}")
+    public Map<String, Long> getAggregate(@PathParam("type") String type, 
@PathParam("property") String property, AggregateQuery aggregateQuery) {
+        return queryService.getAggregate(type, property, aggregateQuery);
+    }
+
+    /**
+     * Retrieves the specified metrics for the specified field of items of the 
specified type as defined by the Item subclass public field {@code ITEM_TYPE} 
and matching the
+     * specified {@link Condition}.
+     *
+     * @param condition   the condition the items must satisfy
+     * @param metricsType a String specifying which metrics should be 
computed, separated by a slash ({@code /}) (possible values: {@code sum} for 
the sum of the
+     *                    values, {@code avg} for the average of the values, 
{@code min} for the minimum value and {@code max} for the maximum value)
+     * @param property    the name of the field for which the metrics should 
be computed
+     * @param type        the String representation of the item type we want 
to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field
+     * @return a Map associating computed metric name as key to its associated 
value
+     * @see Item Item for a discussion of {@code ITEM_TYPE}
+     */
+    @POST
+    @Path("/{type}/{property}/{metricTypes:((sum|avg|min|max)/?)*}")
+    public Map<String, Double> getMetric(@PathParam("type") String type, 
@PathParam("property") String property, @PathParam("metricTypes") String 
metricsType, Condition condition) {
+        return queryService.getMetric(type, property, metricsType, condition);
+    }
+
+    /**
+     * Retrieves the number of items of the specified type as defined by the 
Item subclass public field {@code ITEM_TYPE} and matching the specified {@link 
Condition}.
+     *
+     * @param condition the condition the items must satisfy
+     * @param validate optional parameter, in case of draft condition that 
have missing required parameters an IllegalArgumentException is throw
+     *                 and this end point will return status code 400, to 
avoid that you can set validate to false.
+     * @param type      the String representation of the item type we want to 
retrieve the count of, as defined by its class' {@code ITEM_TYPE} field
+     * @param response  the httpServletResponse
+     * @return the number of items of the specified type.
+     *         0 and status code 400 in case of IllegalArgumentException (bad 
condition) and validate null or true
+     *         0 and status code 200 in case of IllegalArgumentException (bad 
condition) and validate false
+     * @see Item Item for a discussion of {@code ITEM_TYPE}
+     */
+    @POST
+    @Path("/{type}/count")
+    public long getQueryCount(@PathParam("type") String type, 
@QueryParam("validate") Boolean validate, Condition condition,  @Context final 
HttpServletResponse response) {
+        long count = 0;
+        try {
+            count = queryService.getQueryCount(type, condition);
+        } catch (IllegalArgumentException e) {
+            if(validate == null || validate) {
+                logger.error(e.getMessage(), e);
+                
response.setStatus(Response.Status.BAD_REQUEST.getStatusCode());
+            }
+        }
+        return count;
+    }
+
+}

Reply via email to