/** * Copyright (C) 2010-2017 Structr GmbH * * This file is part of Structr <http://structr.org>. * * Structr is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Structr 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 Public License for more details. * * You should have received a copy of the GNU General Public License * along with Structr. If not, see <http://www.gnu.org/licenses/>. */ package org.structr.bolt.index; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.structr.api.QueryResult; import org.structr.api.graph.PropertyContainer; import org.structr.api.index.Index; import org.structr.api.search.ArrayQuery; import org.structr.api.search.EmptyQuery; import org.structr.api.search.ExactQuery; import org.structr.api.search.FulltextQuery; import org.structr.api.search.GroupQuery; import org.structr.api.search.NotEmptyQuery; import org.structr.api.search.QueryPredicate; import org.structr.api.search.RangeQuery; import org.structr.api.search.SpatialQuery; import org.structr.api.search.TypeConverter; import org.structr.api.search.TypeQuery; import org.structr.api.search.UuidQuery; import org.structr.api.util.FixedSizeCache; import org.structr.api.util.Iterables; import org.structr.bolt.*; import org.structr.bolt.index.converter.BooleanTypeConverter; import org.structr.bolt.index.converter.DateTypeConverter; import org.structr.bolt.index.converter.DoubleTypeConverter; import org.structr.bolt.index.converter.IntTypeConverter; import org.structr.bolt.index.converter.LongTypeConverter; import org.structr.bolt.index.converter.StringTypeConverter; import org.structr.bolt.index.factory.ArrayQueryFactory; import org.structr.bolt.index.factory.EmptyQueryFactory; import org.structr.bolt.index.factory.GroupQueryFactory; import org.structr.bolt.index.factory.KeywordQueryFactory; import org.structr.bolt.index.factory.NotEmptyQueryFactory; import org.structr.bolt.index.factory.QueryFactory; import org.structr.bolt.index.factory.RangeQueryFactory; import org.structr.bolt.index.factory.SpatialQueryFactory; import org.structr.bolt.index.factory.TypeQueryFactory; import org.structr.bolt.index.factory.UuidQueryFactory; /** * */ public abstract class AbstractCypherIndex<T extends PropertyContainer> implements Index<T>, QueryFactory { private static final Logger logger = LoggerFactory.getLogger(AbstractCypherIndex.class.getName()); public static final TypeConverter DEFAULT_CONVERTER = new StringTypeConverter(); public static final Map<Class, TypeConverter> CONVERTERS = new HashMap<>(); public static final Map<Class, QueryFactory> FACTORIES = new HashMap<>(); public static final Set<Class> INDEXABLE = new HashSet<>(Arrays.asList(new Class[] { String.class, Boolean.class, Double.class, Integer.class, Long.class, Character.class, Float.class })); static { FACTORIES.put(NotEmptyQuery.class, new NotEmptyQueryFactory()); FACTORIES.put(FulltextQuery.class, new KeywordQueryFactory()); FACTORIES.put(SpatialQuery.class, new SpatialQueryFactory()); FACTORIES.put(GroupQuery.class, new GroupQueryFactory()); FACTORIES.put(RangeQuery.class, new RangeQueryFactory()); FACTORIES.put(ExactQuery.class, new KeywordQueryFactory()); FACTORIES.put(ArrayQuery.class, new ArrayQueryFactory()); FACTORIES.put(EmptyQuery.class, new EmptyQueryFactory()); FACTORIES.put(TypeQuery.class, new TypeQueryFactory()); FACTORIES.put(UuidQuery.class, new UuidQueryFactory()); CONVERTERS.put(Boolean.class, new BooleanTypeConverter()); CONVERTERS.put(String.class, new StringTypeConverter()); CONVERTERS.put(Date.class, new DateTypeConverter()); CONVERTERS.put(Long.class, new LongTypeConverter()); CONVERTERS.put(Integer.class, new IntTypeConverter()); CONVERTERS.put(Double.class, new DoubleTypeConverter()); } protected final FixedSizeCache<Integer, CachedQueryResult> queryCache; protected final BoltDatabaseService db; public AbstractCypherIndex(final BoltDatabaseService db, final int queryCacheSize) { this.queryCache = new FixedSizeCache<>(queryCacheSize); this.db = db; } public abstract QueryResult<T> getResult(final CypherQuery query); public abstract String getQueryPrefix(final String mainType, final String sourceTypeLabel, final String targetTypeLabel); public abstract String getQuerySuffix(); @Override public void add(final PropertyContainer t, final String key, final Object value, final Class typeHint) { Object indexValue = value; if (value != null) { if (value.getClass().isEnum()) { indexValue = indexValue.toString(); } if (!INDEXABLE.contains(value.getClass())) { return; } } t.setProperty(key, indexValue); } @Override public void remove(final PropertyContainer t) { } @Override public void remove(final PropertyContainer t, final String key) { } @Override public Iterable<T> query(final QueryPredicate predicate) { final CypherQuery query = new CypherQuery(this); createQuery(this, predicate, query, true); final String sortKey = predicate.getSortKey(); if (sortKey != null) { query.sort(predicate.getSortType(), sortKey, predicate.sortDescending()); } return getResult(query); } public void invalidateCache() { if (!queryCache.isEmpty()) { queryCache.clear(); } } // ----- interface QueryFactory ----- @Override public boolean createQuery(final QueryFactory parent, final QueryPredicate predicate, final CypherQuery query, final boolean isFirst) { final Class type = predicate.getQueryType(); if (type != null) { final QueryFactory factory = FACTORIES.get(type); if (factory != null) { return factory.createQuery(this, predicate, query, isFirst); } else { logger.warn("No query factory registered for type {}", type); } } return false; } // ----- nested classes ----- protected class CachedQueryResult implements QueryResult<T> { private Collection<T> result = null; public CachedQueryResult(final Iterable<T> source) { if (source instanceof Collection) { this.result = (Collection)source; } else { this.result = Iterables.toList(source); } } @Override public void close() { } @Override public Iterator<T> iterator() { return result.iterator(); } public boolean isEmpty() { return result.isEmpty(); } } }