http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/FolderView.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/FolderView.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/FolderView.java deleted file mode 100644 index 7d3f001..0000000 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/FolderView.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * 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; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * Folder view of notes of Notebook. - * FolderView allows you to see notes from perspective of folders. - */ -public class FolderView implements NoteNameListener, FolderListener { - // key: folderId - private final Map<String, Folder> folders = new LinkedHashMap<>(); - // key: a note, value: a folder where the note belongs to - private final Map<Note, Folder> index = new LinkedHashMap<>(); - - private static final Logger logger = LoggerFactory.getLogger(FolderView.class); - - public Folder getFolder(String folderId) { - String normalizedFolderId = Folder.normalizeFolderId(folderId); - return folders.get(normalizedFolderId); - } - - /** - * Rename folder of which id is folderId to newFolderId - * - * @param oldFolderId folderId to rename - * @param newFolderId newFolderId - * @return `null` if folder not exists, else old Folder - * in order to know which notes and child folders are renamed - */ - public Folder renameFolder(String oldFolderId, String newFolderId) { - String normOldFolderId = Folder.normalizeFolderId(oldFolderId); - String normNewFolderId = Folder.normalizeFolderId(newFolderId); - - if (!hasFolder(normOldFolderId)) - return null; - - if (oldFolderId.equals(Folder.ROOT_FOLDER_ID)) // cannot rename the root folder - return null; - - // check whether oldFolderId and newFolderId are same or not - if (normOldFolderId.equals(normNewFolderId)) - return getFolder(normOldFolderId); - - logger.info("Rename {} to {}", normOldFolderId, normNewFolderId); - - Folder oldFolder = getFolder(normOldFolderId); - removeFolder(oldFolderId); - - oldFolder.rename(normNewFolderId); - - return oldFolder; - } - - public Folder getFolderOf(Note note) { - return index.get(note); - } - - public void putNote(Note note) { - if (note.isNameEmpty()) { - return; - } - - String folderId = note.getFolderId(); - - Folder folder = getOrCreateFolder(folderId); - folder.addNote(note); - - synchronized (index) { - index.put(note, folder); - } - } - - private Folder getOrCreateFolder(String folderId) { - if (folders.containsKey(folderId)) - return folders.get(folderId); - - return createFolder(folderId); - } - - private Folder createFolder(String folderId) { - folderId = Folder.normalizeFolderId(folderId); - - Folder newFolder = new Folder(folderId); - newFolder.addFolderListener(this); - - logger.info("Create folder {}", folderId); - - synchronized (folders) { - folders.put(folderId, newFolder); - } - - Folder parentFolder = getOrCreateFolder(newFolder.getParentFolderId()); - - newFolder.setParent(parentFolder); - parentFolder.addChild(newFolder); - - return newFolder; - } - - private void removeFolder(String folderId) { - Folder removedFolder; - - synchronized (folders) { - removedFolder = folders.remove(folderId); - } - - if (removedFolder != null) { - logger.info("Remove folder {}", folderId); - Folder parent = removedFolder.getParent(); - parent.removeChild(folderId); - removeFolderIfEmpty(parent.getId()); - } - } - - private void removeFolderIfEmpty(String folderId) { - if (!hasFolder(folderId)) - return; - - Folder folder = getFolder(folderId); - if (folder.countNotes() == 0 && !folder.hasChild()) { - logger.info("Folder {} is empty", folder.getId()); - removeFolder(folderId); - } - } - - public void removeNote(Note note) { - if (!index.containsKey(note)) { - return; - } - - Folder folder = index.get(note); - folder.removeNote(note); - - removeFolderIfEmpty(folder.getId()); - - synchronized (index) { - index.remove(note); - } - } - - public void clear() { - synchronized (folders) { - folders.clear(); - } - synchronized (index) { - index.clear(); - } - } - - public boolean hasFolder(String folderId) { - return getFolder(folderId) != null; - } - - public boolean hasNote(Note note) { - return index.containsKey(note); - } - - public int countFolders() { - return folders.size(); - } - - public int countNotes() { - int count = 0; - - for (Folder folder : folders.values()) { - count += folder.countNotes(); - } - - return count; - } - - /** - * Fired after a note's setName() run. - * When the note's name changed, FolderView should check if the note is in the right folder. - * - * @param note - * @param oldName - */ - @Override - public void onNoteNameChanged(Note note, String oldName) { - if (note.isNameEmpty()) { - return; - } - logger.info("Note name changed: {} -> {}", oldName, note.getName()); - // New note - if (!index.containsKey(note)) { - putNote(note); - } - // Existing note - else { - // If the note is in the right place, just return - Folder folder = index.get(note); - if (folder.getId().equals(note.getFolderId())) { - return; - } - // The note's folder is changed! - removeNote(note); - putNote(note); - } - } - - @Override - public void onFolderRenamed(Folder folder, String oldFolderId) { - if (getFolder(folder.getId()) == folder) // the folder is at the right place - return; - logger.info("folder renamed: {} -> {}", oldFolderId, folder.getId()); - - if (getFolder(oldFolderId) == folder) - folders.remove(oldFolderId); - - Folder newFolder = getOrCreateFolder(folder.getId()); - newFolder.merge(folder); - - for (Note note : folder.getNotes()) { - index.put(note, newFolder); - } - } -}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java index 61a36ab..8f916e1 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java @@ -18,36 +18,30 @@ package org.apache.zeppelin.notebook; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.apache.commons.lang.StringUtils; import org.apache.zeppelin.common.JsonSerializable; -import org.apache.zeppelin.completer.CompletionType; import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.display.AngularObject; import org.apache.zeppelin.display.AngularObjectRegistry; import org.apache.zeppelin.display.Input; import org.apache.zeppelin.interpreter.InterpreterFactory; import org.apache.zeppelin.interpreter.InterpreterGroup; -import org.apache.zeppelin.interpreter.InterpreterInfo; import org.apache.zeppelin.interpreter.InterpreterResult; -import org.apache.zeppelin.interpreter.InterpreterResultMessage; import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.interpreter.InterpreterSettingManager; import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; -import org.apache.zeppelin.notebook.repo.NotebookRepo; import org.apache.zeppelin.notebook.utility.IdHashes; -import org.apache.zeppelin.scheduler.Job; import org.apache.zeppelin.scheduler.Job.Status; -import org.apache.zeppelin.search.SearchService; import org.apache.zeppelin.user.AuthenticationInfo; import org.apache.zeppelin.user.Credentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -55,18 +49,13 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import static java.lang.String.format; /** - * Binded interpreters for a note + * Represent the note of Zeppelin. All the note and its paragraph operations are done + * via this class. */ public class Note implements JsonSerializable { private static final Logger logger = LoggerFactory.getLogger(Note.class); - private static final long serialVersionUID = 7920699076577612429L; private static Gson gson = new GsonBuilder() .setPrettyPrinting() .setDateFormat("yyyy-MM-dd HH:mm:ss.SSS") @@ -74,15 +63,7 @@ public class Note implements JsonSerializable { .registerTypeAdapterFactory(Input.TypeAdapterFactory) .create(); - // threadpool for delayed persist of note - private static final ScheduledThreadPoolExecutor delayedPersistThreadPool = - new ScheduledThreadPoolExecutor(0); - - static { - delayedPersistThreadPool.setRemoveOnCancelPolicy(true); - } - - final List<Paragraph> paragraphs = new LinkedList<>(); + private List<Paragraph> paragraphs = new LinkedList<>(); private String name = ""; private String id; @@ -90,18 +71,6 @@ public class Note implements JsonSerializable { private Map<String, Object> noteParams = new LinkedHashMap<>(); private Map<String, Input> noteForms = new LinkedHashMap<>(); private Map<String, List<AngularObject>> angularObjects = new HashMap<>(); - - private transient InterpreterFactory factory; - private transient InterpreterSettingManager interpreterSettingManager; - private transient ParagraphJobListener paragraphJobListener; - private transient NotebookRepo repo; - private transient SearchService index; - private transient ScheduledFuture delayedPersist; - private transient Object delayedPersistLock = new Object(); - private transient NoteEventListener noteEventListener; - private transient Credentials credentials; - private transient NoteNameListener noteNameListener; - /* * note configurations. * - looknfeel - cron @@ -115,29 +84,70 @@ public class Note implements JsonSerializable { private Map<String, Object> info = new HashMap<>(); + /********************************** transient fields ******************************************/ + private transient boolean loaded = false; + private transient String path; + private transient InterpreterFactory interpreterFactory; + private transient InterpreterSettingManager interpreterSettingManager; + private transient ParagraphJobListener paragraphJobListener; + private transient List<NoteEventListener> noteEventListeners = new ArrayList<>(); + private transient Credentials credentials; + + public Note() { generateId(); } - public Note(String name, String defaultInterpreterGroup, NotebookRepo repo, InterpreterFactory factory, + public Note(String path, String defaultInterpreterGroup, InterpreterFactory factory, InterpreterSettingManager interpreterSettingManager, ParagraphJobListener paragraphJobListener, - SearchService noteIndex, Credentials credentials, NoteEventListener noteEventListener) { - this.name = name; + Credentials credentials, List<NoteEventListener> noteEventListener) { + setPath(path); this.defaultInterpreterGroup = defaultInterpreterGroup; - this.repo = repo; - this.factory = factory; + this.interpreterFactory = factory; this.interpreterSettingManager = interpreterSettingManager; this.paragraphJobListener = paragraphJobListener; - this.index = noteIndex; - this.noteEventListener = noteEventListener; + this.noteEventListeners = noteEventListener; this.credentials = credentials; generateId(); + + setCronSupported(ZeppelinConfiguration.create()); + } + + public Note(NoteInfo noteInfo) { + this.id = noteInfo.getId(); + setPath(noteInfo.getPath()); + } + + public String getPath() { + return path; + } + + public String getParentPath() { + int pos = path.lastIndexOf("/"); + if (pos == 0) { + return "/"; + } else { + return path.substring(0, pos); + } + } + + private String getName(String path) { + int pos = path.lastIndexOf("/"); + return path.substring(pos + 1); } private void generateId() { id = IdHashes.generateId(); } + public boolean isLoaded() { + return loaded; + } + + public void setLoaded(boolean loaded) { + this.loaded = loaded; + } + public boolean isPersonalizedMode() { Object v = getConfig().get("personalizedMode"); return null != v && "true".equals(v); @@ -150,7 +160,7 @@ public class Note implements JsonSerializable { } else { valueString = "false"; } - getConfig().put("personalizedMode", valueString); + config.put("personalizedMode", valueString); clearUserParagraphs(value); } @@ -172,12 +182,18 @@ public class Note implements JsonSerializable { } public String getName() { - if (isNameEmpty()) { - name = getId(); - } return name; } + public void setPath(String path) { + if (!path.startsWith("/")) { + this.path = "/" + path; + } else { + this.path = path; + } + this.name = getName(path); + } + public String getDefaultInterpreterGroup() { return defaultInterpreterGroup; } @@ -202,117 +218,36 @@ public class Note implements JsonSerializable { this.noteForms = noteForms; } - public String getNameWithoutPath() { - String notePath = getName(); - - int lastSlashIndex = notePath.lastIndexOf("/"); - // The note is in the root folder - if (lastSlashIndex < 0) { - return notePath; - } - - return notePath.substring(lastSlashIndex + 1); - } - - /** - * @return normalized folder path, which is folderId - */ - public String getFolderId() { - String notePath = getName(); - - // Ignore first '/' - if (notePath.charAt(0) == '/') - notePath = notePath.substring(1); - - int lastSlashIndex = notePath.lastIndexOf("/"); - // The root folder - if (lastSlashIndex < 0) { - return Folder.ROOT_FOLDER_ID; - } - - String folderId = notePath.substring(0, lastSlashIndex); - - return folderId; - } - - public boolean isNameEmpty() { - return this.name.trim().isEmpty(); - } - - private String normalizeNoteName(String name) { - name = name.trim(); - name = name.replace("\\", "/"); - while (name.contains("///")) { - name = name.replaceAll("///", "/"); - } - name = name.replaceAll("//", "/"); - if (name.length() == 0) { - name = "/"; - } - return name; - } - public void setName(String name) { - String oldName = this.name; - - if (name.indexOf('/') >= 0 || name.indexOf('\\') >= 0) { - name = normalizeNoteName(name); - } this.name = name; - - if (this.noteNameListener != null && !oldName.equals(name)) { - noteNameListener.onNoteNameChanged(this, oldName); + if (this.path == null) { + if (name.startsWith("/")) { + this.path = name; + } else { + this.path = "/" + name; + } + } else { + int pos = this.path.indexOf("/"); + this.path = this.path.substring(0, pos + 1) + this.name; } } - public void setNoteNameListener(NoteNameListener listener) { - this.noteNameListener = listener; + public InterpreterFactory getInterpreterFactory() { + return interpreterFactory; } - public void setInterpreterFactory(InterpreterFactory factory) { - this.factory = factory; - synchronized (paragraphs) { - for (Paragraph p : paragraphs) { - p.setInterpreterFactory(factory); - } - } + public void setInterpreterFactory(InterpreterFactory interpreterFactory) { + this.interpreterFactory = interpreterFactory; } void setInterpreterSettingManager(InterpreterSettingManager interpreterSettingManager) { this.interpreterSettingManager = interpreterSettingManager; } - public void initializeJobListenerForParagraph(Paragraph paragraph) { - final Note paragraphNote = paragraph.getNote(); - if (!paragraphNote.getId().equals(this.getId())) { - throw new IllegalArgumentException( - format("The paragraph %s from note %s " + "does not belong to note %s", paragraph.getId(), - paragraphNote.getId(), this.getId())); - } - - boolean foundParagraph = false; - for (Paragraph ownParagraph : paragraphs) { - if (paragraph.getId().equals(ownParagraph.getId())) { - paragraph.setListener(paragraphJobListener); - foundParagraph = true; - } - } - - if (!foundParagraph) { - throw new IllegalArgumentException( - format("Cannot find paragraph %s " + "from note %s", paragraph.getId(), - paragraphNote.getId())); - } - } - void setParagraphJobListener(ParagraphJobListener paragraphJobListener) { this.paragraphJobListener = paragraphJobListener; } - void setNotebookRepo(NotebookRepo repo) { - this.repo = repo; - } - public Boolean isCronSupported(ZeppelinConfiguration config) { if (config.isZeppelinNotebookCronEnable()) { config.getZeppelinNotebookCronFolders(); @@ -334,10 +269,6 @@ public class Note implements JsonSerializable { getConfig().put("isZeppelinNotebookCronEnable", isCronSupported(config)); } - public void setIndex(SearchService index) { - this.index = index; - } - public Credentials getCredentials() { return credentials; } @@ -346,7 +277,6 @@ public class Note implements JsonSerializable { this.credentials = credentials; } - Map<String, List<AngularObject>> getAngularObjects() { return angularObjects; } @@ -366,8 +296,7 @@ public class Note implements JsonSerializable { void addCloneParagraph(Paragraph srcParagraph, AuthenticationInfo subject) { // Keep paragraph original ID - final Paragraph newParagraph = new Paragraph(srcParagraph.getId(), this, - paragraphJobListener, factory); + Paragraph newParagraph = new Paragraph(srcParagraph.getId(), this, paragraphJobListener); Map<String, Object> config = new HashMap<>(srcParagraph.getConfig()); Map<String, Object> param = srcParagraph.settings.getParams(); @@ -384,9 +313,7 @@ public class Note implements JsonSerializable { logger.debug("newParagraph user: " + newParagraph.getUser()); - try { - Gson gson = new Gson(); String resultJson = gson.toJson(srcParagraph.getReturn()); InterpreterResult result = InterpreterResult.fromJson(resultJson); newParagraph.setReturn(result, null); @@ -399,8 +326,31 @@ public class Note implements JsonSerializable { synchronized (paragraphs) { paragraphs.add(newParagraph); } - if (noteEventListener != null) { - noteEventListener.onParagraphCreate(newParagraph); + + try { + fireParagraphCreateEvent(newParagraph); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + public void fireParagraphCreateEvent(Paragraph p) throws IOException { + for (NoteEventListener listener : noteEventListeners) { + listener.onParagraphCreate(p); + } + } + + public void fireParagraphRemoveEvent(Paragraph p) throws IOException { + for (NoteEventListener listener : noteEventListeners) { + listener.onParagraphRemove(p); + } + } + + + public void fireParagraphUpdateEvent(Paragraph p) throws IOException { + for (NoteEventListener listener : noteEventListeners) { + listener.onParagraphUpdate(p); } } @@ -410,28 +360,25 @@ public class Note implements JsonSerializable { * @param index index of paragraphs */ public Paragraph insertNewParagraph(int index, AuthenticationInfo authenticationInfo) { - Paragraph paragraph = createParagraph(index, authenticationInfo); + Paragraph paragraph = new Paragraph(this, paragraphJobListener); + paragraph.setAuthenticationInfo(authenticationInfo); + setParagraphMagic(paragraph, index); insertParagraph(paragraph, index); return paragraph; } - private Paragraph createParagraph(int index, AuthenticationInfo authenticationInfo) { - Paragraph p = new Paragraph(this, paragraphJobListener, factory); - p.setAuthenticationInfo(authenticationInfo); - setParagraphMagic(p, index); - return p; - } - public void addParagraph(Paragraph paragraph) { insertParagraph(paragraph, paragraphs.size()); } - public void insertParagraph(Paragraph paragraph, int index) { + private void insertParagraph(Paragraph paragraph, int index) { synchronized (paragraphs) { paragraphs.add(index, paragraph); } - if (noteEventListener != null) { - noteEventListener.onParagraphCreate(paragraph); + try { + fireParagraphCreateEvent(paragraph); + } catch (IOException e) { + e.printStackTrace(); } } @@ -449,11 +396,11 @@ public class Note implements JsonSerializable { while (i.hasNext()) { Paragraph p = i.next(); if (p.getId().equals(paragraphId)) { - index.deleteIndexDoc(this, p); i.remove(); - - if (noteEventListener != null) { - noteEventListener.onParagraphRemove(p); + try { + fireParagraphRemoveEvent(p); + } catch (IOException e) { + e.printStackTrace(); } return p; } @@ -714,11 +661,7 @@ public class Note implements JsonSerializable { } public boolean isTrash() { - String path = getName(); - if (path.charAt(0) == '/') { - path = path.substring(1); - } - return path.split("/")[0].equals(Folder.TRASH_FOLDER_ID); + return this.path.startsWith("/" + NoteManager.TRASH_FOLDER); } public List<InterpreterCompletion> completion(String paragraphId, String buffer, int cursor) { @@ -792,26 +735,6 @@ public class Note implements JsonSerializable { } } - public void persist(AuthenticationInfo subject) throws IOException { - Preconditions.checkNotNull(subject, "AuthenticationInfo should not be null"); - stopDelayedPersistTimer(); - snapshotAngularObjectRegistry(subject.getUser()); - index.updateIndexDoc(this); - repo.save(this, subject); - } - - /** - * Persist this note with maximum delay. - */ - public void persist(int maxDelaySec, AuthenticationInfo subject) { - startDelayedPersistTimer(maxDelaySec, subject); - } - - void unpersist(AuthenticationInfo subject) throws IOException { - repo.remove(getId(), subject); - } - - /** * Return new note for specific user. this inserts and replaces user paragraph which doesn't * exists in original paragraph @@ -838,35 +761,6 @@ public class Note implements JsonSerializable { return newNote; } - private void startDelayedPersistTimer(int maxDelaySec, final AuthenticationInfo subject) { - synchronized (delayedPersistLock) { - if (delayedPersist != null) { - return; - } - - delayedPersist = delayedPersistThreadPool.schedule(new Runnable() { - - @Override - public void run() { - try { - persist(subject); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - }, maxDelaySec, TimeUnit.SECONDS); - } - } - - private void stopDelayedPersistTimer() { - synchronized (delayedPersistLock) { - if (delayedPersist == null) { - return; - } - delayedPersist.cancel(false); - } - } - public Map<String, Object> getConfig() { if (config == null) { config = new HashMap<>(); @@ -889,8 +783,13 @@ public class Note implements JsonSerializable { this.info = info; } - void setNoteEventListener(NoteEventListener noteEventListener) { - this.noteEventListener = noteEventListener; + @Override + public String toString() { + if (this.path != null) { + return this.path; + } else { + return "/" + this.name; + } } @Override @@ -944,9 +843,9 @@ public class Note implements JsonSerializable { if (paragraphs != null ? !paragraphs.equals(note.paragraphs) : note.paragraphs != null) { return false; } - //TODO(zjffdu) exclude name because FolderView.index use Note as key and consider different name + //TODO(zjffdu) exclude path because FolderView.index use Note as key and consider different path //as same note - // if (name != null ? !name.equals(note.name) : note.name != null) return false; + // if (path != null ? !path.equals(note.path) : note.path != null) return false; if (id != null ? !id.equals(note.id) : note.id != null) { return false; } @@ -964,7 +863,7 @@ public class Note implements JsonSerializable { @Override public int hashCode() { int result = paragraphs != null ? paragraphs.hashCode() : 0; - // result = 31 * result + (name != null ? name.hashCode() : 0); + // result = 31 * result + (path != null ? path.hashCode() : 0); result = 31 * result + (id != null ? id.hashCode() : 0); result = 31 * result + (angularObjects != null ? angularObjects.hashCode() : 0); result = 31 * result + (config != null ? config.hashCode() : 0); @@ -976,4 +875,8 @@ public class Note implements JsonSerializable { public static Gson getGson() { return gson; } + + public void setNoteEventListeners(List<NoteEventListener> noteEventListeners) { + this.noteEventListeners = noteEventListeners; + } } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteEventAsyncListener.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteEventAsyncListener.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteEventAsyncListener.java new file mode 100644 index 0000000..b593673 --- /dev/null +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteEventAsyncListener.java @@ -0,0 +1,228 @@ +/* + * 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; + +import org.apache.zeppelin.scheduler.Job; +import org.apache.zeppelin.user.AuthenticationInfo; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * An special NoteEventListener which handle events asynchronously + */ +public abstract class NoteEventAsyncListener implements NoteEventListener { + + private BlockingQueue<NoteEvent> eventsQueue = new LinkedBlockingQueue<>(); + + private Thread eventHandlerThread; + + public NoteEventAsyncListener(String name) { + this.eventHandlerThread = new EventHandlingThread(); + this.eventHandlerThread.setName(name); + this.eventHandlerThread.start(); + } + + public abstract void handleNoteCreateEvent(NoteCreateEvent noteCreateEvent); + + public abstract void handleNoteRemoveEvent(NoteRemoveEvent noteRemoveEvent); + + public abstract void handleNoteUpdateEvent(NoteUpdateEvent noteUpdateEvent); + + public abstract void handleParagraphCreateEvent(ParagraphCreateEvent paragraphCreateEvent); + + public abstract void handleParagraphRemoveEvent(ParagraphRemoveEvent paragraphRemoveEvent); + + public abstract void handleParagraphUpdateEvent(ParagraphUpdateEvent paragraphUpdateEvent); + + + public void close() { + this.eventHandlerThread.interrupt(); + } + + @Override + public void onNoteCreate(Note note, AuthenticationInfo subject) { + eventsQueue.add(new NoteCreateEvent(note, subject)); + } + + @Override + public void onNoteRemove(Note note, AuthenticationInfo subject) { + eventsQueue.add(new NoteRemoveEvent(note, subject)); + } + + @Override + public void onNoteUpdate(Note note, AuthenticationInfo subject) { + eventsQueue.add(new NoteUpdateEvent(note, subject)); + } + + @Override + public void onParagraphCreate(Paragraph p) { + eventsQueue.add(new ParagraphCreateEvent(p)); + } + + @Override + public void onParagraphRemove(Paragraph p) { + eventsQueue.add(new ParagraphRemoveEvent(p)); + } + + @Override + public void onParagraphUpdate(Paragraph p) { + eventsQueue.add(new ParagraphUpdateEvent(p)); + } + + @Override + public void onParagraphStatusChange(Paragraph p, Job.Status status) { + eventsQueue.add(new ParagraphStatusChangeEvent(p)); + } + + class EventHandlingThread extends Thread { + + @Override + public void run() { + while(!Thread.interrupted()) { + try { + NoteEvent event = eventsQueue.take(); + if (event instanceof NoteCreateEvent) { + handleNoteCreateEvent((NoteCreateEvent) event); + } else if (event instanceof NoteRemoveEvent) { + handleNoteRemoveEvent((NoteRemoveEvent) event); + } else if (event instanceof NoteUpdateEvent) { + handleNoteUpdateEvent((NoteUpdateEvent) event); + } else if (event instanceof ParagraphCreateEvent) { + handleParagraphCreateEvent((ParagraphCreateEvent) event); + } else if (event instanceof ParagraphRemoveEvent) { + handleParagraphRemoveEvent((ParagraphRemoveEvent) event); + } else if (event instanceof ParagraphUpdateEvent) { + handleParagraphUpdateEvent((ParagraphUpdateEvent) event); + } else { + throw new RuntimeException("Unknown event: " + event.getClass().getSimpleName()); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + /** + * Used for testing + * + * @throws InterruptedException + */ + public void drainEvents() throws InterruptedException { + while(!eventsQueue.isEmpty()) { + Thread.sleep(1000); + } + Thread.sleep(5000); + } + + interface NoteEvent { + + } + + public static class NoteCreateEvent implements NoteEvent { + private Note note; + private AuthenticationInfo subject; + + public NoteCreateEvent(Note note, AuthenticationInfo subject) { + this.note = note; + this.subject = subject; + } + + public Note getNote() { + return note; + } + } + + public static class NoteUpdateEvent implements NoteEvent { + private Note note; + private AuthenticationInfo subject; + + public NoteUpdateEvent(Note note, AuthenticationInfo subject) { + this.note = note; + this.subject = subject; + } + + public Note getNote() { + return note; + } + } + + + public static class NoteRemoveEvent implements NoteEvent { + private Note note; + private AuthenticationInfo subject; + + public NoteRemoveEvent(Note note, AuthenticationInfo subject) { + this.note = note; + this.subject = subject; + } + + public Note getNote() { + return note; + } + } + + public static class ParagraphCreateEvent implements NoteEvent { + private Paragraph p; + + public ParagraphCreateEvent(Paragraph p) { + this.p = p; + } + + public Paragraph getParagraph() { + return p; + } + } + + public static class ParagraphUpdateEvent implements NoteEvent { + private Paragraph p; + + public ParagraphUpdateEvent(Paragraph p) { + this.p = p; + } + + public Paragraph getParagraph() { + return p; + } + } + + public static class ParagraphRemoveEvent implements NoteEvent { + private Paragraph p; + + public ParagraphRemoveEvent(Paragraph p) { + this.p = p; + } + + public Paragraph getParagraph() { + return p; + } + } + + public static class ParagraphStatusChangeEvent implements NoteEvent { + private Paragraph p; + + public ParagraphStatusChangeEvent(Paragraph p) { + this.p = p; + } + + public Paragraph getParagraph() { + return p; + } + } +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteEventListener.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteEventListener.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteEventListener.java index 5f98f70..442b4a6 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteEventListener.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteEventListener.java @@ -17,12 +17,20 @@ package org.apache.zeppelin.notebook; import org.apache.zeppelin.scheduler.Job; +import org.apache.zeppelin.user.AuthenticationInfo; + +import java.io.IOException; /** - * NoteEventListener + * Notebook event */ public interface NoteEventListener { - public void onParagraphRemove(Paragraph p); - public void onParagraphCreate(Paragraph p); - public void onParagraphStatusChange(Paragraph p, Job.Status status); + void onNoteRemove(Note note, AuthenticationInfo subject) throws IOException; + void onNoteCreate(Note note, AuthenticationInfo subject) throws IOException; + void onNoteUpdate(Note note, AuthenticationInfo subject) throws IOException; + + void onParagraphRemove(Paragraph p) throws IOException; + void onParagraphCreate(Paragraph p) throws IOException; + void onParagraphUpdate(Paragraph p) throws IOException; + void onParagraphStatusChange(Paragraph p, Job.Status status) throws IOException; } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteInfo.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteInfo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteInfo.java index d316dfb..440b5fe 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteInfo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteInfo.java @@ -17,28 +17,22 @@ package org.apache.zeppelin.notebook; -import java.util.HashMap; -import java.util.Map; - /** - * + * Metadata of Note: noteId & note Path */ public class NoteInfo { String id; - String name; - private Map<String, Object> config = new HashMap<>(); + String path; - public NoteInfo(String id, String name, Map<String, Object> config) { + public NoteInfo(String id, String path) { super(); this.id = id; - this.name = name; - this.config = config; + this.path = path; } public NoteInfo(Note note) { id = note.getId(); - name = note.getName(); - config = note.getConfig(); + path = note.getPath(); } public String getId() { @@ -49,20 +43,26 @@ public class NoteInfo { this.id = id; } - public String getName() { - return name; + public String getPath() { + return path; } - public void setName(String name) { - this.name = name; + public void setPath(String path) { + this.path = path; } - public Map<String, Object> getConfig() { - return config; + public String getNoteName() { + int pos = this.path.lastIndexOf("/"); + return path.substring(pos + 1); } - public void setConfig(Map<String, Object> config) { - this.config = config; + public String getParent() { + int pos = this.path.lastIndexOf("/"); + return path.substring(0, pos); } + @Override + public String toString() { + return path + "_" + id + ".zpln"; + } } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteManager.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteManager.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteManager.java new file mode 100644 index 0000000..edaabfe --- /dev/null +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteManager.java @@ -0,0 +1,569 @@ +/* + * 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; + +import org.apache.commons.lang3.StringUtils; +import org.apache.zeppelin.notebook.repo.NotebookRepo; +import org.apache.zeppelin.scheduler.Job; +import org.apache.zeppelin.user.AuthenticationInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Manager class for note. It handle all the note related operations, such as get, create, + * delete & move note. + * + * It load 2 kinds of metadata into memory: + * 1. Mapping from noteId to note name + * 2. The tree structure of notebook folder + * + * Note will be loaded lazily. Initially only noteId nad note name is loaded, + * other note content is loaded until getNote is called. + * + * TODO(zjffdu) implement the lifecycle manager of Note + * (release memory if note is not used for some period) + */ +public class NoteManager { + private static final Logger LOGGER = LoggerFactory.getLogger(NoteManager.class); + public static String TRASH_FOLDER = "~Trash"; + private Folder root; + private Folder trash; + + private NotebookRepo notebookRepo; + private Map<String, String> notesInfo; + + public NoteManager(NotebookRepo notebookRepo) throws IOException { + this.notebookRepo = notebookRepo; + this.root = new Folder("/", notebookRepo); + this.trash = this.root.getOrCreateFolder(TRASH_FOLDER); + init(); + } + + // build the tree structure of notes + private void init() throws IOException { + this.notesInfo = notebookRepo.list(AuthenticationInfo.ANONYMOUS).values().stream() + .collect(Collectors.toMap(noteInfo -> noteInfo.getId(), notesInfo -> notesInfo.getPath())); + this.notesInfo.entrySet().stream() + .forEach(entry -> + { + try { + addOrUpdateNoteNode(new Note(new NoteInfo(entry.getKey(), entry.getValue()))); + } catch (IOException e) { + LOGGER.warn(e.getMessage()); + } + }); + } + + public Map<String, String> getNotesInfo() { + return notesInfo; + } + + //TODO(zjffdu) This is inefficient + public List<Note> getAllNotes() { + List<Note> notes = new ArrayList<>(); + for (String notePath : notesInfo.values()) { + try { + notes.add(getNoteNode(notePath).getNote()); + } catch (IOException e) { + LOGGER.warn("Fail to load note: " + notePath, e); + } + } + return notes; + } + + /** + * + * @throws IOException + */ + public void reloadNotes() throws IOException { + this.root = new Folder("/", notebookRepo); + this.trash = this.root.getOrCreateFolder(TRASH_FOLDER); + init(); + } + + private void addOrUpdateNoteNode(Note note, boolean checkDuplicates) throws IOException { + String notePath = note.getPath(); + String[] tokens = notePath.split("/"); + Folder curFolder = root; + for (int i = 0; i < tokens.length - 1; ++i) { + if (!StringUtils.isBlank(tokens[i])) { + curFolder = curFolder.getOrCreateFolder(tokens[i]); + } + } + if (checkDuplicates && curFolder.containsNote(tokens[tokens.length - 1])) { + throw new IOException("Note " + note.getPath() + " existed"); + } + curFolder.addNote(tokens[tokens.length -1], note); + this.notesInfo.put(note.getId(), note.getPath()); + } + + private void addOrUpdateNoteNode(Note note) throws IOException { + addOrUpdateNoteNode(note, false); + } + + /** + * Check whether there exist note under this notePath. + * + * @param notePath + * @return + */ + public boolean containsNote(String notePath) { + try { + getNoteNode(notePath); + return true; + } catch (IOException e) { + return false; + } + } + + /** + * Check whether there exist such folder. + * + * @param folderPath + * @return + */ + public boolean containsFolder(String folderPath) { + try { + getFolder(folderPath); + return true; + } catch (IOException e) { + return false; + } + } + + /** + * Save note to NoteManager, it won't check duplicates, this is used when updating note. + * + * @param note + * @param subject + * @throws IOException + */ + public void saveNote(Note note, AuthenticationInfo subject) throws IOException { + addOrUpdateNoteNode(note); + this.notebookRepo.save(note, subject); + note.setLoaded(true); + } + + /** + * Add or update Note + * + * @param note + * @throws IOException + */ + public void saveNote(Note note) throws IOException { + saveNote(note, AuthenticationInfo.ANONYMOUS); + } + + /** + * Remove note from NotebookRepo and NoteManager + * + * @param noteId + * @param subject + * @throws IOException + */ + public void removeNote(String noteId, AuthenticationInfo subject) throws IOException { + String notePath = this.notesInfo.remove(noteId); + Folder folder = getOrCreateFolder(getFolderName(notePath)); + folder.removeNote(getNoteName(notePath)); + this.notebookRepo.remove(noteId, notePath, subject); + } + + public void moveNote(String noteId, + String newNotePath, + AuthenticationInfo subject) throws IOException { + String notePath = this.notesInfo.get(noteId); + if (noteId == null) { + throw new IOException("No metadata found for this note: " + noteId); + } + + // move the old NoteNode from notePath to newNotePath + NoteNode noteNode = getNoteNode(notePath); + noteNode.getParent().removeNote(getNoteName(notePath)); + noteNode.setNotePath(newNotePath); + String newParent = getFolderName(newNotePath); + Folder newFolder = getOrCreateFolder(newParent); + newFolder.addNoteNode(noteNode); + + // update noteInfo mapping + this.notesInfo.put(noteId, newNotePath); + + // update notebookrepo + this.notebookRepo.move(noteId, notePath, newNotePath, subject); + } + + + public void moveFolder(String folderPath, + String newFolderPath, + AuthenticationInfo subject) throws IOException { + + // update notebookrepo + this.notebookRepo.move(folderPath, newFolderPath, subject); + + // update filesystem tree + Folder folder = getFolder(folderPath); + folder.getParent().removeFolder(folder.getName(), subject); + Folder newFolder = getOrCreateFolder(newFolderPath); + newFolder.getParent().addFolder(newFolder.getName(), folder); + + // update notesInfo + for (Note note : folder.getRawNotesRecursively()) { + notesInfo.put(note.getId(), note.getPath()); + } + } + + /** + * Remove the folder from the tree and returns the affected NoteInfo under this folder. + * + * @param folderPath + * @param subject + * @return + * @throws IOException + */ + public List<Note> removeFolder(String folderPath, AuthenticationInfo subject) throws IOException { + + // update notebookrepo + this.notebookRepo.remove(folderPath, subject); + + // update filesystem tree + Folder folder = getFolder(folderPath); + List<Note> notes = folder.getParent().removeFolder(folder.getName(), subject); + + // update notesInfo + for (Note note : notes) { + this.notesInfo.remove(note.getId()); + } + + return notes; + } + + public Note getNote(String noteId) throws IOException { + String notePath = this.notesInfo.get(noteId); + if (notePath == null) { + return null; + } + NoteNode noteNode = getNoteNode(notePath); + return noteNode.getNote(); + } + + /** + * + * @param folderName Absolute path of folder name + * @return + */ + public Folder getOrCreateFolder(String folderName) { + String[] tokens = folderName.split("/"); + Folder curFolder = root; + for (int i = 0; i < tokens.length; ++i) { + if (!StringUtils.isBlank(tokens[i])) { + curFolder = curFolder.getOrCreateFolder(tokens[i]); + } + } + return curFolder; + } + + private NoteNode getNoteNode(String notePath) throws IOException { + String[] tokens = notePath.split("/"); + Folder curFolder = root; + for (int i = 0; i < tokens.length - 1; ++i) { + if (!StringUtils.isBlank(tokens[i])) { + curFolder = curFolder.getFolder(tokens[i]); + if (curFolder == null) { + throw new IOException("Can not find note: " + notePath); + } + } + } + NoteNode noteNode = curFolder.getNote(tokens[tokens.length - 1]); + if (noteNode == null) { + throw new IOException("Can not find note: " + notePath); + } + return noteNode; + } + + private Folder getFolder(String folderPath) throws IOException { + String[] tokens = folderPath.split("/"); + Folder curFolder = root; + for (int i = 0; i < tokens.length; ++i) { + if (!StringUtils.isBlank(tokens[i])) { + curFolder = curFolder.getFolder(tokens[i]); + if (curFolder == null) { + throw new IOException("Can not find folder: " + folderPath); + } + } + } + return curFolder; + } + + public Folder getTrashFolder() { + return this.trash; + } + + private String getFolderName(String notePath) { + int pos = notePath.lastIndexOf("/"); + return notePath.substring(0, pos); + } + + private String getNoteName(String notePath) { + int pos = notePath.lastIndexOf("/"); + return notePath.substring(pos + 1); + } + + /** + * Represent one folder that could contains sub folders and note files. + */ + public static class Folder { + + private String name; + private Folder parent; + private NotebookRepo notebookRepo; + + // noteName -> NoteNode + private Map<String, NoteNode> notes = new HashMap<>(); + // folderName -> Folder + private Map<String, Folder> subFolders = new HashMap<>(); + + public Folder(String name, NotebookRepo notebookRepo) { + this.name = name; + this.notebookRepo = notebookRepo; + } + + public Folder(String name, Folder parent, NotebookRepo notebookRepo) { + this(name, notebookRepo); + this.parent = parent; + } + + public synchronized Folder getOrCreateFolder(String folderName) { + if (StringUtils.isBlank(folderName)) { + return this; + } + if (!subFolders.containsKey(folderName)) { + subFolders.put(folderName, new Folder(folderName, this, notebookRepo)); + } + return subFolders.get(folderName); + } + + public Folder getParent() { + return parent; + } + + public void setParent(Folder parent) { + this.parent = parent; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Folder getFolder(String folderName) { + return subFolders.get(folderName); + } + + public Map<String, Folder> getFolders() { + return subFolders; + } + + public NoteNode getNote(String noteName) { + return this.notes.get(noteName); + } + + public void addNote(String noteName, Note note) { + notes.put(noteName, new NoteNode(note, this, notebookRepo)); + } + + /** + * Attach another folder under this folder, this is used when moving folder. + * The path of notes under this folder also need to be updated. + */ + public void addFolder(String folderName, Folder folder) throws IOException { + subFolders.put(folderName, folder); + folder.setParent(this); + folder.setName(folderName); + for (NoteNode noteNode : folder.getNoteNodeRecursively()) { + noteNode.updateNotePath(); + } + } + + public boolean containsNote(String noteName) { + return notes.containsKey(noteName); + } + + /** + * Attach note under this folder, this is used when moving note + * @param noteNode + */ + public void addNoteNode(NoteNode noteNode) { + this.notes.put(noteNode.getNoteName(), noteNode); + noteNode.setParent(this); + } + + public void removeNote(String noteName) { + this.notes.remove(noteName); + } + + public List<Note> removeFolder(String folderName, + AuthenticationInfo subject) throws IOException { + Folder folder = this.subFolders.remove(folderName); + return folder.getRawNotesRecursively(); + } + + public List<Note> getRawNotesRecursively() { + List<Note> notesInfo = new ArrayList<>(); + for (NoteNode noteNode : this.notes.values()) { + notesInfo.add(noteNode.getRawNote()); + } + for (Folder folder : subFolders.values()) { + notesInfo.addAll(folder.getRawNotesRecursively()); + } + return notesInfo; + } + + public List<NoteNode> getNoteNodeRecursively() { + List<NoteNode> notes = new ArrayList<>(); + notes.addAll(this.notes.values()); + for (Folder folder : subFolders.values()) { + notes.addAll(folder.getNoteNodeRecursively()); + } + return notes; + } + + public Map<String, NoteNode> getNotes() { + return notes; + } + + public String getPath() { + // root + if (name.equals("/")) { + return name; + } + // folder under root + if (parent.name.equals("/")) { + return "/" + name; + } + // other cases + return parent.toString() + "/" + name; + } + + @Override + public String toString() { + return getPath(); + } + } + + /** + * One node in the file system tree structure which represent the note. + * This class has 2 usage scenarios: + * 1. metadata of note (only noteId and note name is loaded via reading the file name) + * 2. the note object (note content is loaded from NotebookRepo) + * + * It will load note from NotebookRepo lazily until method getNote is called. + */ + public static class NoteNode { + + private Folder parent; + private Note note; + private NotebookRepo notebookRepo; + + public NoteNode(Note note, Folder parent, NotebookRepo notebookRepo) { + this.note = note; + this.parent = parent; + this.notebookRepo = notebookRepo; + } + + /** + * This method will load note from NotebookRepo. If you just want to get noteId, noteName or + * notePath, you can call method getNoteId, getNoteName & getNotePath + * @return + * @throws IOException + */ + public synchronized Note getNote() throws IOException { + if (!note.isLoaded()) { + note = notebookRepo.get(note.getId(), note.getPath(), AuthenticationInfo.ANONYMOUS); + if (parent.toString().equals("/")) { + note.setPath("/" + note.getName()); + } else { + note.setPath(parent.toString() + "/" + note.getName()); + } + note.setLoaded(true); + } + return note; + } + + public String getNoteId() { + return this.note.getId(); + } + + public String getNoteName() { + return this.note.getName(); + } + + public String getNotePath() { + if (parent.getPath().equals("/")) { + return parent.getPath() + note.getName(); + } else { + return parent.getPath() + "/" + note.getName(); + } + } + + /** + * This method will just return the note object without checking whether it is loaded + * from NotebookRepo. + * + * @return + */ + public Note getRawNote() { + return this.note; + } + + public Folder getParent() { + return parent; + } + + @Override + public String toString() { + return getNotePath(); + } + + public void setParent(Folder parent) { + this.parent = parent; + } + + public void setNotePath(String notePath) { + this.note.setPath(notePath); + } + + /** + * This is called when the ancestor folder is moved. + */ + public void updateNotePath() { + this.note.setPath(getNotePath()); + } + } + + +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteNameListener.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteNameListener.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteNameListener.java deleted file mode 100644 index 28b53fb..0000000 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NoteNameListener.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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; - -/** - * NoteNameListener. It's used by FolderView. - */ -public interface NoteNameListener { - /** - * Fired after note name changed - * @param note - * @param oldName - */ - void onNoteNameChanged(Note note, String oldName); -}