diff --git a/bundles/core/org.eclipse.smarthome.core.persistence/META-INF/MANIFEST.MF b/bundles/core/org.eclipse.smarthome.core.persistence/META-INF/MANIFEST.MF index 0d6c7732fd1..f5c4e808829 100644 --- a/bundles/core/org.eclipse.smarthome.core.persistence/META-INF/MANIFEST.MF +++ b/bundles/core/org.eclipse.smarthome.core.persistence/META-INF/MANIFEST.MF @@ -11,5 +11,6 @@ Import-Package: org.eclipse.smarthome.core.items, org.eclipse.smarthome.core.persistence, org.eclipse.smarthome.core.types, org.slf4j -Export-Package: org.eclipse.smarthome.core.persistence +Export-Package: org.eclipse.smarthome.core.persistence, + org.eclipse.smarthome.core.persistence.dto Bundle-ClassPath: . diff --git a/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/HistoricItem.java b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/HistoricItem.java index 723ba2a7ecb..8b0d567a039 100644 --- a/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/HistoricItem.java +++ b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/HistoricItem.java @@ -27,23 +27,23 @@ public interface HistoricItem { /** * returns the timestamp of the persisted item - * + * * @return the timestamp of the item */ Date getTimestamp(); /** * returns the current state of the item - * + * * @return the current state */ - public State getState(); + State getState(); /** * returns the name of the item - * + * * @return the name of the item */ - public String getName(); + String getName(); } diff --git a/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/ModifiablePersistenceService.java b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/ModifiablePersistenceService.java new file mode 100644 index 00000000000..777589cb665 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/ModifiablePersistenceService.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2014-2016 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.persistence; + +import java.util.Date; + +import org.eclipse.smarthome.core.items.Item; +import org.eclipse.smarthome.core.types.State; + +/** + * This class provides an interface to the a {@link PersistenceService} to allow data to be stored + * at a specific time. This allows bindings that interface to devices that store data internally, + * and then periodically provide it to the server to be accommodated. + * + * @author Chris Jackson - Initial implementation and API + * + */ +public interface ModifiablePersistenceService extends QueryablePersistenceService { + /** + *

+ * Stores the historic item value. This allows the item, time and value to be specified. + *

+ *

+ * Adding data with the same time as an existing record should update the current record value rather than adding a + * new record. + *

+ *

+ * Implementors should keep in mind that all registered {@link PersistenceService}s are called synchronously. Hence + * long running operations should be processed asynchronously. E.g. store adds things to a queue which + * is processed by some asynchronous workers (Quartz Job, Thread, etc.). + *

+ * + * @param item the data to be stored + * @param date the date of the record + * @param state the state to be recorded + */ + void store(Item item, Date date, State state); + + /** + * Removes data associated with an item from a persistence service. + * If all data is removed for the specified item, the persistence service should free any resources associated with + * the item (eg. remove any tables or delete files from the storage). + * + * @param filter the filter to apply to the data removal. ItemName can not be null. + * @return true if the query executed successfully + * @throws {@link IllegalArgumentException} if item name is null. + */ + boolean remove(FilterCriteria filter) throws IllegalArgumentException; +} diff --git a/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/PersistenceItemInfo.java b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/PersistenceItemInfo.java new file mode 100644 index 00000000000..472d42a5a44 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/PersistenceItemInfo.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2014-2016 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.persistence; + +import java.util.Date; + +/** + * This class provides information about an item that is stored in a persistence service. + * It is used to return information about the item to the system + * + * @author Chris Jackson - Initial contribution + * + */ +public interface PersistenceItemInfo { + /** + * Returns the item name. + * It should be noted that the item name is as stored in the persistence service and as such may not be linked to an + * item. This may be the case if the item was removed from the system, but data still exists in the persistence + * service. + * + * @return Item name + */ + String getName(); + + /** + * Returns a counter with the number of rows of data associated with the item + * Note that this should be used as a guide to the amount of data and may note be 100% accurate. If accurate + * information is required, the {@link QueryablePersistenceService#query} method should be used. + * + * @return count of the number of rows of data. May return null if the persistence service doesn't support this. + */ + Integer getCount(); + + /** + * Returns the earliest timestamp from data in the persistence database + * + * @return the earliest {@link Date} stored in the database. May return null if the persistence service doesn't + * support this. + */ + Date getEarliest(); + + /** + * Returns the latest timestamp from data in the persistence database + * + * @return the latest {@link Date} stored in the database. May return null if the persistence service doesn't + * support this. + */ + Date getLatest(); +} diff --git a/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/PersistenceService.java b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/PersistenceService.java index d8d417ef57d..30e23323565 100644 --- a/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/PersistenceService.java +++ b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/PersistenceService.java @@ -7,6 +7,8 @@ */ package org.eclipse.smarthome.core.persistence; +import java.util.Locale; + import org.eclipse.smarthome.core.items.Item; /** @@ -20,12 +22,22 @@ public interface PersistenceService { /** - * Returns the name of this {@link PersistenceService}. - * This name is used to uniquely identify the {@link PersistenceService}. - * - * @return the name to uniquely identify the {@link PersistenceService}. + * Returns the id of this {@link PersistenceService}. + * This id is used to uniquely identify the {@link PersistenceService}. + * + * @return the id to uniquely identify the {@link PersistenceService}. + */ + String getId(); + + /** + * Returns the label of this {@link PersistenceService}. + * This label provides a user friendly name for the {@link PersistenceService}. + * + * @param locale the language to return the label in, or null for the default language + * + * @return the label of the {@link PersistenceService}. */ - String getName(); + String getLabel(Locale locale); /** * Stores the current value of the given item. @@ -34,7 +46,7 @@ public interface PersistenceService { * long running operations should be processed asynchronously. E.g. store adds things to a queue which * is processed by some asynchronous workers (Quartz Job, Thread, etc.). *

- * + * * @param item the item which state should be persisted. */ void store(Item item); @@ -48,7 +60,7 @@ public interface PersistenceService { * long running operations should be processed asynchronously. E.g. store adds things to a queue which * is processed by some asynchronous workers (Quartz Job, Thread, etc.). *

- * + * * @param item the item which state should be persisted. * @param alias the alias under which the item should be persisted. */ diff --git a/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/QueryablePersistenceService.java b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/QueryablePersistenceService.java index edcc758f651..9986f28527d 100644 --- a/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/QueryablePersistenceService.java +++ b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/QueryablePersistenceService.java @@ -7,20 +7,34 @@ */ package org.eclipse.smarthome.core.persistence; +import java.util.Set; + +import org.eclipse.smarthome.core.items.Item; + /** * A queryable persistence service which can be used to store and retrieve * data from openHAB. This is most likely some kind of database system. * * @author Kai Kreuzer - Initial contribution and API + * @author Chris Jackson - Added getItems method */ public interface QueryablePersistenceService extends PersistenceService { /** * Queries the {@link PersistenceService} for data with a given filter criteria - * + * * @param filter the filter to apply to the query * @return a time series of items */ Iterable query(FilterCriteria filter); + /** + * Returns a list of items that are stored in the persistence service + * + * This is returned as a string to allow the persistence service to return items that are no long available as an + * ESH {@link Item}. + * + * @return list of strings of item names contained in the store. Not null. + */ + Set getItemInfo(); } diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ItemHistoryBean.java b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/dto/ItemHistoryDTO.java similarity index 91% rename from bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ItemHistoryBean.java rename to bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/dto/ItemHistoryDTO.java index 92aa189cdda..7092276b1dc 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ItemHistoryBean.java +++ b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/dto/ItemHistoryDTO.java @@ -5,7 +5,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.smarthome.io.rest.core.persistence; +package org.eclipse.smarthome.core.persistence.dto; import java.util.ArrayList; import java.util.List; @@ -18,7 +18,7 @@ * @author Chris Jackson - Initial Contribution * */ -public class ItemHistoryBean { +public class ItemHistoryDTO { public String name; public String totalrecords; @@ -26,7 +26,7 @@ public class ItemHistoryBean { public List data = new ArrayList(); - public ItemHistoryBean() { + public ItemHistoryDTO() { }; /** diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ServiceBean.java b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/dto/PersistenceServiceDTO.java similarity index 50% rename from bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ServiceBean.java rename to bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/dto/PersistenceServiceDTO.java index 1b67273ae6a..b2897bc072d 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ServiceBean.java +++ b/bundles/core/org.eclipse.smarthome.core.persistence/src/main/java/org/eclipse/smarthome/core/persistence/dto/PersistenceServiceDTO.java @@ -5,17 +5,30 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.smarthome.io.rest.core.persistence; +package org.eclipse.smarthome.core.persistence.dto; /** - * This is a java bean that is used to serialize items to JSON. + * This is a java bean that is used to serialize services to JSON. * * @author Chris Jackson - Initial Contribution * */ -public class ServiceBean { - ServiceBean() { +public class PersistenceServiceDTO { + public PersistenceServiceDTO() { } - public String name; + /** + * Service Id + */ + public String id; + + /** + * Service label + */ + public String label; + + /** + * Persistence service class + */ + public String type; } diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/META-INF/MANIFEST.MF b/bundles/io/org.eclipse.smarthome.io.rest.core/META-INF/MANIFEST.MF index 9922b6f9c4d..a08c4d09e2a 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/META-INF/MANIFEST.MF +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/META-INF/MANIFEST.MF @@ -29,6 +29,7 @@ Import-Package: com.google.common.base, org.eclipse.smarthome.core.library.items, org.eclipse.smarthome.core.library.types, org.eclipse.smarthome.core.persistence, + org.eclipse.smarthome.core.persistence.dto, org.eclipse.smarthome.core.thing, org.eclipse.smarthome.core.thing.dto, org.eclipse.smarthome.core.thing.link, @@ -40,6 +41,7 @@ Import-Package: com.google.common.base, org.eclipse.smarthome.io.rest, org.eclipse.smarthome.io.rest.core.item, org.eclipse.smarthome.io.rest.core.thing, + org.eclipse.smarthome.model.persistence.extensions, org.osgi.framework, org.osgi.service.cm, org.osgi.service.component, diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/OSGI-INF/persistenceresource.xml b/bundles/io/org.eclipse.smarthome.io.rest.core/OSGI-INF/persistenceresource.xml index 2fe512ed8ec..32cdbe10636 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/OSGI-INF/persistenceresource.xml +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/OSGI-INF/persistenceresource.xml @@ -11,6 +11,7 @@ + diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ItemHistoryListBean.java b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ItemHistoryListDTO.java similarity index 69% rename from bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ItemHistoryListBean.java rename to bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ItemHistoryListDTO.java index 1219097c5d6..3ac53ecf954 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ItemHistoryListBean.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/ItemHistoryListDTO.java @@ -11,19 +11,21 @@ import java.util.Collection; import java.util.List; +import org.eclipse.smarthome.core.persistence.dto.ItemHistoryDTO; + /** * This is a java bean that is used to serialize item lists. * * @author Chris Jackson - Initial Contribution * */ -public class ItemHistoryListBean { - public final List item = new ArrayList(); +public class ItemHistoryListDTO { + public final List item = new ArrayList(); - public ItemHistoryListBean() { + public ItemHistoryListDTO() { } - public ItemHistoryListBean(Collection list) { + public ItemHistoryListDTO(Collection list) { item.addAll(list); } } diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/PersistenceResource.java b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/PersistenceResource.java index a4cc6db1723..0d3155b2811 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/PersistenceResource.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/persistence/PersistenceResource.java @@ -20,9 +20,13 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @@ -33,17 +37,26 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import org.eclipse.smarthome.core.items.Item; +import org.eclipse.smarthome.core.items.ItemNotFoundException; +import org.eclipse.smarthome.core.items.ItemRegistry; import org.eclipse.smarthome.core.library.types.DateTimeType; import org.eclipse.smarthome.core.library.types.OnOffType; import org.eclipse.smarthome.core.library.types.OpenClosedType; import org.eclipse.smarthome.core.persistence.FilterCriteria; import org.eclipse.smarthome.core.persistence.FilterCriteria.Ordering; import org.eclipse.smarthome.core.persistence.HistoricItem; +import org.eclipse.smarthome.core.persistence.ModifiablePersistenceService; import org.eclipse.smarthome.core.persistence.PersistenceService; import org.eclipse.smarthome.core.persistence.QueryablePersistenceService; +import org.eclipse.smarthome.core.persistence.dto.ItemHistoryDTO; +import org.eclipse.smarthome.core.persistence.dto.PersistenceServiceDTO; import org.eclipse.smarthome.core.types.State; +import org.eclipse.smarthome.core.types.TypeParser; import org.eclipse.smarthome.io.rest.JSONResponse; +import org.eclipse.smarthome.io.rest.LocaleUtil; import org.eclipse.smarthome.io.rest.RESTResource; +import org.eclipse.smarthome.model.persistence.extensions.PersistenceExtensions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,7 +70,8 @@ * This class acts as a REST resource for history data and provides different methods to interact with the persistence * store * - * @author Chris Jackson - Initial Contribution + * @author Chris Jackson - Initial Contribution and add support for ModifiablePersistenceService + * */ @Path(PersistenceResource.PATH) @Api(value = PersistenceResource.PATH) @@ -66,43 +80,110 @@ public class PersistenceResource implements RESTResource { private final Logger logger = LoggerFactory.getLogger(PersistenceResource.class); private final int MILLISECONDS_PER_DAY = 86400000; + private final String MODIFYABLE = "Modifiable"; + private final String QUERYABLE = "Queryable"; + private final String STANDARD = "Standard"; + // The URI path to this resource public static final String PATH = "persistence"; - static private Map persistenceServices = new HashMap(); + private ItemRegistry itemRegistry; + private Map persistenceServices = new HashMap(); public void addPersistenceService(PersistenceService service) { - persistenceServices.put(service.getName(), service); + persistenceServices.put(service.getId(), service); } public void removePersistenceService(PersistenceService service) { - persistenceServices.remove(service.getName()); + persistenceServices.remove(service.getId()); + } + + protected void setItemRegistry(ItemRegistry itemRegistry) { + this.itemRegistry = itemRegistry; + } + + protected void unsetItemRegistry(ItemRegistry itemRegistry) { + this.itemRegistry = null; } @GET @Produces({ MediaType.APPLICATION_JSON }) @ApiOperation(value = "Gets a list of persistence services.", response = String.class, responseContainer = "List") - @ApiResponses(value = @ApiResponse(code = 200, message = "OK") ) - public Response httpGetPersistenceServices(@Context HttpHeaders headers) { - Object responseObject = getPersistenceServiceList(); + @ApiResponses(value = @ApiResponse(code = 200, message = "OK")) + public Response httpGetPersistenceServices(@Context HttpHeaders headers, + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = HttpHeaders.ACCEPT_LANGUAGE) String language) { + + Locale locale = LocaleUtil.getLocale(language); + + Object responseObject = getPersistenceServiceList(locale); return Response.ok(responseObject).build(); } @GET - @Path("/{itemname: [a-zA-Z_0-9]*}") + @Path("/items") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Gets item persistence data from the persistence service.", response = ItemHistoryBean.class) + @ApiOperation(value = "Gets a list of items available via a specific persistence service.", response = String.class, responseContainer = "List") + @ApiResponses(value = @ApiResponse(code = 200, message = "OK")) + public Response httpGetPersistenceServiceItems(@Context HttpHeaders headers, + @ApiParam(value = "Name of the persistence service. If not provided the default service will be used", required = false) @QueryParam("servicename") String serviceName) { + + return getServiceItemList(serviceName); + } + + @GET + @Path("/items/{itemname: [a-zA-Z_0-9]*}") + @Produces({ MediaType.APPLICATION_JSON }) + @ApiOperation(value = "Gets item persistence data from the persistence service.", response = ItemHistoryDTO.class) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Unknown Item or persistence service") }) public Response httpGetPersistenceItemData(@Context HttpHeaders headers, - @ApiParam(value = "The item name", required = true) @PathParam("itemname") String itemName, @ApiParam(value = "Name of the persistence service. If not provided the default service will be used", required = false) @QueryParam("servicename") String serviceName, - @ApiParam(value = "Start time of the data to return. Will default to 1 day before endtime", required = false) @QueryParam("starttime") String startTime, - @ApiParam(value = "End time of the data to return. Will default to current time.", required = false) @QueryParam("endtime") String endTime, + @ApiParam(value = "The item name", required = true) @PathParam("itemname") String itemName, + @ApiParam(value = "Start time of the data to return. Will default to 1 day before endtime. [" + + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + + "]", required = false) @QueryParam("starttime") String startTime, + @ApiParam(value = "End time of the data to return. Will default to current time. [" + + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + + "]", required = false) @QueryParam("endtime") String endTime, @ApiParam(value = "Page number of data to return. This parameter will enable paging.", required = false) @QueryParam("page") int pageNumber, - @ApiParam(value = "The length of each page.", required = false) @QueryParam("pagelength") int pageLength) { + @ApiParam(value = "The length of each page.", required = false) @QueryParam("pagelength") int pageLength, + @ApiParam(value = "Gets one value before and after the requested period.", required = false) @QueryParam("boundary") boolean boundary) { - return getItemHistoryBean(serviceName, itemName, startTime, endTime, pageNumber, pageLength); + return getItemHistoryDTO(serviceName, itemName, startTime, endTime, pageNumber, pageLength, boundary); + } + + @DELETE + @Path("/items/{itemname: [a-zA-Z_0-9]*}") + @Produces({ MediaType.APPLICATION_JSON }) + @ApiOperation(value = "Delete item data from a specific persistence service.", response = String.class, responseContainer = "List") + @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 400, message = "Invalid filter parameters"), + @ApiResponse(code = 404, message = "Unknown persistence service") }) + public Response httpDeletePersistenceServiceItem(@Context HttpHeaders headers, + @ApiParam(value = "Name of the persistence service.", required = true) @QueryParam("servicename") String serviceName, + @ApiParam(value = "The item name.", required = true) @PathParam("itemname") String itemName, + @ApiParam(value = "Start time of the data to return. [" + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + + "]", required = true) @QueryParam("starttime") String startTime, + @ApiParam(value = "End time of the data to return. [" + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + + "]", required = true) @QueryParam("endtime") String endTime) { + + return deletePersistenceItemData(serviceName, itemName, startTime, endTime); + } + + @PUT + @Path("/items/{itemname: [a-zA-Z_0-9]*}") + @Produces({ MediaType.APPLICATION_JSON }) + @ApiOperation(value = "Stores item persistence data into the persistence service.", response = ItemHistoryDTO.class) + @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 404, message = "Unknown Item or persistence service") }) + public Response httpPutPersistenceItemData(@Context HttpHeaders headers, + @ApiParam(value = "Name of the persistence service. If not provided the default service will be used", required = false) @QueryParam("servicename") String serviceName, + @ApiParam(value = "The item name.", required = true) @PathParam("itemname") String itemName, + @ApiParam(value = "Time of the data to be stored. Will default to current time. [" + + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + "]", required = true) @QueryParam("time") String time, + @ApiParam(value = "The state to store.", required = true) @QueryParam("state") String value) { + + return putItemState(serviceName, itemName, value, time); } private Date convertTime(String sTime) { @@ -110,27 +191,28 @@ private Date convertTime(String sTime) { return dateTime.getCalendar().getTime(); } - private Response getItemHistoryBean(String serviceName, String itemName, String timeBegin, String timeEnd, - int pageNumber, int pageLength) { + private Response getItemHistoryDTO(String serviceName, String itemName, String timeBegin, String timeEnd, + int pageNumber, int pageLength, boolean boundary) { // Benchmarking timer... long timerStart = System.currentTimeMillis(); // If serviceName is null, then use the default service PersistenceService service = null; if (serviceName == null) { - // TODO: Add handler for default service once this is available in ESH + service = persistenceServices.get(PersistenceExtensions.getDefaultService()); } else { service = persistenceServices.get(serviceName); } if (service == null) { logger.debug("Persistence service not found '{}'.", serviceName); - return JSONResponse.createErrorResponse(Status.CONFLICT, "Persistence service not found: " + serviceName); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, + "Persistence service not found: " + serviceName); } if (!(service instanceof QueryablePersistenceService)) { logger.debug("Persistence service not queryable '{}'.", serviceName); - return JSONResponse.createErrorResponse(Status.CONFLICT, + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "Persistence service not queryable: " + serviceName); } @@ -165,29 +247,38 @@ private Response getItemHistoryBean(String serviceName, String itemName, String Long quantity = 0l; - ItemHistoryBean bean = new ItemHistoryBean(); - bean.name = itemName; + ItemHistoryDTO dto = new ItemHistoryDTO(); + dto.name = itemName; - // First, get the value at the start time. - // This is necessary for values that don't change often otherwise data will start after the start of the graph - // (or not at all if there's no change during the graph period) filter = new FilterCriteria(); - filter.setEndDate(dateTimeBegin); filter.setItemName(itemName); - filter.setPageSize(1); - filter.setOrdering(Ordering.DESCENDING); - result = qService.query(filter); - if (result != null && result.iterator().hasNext()) { - bean.addData(dateTimeBegin.getTime(), result.iterator().next().getState()); - quantity++; + + // If "boundary" is true then we want to get one value before and after the requested period + // This is necessary for values that don't change often otherwise data will start after the start of the graph + // (or not at all if there's no change during the graph period) + if (boundary) { + // Get the value before the start time. + filter.setEndDate(dateTimeBegin); + filter.setPageSize(1); + filter.setOrdering(Ordering.DESCENDING); + result = qService.query(filter); + if (result != null && result.iterator().hasNext()) { + dto.addData(dateTimeBegin.getTime(), result.iterator().next().getState()); + quantity++; + } + } + + if (pageLength == 0) { + filter.setPageNumber(0); + filter.setPageSize(Integer.MAX_VALUE); + } else { + filter.setPageNumber(pageNumber); + filter.setPageSize(pageLength); } - filter.setPageSize(pageLength); - filter.setPageNumber(pageNumber); filter.setBeginDate(dateTimeBegin); filter.setEndDate(dateTimeEnd); filter.setOrdering(Ordering.ASCENDING); - filter.setPageSize(Integer.MAX_VALUE); result = qService.query(filter); if (result != null) { @@ -201,24 +292,30 @@ private Response getItemHistoryBean(String serviceName, String itemName, String // For 'binary' states, we need to replicate the data // to avoid diagonal lines if (state instanceof OnOffType || state instanceof OpenClosedType) { - bean.addData(historicItem.getTimestamp().getTime(), state); + dto.addData(historicItem.getTimestamp().getTime(), state); } - bean.addData(historicItem.getTimestamp().getTime(), state); + dto.addData(historicItem.getTimestamp().getTime(), state); quantity++; } + } - // Add the last value again at the end time - if (state != null) { - bean.addData(dateTimeEnd.getTime(), state); + if (boundary) { + // Get the value after the end time. + filter.setBeginDate(dateTimeEnd); + filter.setPageSize(1); + filter.setOrdering(Ordering.ASCENDING); + result = qService.query(filter); + if (result != null && result.iterator().hasNext()) { + dto.addData(result.iterator().next().getTimestamp().getTime(), result.iterator().next().getState()); quantity++; } } - bean.datapoints = Long.toString(quantity); - logger.debug("Persistence returned {} rows in {}ms", bean.datapoints, System.currentTimeMillis() - timerStart); + dto.datapoints = Long.toString(quantity); + logger.debug("Persistence returned {} rows in {}ms", dto.datapoints, System.currentTimeMillis() - timerStart); - return JSONResponse.createResponse(Status.OK, bean, ""); + return JSONResponse.createResponse(Status.OK, dto, ""); } /** @@ -226,16 +323,157 @@ private Response getItemHistoryBean(String serviceName, String itemName, String * * @return list of persistence services as {@link ServiceBean} */ - private List getPersistenceServiceList() { - List beanList = new ArrayList(); + private List getPersistenceServiceList(Locale locale) { + List dtoList = new ArrayList(); for (Map.Entry service : persistenceServices.entrySet()) { - ServiceBean serviceBean = new ServiceBean(); - serviceBean.name = service.getKey(); + PersistenceServiceDTO serviceDTO = new PersistenceServiceDTO(); + serviceDTO.id = service.getKey(); + PersistenceService persistence = service.getValue(); + serviceDTO.label = persistence.getLabel(locale); + + if (persistence instanceof ModifiablePersistenceService) { + serviceDTO.type = MODIFYABLE; + } else if (persistence instanceof QueryablePersistenceService) { + serviceDTO.type = QUERYABLE; + } else { + serviceDTO.type = STANDARD; + } - beanList.add(serviceBean); + dtoList.add(serviceDTO); } - return beanList; + return dtoList; + } + + private Response getServiceItemList(String serviceName) { + // If serviceName is null, then use the default service + PersistenceService service = null; + if (serviceName == null) { + serviceName = PersistenceExtensions.getDefaultService(); + } + service = persistenceServices.get(serviceName); + + if (service == null) { + logger.debug("Persistence service not found '{}'.", serviceName); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, + "Persistence service not found: " + serviceName); + } + + if (!(service instanceof QueryablePersistenceService)) { + logger.debug("Persistence service not queryable '{}'.", serviceName); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, + "Persistence service not queryable: " + serviceName); + } + + QueryablePersistenceService qService = (QueryablePersistenceService) service; + + return JSONResponse.createResponse(Status.OK, qService.getItemInfo(), ""); + } + + private Response deletePersistenceItemData(String serviceName, String itemName, String timeBegin, String timeEnd) { + // For deleting, we must specify a servicename - don't use the default service + if (serviceName == null || serviceName.length() == 0) { + logger.debug("Persistence service must be specified for delete operations."); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, + "Persistence service must be specified for delete operations."); + } + + PersistenceService service = persistenceServices.get(serviceName); + if (service == null) { + logger.debug("Persistence service not found '{}'.", serviceName); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, + "Persistence service not found: " + serviceName); + } + + if (!(service instanceof ModifiablePersistenceService)) { + logger.warn("Persistence service not modifiable '{}'.", serviceName); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, + "Persistence service not modifiable: " + serviceName); + } + + ModifiablePersistenceService mService = (ModifiablePersistenceService) service; + + if (timeBegin == null | timeEnd == null) { + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "The start and end time must be set"); + } + + Date dateTimeBegin = convertTime(timeBegin); + Date dateTimeEnd = convertTime(timeEnd); + if (dateTimeEnd.before(dateTimeBegin)) { + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "Start time must be earlier than end time"); + } + + FilterCriteria filter; + + // First, get the value at the start time. + // This is necessary for values that don't change often otherwise data will start after the start of the graph + // (or not at all if there's no change during the graph period) + filter = new FilterCriteria(); + filter.setBeginDate(dateTimeBegin); + filter.setEndDate(dateTimeEnd); + filter.setItemName(itemName); + try { + mService.remove(filter); + } catch (IllegalArgumentException e) { + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "Invalid filter parameters."); + } + + return Response.status(Status.OK).build(); + } + + private Response putItemState(String serviceName, String itemName, String value, String time) { + // If serviceName is null, then use the default service + PersistenceService service = null; + if (serviceName == null) { + serviceName = PersistenceExtensions.getDefaultService(); + } + service = persistenceServices.get(serviceName); + + if (service == null) { + logger.warn("Persistence service not found '{}'.", serviceName); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, + "Persistence service not found: " + serviceName); + } + + Item item; + try { + if (itemRegistry == null) { + logger.warn("Item registry not set."); + return JSONResponse.createErrorResponse(Status.CONFLICT, "Item registry not set."); + } + item = itemRegistry.getItem(itemName); + } catch (ItemNotFoundException e) { + logger.warn("Item not found '{}'.", itemName); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "Item not found: " + itemName); + } + + // Try to parse a State from the input + State state = TypeParser.parseState(item.getAcceptedDataTypes(), value); + if (state == null) { + // State could not be parsed + logger.warn("Can't persist item {} with invalid state '{}'.", itemName, value); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "State could not be parsed: " + value); + } + + Date dateTime = null; + if (time != null && time.length() != 0) { + dateTime = convertTime(time); + } + if (dateTime == null || dateTime.getTime() == 0) { + logger.warn("Error with persistence store to {}. Time badly formatted {}.", itemName, time); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "Time badly formatted."); + } + + if (!(service instanceof ModifiablePersistenceService)) { + logger.warn("Persistence service not modifiable '{}'.", serviceName); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, + "Persistence service not modifiable: " + serviceName); + } + + ModifiablePersistenceService mService = (ModifiablePersistenceService) service; + + mService.store(item, dateTime, state); + return Response.status(Status.OK).build(); } } diff --git a/bundles/io/org.eclipse.smarthome.io.rest/src/main/java/org/eclipse/smarthome/io/rest/JSONResponse.java b/bundles/io/org.eclipse.smarthome.io.rest/src/main/java/org/eclipse/smarthome/io/rest/JSONResponse.java index 9c9a4d23391..28284cbdd33 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest/src/main/java/org/eclipse/smarthome/io/rest/JSONResponse.java +++ b/bundles/io/org.eclipse.smarthome.io.rest/src/main/java/org/eclipse/smarthome/io/rest/JSONResponse.java @@ -13,6 +13,7 @@ import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.ext.Provider; +import org.eclipse.smarthome.core.library.types.DateTimeType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +36,8 @@ public class JSONResponse { // also dump stacktrace? private final static boolean WITH_STACKTRACE = false; - final static Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + final static Gson GSON = new GsonBuilder().setDateFormat(DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS) + .setPrettyPrinting().create(); /** * hide ctor a bit from public diff --git a/bundles/model/org.eclipse.smarthome.model.persistence.tests/src/test/java/org/eclipse/smarthome/model/persistence/tests/TestPersistenceService.java b/bundles/model/org.eclipse.smarthome.model.persistence.tests/src/test/java/org/eclipse/smarthome/model/persistence/tests/TestPersistenceService.java index 202c8579538..6fa82624123 100644 --- a/bundles/model/org.eclipse.smarthome.model.persistence.tests/src/test/java/org/eclipse/smarthome/model/persistence/tests/TestPersistenceService.java +++ b/bundles/model/org.eclipse.smarthome.model.persistence.tests/src/test/java/org/eclipse/smarthome/model/persistence/tests/TestPersistenceService.java @@ -10,12 +10,15 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.Locale; +import java.util.Set; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.library.types.DecimalType; import org.eclipse.smarthome.core.persistence.FilterCriteria; import org.eclipse.smarthome.core.persistence.FilterCriteria.Ordering; import org.eclipse.smarthome.core.persistence.HistoricItem; +import org.eclipse.smarthome.core.persistence.PersistenceItemInfo; import org.eclipse.smarthome.core.persistence.QueryablePersistenceService; import org.eclipse.smarthome.core.types.State; @@ -27,7 +30,7 @@ public class TestPersistenceService implements QueryablePersistenceService { @Override - public String getName() { + public String getId() { return "test"; } @@ -45,13 +48,16 @@ public Iterable query(FilterCriteria filter) { int startValue = 1950; int endValue = 2012; - if (filter.getBeginDate() != null) + if (filter.getBeginDate() != null) { startValue = filter.getBeginDate().getYear() + 1900; - if (filter.getEndDate() != null) + } + if (filter.getEndDate() != null) { endValue = filter.getEndDate().getYear() + 1900; + } - if (endValue <= startValue || startValue < 1950) + if (endValue <= startValue || startValue < 1950) { return Collections.emptyList(); + } ArrayList results = new ArrayList(endValue - startValue); for (int i = startValue; i <= endValue; i++) { @@ -79,4 +85,14 @@ public String getName() { return results; } + @Override + public Set getItemInfo() { + return null; + } + + @Override + public String getLabel(Locale locale) { + return "Test Label"; + } + } diff --git a/bundles/model/org.eclipse.smarthome.model.persistence/src/org/eclipse/smarthome/model/persistence/extensions/PersistenceExtensions.java b/bundles/model/org.eclipse.smarthome.model.persistence/src/org/eclipse/smarthome/model/persistence/extensions/PersistenceExtensions.java index b3870863d5a..1735b7c5a42 100644 --- a/bundles/model/org.eclipse.smarthome.model.persistence/src/org/eclipse/smarthome/model/persistence/extensions/PersistenceExtensions.java +++ b/bundles/model/org.eclipse.smarthome.model.persistence/src/org/eclipse/smarthome/model/persistence/extensions/PersistenceExtensions.java @@ -53,11 +53,11 @@ public PersistenceExtensions() { } public void addPersistenceService(PersistenceService service) { - services.put(service.getName(), service); + services.put(service.getId(), service); } public void removePersistenceService(PersistenceService service) { - services.remove(service.getName()); + services.remove(service.getId()); } /** @@ -718,6 +718,15 @@ private static boolean isDefaultServiceAvailable() { } } + /** + * Get the default persistence service id + * + * @return {@link String} default service id + */ + public static String getDefaultService() { + return defaultService; + } + @Override @SuppressWarnings("rawtypes") public void updated(Dictionary config) throws ConfigurationException { diff --git a/bundles/model/org.eclipse.smarthome.model.persistence/src/org/eclipse/smarthome/model/persistence/internal/PersistenceManager.java b/bundles/model/org.eclipse.smarthome.model.persistence/src/org/eclipse/smarthome/model/persistence/internal/PersistenceManager.java index 27aa5d29d7d..94dc376b0ae 100644 --- a/bundles/model/org.eclipse.smarthome.model.persistence/src/org/eclipse/smarthome/model/persistence/internal/PersistenceManager.java +++ b/bundles/model/org.eclipse.smarthome.model.persistence/src/org/eclipse/smarthome/model/persistence/internal/PersistenceManager.java @@ -138,15 +138,15 @@ public void unsetItemRegistry(ItemRegistry itemRegistry) { } public void addPersistenceService(PersistenceService persistenceService) { - logger.debug("Initializing {} persistence service.", persistenceService.getName()); - persistenceServices.put(persistenceService.getName(), persistenceService); - stopEventHandling(persistenceService.getName()); - startEventHandling(persistenceService.getName()); + logger.debug("Initializing {} persistence service.", persistenceService.getId()); + persistenceServices.put(persistenceService.getId(), persistenceService); + stopEventHandling(persistenceService.getId()); + startEventHandling(persistenceService.getId()); } public void removePersistenceService(PersistenceService persistenceService) { - stopEventHandling(persistenceService.getName()); - persistenceServices.remove(persistenceService.getName()); + stopEventHandling(persistenceService.getId()); + persistenceServices.remove(persistenceService.getId()); } @Override diff --git a/bundles/ui/org.eclipse.smarthome.ui/src/main/java/org/eclipse/smarthome/ui/internal/chart/DefaultChartProvider.java b/bundles/ui/org.eclipse.smarthome.ui/src/main/java/org/eclipse/smarthome/ui/internal/chart/DefaultChartProvider.java index 85788219cbb..1671cd80968 100644 --- a/bundles/ui/org.eclipse.smarthome.ui/src/main/java/org/eclipse/smarthome/ui/internal/chart/DefaultChartProvider.java +++ b/bundles/ui/org.eclipse.smarthome.ui/src/main/java/org/eclipse/smarthome/ui/internal/chart/DefaultChartProvider.java @@ -81,12 +81,13 @@ public void unsetItemUIRegistry(ItemUIRegistry itemUIRegistry) { } public void addPersistenceService(PersistenceService service) { - if (service instanceof QueryablePersistenceService) - persistenceServices.put(service.getName(), (QueryablePersistenceService) service); + if (service instanceof QueryablePersistenceService) { + persistenceServices.put(service.getId(), (QueryablePersistenceService) service); + } } public void removePersistenceService(PersistenceService service) { - persistenceServices.remove(service.getName()); + persistenceServices.remove(service.getId()); } static public Map getPersistenceServices() { @@ -168,8 +169,9 @@ public BufferedImage createChart(String service, String theme, Date startTime, D String[] itemNames = items.split(","); for (String itemName : itemNames) { Item item = itemUIRegistry.getItem(itemName); - if (addItem(chart, persistenceService, startTime, endTime, item, seriesCounter)) + if (addItem(chart, persistenceService, startTime, endTime, item, seriesCounter)) { seriesCounter++; + } } } @@ -181,8 +183,9 @@ public BufferedImage createChart(String service, String theme, Date startTime, D if (item instanceof GroupItem) { GroupItem groupItem = (GroupItem) item; for (Item member : groupItem.getMembers()) { - if (addItem(chart, persistenceService, startTime, endTime, member, seriesCounter)) + if (addItem(chart, persistenceService, startTime, endTime, member, seriesCounter)) { seriesCounter++; + } } } else { throw new ItemNotFoundException("Item '" + item.getName() + "' defined in groups is not a group."); @@ -284,8 +287,8 @@ boolean addItem(Chart chart, QueryablePersistenceService service, Date timeBegin // If the start value is below the median, then count legend position down // Otherwise count up. // We use this to decide whether to put the legend in the top or bottom corner. - if (yData.iterator().next().floatValue() > ((series.getyMax().floatValue() - series.getyMin().floatValue()) / 2 + series - .getyMin().floatValue())) { + if (yData.iterator().next().floatValue() > ((series.getyMax().floatValue() - series.getyMin().floatValue()) / 2 + + series.getyMin().floatValue())) { legendPosition++; } else { legendPosition--;