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; + } + +}
