/*
* 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.URL;
import java.util.LinkedList;
import java.util.List;
/**
* InterpreterMessageOutputStream
*/
public class InterpreterResultMessageOutput extends OutputStream {
Logger logger = LoggerFactory.getLogger(InterpreterResultMessageOutput.class);
private final int NEW_LINE_CHAR = '\n';
private List<String> resourceSearchPaths;
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final List<Object> outList = new LinkedList<>();
private InterpreterOutputChangeWatcher watcher;
private final InterpreterResultMessageOutputListener flushListener;
private InterpreterResult.Type type = InterpreterResult.Type.TEXT;
private boolean firstWrite = true;
public InterpreterResultMessageOutput(
InterpreterResult.Type type,
InterpreterResultMessageOutputListener listener) {
this.type = type;
this.flushListener = listener;
}
public InterpreterResultMessageOutput(
InterpreterResult.Type type,
InterpreterResultMessageOutputListener flushListener,
InterpreterOutputChangeListener listener) throws IOException {
this.type = type;
this.flushListener = flushListener;
watcher = new InterpreterOutputChangeWatcher(listener);
watcher.start();
}
public InterpreterResult.Type getType() {
return type;
}
public void setType(InterpreterResult.Type type) {
if (this.type != type) {
clear();
this.type = type;
}
}
public void clear() {
synchronized (outList) {
buffer.reset();
outList.clear();
if (watcher != null) {
watcher.clear();
}
if (flushListener != null) {
flushListener.onUpdate(this);
}
}
}
@Override
public void write(int b) throws IOException {
synchronized (outList) {
buffer.write(b);
if (b == NEW_LINE_CHAR) {
// first time use of this outputstream.
if (firstWrite) {
// clear the output on gui
if (flushListener != null) {
flushListener.onUpdate(this);
}
firstWrite = false;
}
if (isAppendSupported()) {
flush(true);
}
}
}
}
@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 {
synchronized (outList) {
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 {
outList.add(file);
if (watcher != null) {
watcher.watch(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 {
outList.add(url);
}
public void setResourceSearchPaths(List<String> resourceSearchPaths) {
this.resourceSearchPaths = resourceSearchPaths;
}
public void writeResource(String resourceName) throws IOException {
// search file under provided paths first, for dev mode
for (String path : resourceSearchPaths) {
File res = new File(path + "/" + resourceName);
if (res.isFile()) {
write(res);
return;
}
}
// search from classpath
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = this.getClass().getClassLoader();
}
if (cl == null) {
cl = ClassLoader.getSystemClassLoader();
}
write(cl.getResource(resourceName));
}
public byte[] toByteArray() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
List<Object> all = new LinkedList<>();
synchronized (outList) {
all.addAll(outList);
}
for (Object o : all) {
if (o instanceof File) {
File f = (File) o;
FileInputStream fin = new FileInputStream(f);
copyStream(fin, out);
fin.close();
} else if (o instanceof byte[]) {
out.write((byte[]) o);
} else if (o instanceof Integer) {
out.write((int) o);
} else if (o instanceof URL) {
InputStream fin = ((URL) o).openStream();
copyStream(fin, out);
fin.close();
} else {
// can not handle the object
}
}
out.close();
return out.toByteArray();
}
public InterpreterResultMessage toInterpreterResultMessage() throws IOException {
return new InterpreterResultMessage(type, new String(toByteArray()));
}
private void flush(boolean append) throws IOException {
synchronized (outList) {
buffer.flush();
byte[] bytes = buffer.toByteArray();
if (bytes != null && bytes.length > 0) {
outList.add(bytes);
if (append) {
if (flushListener != null) {
flushListener.onAppend(this, bytes);
}
} else {
if (flushListener != null) {
flushListener.onUpdate(this);
}
}
}
buffer.reset();
}
}
public void flush() throws IOException {
flush(isAppendSupported());
}
public boolean isAppendSupported() {
return type == InterpreterResult.Type.TEXT;
}
private void copyStream(InputStream in, OutputStream out) throws IOException {
int bufferSize = 8192;
byte[] buffer = new byte[bufferSize];
while (true) {
int bytesRead = in.read(buffer);
if (bytesRead == -1) {
break;
} else {
out.write(buffer, 0, bytesRead);
}
}
}
@Override
public void close() throws IOException {
flush();
if (watcher != null) {
watcher.clear();
watcher.shutdown();
}
}
public String toString() {
try {
return "%" + type.name().toLowerCase() + " " + new String(toByteArray());
} catch (IOException e) {
logger.error(e.getMessage(), e);
return "%" + type.name().toLowerCase() + "\n";
}
}
}