/* * 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.notebook.repo; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.LinkedList; import java.util.List; import org.apache.commons.io.IOUtils; import org.apache.commons.vfs2.FileContent; import org.apache.commons.vfs2.FileObject; import org.apache.commons.vfs2.FileSystemManager; import org.apache.commons.vfs2.FileType; import org.apache.commons.vfs2.NameScope; import org.apache.commons.vfs2.Selectors; import org.apache.commons.vfs2.VFS; import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.NoteInfo; import org.apache.zeppelin.notebook.Paragraph; import org.apache.zeppelin.scheduler.Job.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.GsonBuilder; /** * */ public class VFSNotebookRepo implements NotebookRepo { Logger logger = LoggerFactory.getLogger(VFSNotebookRepo.class); private FileSystemManager fsManager; private URI filesystemRoot; private ZeppelinConfiguration conf; public VFSNotebookRepo(ZeppelinConfiguration conf) throws IOException { this.conf = conf; try { filesystemRoot = new URI(conf.getNotebookDir()); } catch (URISyntaxException e1) { throw new IOException(e1); } if (filesystemRoot.getScheme() == null) { // it is local path try { this.filesystemRoot = new URI(new File( conf.getRelativeDir(filesystemRoot.getPath())).getAbsolutePath()); } catch (URISyntaxException e) { throw new IOException(e); } } else { this.filesystemRoot = filesystemRoot; } fsManager = VFS.getManager(); } private String getPath(String path) { if (path == null || path.trim().length() == 0) { return filesystemRoot.toString(); } if (path.startsWith("/")) { return filesystemRoot.toString() + path; } else { return filesystemRoot.toString() + "/" + path; } } private boolean isDirectory(FileObject fo) throws IOException { if (fo == null) return false; if (fo.getType() == FileType.FOLDER) { return true; } else { return false; } } @Override public List<NoteInfo> list() throws IOException { FileObject rootDir = getRootDir(); FileObject[] children = rootDir.getChildren(); List<NoteInfo> infos = new LinkedList<NoteInfo>(); for (FileObject f : children) { String fileName = f.getName().getBaseName(); if (f.isHidden() || fileName.startsWith(".") || fileName.startsWith("#") || fileName.startsWith("~")) { // skip hidden, temporary files continue; } if (!isDirectory(f)) { // currently single note is saved like, [NOTE_ID]/note.json. // so it must be a directory continue; } NoteInfo info = null; try { info = getNoteInfo(f); if (info != null) { infos.add(info); } } catch (IOException e) { logger.error("Can't read note " + f.getName().toString(), e); } } return infos; } private Note getNote(FileObject noteDir) throws IOException { if (!isDirectory(noteDir)) { throw new IOException(noteDir.getName().toString() + " is not a directory"); } FileObject noteJson = noteDir.resolveFile("note.json", NameScope.CHILD); if (!noteJson.exists()) { throw new IOException(noteJson.getName().toString() + " not found"); } GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setPrettyPrinting(); Gson gson = gsonBuilder.create(); FileContent content = noteJson.getContent(); InputStream ins = content.getInputStream(); String json = IOUtils.toString(ins, conf.getString(ConfVars.ZEPPELIN_ENCODING)); ins.close(); Note note = gson.fromJson(json, Note.class); // note.setReplLoader(replLoader); // note.jobListenerFactory = jobListenerFactory; for (Paragraph p : note.getParagraphs()) { if (p.getStatus() == Status.PENDING || p.getStatus() == Status.RUNNING) { p.setStatus(Status.ABORT); } } return note; } private NoteInfo getNoteInfo(FileObject noteDir) throws IOException { Note note = getNote(noteDir); return new NoteInfo(note); } @Override public Note get(String noteId) throws IOException { FileObject rootDir = fsManager.resolveFile(getPath("/")); FileObject noteDir = rootDir.resolveFile(noteId, NameScope.CHILD); return getNote(noteDir); } protected FileObject getRootDir() throws IOException { FileObject rootDir = fsManager.resolveFile(getPath("/")); if (!rootDir.exists()) { throw new IOException("Root path does not exists"); } if (!isDirectory(rootDir)) { throw new IOException("Root path is not a directory"); } return rootDir; } @Override public synchronized void save(Note note) throws IOException { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setPrettyPrinting(); Gson gson = gsonBuilder.create(); String json = gson.toJson(note); FileObject rootDir = getRootDir(); FileObject noteDir = rootDir.resolveFile(note.id(), NameScope.CHILD); if (!noteDir.exists()) { noteDir.createFolder(); } if (!isDirectory(noteDir)) { throw new IOException(noteDir.getName().toString() + " is not a directory"); } FileObject noteJson = noteDir.resolveFile(".note.json", NameScope.CHILD); // false means not appending. creates file if not exists OutputStream out = noteJson.getContent().getOutputStream(false); out.write(json.getBytes(conf.getString(ConfVars.ZEPPELIN_ENCODING))); out.close(); noteJson.moveTo(noteDir.resolveFile("note.json", NameScope.CHILD)); } @Override public void remove(String noteId) throws IOException { FileObject rootDir = fsManager.resolveFile(getPath("/")); FileObject noteDir = rootDir.resolveFile(noteId, NameScope.CHILD); if (!noteDir.exists()) { // nothing to do return; } if (!isDirectory(noteDir)) { // it is not look like zeppelin note savings throw new IOException("Can not remove " + noteDir.getName().toString()); } noteDir.delete(Selectors.SELECT_SELF_AND_CHILDREN); } @Override public void close() { //no-op } }