/*
* #%L
* server
* %%
* Copyright (C) 2012 - 2015 valdasraps
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package lt.emasina.resthub.server.factory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.log4j.Log4j;
import lt.emasina.resthub.exception.QueryException;
import lt.emasina.resthub.server.exporter.Exporter;
import lt.emasina.resthub.server.handler.Handler;
import lt.emasina.resthub.server.query.Query;
import lt.emasina.resthub.server.query.QueryId;
import lt.emasina.resthub.server.query.QueryMap;
import lt.emasina.resthub.server.table.TableId;
import org.restlet.resource.ResourceException;
/**
* QueryFactory
*
* @author valdo
*/
@Log4j
@Singleton
public class QueryFactory {
private static final int TIME_TO_LIVE_SECS = 60 * 60;
@Inject
private CacheFactory ccf;
@Inject
private ResourceFactory rf;
private final QueryMap queries = new QueryMap();
public Collection<Query> getQueries() {
return queries.getQueries();
}
public Query getQuery(String id) {
return queries.get(id);
}
public String createQuery(String sql) throws QueryException {
// 0: search for query by MD5 (fast-forward)
String id = queries.getId(QueryId.getMD5(sql));
if (id != null) {
return id;
}
// 1: Not found. Create QID, normalize query and search again
QueryId qid = rf.create(sql);
id = queries.getId(qid.getMd5());
if (id != null) {
return id;
}
// 2: Not found. Create new query and add to list
Query q = rf.create(qid);
queries.add(q);
// 3: Create query cache
if (q.isCacheable()) {
ccf.add(q);
}
return qid.getId();
}
private final Map<Integer, Exporter<?>> exporters = new ConcurrentHashMap<>();
@SuppressWarnings("unchecked")
public <E extends Exporter<?>, H extends Handler<?, E>> E getExporter(H handler) throws ResourceException {
Integer id = handler.getId();
E de = (E) exporters.get(id);
if (de != null) {
if (log.isDebugEnabled()) {
log.debug(String.format("got DataExporter from map %d for %d", de.hashCode(), id));
}
return de;
}
synchronized (this) {
de = (E) exporters.get(id);
if (de == null) {
de = handler.createExporter();
exporters.put(id, de);
if (log.isDebugEnabled()) {
log.debug(String.format("created DataExporter %d for %s", de.hashCode(), id));
}
} else {
log.debug(String.format("got DataExporter from map after lock %d for %s", de.hashCode(), id));
}
return de;
}
}
@SuppressWarnings("unchecked")
public <E extends Exporter<?>, H extends Handler<?, E>> void removeExporter(H handler) {
E removed = (E) exporters.remove(handler.getId());
if (log.isDebugEnabled() && removed != null) {
log.debug(String.format("removed DataExporter %d for %s", removed.hashCode(), handler.getId()));
}
}
public boolean removeQuery(String id) {
if (log.isDebugEnabled()) {
log.debug(String.format("removing query: %s", id));
}
Query q = queries.get(id);
if (q != null) {
ccf.remove(q);
}
return queries.remove(id);
}
public void removeQueries(TableId id) {
for (String qid: getQueries(id)) {
removeQuery(qid);
}
}
public Collection<String> getQueries(TableId id) {
Set<String> qids = new HashSet<>();
for (Query q: queries.getQueries()) {
if (q.getTables().contains(id)) {
qids.add(q.getQid().getId());
}
}
return qids;
}
public synchronized void cleanQueries() {
List<String> toRemove = new ArrayList<>();
Date threshold = new Date(new Date().getTime() - (TIME_TO_LIVE_SECS * 1000));
for (Query q: queries.getQueries()) {
String id = q.getQid().getId();
Date lastAccess = queries.getLastAccess(id);
if (lastAccess.before(threshold)) {
toRemove.add(id);
if (log.isDebugEnabled()) {
log.debug(String.format("removing query %s due to expired access time: %s", id, lastAccess));
}
}
}
for (String id: toRemove) {
removeQuery(id);
}
}
private static final AtomicLong UID = new AtomicLong(new Date().getTime());
public static String nextUID() {
return Long.toHexString(UID.incrementAndGet());
}
}