/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * 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 net.ontopia.topicmaps.query.utils; import java.io.IOException; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.util.Collection; import java.util.HashMap; import java.util.Map; import net.ontopia.infoset.core.LocatorIF; import net.ontopia.topicmaps.core.TopicMapIF; import net.ontopia.topicmaps.query.core.DeclarationContextIF; import net.ontopia.topicmaps.query.core.InvalidQueryException; import net.ontopia.topicmaps.query.core.QueryProcessorFactoryIF; import net.ontopia.topicmaps.query.core.QueryProcessorIF; import net.ontopia.topicmaps.query.impl.basic.PredicateFactory; import net.ontopia.topicmaps.query.impl.utils.TologQueryProcessorFactory; import net.ontopia.topicmaps.query.parser.GlobalParseContext; import net.ontopia.topicmaps.query.parser.LocalParseContext; import net.ontopia.topicmaps.query.parser.ParseContextIF; import net.ontopia.topicmaps.query.parser.TologOptions; import net.ontopia.topicmaps.query.parser.TologParser; import net.ontopia.utils.ServiceUtils; import org.apache.commons.collections4.map.AbstractReferenceMap; import org.apache.commons.collections4.map.ReferenceMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * PUBLIC: Utility methods for getting QueryProcessorIFs for a topic * map. * * @since 1.4 */ @SuppressWarnings("unchecked") public class QueryUtils { static Logger log = LoggerFactory.getLogger(QueryUtils.class.getName()); // QueryProcessorIF cache structure {TopicMapIF : {LocatorIF : {String : SoftReference(QueryProcessorIF)}}} private static Map<TopicMapIF, Map<LocatorIF, Map<String, Reference<QueryProcessorIF>>>> qpcache = new ReferenceMap<TopicMapIF, Map<LocatorIF, Map<String, Reference<QueryProcessorIF>>>> (AbstractReferenceMap.ReferenceStrength.SOFT, AbstractReferenceMap.ReferenceStrength.HARD); private static final String DEFAULT_LANGUAGE = TologQueryProcessorFactory.NAME; private static Map<String, QueryProcessorFactoryIF> qpFactoryMap; static { loadQueryProcessorFactories(); } /** * INTERNAL: Reads all the service descriptors and stores the available * QueryProcessorFactoryIF implementations into a map for quick access. */ private static void loadQueryProcessorFactories() { qpFactoryMap = new HashMap<String, QueryProcessorFactoryIF>(); try { for (QueryProcessorFactoryIF factory : ServiceUtils.loadServices(QueryProcessorFactoryIF.class)) { qpFactoryMap.put(factory.getQueryLanguage().toUpperCase(), factory); } } catch (IOException e) { log.error("Could not read from QueryProcessorFactoryIF service descriptor.", e); } // if TOLOG has not been found so far, include it now if (!qpFactoryMap.containsKey(DEFAULT_LANGUAGE)) { qpFactoryMap.put(DEFAULT_LANGUAGE, new TologQueryProcessorFactory()); } } /** * PUBLIC: Returns all available query language implementations. * * @return a {@link Collection} of all available query languages. * @since 5.1 */ public static Collection<String> getAvailableQueryLanguages() { return qpFactoryMap.keySet(); } /** * PUBLIC: Returns the {@link QueryProcessorFactoryIF} instance associated * with a specific query language. If the language is not available, null will * be returned. * * @param language the query language to be used (case insensitive). * @return the {@link QueryProcessorFactoryIF} instance for this language, or * null, if not available. * @since 5.1 */ public static QueryProcessorFactoryIF getQueryProcessorFactory(String language) { return qpFactoryMap.get(language.toUpperCase()); } /** * PUBLIC: Returns a query processor for the given topic map; will * always return the same processor with the default query language * for the same topic map. The base address of the topic map store * will be the base address of the query processor. */ public static QueryProcessorIF getQueryProcessor(TopicMapIF topicmap) { return getQueryProcessor(topicmap, (LocatorIF) null); } public static QueryProcessorIF getQueryProcessor(String queryLanguage, TopicMapIF topicmap) { return getQueryProcessor(queryLanguage, topicmap, null); } public static QueryProcessorIF getQueryProcessor(TopicMapIF topicmap, LocatorIF base) { return getQueryProcessor(DEFAULT_LANGUAGE, topicmap, base); } /** * PUBLIC: Returns the default query processor for the given topic * map and base address. Will always return the same processor for * the same (query language, topic map, base address) combination. * * @since 2.0 */ public static QueryProcessorIF getQueryProcessor(String queryLanguage, TopicMapIF topicmap, LocatorIF base) { // Get {LocatorIF : QueryProcessorIF} entry Map<LocatorIF, Map<String, Reference<QueryProcessorIF>>> qps = qpcache.get(topicmap); if (qps == null) { qps = new HashMap<LocatorIF, Map<String, Reference<QueryProcessorIF>>>(); qpcache.put(topicmap, qps); } Map<String, Reference<QueryProcessorIF>> refmap = qps.get(base); if (refmap == null) { refmap = new HashMap<String, Reference<QueryProcessorIF>>(); qps.put(base, refmap); } // Get QueryProcessorIF Reference<QueryProcessorIF> ref = refmap.get(queryLanguage); if (ref != null) { if (ref.get() != null) { return ref.get(); } } QueryProcessorIF qp = createQueryProcessor(queryLanguage, topicmap, base); refmap.put(queryLanguage, new SoftReference<QueryProcessorIF>(qp)); return qp; } /** * PUBLIC: Factory method for creating a query processor for a given * topic map; always returns a new processor. The base address of * the topic map store will be the base address of the query * processor. * * @since 2.0 */ public static QueryProcessorIF createQueryProcessor(TopicMapIF topicmap) { return createQueryProcessor(topicmap, (LocatorIF) null); } /** * PUBLIC: Factory method for creating a new query processor for a * given topic map and base address. Always returns a new processor. * * @since 2.0 */ public static QueryProcessorIF createQueryProcessor(TopicMapIF topicmap, LocatorIF base) { return createQueryProcessor(DEFAULT_LANGUAGE, topicmap, base, null); } public static QueryProcessorIF createQueryProcessor(String queryLanguage, TopicMapIF topicmap, LocatorIF base) { return createQueryProcessor(queryLanguage, topicmap, base, null); } /** * EXPERIMENTAL: ... */ public static QueryProcessorIF createQueryProcessor(TopicMapIF topicmap, Map properties) { return createQueryProcessor(DEFAULT_LANGUAGE, topicmap, (LocatorIF) null, properties); } public static QueryProcessorIF createQueryProcessor(TopicMapIF topicmap, LocatorIF base, Map properties) { return createQueryProcessor(DEFAULT_LANGUAGE, topicmap, base, properties); } public static QueryProcessorIF createQueryProcessor(String queryLanguage, TopicMapIF topicmap, Map properties) { return createQueryProcessor(queryLanguage, topicmap, (LocatorIF) null, properties); } /** * EXPERIMENTAL: ... */ public static QueryProcessorIF createQueryProcessor(String queryLanguage, TopicMapIF topicmap, LocatorIF base, Map properties) { QueryProcessorFactoryIF factory = getQueryProcessorFactory(queryLanguage); return (factory == null) ? null : factory.createQueryProcessor(topicmap, base, properties); } /** * PUBLIC: Parses a set of tolog declarations and returns an * object representing the resulting declaration context. The * context cannot be introspected, but it can be given to a query * processor to execute queries in that context. * * @since 2.1 */ public static DeclarationContextIF parseDeclarations(TopicMapIF topicmap, String declarations) throws InvalidQueryException { return parseDeclarations(topicmap, declarations, null); } /** * PUBLIC: Parses a set of tolog declarations in an existing * context, and returns an object representing the resulting nested * declaration context. The context cannot be introspected, but it * can be given to a query processor to execute queries in that * context. * * @since 2.1 */ public static DeclarationContextIF parseDeclarations(TopicMapIF topicmap, String declarations, DeclarationContextIF context) throws InvalidQueryException { // find the base LocatorIF base = topicmap.getStore().getBaseAddress(); ParseContextIF pctxt = (ParseContextIF) context; if (pctxt == null) { // create the innermost context pctxt = new GlobalParseContext(new PredicateFactory(topicmap, base), topicmap, base); } // create a nested context pctxt = new LocalParseContext(pctxt); // parse the declarations into this TologParser parser = new TologParser(pctxt, TologOptions.defaults); return (DeclarationContextIF) parser.parseDeclarations(declarations); } }