/** * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to you 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.apache.zeppelin.geode; import java.util.Iterator; import java.util.List; import java.util.Properties; import org.apache.commons.lang.StringUtils; import org.apache.zeppelin.interpreter.Interpreter; import org.apache.zeppelin.interpreter.InterpreterContext; import org.apache.zeppelin.interpreter.InterpreterResult; import org.apache.zeppelin.interpreter.InterpreterResult.Code; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; import org.apache.zeppelin.scheduler.Scheduler; import org.apache.zeppelin.scheduler.SchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.client.ClientCacheFactory; import org.apache.geode.cache.query.QueryService; import org.apache.geode.cache.query.SelectResults; import org.apache.geode.cache.query.Struct; import org.apache.geode.pdx.PdxInstance; /** * Apache Geode OQL Interpreter (http://geode.apache.org) * * <ul> * <li>{@code geode.locator.host} - The Geode Locator {@code <HOST>} to connect to.</li> * <li>{@code geode.locator.port} - The Geode Locator {@code <PORT>} to connect to.</li> * <li>{@code geode.max.result} - Max number of OQL result to display.</li> * </ul> * <p> * Sample usages: <br/> * {@code %geode.oql} <br/> * {@code SELECT * FROM /regionEmployee e WHERE e.companyId > 95} <br/> * {@code SELECT * FROM /regionEmployee ORDER BY employeeId} <br/> * {@code * SELECT * FROM /regionEmployee * WHERE companyId IN SET(1, 3, 7) OR lastName IN SET('NameA', 'NameB') * } <br/> * {@code * SELECT e.employeeId, c.id as companyId FROM /regionEmployee e, /regionCompany c * WHERE e.companyId = c.id * } * </p> * <p> * OQL specification and sample queries: * http://geode-docs.cfapps.io/docs/getting_started/querying_quick_reference.html * </p> * <p> * When the Zeppelin server is collocated with Geode Shell (gfsh) one can use the %sh interpreter to * run Geode shell commands: <br/> * {@code * %sh * source /etc/geode/conf/geode-env.sh * gfsh << EOF * connect --locator=ambari.localdomain[10334] * destroy region --name=/regionEmployee * create region --name=regionEmployee --type=REPLICATE * exit; * EOF *} * </p> * <p> * Known issue:http://gemfire.docs.pivotal.io/bugnotes/KnownIssuesGemFire810.html #43673 Using query * "select * from /exampleRegion.entrySet" fails in a client-server topology and/or in a * PartitionedRegion. * </p> */ public class GeodeOqlInterpreter extends Interpreter { private Logger logger = LoggerFactory.getLogger(GeodeOqlInterpreter.class); private static final char NEWLINE = '\n'; private static final char TAB = '\t'; private static final char WHITESPACE = ' '; private static final String TABLE_MAGIC_TAG = "%table "; private ClientCache clientCache = null; private QueryService queryService = null; private Exception exceptionOnConnect; private int maxResult; public GeodeOqlInterpreter(Properties property) { super(property); } protected ClientCache getClientCache() { String locatorHost = getProperty("geode.locator.host"); int locatorPort = Integer.valueOf(getProperty("geode.locator.port")); ClientCache clientCache = new ClientCacheFactory().addPoolLocator(locatorHost, locatorPort).create(); return clientCache; } @Override public void open() { logger.info("Geode open connection called!"); // Close the previous open connections. close(); try { maxResult = Integer.valueOf(getProperty("geode.max.result")); clientCache = getClientCache(); queryService = clientCache.getQueryService(); exceptionOnConnect = null; logger.info("Successfully created Geode connection"); } catch (Exception e) { logger.error("Cannot open connection", e); exceptionOnConnect = e; } } @Override public void close() { try { if (clientCache != null) { clientCache.close(); } if (queryService != null) { queryService.closeCqs(); } } catch (Exception e) { logger.error("Cannot close connection", e); } finally { clientCache = null; queryService = null; exceptionOnConnect = null; } } private InterpreterResult executeOql(String oql) { try { if (getExceptionOnConnect() != null) { return new InterpreterResult(Code.ERROR, getExceptionOnConnect().getMessage()); } @SuppressWarnings("unchecked") SelectResults<Object> results = (SelectResults<Object>) getQueryService().newQuery(oql).execute(); StringBuilder msg = new StringBuilder(TABLE_MAGIC_TAG); boolean isTableHeaderSet = false; Iterator<Object> iterator = results.iterator(); int rowDisplayCount = 0; while (iterator.hasNext() && (rowDisplayCount < getMaxResult())) { Object entry = iterator.next(); rowDisplayCount++; if (entry instanceof Number) { handleNumberEntry(isTableHeaderSet, entry, msg); } else if (entry instanceof Struct) { handleStructEntry(isTableHeaderSet, entry, msg); } else if (entry instanceof PdxInstance) { handlePdxInstanceEntry(isTableHeaderSet, entry, msg); } else { handleUnsupportedTypeEntry(isTableHeaderSet, entry, msg); } isTableHeaderSet = true; msg.append(NEWLINE); } return new InterpreterResult(Code.SUCCESS, msg.toString()); } catch (Exception ex) { logger.error("Cannot run " + oql, ex); return new InterpreterResult(Code.ERROR, ex.getMessage()); } } /** * Zeppelin's %TABLE convention uses tab (\t) to delimit fields and new-line (\n) to delimit rows * To complain with this convention we need to replace any occurrences of tab and/or newline * characters in the content. */ private String replaceReservedChars(String str) { if (StringUtils.isBlank(str)) { return str; } return str.replace(TAB, WHITESPACE).replace(NEWLINE, WHITESPACE); } private void handleStructEntry(boolean isHeaderSet, Object entry, StringBuilder msg) { Struct struct = (Struct) entry; if (!isHeaderSet) { for (String titleName : struct.getStructType().getFieldNames()) { msg.append(replaceReservedChars(titleName)).append(TAB); } msg.append(NEWLINE); } for (String titleName : struct.getStructType().getFieldNames()) { msg.append(replaceReservedChars("" + struct.get(titleName))).append(TAB); } } private void handlePdxInstanceEntry(boolean isHeaderSet, Object entry, StringBuilder msg) { PdxInstance pdxEntry = (PdxInstance) entry; if (!isHeaderSet) { for (String titleName : pdxEntry.getFieldNames()) { msg.append(replaceReservedChars(titleName)).append(TAB); } msg.append(NEWLINE); } for (String titleName : pdxEntry.getFieldNames()) { msg.append(replaceReservedChars("" + pdxEntry.getField(titleName))).append(TAB); } } private void handleNumberEntry(boolean isHeaderSet, Object entry, StringBuilder msg) { if (!isHeaderSet) { msg.append("Result").append(NEWLINE); } msg.append((Number) entry); } private void handleUnsupportedTypeEntry(boolean isHeaderSet, Object entry, StringBuilder msg) { if (!isHeaderSet) { msg.append("Unsuppoted Type").append(NEWLINE); } msg.append("" + entry); } @Override public InterpreterResult interpret(String cmd, InterpreterContext contextInterpreter) { logger.info("Run OQL command '{}'", cmd); return executeOql(cmd); } @Override public void cancel(InterpreterContext context) { // Do nothing } @Override public FormType getFormType() { return FormType.SIMPLE; } @Override public int getProgress(InterpreterContext context) { return 0; } @Override public Scheduler getScheduler() { return SchedulerFactory.singleton().createOrGetFIFOScheduler( GeodeOqlInterpreter.class.getName() + this.hashCode()); } @Override public List<InterpreterCompletion> completion(String buf, int cursor, InterpreterContext interpreterContext) { return null; } public int getMaxResult() { return maxResult; } // Test only QueryService getQueryService() { return this.queryService; } Exception getExceptionOnConnect() { return this.exceptionOnConnect; } }