/* * Copyright 2011 Future Systems * * 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 org.krakenapps.dom.api.impl; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.felix.ipojo.annotations.Component; import org.apache.felix.ipojo.annotations.Provides; import org.apache.felix.ipojo.annotations.Requires; import org.krakenapps.api.PrimitiveParseCallback; import org.krakenapps.confdb.Config; import org.krakenapps.confdb.ConfigDatabase; import org.krakenapps.confdb.ConfigIterator; import org.krakenapps.confdb.ConfigParser; import org.krakenapps.confdb.ConfigService; import org.krakenapps.confdb.ObjectBuilder; import org.krakenapps.confdb.Predicate; import org.krakenapps.confdb.Predicates; import org.krakenapps.dom.api.ConfigManager; import org.krakenapps.dom.api.DOMException; import org.krakenapps.dom.api.DefaultEntityEventProvider; import org.krakenapps.dom.api.EntityState; import org.krakenapps.dom.api.Transaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component(name = "dom-config-manager") @Provides public class ConfigManagerImpl implements ConfigManager { private static final String COMMITER = "kraken-dom"; private final Logger logger = LoggerFactory.getLogger(ConfigManagerImpl.class.getName()); @Requires private ConfigService confsvc; private ConcurrentMap<Class<?>, ConfigParser> parsers = new ConcurrentHashMap<Class<?>, ConfigParser>(); public void setConfigService(ConfigService conf) { this.confsvc = conf; } private PrimitiveParseCallback getCallback(String domain) { return new ParseCallback(domain); } @Override public void setParser(Class<?> cls, ConfigParser parser) { parsers.put(cls, parser); } @Override public ConfigDatabase findDatabase(String domain) { ConfigDatabase db = confsvc.getDatabase("kraken-dom-" + domain); return db; } @Override public ConfigDatabase getDatabase(String domain) { ConfigDatabase db = findDatabase(domain); if (db == null) { Map<String, Object> m = new HashMap<String, Object>(); m.put("domain", domain); throw new DOMException("organization-not-found", m); } return db; } @Override public <T> int count(String domain, Class<T> cls, Predicate pred) { ConfigIterator it = null; try { it = getDatabase(domain).ensureCollection(cls).find(pred); return it.count(); } finally { if (it != null) it.close(); } } @Override public List<Config> matches(String domain, Class<?> cls, Predicate pred, int offset, int limit) { ConfigIterator it = null; try { it = getDatabase(domain).ensureCollection(cls).find(pred); return it.getConfigs(offset, limit); } finally { if (it != null) it.close(); } } @Override public <T> Collection<T> all(String domain, Class<T> cls) { return all(domain, cls, null); } @Override public <T> Collection<T> all(String domain, Class<T> cls, Predicate pred) { ConfigIterator it = null; try { it = getDatabase(domain).ensureCollection(cls).find(pred); it.setParser(parsers.get(cls)); return it.getDocuments(cls, getCallback(domain)); } finally { if (it != null) it.close(); } } @Override public <T> Collection<T> all(String domain, Class<T> cls, Predicate pred, int offset, int limit) { ConfigIterator it = null; try { it = getDatabase(domain).ensureCollection(cls).find(pred); it.setParser(parsers.get(cls)); return it.getDocuments(cls, getCallback(domain), offset, limit); } finally { if (it != null) it.close(); } } @Override public <T> T find(String domain, Class<T> cls, Predicate pred) { Config c = getDatabase(domain).ensureCollection(cls).findOne(pred); if (c == null) return null; return c.getDocument(cls, getCallback(domain)); } @Override public <T> Collection<T> findObjects(String domain, Class<?> cls, ObjectBuilder<T> builder) { return findObjects(domain, cls, builder, null, 0, Integer.MAX_VALUE); } @Override public <T> Collection<T> findObjects(String domain, Class<?> cls, ObjectBuilder<T> builder, Predicate pred, int offset, int limit) { ConfigIterator it = null; try { it = getDatabase(domain).ensureCollection(cls).find(pred); return it.getObjects(builder, offset, limit); } finally { if (it != null) it.close(); } } @Override public <T> T get(String domain, Class<T> cls, Predicate pred, String notFoundMessage) { T t = find(domain, cls, pred); if (t == null) throw new DOMException(notFoundMessage); return t; } @Override public <T> void adds(String domain, Class<T> cls, List<Predicate> preds, List<T> docs, String alreadyExistMessage, DefaultEntityEventProvider<T> provider) { adds(domain, cls, preds, docs, alreadyExistMessage, provider, null); } @Override public <T> void adds(String domain, Class<T> cls, List<Predicate> preds, List<T> docs, String alreadyExistMessage, DefaultEntityEventProvider<T> provider, Object state) { if (docs.isEmpty()) return; ConfigDatabase db = getDatabase(domain); Transaction xact = new Transaction(domain, db); xact.addEventProvider(cls, provider); try { adds(xact, cls, preds, docs, alreadyExistMessage, state); xact.commit(COMMITER, "added " + docs.size() + " " + cls.getSimpleName() + "(s)"); } catch (Throwable t) { xact.rollback(); if (t instanceof DOMException) { throw (DOMException) t; } throw new RuntimeException(t); } } @Override public <T> void adds(Transaction xact, Class<T> cls, List<Predicate> preds, List<T> docs, String alreadyExistMessage) { adds(xact, cls, preds, docs, alreadyExistMessage, null); } @Override public <T> void adds(Transaction xact, Class<T> cls, List<Predicate> preds, List<T> docs, String alreadyExistMessage, Object state) { if (docs.isEmpty()) return; Collection<EntityState> entities = new ArrayList<EntityState>(); for (T doc : docs) { EntityState es = new EntityState(doc, state); entities.add(es); } xact.checkAddability(cls, entities); ConfigDatabase db = xact.getConfigDatabase(); Predicate pred = Predicates.or(preds.toArray(new Predicate[0])); Config dup = db.findOne(cls, pred); if (dup != null) { logger.error("kraken dom: already exists doc [{}]", dup.getDocument()); throw new DOMException(alreadyExistMessage); } Iterator<T> docIterator = docs.iterator(); while (docIterator.hasNext()) { T doc = docIterator.next(); if (logger.isDebugEnabled()) logger.debug("kraken dom: adding doc [{}]", doc); xact.add(doc, state); } } @Override public <T> void add(String domain, Class<T> cls, Predicate pred, T doc, String alreadyExistMessage, DefaultEntityEventProvider<T> provider) { add(domain, cls, pred, doc, alreadyExistMessage, provider, null); } @Override public <T> void add(String domain, Class<T> cls, Predicate pred, T doc, String alreadyExistMessage, DefaultEntityEventProvider<T> provider, Object state) { ConfigDatabase db = getDatabase(domain); Transaction xact = new Transaction(domain, db); xact.addEventProvider(cls, provider); try { add(xact, cls, pred, doc, alreadyExistMessage, state); xact.commit(COMMITER, "added 1 " + cls.getSimpleName()); } catch (Throwable e) { xact.rollback(); if (e instanceof DOMException) throw (DOMException) e; throw new RuntimeException(e); } } @Override public <T> void add(Transaction xact, Class<T> cls, Predicate pred, T doc, String alreadyExistMessage) { add(xact, cls, pred, doc, alreadyExistMessage, null); } @Override public <T> void add(Transaction xact, Class<T> cls, Predicate pred, T doc, String alreadyExistMessage, Object state) { ConfigDatabase db = xact.getConfigDatabase(); if (db.findOne(cls, pred) != null) throw new DOMException(alreadyExistMessage); xact.checkAddability(cls, new EntityState(doc, state)); xact.add(doc, state); } @Override public <T> void updateForGuids(Transaction xact, Class<T> cls, List<String> guids, List<T> docs, String notFoundMessage) { updateForGuids(xact, cls, guids, docs, notFoundMessage, null); } @Override public <T> void updateForGuids(String domain, Class<T> cls, List<String> guids, List<T> docs, String notFoundMessage, DefaultEntityEventProvider<T> provider) { updateForGuids(domain, cls, guids, docs, notFoundMessage, provider, null); } @Override public <T> void updateForGuids(String domain, Class<T> cls, List<String> guids, List<T> docs, String notFoundMessage, DefaultEntityEventProvider<T> provider, Object state) { if (docs.isEmpty()) return; ConfigDatabase db = getDatabase(domain); Transaction xact = new Transaction(domain, db); xact.addEventProvider(cls, provider); try { updateForGuids(xact, cls, guids, docs, notFoundMessage, state); xact.commit(COMMITER, "updated " + docs.size() + " " + cls.getSimpleName() + "(s)"); } catch (Throwable e) { xact.rollback(); if (e instanceof DOMException) throw (DOMException) e; throw new RuntimeException(e); } } @Override public <T> void updateForGuids(Transaction xact, Class<T> cls, List<String> guids, List<T> docs, String notFoundMessage, Object state) { if (docs.isEmpty()) return; if (guids.size() != docs.size()) throw new IllegalArgumentException("preds and docs must has equal size"); ConfigDatabase db = xact.getConfigDatabase(); ConfigIterator confIt = null; try { confIt = db.find(cls, in(guids)); Iterator<T> docIterator = docs.iterator(); Map<String, T> docMap = new HashMap<String, T>(); for (String guid : guids) { T t = docIterator.next(); docMap.put(guid, t); } while (confIt.hasNext()) { Config c = confIt.next(); @SuppressWarnings("unchecked") Map<String, Object> config = (Map<String, Object>) c.getDocument(); String configGuid = (String) config.get("guid"); if (configGuid == null) throw new DOMException(notFoundMessage); T doc = docMap.get(configGuid); if (doc == null) throw new DOMException(notFoundMessage); xact.update(c, doc, state); } } finally { if (confIt != null) { confIt.close(); } } } private Predicate in(List<String> guids) { Predicate[] preds = new Predicate[guids.size()]; int i = 0; for (String guid : guids) preds[i++] = Predicates.field("guid", guid); return Predicates.or(preds); } @Override public <T> void updates(Transaction xact, Class<T> cls, List<Predicate> preds, List<T> docs, String notFoundMessage) { updates(xact, cls, preds, docs, notFoundMessage, null); } @Override public <T> void updates(String domain, Class<T> cls, List<Predicate> preds, List<T> docs, String notFoundMessage, DefaultEntityEventProvider<T> provider) { updates(domain, cls, preds, docs, notFoundMessage, provider, null); } @Override public <T> void updates(String domain, Class<T> cls, List<Predicate> preds, List<T> docs, String notFoundMessage, DefaultEntityEventProvider<T> provider, Object state) { if (docs.isEmpty()) return; ConfigDatabase db = getDatabase(domain); Transaction xact = new Transaction(domain, db); xact.addEventProvider(cls, provider); try { updates(xact, cls, preds, docs, notFoundMessage, state); xact.commit(COMMITER, "updated " + docs.size() + " " + cls.getSimpleName() + "(s)"); } catch (Throwable e) { xact.rollback(); if (e instanceof DOMException) throw (DOMException) e; throw new RuntimeException(e); } } @Deprecated @Override public <T> void updates(Transaction xact, Class<T> cls, List<Predicate> preds, List<T> docs, String notFoundMessage, Object state) { if (docs.isEmpty()) return; if (preds.size() != docs.size()) throw new IllegalArgumentException("preds and docs must has equal size"); ConfigDatabase db = xact.getConfigDatabase(); Predicate pred = Predicates.or(preds.toArray(new Predicate[0])); ConfigIterator confIt = null; try { confIt = db.find(cls, pred); Iterator<T> docIterator = docs.iterator(); while (docIterator.hasNext()) { if (!confIt.hasNext()) { logger.debug("kraken dom: updates(), no config for update [{}]", docIterator.next()); throw new DOMException(notFoundMessage); } Config c = confIt.next(); T nextDoc = docIterator.next(); logger.debug("kraken dom: updates(), found config [{}] update to [{}]", c.getDocument(), nextDoc); xact.update(c, nextDoc, state); } } finally { if (confIt != null) { confIt.close(); } } } @Override public <T> void update(String domain, Class<T> cls, Predicate pred, T doc, String notFoundMessage, DefaultEntityEventProvider<T> provider) { update(domain, cls, pred, doc, notFoundMessage, provider, null); } @Override public <T> void update(String domain, Class<T> cls, Predicate pred, T doc, String notFoundMessage, DefaultEntityEventProvider<T> provider, Object state) { ConfigDatabase db = getDatabase(domain); Transaction xact = new Transaction(domain, db); xact.addEventProvider(cls, provider); try { update(xact, cls, pred, doc, notFoundMessage, state); xact.commit(COMMITER, "updated 1 " + cls.getSimpleName()); } catch (Throwable e) { xact.rollback(); if (e instanceof DOMException) throw (DOMException) e; throw new RuntimeException(e); } } @Override public <T> void update(Transaction xact, Class<T> cls, Predicate pred, T doc, String notFoundMessage) { update(xact, cls, pred, doc, notFoundMessage, null); } @Override public <T> void update(Transaction xact, Class<T> cls, Predicate pred, T doc, String notFoundMessage, Object state) { ConfigDatabase db = xact.getConfigDatabase(); Config c = get(db, cls, pred, notFoundMessage); xact.update(c, doc, state); } @Override public <T> Collection<T> removes(String domain, Class<T> cls, List<Predicate> preds, String notFoundMessage, DefaultEntityEventProvider<T> provider) { return removes(domain, cls, preds, notFoundMessage, provider, null, null); } @Override public <T> Collection<T> removes(String domain, Class<T> cls, List<Predicate> preds, String notFoundMessage, DefaultEntityEventProvider<T> provider, Object removingState, Object removedState) { if (preds.isEmpty()) return new ArrayList<T>(); ConfigDatabase db = getDatabase(domain); Transaction xact = new Transaction(domain, db); Collection<T> docs = null; try { docs = removes(xact, domain, cls, preds, notFoundMessage, provider, removingState, removedState); xact.commit(COMMITER, "removed " + preds.size() + " " + cls.getSimpleName() + "(s)"); } catch (Throwable e) { xact.rollback(); if (e instanceof DOMException) throw (DOMException) e; throw new RuntimeException(e); } return docs; } @Override public <T> Collection<T> removes(Transaction xact, String domain, Class<T> cls, List<Predicate> preds, String notFoundMessage, DefaultEntityEventProvider<T> provider) { return removes(xact, domain, cls, preds, notFoundMessage, provider, null, null); } @Override public <T> Collection<T> removes(Transaction xact, String domain, Class<T> cls, List<Predicate> preds, String notFoundMessage, DefaultEntityEventProvider<T> provider, Object removingState, Object removedState) { if (preds.isEmpty()) return new ArrayList<T>(); ConfigDatabase db = xact.getConfigDatabase(); xact.addEventProvider(cls, provider); Predicate pred = Predicates.or(preds.toArray(new Predicate[0])); if (notFoundMessage != null && db.findOne(cls, pred) == null) throw new DOMException(notFoundMessage); Collection<T> docs = new ArrayList<T>(); ConfigIterator it = db.find(cls, pred); try { List<EntityState> entities = new LinkedList<EntityState>(); ConfigParser parser = parsers.get(cls); if (parser == null) logger.trace("kraken dom: no parser for " + cls.getName()); it.setParser(parser); while (it.hasNext()) { Config c = it.next(); T doc = c.getDocument(cls, getCallback(domain)); docs.add(doc); xact.removePreChecked(c, doc, false, removedState); entities.add(new EntityState(doc, removingState)); } xact.checkRemovability(cls, entities); } finally { it.close(); } return docs; } @Override public <T> T remove(String domain, Class<T> cls, Predicate pred, String notFoundMessage, DefaultEntityEventProvider<T> provider) { return remove(domain, cls, pred, notFoundMessage, provider, null, null); } @Override public <T> T remove(String domain, Class<T> cls, Predicate pred, String notFoundMessage, DefaultEntityEventProvider<T> provider, Object removingState, Object removedState) { ConfigDatabase db = getDatabase(domain); Transaction xact = new Transaction(domain, db); T doc = null; try { doc = remove(xact, domain, cls, pred, notFoundMessage, provider, removingState, removedState); xact.commit(COMMITER, "removed 1 " + cls.getSimpleName()); } catch (Throwable e) { xact.rollback(); if (e instanceof DOMException) throw (DOMException) e; throw new RuntimeException(e); } return doc; } @Override public <T> T remove(Transaction xact, String domain, Class<T> cls, Predicate pred, String notFoundMessage, DefaultEntityEventProvider<T> provider) { return remove(xact, domain, cls, pred, notFoundMessage, provider, null, null); } @Override public <T> T remove(Transaction xact, String domain, Class<T> cls, Predicate pred, String notFoundMessage, DefaultEntityEventProvider<T> provider, Object removingState, Object removedState) { ConfigDatabase db = xact.getConfigDatabase(); xact.addEventProvider(cls, provider); Config c = get(db, cls, pred, notFoundMessage); T doc = c.getDocument(cls, getCallback(domain)); xact.remove(c, doc, removingState, removedState); return doc; } private Config get(ConfigDatabase db, Class<?> cls, Predicate pred, String notFoundMessage) { Config c = db.findOne(cls, pred); if (c == null) throw new DOMException(notFoundMessage); return c; } @Override public PrimitiveParseCallback getParseCallback(String domain) { return getCallback(domain); } private class ParseCallback implements PrimitiveParseCallback { private String domain; private SoftReference<ConcurrentMap<JoinKey, Object>> cache; public ParseCallback(String domain) { this.domain = domain; this.cache = new SoftReference<ConcurrentMap<JoinKey, Object>>( new ConcurrentHashMap<ConfigManagerImpl.JoinKey, Object>()); } @SuppressWarnings("unchecked") @Override public <T> T onParse(Class<T> cls, Map<String, Object> referenceKey) { JoinKey joinKey = new JoinKey(cls.getName(), referenceKey); ConcurrentMap<JoinKey, Object> m = cache.get(); if (m == null) { m = new ConcurrentHashMap<ConfigManagerImpl.JoinKey, Object>(); cache = new SoftReference<ConcurrentMap<JoinKey, Object>>(m); } else { Object cached = m.get(joinKey); if (cached != null) { return (T) cached; } } T ret = find(domain, cls, Predicates.field(referenceKey)); if (ret != null) m.put(joinKey, ret); else if (logger.isTraceEnabled()) logger.trace("kraken dom: cannot find join [{}] key [{}]", cls.getName(), referenceKey); return ret; } } private static class JoinKey { private String className; private Object key; public JoinKey(String className, Object key) { this.className = className; this.key = key; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((className == null) ? 0 : className.hashCode()); result = prime * result + ((key == null) ? 0 : key.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; JoinKey other = (JoinKey) obj; if (className == null) { if (other.className != null) return false; } else if (!className.equals(other.className)) return false; if (key == null) { if (other.key != null) return false; } else if (!key.equals(other.key)) return false; return true; } } }