package io.github.infolis.datastore; import io.github.infolis.model.BaseModel; import io.github.infolis.util.SerializationUtils; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import javax.ws.rs.BadRequestException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; public class LocalClient extends AbstractClient { private static final Map<UUID, Map<String, String>> _jsonDB = new HashMap<>(); private static final Object lock = new Object(); private final Logger log = LoggerFactory.getLogger(LocalClient.class); private final AtomicInteger counter = new AtomicInteger(); public final Multimap<String, String> endpointForId = HashMultimap.<String,String> create(); private final UUID _storeId; public LocalClient(UUID uuid) { this._storeId = uuid; newJsonDb(); } public void newJsonDb() { synchronized(lock) { _jsonDB.put(_storeId, new HashMap<String, String>()); } } public Map<String, String> jsonDB() { synchronized(lock) { return _jsonDB.get(_storeId); } } private <T extends BaseModel> void store(Class<T> clazz, T thing) { try { jsonDB().put(thing.getUri(), SerializationUtils.jacksonMapper.writeValueAsString(thing)); String endpoint = CentralClient.getEndpointForClass(clazz); endpointForId.put(endpoint, thing.getUri()); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } @Override public <T extends BaseModel> T get(Class<T> clazz, String id) throws BadRequestException { try { String json = jsonDB().get(id); if (null == json) { return null; } T readValue = SerializationUtils.jacksonMapper.readValue(json, clazz); readValue.setUri(id); return readValue; } catch (IOException e) { throw new RuntimeException(e); } } @Override public <T extends BaseModel> void put(Class<T> clazz, T thing) throws BadRequestException { log.debug("PUT {}", thing.getUri()); store(clazz, thing); } @Override public <T extends BaseModel> void put(Class<T> clazz, T thing, String uri) throws BadRequestException { String id = uri; log.debug("PUT {}", id); thing.setUri(id); store(clazz, thing); } @Override public <T extends BaseModel> void post(Class<T> clazz, T thing) { String id = String.format("%s_%s", CentralClient.getEndpointForClass(clazz), counter.getAndIncrement()); log.debug("POST {}", id); thing.setUri(id); store(clazz, thing); } @SuppressWarnings("unchecked") @Override public <T extends BaseModel> List<T> search(Class<T> clazz, Multimap<String, String> query) { String endpoint = CentralClient.getEndpointForClass(clazz); List<String> ids = new ArrayList<>(); for (String id : endpointForId.get(endpoint)) { String json = jsonDB().get(id); // log.debug("{} -> {}", json); Map<String, Object> obj = null; try { obj = SerializationUtils.jacksonMapper.readValue(json, Map.class); } catch (Exception e) { log.debug("{}", jsonDB()); log.debug("{}", id); log.debug("{}", jsonDB().get(id)); throw new RuntimeException(e); } // TODO make this a boolean AND instead of an OR? for (String searchKey : query.keySet()) { for (String searchValue : query.get(searchKey)) { for (Object key : obj.keySet()) { if (!key.equals(searchKey)) { continue; } Object value = obj.get(key); if (Collection.class.isAssignableFrom(value.getClass())) { for (String subValue : ((Collection<String>) value)) { if (subValue.equals(searchValue)) { ids.add(id); break; } } } else if (value.toString().equals(searchValue)) { ids.add(id); } } } } } return get(clazz, ids); } @Override public void clear() { jsonDB().clear(); } @Override public void dump(Path directory, String basename) { /* * { "file": { "1234": {"filename":"foo"} }, "execution": { "5678": * {"algorithm":"bar"}, } } */ Map<String, List<String>> dumpByEndpoint = new HashMap<String, List<String>>(); for (String endpoint : endpointForId.keySet()) { Collection<String> uris = endpointForId.get(endpoint); if (uris.isEmpty()) { continue; } List<String> entries = new ArrayList<>(); dumpByEndpoint.put(endpoint, entries); for (String uri : uris) { StringBuilder sb = new StringBuilder(); sb.append(" "); sb.append('"'); sb.append(uri); sb.append('"'); sb.append(": "); sb.append(jsonDB().get(uri)); entries.add(sb.toString()); } } List<String> entries = new ArrayList<>(); for (Entry<String, List<String>> entry : dumpByEndpoint.entrySet()) { StringBuilder sb = new StringBuilder(); sb.append(" "); sb.append('"'); sb.append(entry.getKey()); sb.append('"'); sb.append(": "); sb.append("{\n "); sb.append(StringUtils.join(entry.getValue(), ",\n ")); sb.append("\n }"); entries.add(sb.toString()); } StringBuilder sb = new StringBuilder(); sb.append("{\n"); sb.append(StringUtils.join(entries, ",\n")); sb.append("\n}"); try { Files.createDirectories(directory); Path outputFile = directory.resolve(basename + ".json"); OutputStream os = Files.newOutputStream(outputFile); IOUtils.write(sb.toString(), os); os.close(); log.debug("Dumped to {}", outputFile); } catch (IOException e) { throw new RuntimeException(e); } } }