/* * #! * Ontopia Navigator * #- * 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.nav2.taglibs.tolog; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Arrays; import java.util.ArrayList; import javax.servlet.jsp.JspTagException; import javax.servlet.jsp.PageContext; import net.ontopia.topicmaps.core.TopicMapIF; import net.ontopia.topicmaps.nav2.core.ContextManagerIF; import net.ontopia.topicmaps.nav2.core.NavigatorRuntimeException; import net.ontopia.topicmaps.nav2.taglibs.logic.ContextTag; import net.ontopia.topicmaps.nav2.utils.FrameworkUtils; import net.ontopia.topicmaps.query.core.DeclarationContextIF; import net.ontopia.topicmaps.query.core.InvalidQueryException; import net.ontopia.topicmaps.query.core.ParsedQueryIF; import net.ontopia.topicmaps.query.core.QueryProcessorIF; import net.ontopia.topicmaps.query.core.QueryResultIF; import net.ontopia.utils.ObjectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * INTERNAL: Generic Tolog Tag that has support for executing one query. */ public class QueryWrapper { // initialization of logging facility private static Logger log = LoggerFactory.getLogger(QueryWrapper.class.getName()); private Collection users; private ContextTag contextTag; // Query from which to make a query result private String query; private QueryResultIF queryResult; private QueryProcessorIF queryProcessor; private Object currentRow[]; private Object nextRow[]; // Differences in the queryResult between nextRow and currentRow. private boolean differences[]; private boolean totalGroupBy[]; private ContextManagerIF contextManager; /** * Default constructor. Creates executes the inputQuery in the * given PageContext and moves to the first row. */ public QueryWrapper(PageContext pageContext, String inQuery) throws NavigatorRuntimeException { contextTag = FrameworkUtils.getContextTag(pageContext); if (contextTag == null) throw new NavigatorRuntimeException("<tolog:*> tags must be nested" + " directly or indirectly within a <tolog:context> tag, but no" + " <tolog:context> tag was found"); contextManager = contextTag.getContextManager(); this.query = inQuery; if (query == null) throw new NavigatorRuntimeException("QueryWrapper must get a non-null" + "'query' argument"); // get topicmap object on which we should compute TopicMapIF topicmap = contextTag.getTopicMap(); if (topicmap == null) throw new NavigatorRuntimeException("QueryWrapper found no " + "topic map."); // Create a QueryProcessorIF for the topicmap. queryProcessor = contextTag.getQueryProcessor(); queryResult = contextTag.getQueryResult(query); if (queryResult == null) try { // Execute query, using any arguments from the context manager. queryResult = queryProcessor.execute(query, new ContextManagerScopingMapWrapper(contextManager), contextTag.getDeclarationContext()); } catch (InvalidQueryException e) { log.info("Parsing of query '" + query + "' failed with message: " + e); throw new NavigatorRuntimeException(e); } else { BufferedQueryResultIF bufferedQueryResult = ((BufferedQueryResultIF)queryResult); bufferedQueryResult.restart(); query = bufferedQueryResult.getQuery(); } users = new HashSet(); // Move (internally) nextRow to the first result row. next(); } protected void updateTotalGroupBy(boolean groupBy[]) { if (totalGroupBy == null) totalGroupBy = new boolean[groupBy.length]; for (int i = 0; i < totalGroupBy.length; i++) totalGroupBy[i] |= groupBy[i]; } protected void setUsedBy(ForEachTag user) { users.add(user); } protected boolean usedBy(ForEachTag user) { return users.contains(user); } protected boolean fullyGrouped() { for (int i = 0; i < totalGroupBy.length; i++) if (!totalGroupBy[i]) return false; return true; } /** * Finds out on which columns two arrays of Objects differ. * Uses the .toString() method to determine if they're the same. * @return boolean[] each entry true iff the inputs differ at that index. */ private void computeDifferences() { if (currentRow == null || nextRow == null) { differences = null; } else { differences = new boolean[currentRow.length]; for (int i = 0 ; i < currentRow.length; i++) differences[i] = !sameElements(currentRow[i], nextRow[i]); } } /** * Test if two given elements are equal, i.e. the same or otherwise equivalent. */ private boolean sameElements(Object elem1, Object elem2) { return ObjectUtils.equals(elem1, elem2); } /** * @return true if the query result has changed with respect in any of the * columns corresponding to the entries of groupColumns. * This happens iff for any particular index diffs and groupColumns both * contain the value true. */ protected boolean relevantDifferences(boolean groupColumns[]) { if (groupColumns == null || differences == null) return false; boolean retVal= false; for (int i = 0; i < groupColumns.length; i++) { retVal |= (groupColumns[i] && differences[i]); } return retVal; } public ContextManagerIF getContextManager() { return contextManager; } public boolean hasNext() { return nextRow != null; } /** Moves one step forward in the result set of the query. */ public void next() { currentRow = nextRow; if (!lookAhead.isEmpty()) nextRow = (Object[])lookAhead.remove(0); else nextRow = queryResult.next() ? queryResult.getValues() : null; computeDifferences(); } public Object[] getCurrentRow() { return currentRow; } public int getWidth() { return queryResult.getWidth(); } public int getIndex(String columnName) { return queryResult.getIndex(columnName); } public String getQuery() { return query; } public ParsedQueryIF parseQuery() throws NavigatorRuntimeException { ParsedQueryIF parsedQuery; DeclarationContextIF declarationContext = contextTag .getDeclarationContext(); if (declarationContext == null) throw new NavigatorRuntimeException("QueryWrapper found no" + " DeclaractionContextIF on the ContextTag"); try { parsedQuery = queryProcessor.parse(query, declarationContext); } catch (InvalidQueryException e) { throw new NavigatorRuntimeException(e); } return parsedQuery; } /** * Bind (some of) the names of the columns of the result to the current row. * Only bind those columns corresponding to a true entry in groupColumns. * e.g. column 3 is bound if groupColumns[3] is true. */ protected void bindVariables(boolean groupColumns[]) throws JspTagException { String columnNames[] = queryResult.getColumnNames(); for (int i = 0; i < groupColumns.length; i++) { if (groupColumns[i]) { contextManager.setValue(columnNames[i] , currentRow[i] == null ? Collections.EMPTY_LIST : currentRow[i]); } } } protected ArrayList lookAhead = new ArrayList(); protected boolean isOnlyChild(boolean[] parentGroupColumns, boolean[] childGroupColumns) { // look ahead to see if the current child is the only direct child of the parent // at last row if (nextRow == null) return true; // next row is different if (!equalGroup(parentGroupColumns, currentRow, nextRow)) return true; if (!equalGroup(childGroupColumns, currentRow, nextRow)) return false; // check existing look ahead int length = lookAhead.size(); for (int i=0; i < length; i++) { Object[] futureRow = (Object[])lookAhead.get(i); if (!equalGroup(parentGroupColumns, currentRow, futureRow)) return true; if (!equalGroup(childGroupColumns, currentRow, futureRow)) return false; } // peek further while (queryResult.next()) { Object[] futureRow = queryResult.getValues(); lookAhead.add(futureRow); if (!equalGroup(parentGroupColumns, currentRow, futureRow)) return true; if (!equalGroup(childGroupColumns, currentRow, futureRow)) return false; } return true; } protected boolean equalGroup(boolean[] groupColumns, Object[] row1, Object[] row2) { for (int i=0; i < row1.length; i++) { if (groupColumns[i] && !ObjectUtils.equals(row1[i], row2[i])) return false; } return true; } }