]> git.basschouten.com Git - openhab-addons.git/blob
5e09943cfb8f2b1f9469c1ca581fb17081c727b6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.persistence.mapdb.internal;
14
15 import java.io.File;
16 import java.util.Collections;
17 import java.util.Date;
18 import java.util.List;
19 import java.util.Locale;
20 import java.util.Map;
21 import java.util.Optional;
22 import java.util.Set;
23 import java.util.concurrent.ExecutorService;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.mapdb.DB;
30 import org.mapdb.DBMaker;
31 import org.openhab.core.OpenHAB;
32 import org.openhab.core.common.ThreadPoolManager;
33 import org.openhab.core.items.Item;
34 import org.openhab.core.persistence.FilterCriteria;
35 import org.openhab.core.persistence.HistoricItem;
36 import org.openhab.core.persistence.PersistenceItemInfo;
37 import org.openhab.core.persistence.PersistenceService;
38 import org.openhab.core.persistence.QueryablePersistenceService;
39 import org.openhab.core.persistence.strategy.PersistenceStrategy;
40 import org.openhab.core.types.State;
41 import org.openhab.core.types.UnDefType;
42 import org.osgi.service.component.annotations.Activate;
43 import org.osgi.service.component.annotations.Component;
44 import org.osgi.service.component.annotations.Deactivate;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import com.google.gson.Gson;
49 import com.google.gson.GsonBuilder;
50
51 /**
52  * This is the implementation of the MapDB {@link PersistenceService}. To learn more about MapDB please visit their
53  * <a href="http://www.mapdb.org/">website</a>.
54  *
55  * @author Jens Viebig - Initial contribution
56  * @author Martin Kühl - Port to 3.x
57  */
58 @NonNullByDefault
59 @Component(service = { PersistenceService.class, QueryablePersistenceService.class })
60 public class MapDbPersistenceService implements QueryablePersistenceService {
61
62     private static final String SERVICE_ID = "mapdb";
63     private static final String SERVICE_LABEL = "MapDB";
64     private static final String DB_FOLDER_NAME = OpenHAB.getUserDataFolder() + File.separator + "persistence"
65             + File.separator + "mapdb";
66     private static final String DB_FILE_NAME = "storage.mapdb";
67
68     private final Logger logger = LoggerFactory.getLogger(MapDbPersistenceService.class);
69
70     private final ExecutorService threadPool = ThreadPoolManager.getPool(getClass().getSimpleName());
71
72     /** holds the local instance of the MapDB database */
73
74     private @NonNullByDefault({}) DB db;
75     private @NonNullByDefault({}) Map<String, String> map;
76
77     private transient Gson mapper = new GsonBuilder().registerTypeHierarchyAdapter(State.class, new StateTypeAdapter())
78             .create();
79
80     @Activate
81     public void activate() {
82         logger.debug("MapDB persistence service is being activated");
83
84         File folder = new File(DB_FOLDER_NAME);
85         if (!folder.exists()) {
86             if (!folder.mkdirs()) {
87                 logger.warn("Failed to create one or more directories in the path '{}'", DB_FOLDER_NAME);
88                 logger.warn("MapDB persistence service activation has failed.");
89                 return;
90             }
91         }
92
93         File dbFile = new File(DB_FOLDER_NAME, DB_FILE_NAME);
94         db = DBMaker.newFileDB(dbFile).closeOnJvmShutdown().make();
95         map = db.createTreeMap("itemStore").makeOrGet();
96         logger.debug("MapDB persistence service is now activated");
97     }
98
99     @Deactivate
100     public void deactivate() {
101         logger.debug("MapDB persistence service deactivated");
102         if (db != null) {
103             db.close();
104         }
105     }
106
107     @Override
108     public String getId() {
109         return SERVICE_ID;
110     }
111
112     @Override
113     public String getLabel(@Nullable Locale locale) {
114         return SERVICE_LABEL;
115     }
116
117     @Override
118     public Set<PersistenceItemInfo> getItemInfo() {
119         return map.values().stream().map(this::deserialize).flatMap(MapDbPersistenceService::streamOptional)
120                 .collect(Collectors.<PersistenceItemInfo> toUnmodifiableSet());
121     }
122
123     @Override
124     public void store(Item item) {
125         store(item, item.getName());
126     }
127
128     @Override
129     public void store(Item item, @Nullable String alias) {
130         if (item.getState() instanceof UnDefType) {
131             return;
132         }
133
134         // PersistenceManager passes SimpleItemConfiguration.alias which can be null
135         String localAlias = alias == null ? item.getName() : alias;
136         logger.debug("store called for {}", localAlias);
137
138         State state = item.getState();
139         MapDbItem mItem = new MapDbItem();
140         mItem.setName(localAlias);
141         mItem.setState(state);
142         mItem.setTimestamp(new Date());
143         String json = serialize(mItem);
144         map.put(localAlias, json);
145         commit();
146         logger.debug("Stored '{}' with state '{}' in MapDB database", localAlias, state.toString());
147     }
148
149     @Override
150     public Iterable<HistoricItem> query(FilterCriteria filter) {
151         String json = map.get(filter.getItemName());
152         if (json == null) {
153             return Collections.emptyList();
154         }
155         Optional<MapDbItem> item = deserialize(json);
156         if (!item.isPresent()) {
157             return Collections.emptyList();
158         }
159         return Collections.singletonList(item.get());
160     }
161
162     private String serialize(MapDbItem item) {
163         return mapper.toJson(item);
164     }
165
166     @SuppressWarnings("null")
167     private Optional<MapDbItem> deserialize(String json) {
168         MapDbItem item = mapper.<MapDbItem> fromJson(json, MapDbItem.class);
169         if (item == null || !item.isValid()) {
170             logger.warn("Deserialized invalid item: {}", item);
171             return Optional.empty();
172         }
173         return Optional.of(item);
174     }
175
176     private void commit() {
177         threadPool.submit(() -> db.commit());
178     }
179
180     private static <T> Stream<T> streamOptional(Optional<T> opt) {
181         if (!opt.isPresent()) {
182             return Stream.empty();
183         }
184         return Stream.of(opt.get());
185     }
186
187     @Override
188     public List<PersistenceStrategy> getDefaultStrategies() {
189         return List.of(PersistenceStrategy.Globals.RESTORE, PersistenceStrategy.Globals.CHANGE);
190     }
191 }