/* * 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.interpreter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.net.URISyntaxException; import java.net.URL; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** * InterpreterOutput is OutputStream that supposed to print content on notebook * in addition to InterpreterResult which used to return from Interpreter.interpret(). */ public class InterpreterOutput extends OutputStream { Logger logger = LoggerFactory.getLogger(InterpreterOutput.class); private final int NEW_LINE_CHAR = '\n'; private List<InterpreterResultMessageOutput> resultMessageOutputs = new LinkedList<>(); private InterpreterResultMessageOutput currentOut; private List<String> resourceSearchPaths = Collections.synchronizedList(new LinkedList<String>()); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); private final InterpreterOutputListener flushListener; private final InterpreterOutputChangeListener changeListener; private int size = 0; // change static var to set interpreter output limit // limit will be applied to all InterpreterOutput object. // so we can expect the consistent behavior public static int limit = Constants.ZEPPELIN_INTERPRETER_OUTPUT_LIMIT; public InterpreterOutput(InterpreterOutputListener flushListener) { this.flushListener = flushListener; changeListener = null; clear(); } public InterpreterOutput(InterpreterOutputListener flushListener, InterpreterOutputChangeListener listener) throws IOException { this.flushListener = flushListener; this.changeListener = listener; clear(); } public void setType(InterpreterResult.Type type) throws IOException { InterpreterResultMessageOutput out = null; synchronized (resultMessageOutputs) { int index = resultMessageOutputs.size(); InterpreterResultMessageOutputListener listener = createInterpreterResultMessageOutputListener(index); if (changeListener == null) { out = new InterpreterResultMessageOutput(type, listener); } else { out = new InterpreterResultMessageOutput(type, listener, changeListener); } out.setResourceSearchPaths(resourceSearchPaths); buffer.reset(); size = 0; if (currentOut != null) { currentOut.flush(); } resultMessageOutputs.add(out); currentOut = out; } } public InterpreterResultMessageOutputListener createInterpreterResultMessageOutputListener( final int index) { return new InterpreterResultMessageOutputListener() { final int idx = index; @Override public void onAppend(InterpreterResultMessageOutput out, byte[] line) { if (flushListener != null) { flushListener.onAppend(idx, out, line); } } @Override public void onUpdate(InterpreterResultMessageOutput out) { if (flushListener != null) { flushListener.onUpdate(idx, out); } } }; } public InterpreterResultMessageOutput getCurrentOutput() { synchronized (resultMessageOutputs) { return currentOut; } } public InterpreterResultMessageOutput getOutputAt(int index) { synchronized (resultMessageOutputs) { return resultMessageOutputs.get(index); } } public int size() { synchronized (resultMessageOutputs) { return resultMessageOutputs.size(); } } public void clear() { size = 0; truncated = false; buffer.reset(); synchronized (resultMessageOutputs) { for (InterpreterResultMessageOutput out : resultMessageOutputs) { out.clear(); try { out.close(); } catch (IOException e) { logger.error(e.getMessage(), e); } } // clear all ResultMessages resultMessageOutputs.clear(); currentOut = null; startOfTheNewLine = true; firstCharIsPercentSign = false; updateAllResultMessages(); } } private void updateAllResultMessages() { if (flushListener != null) { flushListener.onUpdateAll(this); } } int previousChar = 0; boolean startOfTheNewLine = true; boolean firstCharIsPercentSign = false; boolean truncated = false; @Override public void write(int b) throws IOException { InterpreterResultMessageOutput out; if (truncated) { return; } synchronized (resultMessageOutputs) { currentOut = getCurrentOutput(); if (++size > limit) { if (b == NEW_LINE_CHAR && currentOut != null) { InterpreterResult.Type type = currentOut.getType(); if (type == InterpreterResult.Type.TEXT || type == InterpreterResult.Type.TABLE) { setType(InterpreterResult.Type.HTML); getCurrentOutput().write(ResultMessages.getExceedsLimitSizeMessage(limit, "ZEPPELIN_INTERPRETER_OUTPUT_LIMIT").getData().getBytes()); truncated = true; return; } } } if (startOfTheNewLine) { if (b == '%') { startOfTheNewLine = false; firstCharIsPercentSign = true; buffer.write(b); previousChar = b; return; } else if (b != NEW_LINE_CHAR) { startOfTheNewLine = false; } } if (b == NEW_LINE_CHAR) { if (currentOut != null && currentOut.getType() == InterpreterResult.Type.TABLE) { if (previousChar == NEW_LINE_CHAR) { startOfTheNewLine = true; return; } } else { startOfTheNewLine = true; } } boolean flushBuffer = false; if (firstCharIsPercentSign) { if (b == ' ' || b == NEW_LINE_CHAR || b == '\t') { firstCharIsPercentSign = false; String displaySystem = buffer.toString(); for (InterpreterResult.Type type : InterpreterResult.Type.values()) { if (displaySystem.equals('%' + type.name().toLowerCase())) { // new type detected setType(type); previousChar = b; return; } } // not a defined display system flushBuffer = true; } else { buffer.write(b); previousChar = b; return; } } out = getCurrentOutputForWriting(); if (flushBuffer) { out.write(buffer.toByteArray()); buffer.reset(); } out.write(b); previousChar = b; } } private InterpreterResultMessageOutput getCurrentOutputForWriting() throws IOException { synchronized (resultMessageOutputs) { InterpreterResultMessageOutput out = getCurrentOutput(); if (out == null) { // add text type result message setType(InterpreterResult.Type.TEXT); out = getCurrentOutput(); } return out; } } @Override public void write(byte [] b) throws IOException { write(b, 0, b.length); } @Override public void write(byte [] b, int off, int len) throws IOException { for (int i = off; i < len; i++) { write(b[i]); } } /** * In dev mode, it monitors file and update ZeppelinServer * @param file * @throws IOException */ public void write(File file) throws IOException { InterpreterResultMessageOutput out = getCurrentOutputForWriting(); out.write(file); } public void write(String string) throws IOException { write(string.getBytes()); } /** * write contents in the resource file in the classpath * @param url * @throws IOException */ public void write(URL url) throws IOException { InterpreterResultMessageOutput out = getCurrentOutputForWriting(); out.write(url); } public void addResourceSearchPath(String path) { resourceSearchPaths.add(path); } public void writeResource(String resourceName) throws IOException { InterpreterResultMessageOutput out = getCurrentOutputForWriting(); out.writeResource(resourceName); } public List<InterpreterResultMessage> toInterpreterResultMessage() throws IOException { List<InterpreterResultMessage> list = new LinkedList<>(); synchronized (resultMessageOutputs) { for (InterpreterResultMessageOutput out : resultMessageOutputs) { list.add(out.toInterpreterResultMessage()); } } return list; } public void flush() throws IOException { InterpreterResultMessageOutput out = getCurrentOutput(); if (out != null) { out.flush(); } } public byte[] toByteArray() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); synchronized (resultMessageOutputs) { for (InterpreterResultMessageOutput m : resultMessageOutputs) { out.write(m.toByteArray()); } } return out.toByteArray(); } @Override public void close() throws IOException { synchronized (resultMessageOutputs) { for (InterpreterResultMessageOutput out : resultMessageOutputs) { out.close(); } } } }