diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java new file mode 100644 index 00000000000..89cb47e6b2a --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java @@ -0,0 +1,115 @@ +/* + * 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.rest; + +import java.util.Collections; +import java.util.List; + +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.apache.commons.lang.StringUtils; +import org.apache.zeppelin.annotation.ZeppelinApi; +import org.apache.zeppelin.notebook.repo.NotebookRepoSync; +import org.apache.zeppelin.notebook.repo.NotebookRepoWithSettings; +import org.apache.zeppelin.rest.message.NotebookRepoSettingsRequest; +import org.apache.zeppelin.server.JsonResponse; +import org.apache.zeppelin.socket.NotebookServer; +import org.apache.zeppelin.user.AuthenticationInfo; +import org.apache.zeppelin.utils.SecurityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + +/** + * NoteRepo rest API endpoint. + * + */ +@Path("/notebook-repositories") +@Produces("application/json") +public class NotebookRepoRestApi { + + private static final Logger LOG = LoggerFactory.getLogger(NotebookRepoRestApi.class); + + private Gson gson = new Gson(); + private NotebookRepoSync noteRepos; + private NotebookServer notebookWsServer; + + public NotebookRepoRestApi() {} + + public NotebookRepoRestApi(NotebookRepoSync noteRepos, NotebookServer notebookWsServer) { + this.noteRepos = noteRepos; + this.notebookWsServer = notebookWsServer; + } + + /** + * List all notebook repository + */ + @GET + @ZeppelinApi + public Response listRepoSettings() { + AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); + LOG.info("Getting list of NoteRepo with Settings for user {}", subject.getUser()); + List settings = noteRepos.getNotebookRepos(subject); + return new JsonResponse<>(Status.OK, "", settings).build(); + } + + /** + * Update a specific note repo. + * + * @param message + * @param settingId + * @return + */ + @PUT + @ZeppelinApi + public Response updateRepoSetting(String payload) { + if (StringUtils.isBlank(payload)) { + return new JsonResponse<>(Status.NOT_FOUND, "", Collections.emptyMap()).build(); + } + AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); + NotebookRepoSettingsRequest newSettings = NotebookRepoSettingsRequest.EMPTY; + try { + newSettings = gson.fromJson(payload, NotebookRepoSettingsRequest.class); + } catch (JsonSyntaxException e) { + LOG.error("Cannot update notebook repo settings", e); + return new JsonResponse<>(Status.NOT_ACCEPTABLE, "", + ImmutableMap.of("error", "Invalid payload structure")).build(); + } + + if (NotebookRepoSettingsRequest.isEmpty(newSettings)) { + LOG.error("Invalid property"); + return new JsonResponse<>(Status.NOT_ACCEPTABLE, "", + ImmutableMap.of("error", "Invalid payload")).build(); + } + LOG.info("User {} is going to change repo setting", subject.getUser()); + NotebookRepoWithSettings updatedSettings = + noteRepos.updateNotebookRepo(newSettings.name, newSettings.settings, subject); + if (!updatedSettings.isEmpty()) { + LOG.info("Broadcasting note list to user {}", subject.getUser()); + notebookWsServer.broadcastReloadedNoteList(subject, null); + } + return new JsonResponse<>(Status.OK, "", updatedSettings).build(); + } +} diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NotebookRepoSettingsRequest.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NotebookRepoSettingsRequest.java new file mode 100644 index 00000000000..9884476b411 --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NotebookRepoSettingsRequest.java @@ -0,0 +1,49 @@ +/* + * 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.rest.message; + +import java.util.Collections; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; + +/** + * Represent payload of a notebook repo settings. + */ +public class NotebookRepoSettingsRequest { + + public static final NotebookRepoSettingsRequest EMPTY = new NotebookRepoSettingsRequest(); + + public String name; + public Map settings; + + public NotebookRepoSettingsRequest() { + name = StringUtils.EMPTY; + settings = Collections.emptyMap(); + } + + public boolean isEmpty() { + return this == EMPTY; + } + + public static boolean isEmpty(NotebookRepoSettingsRequest repoSetting) { + if (repoSetting == null) { + return true; + } + return repoSetting.isEmpty(); + } +} diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java index 162094e5ceb..5e8e3d57e83 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java @@ -22,9 +22,7 @@ import java.util.EnumSet; import java.util.HashSet; import java.util.Set; -import java.util.concurrent.ExecutorService; -import javax.net.ssl.SSLContext; import javax.servlet.DispatcherType; import javax.ws.rs.core.Application; @@ -37,9 +35,16 @@ import org.apache.zeppelin.interpreter.InterpreterFactory; import org.apache.zeppelin.notebook.Notebook; import org.apache.zeppelin.notebook.NotebookAuthorization; -import org.apache.zeppelin.notebook.repo.NotebookRepo; import org.apache.zeppelin.notebook.repo.NotebookRepoSync; -import org.apache.zeppelin.rest.*; +import org.apache.zeppelin.rest.ConfigurationsRestApi; +import org.apache.zeppelin.rest.CredentialRestApi; +import org.apache.zeppelin.rest.HeliumRestApi; +import org.apache.zeppelin.rest.InterpreterRestApi; +import org.apache.zeppelin.rest.LoginRestApi; +import org.apache.zeppelin.rest.NotebookRepoRestApi; +import org.apache.zeppelin.rest.NotebookRestApi; +import org.apache.zeppelin.rest.SecurityRestApi; +import org.apache.zeppelin.rest.ZeppelinRestApi; import org.apache.zeppelin.scheduler.SchedulerFactory; import org.apache.zeppelin.search.LuceneSearch; import org.apache.zeppelin.search.SearchService; @@ -47,7 +52,12 @@ import org.apache.zeppelin.user.Credentials; import org.apache.zeppelin.utils.SecurityUtils; import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.server.*; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.servlet.DefaultServlet; @@ -73,8 +83,8 @@ public class ZeppelinServer extends Application { private SchedulerFactory schedulerFactory; private InterpreterFactory replFactory; - private NotebookRepo notebookRepo; private SearchService noteSearchService; + private NotebookRepoSync notebookRepo; private NotebookAuthorization notebookAuthorization; private Credentials credentials; private DependencyResolver depResolver; @@ -308,6 +318,9 @@ public Set getSingletons() { = new NotebookRestApi(notebook, notebookWsServer, noteSearchService); singletons.add(notebookApi); + NotebookRepoRestApi notebookRepoApi = new NotebookRepoRestApi(notebookRepo, notebookWsServer); + singletons.add(notebookRepoApi); + HeliumRestApi heliumApi = new HeliumRestApi(helium, heliumApplicationFactory, notebook); singletons.add(heliumApi); diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRepoRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRepoRestApiTest.java new file mode 100644 index 00000000000..d2217929ed3 --- /dev/null +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRepoRestApiTest.java @@ -0,0 +1,120 @@ +/* + * 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.rest; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.lang.StringUtils; +import org.apache.zeppelin.user.AuthenticationInfo; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +/** + * NotebookRepo rest api test. + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class NotebookRepoRestApiTest extends AbstractTestRestApi { + + Gson gson = new Gson(); + AuthenticationInfo anonymous; + + @BeforeClass + public static void init() throws Exception { + AbstractTestRestApi.startUp(); + } + + @AfterClass + public static void destroy() throws Exception { + AbstractTestRestApi.shutDown(); + } + + @Before + public void setUp() { + anonymous = new AuthenticationInfo("anonymous"); + } + + private List> getListOfReposotiry() throws IOException { + GetMethod get = httpGet("/notebook-repositories"); + Map responce = gson.fromJson(get.getResponseBodyAsString(), new TypeToken>() {}.getType()); + get.releaseConnection(); + return (List>) responce.get("body"); + } + + private void updateNotebookRepoWithNewSetting(String payload) throws IOException { + PutMethod put = httpPut("/notebook-repositories", payload); + int status = put.getStatusCode(); + put.releaseConnection(); + assertThat(status, is(200)); + } + + @Test public void ThatCanGetNotebookRepositoiesSettings() throws IOException { + List> listOfRepositories = getListOfReposotiry(); + assertThat(listOfRepositories.size(), is(not(0))); + } + + @Test public void setNewDirectoryForLocalDirectory() throws IOException { + List> listOfRepositories = getListOfReposotiry(); + String localVfs = StringUtils.EMPTY; + String className = StringUtils.EMPTY; + + for (int i = 0; i < listOfRepositories.size(); i++) { + if (listOfRepositories.get(i).get("name").equals("VFSNotebookRepo")) { + localVfs = (String) ((List>)listOfRepositories.get(i).get("settings")).get(0).get("selected"); + className = (String) listOfRepositories.get(i).get("className"); + break; + } + } + + if (StringUtils.isBlank(localVfs)) { + // no loval VFS set... + return; + } + + String payload = "{ \"name\": \"" + className + "\", \"settings\" : { \"Notebook Path\" : \"/tmp/newDir\" } }"; + updateNotebookRepoWithNewSetting(payload); + + // Verify + listOfRepositories = getListOfReposotiry(); + String updatedPath = StringUtils.EMPTY; + for (int i = 0; i < listOfRepositories.size(); i++) { + if (listOfRepositories.get(i).get("name").equals("VFSNotebookRepo")) { + updatedPath = (String) ((List>)listOfRepositories.get(i).get("settings")).get(0).get("selected"); + break; + } + } + assertThat(updatedPath, is("/tmp/newDir")); + + // go back to normal + payload = "{ \"name\": \"" + className + "\", \"settings\" : { \"Notebook Path\" : \"" + localVfs + "\" } }"; + updateNotebookRepoWithNewSetting(payload); + } +} diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js index 30d24c2d2eb..93f6a996ab3 100644 --- a/zeppelin-web/src/app/app.js +++ b/zeppelin-web/src/app/app.js @@ -70,6 +70,11 @@ templateUrl: 'app/interpreter/interpreter.html', controller: 'InterpreterCtrl' }) + .when('/notebookRepos', { + templateUrl: 'app/notebookRepos/notebookRepos.html', + controller: 'NotebookReposCtrl', + controllerAs: 'noterepo' + }) .when('/credential', { templateUrl: 'app/credential/credential.html', controller: 'CredentialCtrl' diff --git a/zeppelin-web/src/app/notebookRepos/notebookRepos.controller.js b/zeppelin-web/src/app/notebookRepos/notebookRepos.controller.js new file mode 100644 index 00000000000..0d8e0e67389 --- /dev/null +++ b/zeppelin-web/src/app/notebookRepos/notebookRepos.controller.js @@ -0,0 +1,91 @@ +/* + * Licensed 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. + */ +'use strict'; +(function() { + + angular.module('zeppelinWebApp').controller('NotebookReposCtrl', NotebookReposCtrl); + + NotebookReposCtrl.$inject = ['$http', 'baseUrlSrv', 'ngToast']; + + function NotebookReposCtrl($http, baseUrlSrv, ngToast) { + var vm = this; + vm.notebookRepos = []; + vm.showDropdownSelected = showDropdownSelected; + vm.saveNotebookRepo = saveNotebookRepo; + + _init(); + + // Public functions + + function saveNotebookRepo(valueform, repo, data) { + console.log('data %o', data); + $http.put(baseUrlSrv.getRestApiBase() + '/notebook-repositories', { + 'name': repo.className, + 'settings': data + }).success(function(data) { + var index = _.findIndex(vm.notebookRepos, {'className': repo.className}); + if (index >= 0) { + vm.notebookRepos[index] = data.body; + console.log('repos %o, data %o', vm.notebookRepos, data.body); + } + valueform.$show(); + }).error(function() { + ngToast.danger({ + content: 'We couldn\'t save that NotebookRepo\'s settings', + verticalPosition: 'bottom', + timeout: '3000' + }); + valueform.$show(); + }); + + return 'manual'; + } + + function showDropdownSelected(setting) { + var index = _.findIndex(setting.value, {'value': setting.selected}); + if (index < 0) { + return 'No value'; + } else { + return setting.value[index].name; + } + } + + // Private functions + + function _getInterpreterSettings() { + $http.get(baseUrlSrv.getRestApiBase() + '/notebook-repositories') + .success(function(data, status, headers, config) { + vm.notebookRepos = data.body; + console.log('ya notebookRepos %o', vm.notebookRepos); + }).error(function(data, status, headers, config) { + if (status === 401) { + ngToast.danger({ + content: 'You don\'t have permission on this page', + verticalPosition: 'bottom', + timeout: '3000' + }); + setTimeout(function() { + window.location.replace('/'); + }, 3000); + } + console.log('Error %o %o', status, data.message); + }); + } + + function _init() { + _getInterpreterSettings(); + }; + } + +})(); diff --git a/zeppelin-web/src/app/notebookRepos/notebookRepos.html b/zeppelin-web/src/app/notebookRepos/notebookRepos.html new file mode 100644 index 00000000000..65ca372499d --- /dev/null +++ b/zeppelin-web/src/app/notebookRepos/notebookRepos.html @@ -0,0 +1,98 @@ + +
+
+
+
+

+ Notebook Repos +

+
+
+
+
+ Manage your Notebook Repositories' settings. +
+
+
+
+ +
+
+
+ +
+

{{repo.name}}

+ + + +
+
+
+
+
Settings
+ + + + + + + + + + + +
namevalue
+ + + + {{noterepo.showDropdownSelected(setting)}} + + + + + {{setting.selected | breakFilter}} + + + +
+
+
+ +
+ + +
+
+
+
+ Currently there are no settings for this Notebook Repository +
+
+
+
diff --git a/zeppelin-web/src/components/navbar/navbar.html b/zeppelin-web/src/components/navbar/navbar.html index d2ec7286fa7..82493915cc5 100644 --- a/zeppelin-web/src/components/navbar/navbar.html +++ b/zeppelin-web/src/components/navbar/navbar.html @@ -86,6 +86,7 @@
  • About Zeppelin
  • Interpreter
  • +
  • Notebook Repos
  • Credential
  • Configuration
  • diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index 05a349f2cfa..4ffec037a39 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -162,6 +162,7 @@ + diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java index ca72fc1f7f0..70bec4fb303 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java @@ -17,11 +17,19 @@ package org.apache.zeppelin.notebook.repo; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.microsoft.azure.storage.CloudStorageAccount; -import com.microsoft.azure.storage.StorageException; -import com.microsoft.azure.storage.file.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.zeppelin.conf.ZeppelinConfiguration; @@ -33,12 +41,16 @@ import org.apache.zeppelin.user.AuthenticationInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.*; -import java.net.URISyntaxException; -import java.security.InvalidKeyException; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.microsoft.azure.storage.CloudStorageAccount; +import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.file.CloudFile; +import com.microsoft.azure.storage.file.CloudFileClient; +import com.microsoft.azure.storage.file.CloudFileDirectory; +import com.microsoft.azure.storage.file.CloudFileShare; +import com.microsoft.azure.storage.file.ListFileItem; /** * Azure storage backend for notebooks @@ -227,4 +239,15 @@ public List revisionHistory(String noteId, AuthenticationInfo subject) // Auto-generated method stub return null; } + + @Override + public List getSettings(AuthenticationInfo subject) { + LOG.warn("Method not implemented"); + return Collections.emptyList(); + } + + @Override + public void updateSettings(Map settings, AuthenticationInfo subject) { + LOG.warn("Method not implemented"); + } } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java index d1bf1d5e6c8..80162170407 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.List; +import java.util.Map; import org.apache.zeppelin.annotation.ZeppelinApi; import org.apache.zeppelin.notebook.Note; @@ -100,6 +101,22 @@ public interface NotebookRepo { */ @ZeppelinApi public List revisionHistory(String noteId, AuthenticationInfo subject); + /** + * Get NotebookRepo settings got the given user. + * + * @param subject + * @return + */ + @ZeppelinApi public List getSettings(AuthenticationInfo subject); + + /** + * update notebook repo settings. + * + * @param settings + * @param subject + */ + @ZeppelinApi public void updateSettings(Map settings, AuthenticationInfo subject); + /** * Represents the 'Revision' a point in life of the notebook */ diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSettingsInfo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSettingsInfo.java new file mode 100644 index 00000000000..0525502540f --- /dev/null +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSettingsInfo.java @@ -0,0 +1,44 @@ +/* + * 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.util.List; +import java.util.Map; + +/** + * Notebook repo settings. This represent a structure of a notebook repo settings that will mostly + * used in the frontend. + * + */ +public class NotebookRepoSettingsInfo { + + /** + * Type of value, It can be text or list. + */ + public enum Type { + INPUT, DROPDOWN + } + + public static NotebookRepoSettingsInfo newInstance() { + return new NotebookRepoSettingsInfo(); + } + + public Type type; + public List> value; + public String selected; + public String name; +} diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java index 0265eff41c4..fcc5045e264 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java @@ -24,7 +24,6 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -39,6 +38,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.Lists; + /** * Notebook repository sync with remote storage */ @@ -109,6 +110,39 @@ private void initializeDefaultStorage(ZeppelinConfiguration conf) { } } + public List getNotebookRepos(AuthenticationInfo subject) { + List reposSetting = Lists.newArrayList(); + + NotebookRepoWithSettings repoWithSettings; + for (NotebookRepo repo : repos) { + repoWithSettings = NotebookRepoWithSettings + .builder(repo.getClass().getSimpleName()) + .className(repo.getClass().getName()) + .settings(repo.getSettings(subject)) + .build(); + reposSetting.add(repoWithSettings); + } + + return reposSetting; + } + + public NotebookRepoWithSettings updateNotebookRepo(String name, Map settings, + AuthenticationInfo subject) { + NotebookRepoWithSettings updatedSettings = NotebookRepoWithSettings.EMPTY; + for (NotebookRepo repo : repos) { + if (repo.getClass().getName().equals(name)) { + repo.updateSettings(settings, subject); + updatedSettings = NotebookRepoWithSettings + .builder(repo.getClass().getSimpleName()) + .className(repo.getClass().getName()) + .settings(repo.getSettings(subject)) + .build(); + break; + } + } + return updatedSettings; + } + /** * Lists Notebooks from the first repository */ @@ -444,4 +478,24 @@ public List revisionHistory(String noteId, AuthenticationInfo subject) } return revisions; } + + @Override + public List getSettings(AuthenticationInfo subject) { + List repoSettings = Collections.emptyList(); + try { + repoSettings = getRepo(0).getSettings(subject); + } catch (IOException e) { + LOG.error("Cannot get notebook repo settings", e); + } + return repoSettings; + } + + @Override + public void updateSettings(Map settings, AuthenticationInfo subject) { + try { + getRepo(0).updateSettings(settings, subject); + } catch (IOException e) { + LOG.error("Cannot update notebook repo settings", e); + } + } } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoWithSettings.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoWithSettings.java new file mode 100644 index 00000000000..e5f59daa000 --- /dev/null +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoWithSettings.java @@ -0,0 +1,79 @@ +/* + * 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.util.Collections; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +/** + * Representation of a notebook repo with settings. This is mostly a Wrapper around notebook repo + * information plus settings. + */ +public class NotebookRepoWithSettings { + + public static final NotebookRepoWithSettings EMPTY = + NotebookRepoWithSettings.builder(StringUtils.EMPTY).build(); + + public String name; + public String className; + public List settings; + + private NotebookRepoWithSettings() {} + + public static Builder builder(String name) { + return new Builder(name); + } + + private NotebookRepoWithSettings(Builder builder) { + name = builder.name; + className = builder.className; + settings = builder.settings; + } + + public boolean isEmpty() { + return this.equals(EMPTY); + } + + /** + * Simple builder :). + */ + public static class Builder { + private final String name; + private String className = StringUtils.EMPTY; + private List settings = Collections.emptyList(); + + public Builder(String name) { + this.name = name; + } + + public NotebookRepoWithSettings build() { + return new NotebookRepoWithSettings(this); + } + + public Builder className(String className) { + this.className = className; + return this; + } + + public Builder settings(List settings) { + this.settings = settings; + return this; + } + } +} diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java index 0163fc4b859..889fd0318f3 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java @@ -23,14 +23,12 @@ import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.Writer; +import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.Map; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.services.s3.AmazonS3EncryptionClient; -import com.amazonaws.services.s3.model.EncryptionMaterialsProvider; -import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.zeppelin.conf.ZeppelinConfiguration; @@ -45,10 +43,14 @@ import org.slf4j.LoggerFactory; import com.amazonaws.AmazonClientException; +import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3EncryptionClient; +import com.amazonaws.services.s3.model.EncryptionMaterialsProvider; import com.amazonaws.services.s3.model.GetObjectRequest; +import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider; import com.amazonaws.services.s3.model.ListObjectsRequest; import com.amazonaws.services.s3.model.ObjectListing; import com.amazonaws.services.s3.model.PutObjectRequest; @@ -270,4 +272,15 @@ public List revisionHistory(String noteId, AuthenticationInfo subject) // Auto-generated method stub return null; } + + @Override + public List getSettings(AuthenticationInfo subject) { + LOG.warn("Method not implemented"); + return Collections.emptyList(); + } + + @Override + public void updateSettings(Map settings, AuthenticationInfo subject) { + LOG.warn("Method not implemented"); + } } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java index 213fdf87e95..5f3ad96485a 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java @@ -23,11 +23,14 @@ import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; +import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.Map; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; import org.apache.commons.vfs2.FileContent; import org.apache.commons.vfs2.FileObject; import org.apache.commons.vfs2.FileSystemManager; @@ -40,13 +43,14 @@ import org.apache.zeppelin.notebook.ApplicationState; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.NoteInfo; -import org.apache.zeppelin.notebook.Paragraph; import org.apache.zeppelin.notebook.NotebookImportDeserializer; +import org.apache.zeppelin.notebook.Paragraph; import org.apache.zeppelin.scheduler.Job.Status; import org.apache.zeppelin.user.AuthenticationInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.Lists; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -54,7 +58,7 @@ * */ public class VFSNotebookRepo implements NotebookRepo { - Logger logger = LoggerFactory.getLogger(VFSNotebookRepo.class); + private static final Logger LOG = LoggerFactory.getLogger(VFSNotebookRepo.class); private FileSystemManager fsManager; private URI filesystemRoot; @@ -62,12 +66,15 @@ public class VFSNotebookRepo implements NotebookRepo { public VFSNotebookRepo(ZeppelinConfiguration conf) throws IOException { this.conf = conf; + setNotebookDirectory(conf.getNotebookDir()); + } + private void setNotebookDirectory(String notebookDirPath) throws IOException { try { - if (conf.isWindowsPath(conf.getNotebookDir())) { - filesystemRoot = new File(conf.getNotebookDir()).toURI(); + if (conf.isWindowsPath(notebookDirPath)) { + filesystemRoot = new File(notebookDirPath).toURI(); } else { - filesystemRoot = new URI(conf.getNotebookDir()); + filesystemRoot = new URI(notebookDirPath); } } catch (URISyntaxException e1) { throw new IOException(e1); @@ -76,7 +83,7 @@ public VFSNotebookRepo(ZeppelinConfiguration conf) throws IOException { if (filesystemRoot.getScheme() == null) { // it is local path try { this.filesystemRoot = new URI(new File( - conf.getRelativeDir(filesystemRoot.getPath())).getAbsolutePath()); + conf.getRelativeDir(filesystemRoot.getPath())).getAbsolutePath()); } catch (URISyntaxException e) { throw new IOException(e); } @@ -85,11 +92,15 @@ public VFSNotebookRepo(ZeppelinConfiguration conf) throws IOException { fsManager = VFS.getManager(); FileObject file = fsManager.resolveFile(filesystemRoot.getPath()); if (!file.exists()) { - logger.info("Notebook dir doesn't exist, create."); + LOG.info("Notebook dir doesn't exist, create on is {}.", file.getName()); file.createFolder(); } } + private String getNotebookDirPath() { + return filesystemRoot.getPath().toString(); + } + private String getPath(String path) { if (path == null || path.trim().length() == 0) { return filesystemRoot.toString(); @@ -141,7 +152,7 @@ public List list(AuthenticationInfo subject) throws IOException { infos.add(info); } } catch (Exception e) { - logger.error("Can't read note " + f.getName().toString(), e); + LOG.error("Can't read note " + f.getName().toString(), e); } } @@ -285,4 +296,42 @@ public List revisionHistory(String noteId, AuthenticationInfo subject) return null; } + @Override + public List getSettings(AuthenticationInfo subject) { + NotebookRepoSettingsInfo repoSetting = NotebookRepoSettingsInfo.newInstance(); + List settings = Lists.newArrayList(); + + repoSetting.name = "Notebook Path"; + repoSetting.type = NotebookRepoSettingsInfo.Type.INPUT; + repoSetting.value = Collections.emptyList(); + repoSetting.selected = getNotebookDirPath(); + + settings.add(repoSetting); + return settings; + } + + @Override + public void updateSettings(Map settings, AuthenticationInfo subject) { + if (settings == null || settings.isEmpty()) { + LOG.error("Cannot update {} with empty settings", this.getClass().getName()); + return; + } + String newNotebookDirectotyPath = StringUtils.EMPTY; + if (settings.containsKey("Notebook Path")) { + newNotebookDirectotyPath = settings.get("Notebook Path"); + } + + if (StringUtils.isBlank(newNotebookDirectotyPath)) { + LOG.error("Notebook path is invalid"); + return; + } + LOG.warn("{} will change notebook dir from {} to {}", + subject.getUser(), getNotebookDirPath(), newNotebookDirectotyPath); + try { + setNotebookDirectory(newNotebookDirectotyPath); + } catch (IOException e) { + LOG.error("Cannot update notebook directory", e); + } + } + } diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java index d1864c5c7dc..a4b6202188f 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java @@ -21,12 +21,14 @@ import java.net.URISyntaxException; import java.util.Collections; import java.util.List; +import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.NoteInfo; import org.apache.zeppelin.notebook.repo.NotebookRepo; +import org.apache.zeppelin.notebook.repo.NotebookRepoSettingsInfo; import org.apache.zeppelin.notebook.repo.zeppelinhub.rest.ZeppelinhubRestApiHandler; import org.apache.zeppelin.notebook.repo.zeppelinhub.websocket.Client; import org.apache.zeppelin.user.AuthenticationInfo; @@ -234,4 +236,15 @@ public List revisionHistory(String noteId, AuthenticationInfo subject) return history; } + @Override + public List getSettings(AuthenticationInfo subject) { + LOG.warn("Method not implemented"); + return Collections.emptyList(); + } + + @Override + public void updateSettings(Map settings, AuthenticationInfo subject) { + LOG.warn("Method not implemented"); + } + } diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java index e6236c85a21..f40d5f809ea 100644 --- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java +++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.IOException; +import java.util.List; import java.util.Map; import org.apache.commons.io.FileUtils; @@ -31,12 +32,13 @@ import org.apache.zeppelin.interpreter.InterpreterFactory; import org.apache.zeppelin.interpreter.InterpreterOption; import org.apache.zeppelin.interpreter.mock.MockInterpreter1; -import org.apache.zeppelin.notebook.*; -import org.apache.zeppelin.notebook.repo.zeppelinhub.security.Authentication; -import org.apache.zeppelin.scheduler.JobListener; +import org.apache.zeppelin.notebook.JobListenerFactory; +import org.apache.zeppelin.notebook.Note; +import org.apache.zeppelin.notebook.Notebook; +import org.apache.zeppelin.notebook.Paragraph; +import org.apache.zeppelin.notebook.ParagraphJobListener; import org.apache.zeppelin.scheduler.SchedulerFactory; import org.apache.zeppelin.search.SearchService; -import org.apache.zeppelin.search.LuceneSearch; import org.apache.zeppelin.user.AuthenticationInfo; import org.junit.After; import org.junit.Before; @@ -44,6 +46,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableMap; + public class VFSNotebookRepoTest implements JobListenerFactory { private static final Logger LOG = LoggerFactory.getLogger(VFSNotebookRepoTest.class); private ZeppelinConfiguration conf; @@ -141,6 +145,24 @@ public void testSaveNotebook() throws IOException, InterruptedException { assertEquals(note.getName(), "SaveTest"); notebookRepo.remove(note.getId(), null); } + + @Test + public void testUpdateSettings() throws IOException { + AuthenticationInfo subject = new AuthenticationInfo("anonymous"); + File tmpDir = File.createTempFile("temp", Long.toString(System.nanoTime())); + Map settings = ImmutableMap.of("Notebook Path", tmpDir.getAbsolutePath()); + + List repoSettings = notebookRepo.getSettings(subject); + String originalDir = repoSettings.get(0).selected; + + notebookRepo.updateSettings(settings, subject); + repoSettings = notebookRepo.getSettings(subject); + assertEquals(repoSettings.get(0).selected, tmpDir.getAbsolutePath()); + + // restaure + notebookRepo.updateSettings(ImmutableMap.of("Notebook Path", originalDir), subject); + FileUtils.deleteQuietly(tmpDir); + } class NotebookWriter implements Runnable { Note note;