Codebase as of c53e4aed26 as an initial commit for the shrunk repo
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.persistence.mapdb-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-persistence-mapdb" description="MapDB Persistence" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.persistence.mapdb/${project.version}</bundle>
|
||||
</feature>
|
||||
|
||||
</features>
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.persistence.mapdb.internal;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.persistence.HistoricItem;
|
||||
import org.openhab.core.persistence.PersistenceItemInfo;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* This is a Java bean used to persist item states with timestamps in the database.
|
||||
*
|
||||
* @author Jens Viebig - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MapDbItem implements HistoricItem, PersistenceItemInfo {
|
||||
|
||||
private String name = "";
|
||||
private State state = UnDefType.NULL;
|
||||
private Date timestamp = new Date(0);
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(State state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZonedDateTime getTimestamp() {
|
||||
return ZonedDateTime.ofInstant(timestamp.toInstant(), ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
public void setTimestamp(Date timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return DateFormat.getDateTimeInstance().format(timestamp) + ": " + name + " -> " + state.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Integer getCount() {
|
||||
return Integer.valueOf(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Date getEarliest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Date getLatest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return name != null && state != null && timestamp != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.persistence.mapdb.internal;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.mapdb.DB;
|
||||
import org.mapdb.DBMaker;
|
||||
import org.openhab.core.OpenHAB;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.persistence.FilterCriteria;
|
||||
import org.openhab.core.persistence.HistoricItem;
|
||||
import org.openhab.core.persistence.PersistenceItemInfo;
|
||||
import org.openhab.core.persistence.PersistenceService;
|
||||
import org.openhab.core.persistence.QueryablePersistenceService;
|
||||
import org.openhab.core.persistence.strategy.PersistenceStrategy;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* This is the implementation of the MapDB {@link PersistenceService}. To learn more about MapDB please visit their
|
||||
* <a href="http://www.mapdb.org/">website</a>.
|
||||
*
|
||||
* @author Jens Viebig - Initial contribution
|
||||
* @author Martin Kühl - Port to 3.x
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { PersistenceService.class, QueryablePersistenceService.class })
|
||||
public class MapDbPersistenceService implements QueryablePersistenceService {
|
||||
|
||||
private static final String SERVICE_ID = "mapdb";
|
||||
private static final String SERVICE_LABEL = "MapDB";
|
||||
private static final String DB_FOLDER_NAME = OpenHAB.getUserDataFolder() + File.separator + "persistence"
|
||||
+ File.separator + "mapdb";
|
||||
private static final String DB_FILE_NAME = "storage.mapdb";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MapDbPersistenceService.class);
|
||||
|
||||
private final ExecutorService threadPool = ThreadPoolManager.getPool(getClass().getSimpleName());
|
||||
|
||||
/** holds the local instance of the MapDB database */
|
||||
|
||||
private @NonNullByDefault({}) DB db;
|
||||
private @NonNullByDefault({}) Map<String, String> map;
|
||||
|
||||
private transient Gson mapper = new GsonBuilder().registerTypeHierarchyAdapter(State.class, new StateTypeAdapter())
|
||||
.create();
|
||||
|
||||
@Activate
|
||||
public void activate() {
|
||||
logger.debug("MapDB persistence service is being activated");
|
||||
|
||||
File folder = new File(DB_FOLDER_NAME);
|
||||
if (!folder.exists()) {
|
||||
if (!folder.mkdirs()) {
|
||||
logger.warn("Failed to create one or more directories in the path '{}'", DB_FOLDER_NAME);
|
||||
logger.warn("MapDB persistence service activation has failed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
File dbFile = new File(DB_FOLDER_NAME, DB_FILE_NAME);
|
||||
db = DBMaker.newFileDB(dbFile).closeOnJvmShutdown().make();
|
||||
map = db.createTreeMap("itemStore").makeOrGet();
|
||||
logger.debug("MapDB persistence service is now activated");
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
logger.debug("MapDB persistence service deactivated");
|
||||
if (db != null) {
|
||||
db.close();
|
||||
}
|
||||
threadPool.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return SERVICE_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel(@Nullable Locale locale) {
|
||||
return SERVICE_LABEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PersistenceItemInfo> getItemInfo() {
|
||||
return map.values().stream().map(this::deserialize).flatMap(MapDbPersistenceService::streamOptional)
|
||||
.collect(Collectors.<PersistenceItemInfo> toUnmodifiableSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(Item item) {
|
||||
store(item, item.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(Item item, @Nullable String alias) {
|
||||
if (item.getState() instanceof UnDefType) {
|
||||
return;
|
||||
}
|
||||
|
||||
// PersistenceManager passes SimpleItemConfiguration.alias which can be null
|
||||
String localAlias = alias == null ? item.getName() : alias;
|
||||
logger.debug("store called for {}", localAlias);
|
||||
|
||||
State state = item.getState();
|
||||
MapDbItem mItem = new MapDbItem();
|
||||
mItem.setName(localAlias);
|
||||
mItem.setState(state);
|
||||
mItem.setTimestamp(new Date());
|
||||
String json = serialize(mItem);
|
||||
map.put(localAlias, json);
|
||||
commit();
|
||||
logger.debug("Stored '{}' with state '{}' in MapDB database", localAlias, state.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<HistoricItem> query(FilterCriteria filter) {
|
||||
String json = map.get(filter.getItemName());
|
||||
if (json == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Optional<MapDbItem> item = deserialize(json);
|
||||
if (!item.isPresent()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.singletonList(item.get());
|
||||
}
|
||||
|
||||
private String serialize(MapDbItem item) {
|
||||
return mapper.toJson(item);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private Optional<MapDbItem> deserialize(String json) {
|
||||
MapDbItem item = mapper.<MapDbItem> fromJson(json, MapDbItem.class);
|
||||
if (item == null || !item.isValid()) {
|
||||
logger.warn("Deserialized invalid item: {}", item);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(item);
|
||||
}
|
||||
|
||||
private void commit() {
|
||||
threadPool.submit(() -> db.commit());
|
||||
}
|
||||
|
||||
private static <T> Stream<T> streamOptional(Optional<T> opt) {
|
||||
if (!opt.isPresent()) {
|
||||
return Stream.empty();
|
||||
}
|
||||
return Stream.of(opt.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PersistenceStrategy> getDefaultStrategies() {
|
||||
return List.of(PersistenceStrategy.Globals.RESTORE, PersistenceStrategy.Globals.CHANGE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.persistence.mapdb.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.TypeParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* A GSON TypeAdapter for openHAB State values.
|
||||
*
|
||||
* @author Martin Kühl - Initial contribution
|
||||
*/
|
||||
public class StateTypeAdapter extends TypeAdapter<State> {
|
||||
private static final String TYPE_SEPARATOR = "@@@";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(StateTypeAdapter.class);
|
||||
|
||||
@Override
|
||||
public State read(JsonReader reader) throws IOException {
|
||||
if (reader.peek() == JsonToken.NULL) {
|
||||
reader.nextNull();
|
||||
return null;
|
||||
}
|
||||
String value = reader.nextString();
|
||||
String[] parts = value.split(TYPE_SEPARATOR);
|
||||
String valueTypeName = parts[0];
|
||||
String valueAsString = parts[1];
|
||||
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends State> valueType = (Class<? extends State>) Class.forName(valueTypeName);
|
||||
List<Class<? extends State>> types = Collections.singletonList(valueType);
|
||||
return TypeParser.parseState(types, valueAsString);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Couldn't deserialize state '{}': {}", value, e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter writer, State state) throws IOException {
|
||||
if (state == null) {
|
||||
writer.nullValue();
|
||||
return;
|
||||
}
|
||||
String value = state.getClass().getName() + TYPE_SEPARATOR + state.toFullString();
|
||||
writer.value(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.persistence.mapdb;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.persistence.mapdb.internal.StateTypeAdapter;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Martin Kühl - Initial contribution
|
||||
*/
|
||||
public class StateTypeAdapterTest {
|
||||
Gson mapper = new GsonBuilder().registerTypeHierarchyAdapter(State.class, new StateTypeAdapter()).create();
|
||||
|
||||
@Test
|
||||
public void readWriteRoundtripShouldRecreateTheWrittenState() {
|
||||
assertThat(roundtrip(OnOffType.ON), is(equalTo(OnOffType.ON)));
|
||||
assertThat(roundtrip(PercentType.HUNDRED), is(equalTo(PercentType.HUNDRED)));
|
||||
assertThat(roundtrip(HSBType.GREEN), is(equalTo(HSBType.GREEN)));
|
||||
assertThat(roundtrip(StringType.valueOf("test")), is(equalTo(StringType.valueOf("test"))));
|
||||
}
|
||||
|
||||
private State roundtrip(State state) {
|
||||
return mapper.fromJson(mapper.toJson(state), State.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user