/* * Copyright 2013 David Tinker * * Licensed 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 io.qdb.server.repo; import io.qdb.kvstore.KeyValueStore; import io.qdb.server.model.*; import io.qdb.server.model.Queue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import java.io.*; import java.util.*; import java.util.concurrent.ConcurrentMap; /** * Uses a {@link KeyValueStore} to keep our meta data in memory with periodic snapshots written to disk. */ @Singleton public class RepositoryImpl implements Repository { @SuppressWarnings("UnusedDeclaration") private static final Logger log = LoggerFactory.getLogger(RepositoryImpl.class); private final KeyValueStore<String, ModelObject> store; private final ConcurrentMap<String, User> users; private final ConcurrentMap<String, Database> databases; private final ConcurrentMap<String, Queue> queues; private final ConcurrentMap<String, Output> outputs; private final ConcurrentMap<String, Input> inputs; @Inject public RepositoryImpl(KeyValueStore<String, ModelObject> store, @Named("initialAdminPassword") String initialAdminPassword) throws IOException { this.store = store; users = store.getMap("users", User.class); databases = store.getMap("databases", Database.class); queues = store.getMap("queues", Queue.class); outputs = store.getMap("outputs", Output.class); inputs = store.getMap("inputs", Input.class); if (findDatabase("default") == null) updateDatabase(new Database("default")); if (findUser("admin") == null) { User admin = new User(); admin.setId("admin"); admin.setPassword(initialAdminPassword); admin.setAdmin(true); updateUser(admin); log.info("Created initial admin user"); store.saveSnapshot(); } } @Override public void close() throws IOException { store.close(); } @Override public User findUser(String id) throws IOException { return users.get(id); } @SuppressWarnings("unchecked") @Override public List<User> findUsers(int offset, int limit) throws IOException { return find(users, offset, limit); } @Override public int countUsers() throws IOException { return users.size(); } @Override public void updateUser(User user) throws IOException { users.put(user.getId(), user); } @Override public synchronized void deleteUser(String id) throws IOException { users.remove(id); } @Override public Database findDatabase(String id) throws IOException { return databases.get(id); } @SuppressWarnings("unchecked") @Override public List<Database> findDatabasesVisibleTo(User user, int offset, int limit) throws IOException { if (user.isAdmin()) { return find(databases, offset, limit); } else { ArrayList<Database> ans = new ArrayList<Database>(); String[] databases = user.getDatabases(); if (databases != null) { for (int i = offset, n = Math.min(offset + limit, databases.length); i < n; i++) { Database db = findDatabase(databases[i]); if (db != null) ans.add(db); } } return ans; } } @Override public int countDatabasesVisibleTo(User user) throws IOException { if (user.isAdmin()) { return databases.size(); } else { String[] databases = user.getDatabases(); return databases == null ? 0 : databases.length; } } @Override public void updateDatabase(Database db) throws IOException { databases.put(db.getId(), db); } @Override public synchronized void deleteDatabase(String id) throws IOException { Database db = databases.get(id); if (db == null) return; Map<String, String> queues = db.getQueues(); if (queues != null) { for (String qid : queues.values()) deleteQueueImpl(qid, true); } databases.remove(id); } @Override public Queue findQueue(String id) throws IOException { return queues.get(id); } @SuppressWarnings("unchecked") @Override public List<Queue> findQueues(int offset, int limit) throws IOException { return find(queues, offset, limit); } @Override public int countQueues() throws IOException { return queues.size(); } @Override public void updateQueue(Queue queue) throws IOException { queues.put(queue.getId(), queue); } @Override public synchronized void deleteQueue(String id) throws IOException { deleteQueueImpl(id, false); } private void deleteQueueImpl(String id, boolean ignoreDatabase) { Queue q = queues.get(id); if (q == null) return; if (!ignoreDatabase) { Database db = databases.get(q.getDatabase()); if (db != null) { String dq = db.getQueueForQid(id); if (dq != null) { db = db.deepCopy(); db.getQueues().remove(dq); databases.put(db.getId(), db); } } } Map<String, String> outputs = q.getOutputs(); if (outputs != null) { for (Map.Entry<String, String> e : outputs.entrySet()) outputs.remove(e.getValue()); } queues.remove(id); } @Override public Output findOutput(String id) throws IOException { return outputs.get(id); } @SuppressWarnings("unchecked") @Override public List<Output> findOutputs(int offset, int limit) throws IOException { return find(outputs, offset, limit); } @Override public int countOutputs() throws IOException { return outputs.size(); } @Override public void updateOutput(Output output) throws IOException { outputs.put(output.getId(), output); } @Override public synchronized void deleteOutput(String id) throws IOException { Output o = outputs.get(id); if (o == null) return; Queue q = queues.get(o.getQueue()); if (q != null) { String qo = q.getOutputForOid(id); if (qo != null) { q = q.deepCopy(); q.getOutputs().remove(qo); queues.put(q.getId(), q); } } outputs.remove(id); } @Override public Input findInput(String id) throws IOException { return inputs.get(id); } @SuppressWarnings("unchecked") @Override public List<Input> findInputs(int offset, int limit) throws IOException { return find(inputs, offset, limit); } @Override public int countInputs() throws IOException { return inputs.size(); } @Override public void updateInput(Input input) throws IOException { inputs.put(input.getId(), input); } @Override public synchronized void deleteInput(String id) throws IOException { Input o = inputs.get(id); if (o == null) return; Queue q = queues.get(o.getQueue()); if (q != null) { String qi = q.getInputForInputId(id); if (qi != null) { q = q.deepCopy(); q.getInputs().remove(qi); queues.put(q.getId(), q); } } inputs.remove(id); } @SuppressWarnings("unchecked") private List find(ConcurrentMap map, int offset, int limit) { List list = new ArrayList(map.values()); Collections.sort(list); if (limit < 0) limit = Integer.MAX_VALUE - offset; int n = list.size(); if (offset == 0 && limit >= n) return list; return offset >= n ? Collections.EMPTY_LIST : list.subList(offset, Math.min(limit, n)); } }