diff --git a/jdbc/pom.xml b/jdbc/pom.xml index f4e97c955ce..73c66c07201 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -104,6 +104,12 @@ 1.0.8 test + + + org.apache.commons + commons-dbcp2 + 2.0.1 + diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java index 0655f3a65a3..5f784d7eb7b 100644 --- a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java @@ -19,15 +19,16 @@ import java.nio.charset.StandardCharsets; import java.io.IOException; import java.security.PrivilegedExceptionAction; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.sql.Statement; +import java.sql.*; import java.util.*; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.dbcp2.ConnectionFactory; +import org.apache.commons.dbcp2.DriverManagerConnectionFactory; +import org.apache.commons.dbcp2.PoolableConnectionFactory; +import org.apache.commons.dbcp2.PoolingDriver; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.pool2.ObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.hadoop.security.UserGroupInformation; import org.apache.zeppelin.interpreter.Interpreter; import org.apache.zeppelin.interpreter.InterpreterContext; @@ -99,14 +100,15 @@ public class JDBCInterpreter extends Interpreter { static final String EMPTY_COLUMN_VALUE = ""; + private final String CONCURRENT_EXECUTION_KEY = "zeppelin.jdbc.concurrent.use"; private final String CONCURRENT_EXECUTION_COUNT = "zeppelin.jdbc.concurrent.max_connection"; + private final String DBCP_STRING = "jdbc:apache:commons:dbcp:"; + private final HashMap propertiesMap; private final Map paragraphIdStatementMap; - - private final Map> propertyKeyUnusedConnectionListMap; - private final Map paragraphIdConnectionMap; + private final Map poolingDriverMap; private final Map propertyKeySqlCompleterMap; @@ -122,9 +124,8 @@ public InterpreterCompletion apply(CharSequence seq) { public JDBCInterpreter(Properties property) { super(property); propertiesMap = new HashMap<>(); - propertyKeyUnusedConnectionListMap = new HashMap<>(); paragraphIdStatementMap = new HashMap<>(); - paragraphIdConnectionMap = new HashMap<>(); + poolingDriverMap = new HashMap<>(); propertyKeySqlCompleterMap = new HashMap<>(); } @@ -193,22 +194,41 @@ private SqlCompleter createSqlCompleter(Connection jdbcConnection) { return completer; } + private boolean isConnectionInPool(String driverName) { + if (poolingDriverMap.containsKey(driverName)) return true; + return false; + } + + private void createConnectionPool(String url, String propertyKey, Properties properties) { + ConnectionFactory connectionFactory = + new DriverManagerConnectionFactory(url, properties); + + PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory( + connectionFactory, null); + ObjectPool connectionPool = new GenericObjectPool(poolableConnectionFactory); + + poolableConnectionFactory.setPool(connectionPool); + PoolingDriver driver = new PoolingDriver(); + driver.registerPool(propertyKey, connectionPool); + + poolingDriverMap.put(propertyKey, driver); + } + + private Connection getConnectionFromPool(String url, String propertyKey, Properties properties) + throws SQLException { + if (!isConnectionInPool(propertyKey)) { + createConnectionPool(url, propertyKey, properties); + } + + return DriverManager.getConnection(DBCP_STRING + propertyKey); + } + public Connection getConnection(String propertyKey, String user) throws ClassNotFoundException, SQLException, InterpreterException { Connection connection = null; if (propertyKey == null || propertiesMap.get(propertyKey) == null) { return null; } - if (propertyKeyUnusedConnectionListMap.containsKey(propertyKey)) { - ArrayList connectionList = propertyKeyUnusedConnectionListMap.get(propertyKey); - if (0 != connectionList.size()) { - connection = propertyKeyUnusedConnectionListMap.get(propertyKey).remove(0); - if (null != connection && connection.isClosed()) { - connection.close(); - connection = null; - } - } - } if (null == connection) { final Properties properties = (Properties) propertiesMap.get(propertyKey).clone(); logger.info(properties.getProperty(DRIVER_KEY)); @@ -222,16 +242,16 @@ public Connection getConnection(String propertyKey, String user) switch (authType) { case KERBEROS: if (user == null) { - connection = DriverManager.getConnection(url, properties); + connection = getConnectionFromPool(url, propertyKey, properties); } else { if ("hive".equalsIgnoreCase(propertyKey)) { - connection = DriverManager.getConnection(url + ";hive.server2.proxy.user=" + user, - properties); + connection = getConnectionFromPool(url + ";hive.server2.proxy.user=" + user, + propertyKey, properties); } else { UserGroupInformation ugi = null; try { ugi = UserGroupInformation.createProxyUser(user, - UserGroupInformation.getCurrentUser()); + UserGroupInformation.getCurrentUser()); } catch (Exception e) { logger.error("Error in createProxyUser", e); StringBuilder stringBuilder = new StringBuilder(); @@ -239,11 +259,13 @@ public Connection getConnection(String propertyKey, String user) stringBuilder.append(e.getCause()); throw new InterpreterException(stringBuilder.toString()); } + + final String poolKey = propertyKey; try { connection = ugi.doAs(new PrivilegedExceptionAction() { @Override public Connection run() throws Exception { - return DriverManager.getConnection(url, properties); + return getConnectionFromPool(url, poolKey, properties); } }); } catch (Exception e) { @@ -258,7 +280,7 @@ public Connection run() throws Exception { break; default: - connection = DriverManager.getConnection(url, properties); + connection = getConnectionFromPool(url, propertyKey, properties); } } } @@ -266,75 +288,41 @@ public Connection run() throws Exception { return connection; } - public Statement getStatement(String propertyKey, String paragraphId, - InterpreterContext interpreterContext) - throws SQLException, ClassNotFoundException, InterpreterException { - Connection connection; - - if (paragraphIdConnectionMap.containsKey(paragraphId + - interpreterContext.getAuthenticationInfo().getUser())) { - connection = paragraphIdConnectionMap.get(paragraphId + - interpreterContext.getAuthenticationInfo().getUser()); - } else { - connection = getConnection(propertyKey, interpreterContext.getAuthenticationInfo().getUser()); - } - - if (connection == null) { - return null; + private void initStatementMap() { + for (Statement statement : paragraphIdStatementMap.values()) { + try { + statement.close(); + } catch (Exception e) { + logger.error("Error while closing paragraphIdStatementMap statement...", e); + } } + paragraphIdStatementMap.clear(); + } - Statement statement = connection.createStatement(); - if (isStatementClosed(statement)) { - connection = getConnection(propertyKey, interpreterContext.getAuthenticationInfo().getUser()); - statement = connection.createStatement(); + private void initConnectionPoolMap() throws SQLException { + Iterator it = poolingDriverMap.keySet().iterator(); + while (it.hasNext()) { + String driverName = it.next(); + poolingDriverMap.get(driverName).closePool(driverName); + it.remove(); } - paragraphIdConnectionMap.put(paragraphId + interpreterContext.getAuthenticationInfo().getUser(), - connection); - paragraphIdStatementMap.put(paragraphId + interpreterContext.getAuthenticationInfo().getUser(), - statement); + poolingDriverMap.clear(); + } - return statement; + private void saveStatement(String key, Statement statement) throws SQLException { + paragraphIdStatementMap.put(key, statement); + statement.setMaxRows(getMaxResult()); } - private boolean isStatementClosed(Statement statement) { - try { - return statement.isClosed(); - } catch (Throwable t) { - logger.debug("{} doesn't support isClosed method", statement); - return false; - } + private void removeStatement(String key) { + paragraphIdStatementMap.remove(key); } @Override public void close() { try { - for (List connectionList : propertyKeyUnusedConnectionListMap.values()) { - for (Connection c : connectionList) { - try { - c.close(); - } catch (Exception e) { - logger.error("Error while closing propertyKeyUnusedConnectionListMap connection...", e); - } - } - } - - for (Statement statement : paragraphIdStatementMap.values()) { - try { - statement.close(); - } catch (Exception e) { - logger.error("Error while closing paragraphIdStatementMap statement...", e); - } - } - paragraphIdStatementMap.clear(); - - for (Connection connection : paragraphIdConnectionMap.values()) { - try { - connection.close(); - } catch (Exception e) { - logger.error("Error while closing paragraphIdConnectionMap connection...", e); - } - } - paragraphIdConnectionMap.clear(); + initStatementMap(); + initConnectionPoolMap(); } catch (Exception e) { logger.error("Error while closing...", e); } @@ -342,17 +330,21 @@ public void close() { private InterpreterResult executeSql(String propertyKey, String sql, InterpreterContext interpreterContext) { - String paragraphId = interpreterContext.getParagraphId(); + Connection connection; + Statement statement; + ResultSet resultSet = null; try { + connection = getConnection(propertyKey, interpreterContext.getAuthenticationInfo().getUser()); + if (connection == null) { + return new InterpreterResult(Code.ERROR, "Prefix not found."); + } - Statement statement = getStatement(propertyKey, paragraphId, interpreterContext); - + statement = connection.createStatement(); if (statement == null) { return new InterpreterResult(Code.ERROR, "Prefix not found."); } - statement.setMaxRows(getMaxResult()); StringBuilder msg = null; boolean isTableType = false; @@ -364,8 +356,9 @@ private InterpreterResult executeSql(String propertyKey, String sql, isTableType = true; } - ResultSet resultSet = null; try { + saveStatement(paragraphId + + interpreterContext.getAuthenticationInfo().getUser(), statement); boolean isResultSetAvailable = statement.execute(sql); @@ -408,16 +401,24 @@ private InterpreterResult executeSql(String propertyKey, String sql, msg.append(updateCount).append(NEWLINE); } } finally { - try { - if (resultSet != null) { + if (resultSet != null) { + try { resultSet.close(); - } - statement.close(); - } finally { - statement = null; + } catch (SQLException e) { /*ignored*/ } } + if (statement != null) { + try { + statement.close(); + } catch (SQLException e) { /*ignored*/ } + } + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { /*ignored*/ } + } + removeStatement(paragraphId + + interpreterContext.getAuthenticationInfo().getUser()); } - return new InterpreterResult(Code.SUCCESS, msg.toString()); } catch (Exception e) { @@ -452,7 +453,6 @@ public InterpreterResult interpret(String cmd, InterpreterContext contextInterpr cmd = cmd.trim(); logger.info("PropertyKey: {}, SQL command: '{}'", propertyKey, cmd); - return executeSql(propertyKey, cmd, contextInterpreter); } diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java index 727211292b2..7b59aab479d 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java @@ -46,6 +46,7 @@ import org.slf4j.LoggerFactory; import org.apache.zeppelin.annotation.ZeppelinApi; +import org.apache.zeppelin.exception.DuplicateNameException; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.Notebook; import org.apache.zeppelin.notebook.NotebookAuthorization; @@ -235,13 +236,20 @@ public Response exportNoteBook(@PathParam("id") String noteId) throws IOExceptio * @param req - notebook Json * @return JSON with new note ID * @throws IOException + * @throws DuplicateNameException */ @POST @Path("import") @ZeppelinApi public Response importNotebook(String req) throws IOException { AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); - Note newNote = notebook.importNote(req, null, subject); + Note newNote; + try { + newNote = notebook.importNote(req, null, subject); + } catch (DuplicateNameException e) { + return new JsonResponse<>(Status.NOT_ACCEPTABLE, + "Notebook already exists with same name").build(); + } return new JsonResponse<>(Status.CREATED, "", newNote.getId()).build(); } @@ -251,33 +259,37 @@ public Response importNotebook(String req) throws IOException { * @param message - JSON with new note name * @return JSON with new note ID * @throws IOException + * @throws DuplicateNameException */ @POST @Path("/") @ZeppelinApi public Response createNote(String message) throws IOException { LOG.info("Create new notebook by JSON {}", message); - NewNotebookRequest request = gson.fromJson(message, NewNotebookRequest.class); - AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); - Note note = notebook.createNote(subject); - List initialParagraphs = request.getParagraphs(); - if (initialParagraphs != null) { - for (NewParagraphRequest paragraphRequest : initialParagraphs) { - Paragraph p = note.addParagraph(); - p.setTitle(paragraphRequest.getTitle()); - p.setText(paragraphRequest.getText()); + String noteName = null; + Note note; + try { + NewNotebookRequest request = gson.fromJson(message, NewNotebookRequest.class); + noteName = request.getName(); + AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); + note = notebook.createNote(subject, noteName); + List initialParagraphs = request.getParagraphs(); + if (initialParagraphs != null) { + for (NewParagraphRequest paragraphRequest : initialParagraphs) { + Paragraph p = note.addParagraph(); + p.setTitle(paragraphRequest.getTitle()); + p.setText(paragraphRequest.getText()); + } } - } - note.addParagraph(); // add one paragraph to the last - String noteName = request.getName(); - if (noteName.isEmpty()) { - noteName = "Note " + note.getId(); - } + note.addParagraph(); // add one paragraph to the last - note.setName(noteName); - note.persist(subject); - notebookServer.broadcastNote(note); - notebookServer.broadcastNoteList(subject); + note.persist(subject); + notebookServer.broadcastNote(note); + notebookServer.broadcastNoteList(subject); + } catch (DuplicateNameException e) { + return new JsonResponse<>(Status.NOT_ACCEPTABLE, "Notebook with name " + noteName + + "already exists").build(); + } return new JsonResponse<>(Status.CREATED, "", note.getId()).build(); } @@ -311,6 +323,7 @@ public Response deleteNote(@PathParam("notebookId") String notebookId) throws IO * @param notebookId ID of Notebook * @return JSON with status.CREATED * @throws IOException, CloneNotSupportedException, IllegalArgumentException + * @throws DuplicateNameException */ @POST @Path("{notebookId}") @@ -318,15 +331,21 @@ public Response deleteNote(@PathParam("notebookId") String notebookId) throws IO public Response cloneNote(@PathParam("notebookId") String notebookId, String message) throws IOException, CloneNotSupportedException, IllegalArgumentException { LOG.info("clone notebook by JSON {}", message); - NewNotebookRequest request = gson.fromJson(message, NewNotebookRequest.class); String newNoteName = null; - if (request != null) { - newNoteName = request.getName(); + Note newNote; + try { + NewNotebookRequest request = gson.fromJson(message, NewNotebookRequest.class); + if (request != null) { + newNoteName = request.getName(); + } + AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); + newNote = notebook.cloneNote(notebookId, newNoteName, subject); + notebookServer.broadcastNote(newNote); + notebookServer.broadcastNoteList(subject); + } catch (DuplicateNameException e) { + return new JsonResponse<>(Status.NOT_ACCEPTABLE, "Note with name " + newNoteName + + "already exists").build(); } - AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); - Note newNote = notebook.cloneNote(notebookId, newNoteName, subject); - notebookServer.broadcastNote(newNote); - notebookServer.broadcastNoteList(subject); return new JsonResponse<>(Status.CREATED, "", newNote.getId()).build(); } diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java index 10c94107700..9ab9b36ed65 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java @@ -28,6 +28,7 @@ import org.apache.zeppelin.display.AngularObject; import org.apache.zeppelin.display.AngularObjectRegistry; import org.apache.zeppelin.display.AngularObjectRegistryListener; +import org.apache.zeppelin.exception.DuplicateNameException; import org.apache.zeppelin.helium.ApplicationEventListener; import org.apache.zeppelin.helium.HeliumPackage; import org.apache.zeppelin.interpreter.InterpreterGroup; @@ -640,20 +641,23 @@ private void createNote(NotebookSocket conn, HashSet userAndRoles, Notebook notebook, Message message) throws IOException { AuthenticationInfo subject = new AuthenticationInfo(message.principal); - Note note = notebook.createNote(subject); - note.addParagraph(); // it's an empty note. so add one paragraph + String noteName = null; if (message != null) { - String noteName = (String) message.get("name"); - if (noteName == null || noteName.isEmpty()){ - noteName = "Note " + note.getId(); - } - note.setName(noteName); + noteName = (String) message.get("name"); } + try { + Note note = notebook.createNote(subject, noteName); + + note.addParagraph(); // it's an empty note. so add one paragraph - note.persist(subject); - addConnectionToNote(note.getId(), (NotebookSocket) conn); - conn.send(serializeMessage(new Message(OP.NEW_NOTE).put("note", note))); - broadcastNoteList(subject); + note.persist(subject); + addConnectionToNote(note.getId(), (NotebookSocket) conn); + conn.send(serializeMessage(new Message(OP.NEW_NOTE).put("note", note))); + broadcastNoteList(subject); + } catch (DuplicateNameException dne) { + conn.send(serializeMessage(new Message(OP.ERROR_DIALOG).put("info", + "Please provide a unique notebook name"))); + } } private void removeNote(NotebookSocket conn, HashSet userAndRoles, @@ -710,31 +714,42 @@ private void updateParagraph(NotebookSocket conn, HashSet userAndRoles, private void cloneNote(NotebookSocket conn, HashSet userAndRoles, Notebook notebook, Message fromMessage) - throws IOException, CloneNotSupportedException { - String noteId = getOpenNoteId(conn); - String name = (String) fromMessage.get("name"); - Note newNote = notebook.cloneNote(noteId, name, new AuthenticationInfo(fromMessage.principal)); - AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal); - addConnectionToNote(newNote.getId(), (NotebookSocket) conn); - conn.send(serializeMessage(new Message(OP.NEW_NOTE).put("note", newNote))); - broadcastNoteList(subject); + throws IOException, CloneNotSupportedException, IllegalArgumentException { + try { + String noteId = getOpenNoteId(conn); + String name = (String) fromMessage.get("name"); + Note newNote = notebook.cloneNote(noteId, name, + new AuthenticationInfo(fromMessage.principal)); + AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal); + addConnectionToNote(newNote.getId(), (NotebookSocket) conn); + conn.send(serializeMessage(new Message(OP.NEW_NOTE).put("note", newNote))); + broadcastNoteList(subject); + } catch (DuplicateNameException dne) { + conn.send(serializeMessage(new Message(OP.ERROR_DIALOG).put("info", + "Please provide a unique notebook name"))); + } } protected Note importNote(NotebookSocket conn, HashSet userAndRoles, Notebook notebook, Message fromMessage) throws IOException { Note note = null; - if (fromMessage != null) { - String noteName = (String) ((Map) fromMessage.get("notebook")).get("name"); - String noteJson = gson.toJson(fromMessage.get("notebook")); - AuthenticationInfo subject = null; - if (fromMessage.principal != null) { - subject = new AuthenticationInfo(fromMessage.principal); + try { + if (fromMessage != null) { + String noteName = (String) ((Map) fromMessage.get("notebook")).get("name"); + String noteJson = gson.toJson(fromMessage.get("notebook")); + AuthenticationInfo subject = null; + if (fromMessage.principal != null) { + subject = new AuthenticationInfo(fromMessage.principal); + } + note = notebook.importNote(noteJson, noteName, subject); + note.persist(subject); + broadcastNote(note); + broadcastNoteList(subject); } - note = notebook.importNote(noteJson, noteName, subject); - note.persist(subject); - broadcastNote(note); - broadcastNoteList(subject); + } catch (DuplicateNameException dne) { + conn.send(serializeMessage(new Message(OP.IMPORT_ERROR_DIALOG).put("info", + "Please provide a unique notebook name"))); } return note; } diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java index eb080fe9605..622bb363b92 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java @@ -462,5 +462,7 @@ protected Matcher isAllowed() { protected Matcher isNotAllowed() { return responsesWith(405); } + + protected Matcher isNotAcceptable() { return responsesWith(406); } } diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java index c767eb05786..498027067f7 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/InterpreterRestApiTest.java @@ -25,6 +25,7 @@ import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.zeppelin.exception.DuplicateNameException; import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.Paragraph; @@ -47,6 +48,7 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class InterpreterRestApiTest extends AbstractTestRestApi { Gson gson = new Gson(); + private final String EMPTY_STRING = ""; @BeforeClass public static void init() throws Exception { @@ -129,9 +131,9 @@ public void testSettingsCreateWithEmptyJson() throws IOException { } @Test - public void testInterpreterAutoBinding() throws IOException { + public void testInterpreterAutoBinding() throws IOException, DuplicateNameException { // create note - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); // check interpreter is binded GetMethod get = httpGet("/notebook/interpreter/bind/" + note.getId()); @@ -148,9 +150,9 @@ public void testInterpreterAutoBinding() throws IOException { } @Test - public void testInterpreterRestart() throws IOException, InterruptedException { + public void testInterpreterRestart() throws IOException, InterruptedException, DuplicateNameException { // create new note - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); note.addParagraph(); Paragraph p = note.getLastParagraph(); Map config = p.getConfig(); diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java index d7f55f54744..0d2054295fc 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRestApiTest.java @@ -24,6 +24,7 @@ import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.zeppelin.exception.DuplicateNameException; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.NotebookAuthorization; import org.apache.zeppelin.notebook.NotebookAuthorizationInfoSaving; @@ -49,6 +50,7 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class NotebookRestApiTest extends AbstractTestRestApi { Gson gson = new Gson(); + private final String EMPTY_STRING = ""; @BeforeClass public static void init() throws Exception { @@ -61,8 +63,8 @@ public static void destroy() throws Exception { } @Test - public void testPermissions() throws IOException { - Note note1 = ZeppelinServer.notebook.createNote(null); + public void testPermissions() throws IOException, DuplicateNameException { + Note note1 = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); // Set only readers String jsonRequest = "{\"readers\":[\"admin-team\"],\"owners\":[]," + "\"writers\":[]}"; @@ -85,7 +87,7 @@ public void testPermissions() throws IOException { get.releaseConnection(); - Note note2 = ZeppelinServer.notebook.createNote(null); + Note note2 = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); // Set only writers jsonRequest = "{\"readers\":[],\"owners\":[]," + "\"writers\":[\"admin-team\"]}"; @@ -125,8 +127,8 @@ public void testPermissions() throws IOException { } @Test - public void testGetNoteParagraphJobStatus() throws IOException { - Note note1 = ZeppelinServer.notebook.createNote(null); + public void testGetNoteParagraphJobStatus() throws IOException, DuplicateNameException { + Note note1 = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); note1.addParagraph(); String paragraphId = note1.getLastParagraph().getId(); @@ -147,8 +149,8 @@ public void testGetNoteParagraphJobStatus() throws IOException { } @Test - public void testCloneNotebook() throws IOException { - Note note1 = ZeppelinServer.notebook.createNote(null); + public void testCloneNotebook() throws IOException, DuplicateNameException { + Note note1 = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); PostMethod post = httpPost("/notebook/" + note1.getId(), ""); LOG.info("testCloneNotebook response\n" + post.getResponseBodyAsString()); assertThat(post, isCreated()); @@ -171,6 +173,33 @@ public void testCloneNotebook() throws IOException { ZeppelinServer.notebook.removeNote(clonedNotebookId, null); } + + @Test + public void testCloneNotebookWithSameName() throws IOException, DuplicateNameException { + String noteName = "Test_Note"; + String jsonRequest = "{\"name\":\"" + noteName + "\"}"; + Note note1 = ZeppelinServer.notebook.createNote(null, noteName); + PostMethod post = httpPost("/notebook/" + note1.getId(), jsonRequest); + LOG.info("testCloneNotebookWithSameName response\n" + post.getResponseBodyAsString()); + assertThat(post, isNotAcceptable()); + + //cleanup + ZeppelinServer.notebook.removeNote(note1.getId(), null); + } + + @Test + public void testCreateNotebookWithSameName() throws IOException, DuplicateNameException { + String noteName = "Test_Note"; + Note note1 = ZeppelinServer.notebook.createNote(null, noteName); + String jsonRequest = "{\"name\":\"" + noteName + "\"}"; + PostMethod post = httpPost("/notebook/", jsonRequest); + LOG.info("testCloneNotebookWithSameName response\n" + post.getResponseBodyAsString()); + assertThat(post, isNotAcceptable()); + post.releaseConnection(); + + //cleanup + ZeppelinServer.notebook.removeNote(note1.getId(), null); + } } diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java index 4390d74b495..9a450248882 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java @@ -27,6 +27,7 @@ import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.lang3.StringUtils; +import org.apache.zeppelin.exception.DuplicateNameException; import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.Paragraph; @@ -50,6 +51,7 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class ZeppelinRestApiTest extends AbstractTestRestApi { Gson gson = new Gson(); + private final String EMPTY_STRING = ""; @BeforeClass public static void init() throws Exception { @@ -74,10 +76,10 @@ public void getApiRoot() throws IOException { } @Test - public void testGetNotebookInfo() throws IOException { + public void testGetNotebookInfo() throws IOException, DuplicateNameException { LOG.info("testGetNotebookInfo"); // Create note to get info - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); assertNotNull("can't create new note", note); note.setName("note"); Paragraph paragraph = note.addParagraph(); @@ -186,10 +188,10 @@ private void testNotebookCreate(String noteName) throws IOException { } @Test - public void testDeleteNote() throws IOException { + public void testDeleteNote() throws IOException, DuplicateNameException { LOG.info("testDeleteNote"); //Create note and get ID - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); String noteId = note.getId(); testDeleteNotebook(noteId); } @@ -203,9 +205,9 @@ public void testDeleteNoteBadId() throws IOException { @Test - public void testExportNotebook() throws IOException { + public void testExportNotebook() throws IOException, DuplicateNameException { LOG.info("testExportNotebook"); - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); assertNotNull("can't create new note", note); note.setName("source note for export"); Paragraph paragraph = note.addParagraph(); @@ -233,12 +235,12 @@ public void testExportNotebook() throws IOException { } @Test - public void testImportNotebook() throws IOException { + public void testImportNotebook() throws IOException, DuplicateNameException { Map resp; String noteName = "source note for import"; LOG.info("testImortNotebook"); // create test notebook - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); assertNotNull("can't create new note", note); note.setName(noteName); Paragraph paragraph = note.addParagraph(); @@ -252,20 +254,9 @@ public void testImportNotebook() throws IOException { String oldJson = getNoteContent(sourceNoteID); // call notebook post PostMethod importPost = httpPost("/notebook/import/", oldJson); - assertThat(importPost, isCreated()); - resp = - gson.fromJson(importPost.getResponseBodyAsString(), - new TypeToken>() {}.getType()); - String importId = (String) resp.get("body"); - - assertNotNull("Did not get back a notebook id in body", importId); - Note newNote = ZeppelinServer.notebook.getNote(importId); - assertEquals("Compare note names", noteName, newNote.getName()); - assertEquals("Compare paragraphs count", note.getParagraphs().size(), newNote.getParagraphs() - .size()); + assertThat(importPost, isNotAcceptable()); // cleanup ZeppelinServer.notebook.removeNote(note.getId(), null); - ZeppelinServer.notebook.removeNote(newNote.getId(), null); importPost.releaseConnection(); } @@ -297,10 +288,11 @@ private void testDeleteNotebook(String notebookId) throws IOException { } @Test - public void testCloneNotebook() throws IOException, CloneNotSupportedException, IllegalArgumentException { + public void testCloneNotebook() throws IOException, CloneNotSupportedException, + IllegalArgumentException, DuplicateNameException { LOG.info("testCloneNotebook"); // Create note to clone - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); assertNotNull("can't create new note", note); note.setName("source note for clone"); Paragraph paragraph = note.addParagraph(); @@ -346,10 +338,10 @@ public void testListNotebooks() throws IOException { } @Test - public void testNoteJobs() throws IOException, InterruptedException { + public void testNoteJobs() throws IOException, InterruptedException, DuplicateNameException { LOG.info("testNoteJobs"); // Create note to run test. - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); assertNotNull("can't create new note", note); note.setName("note for run test"); Paragraph paragraph = note.addParagraph(); @@ -401,10 +393,11 @@ public void testNoteJobs() throws IOException, InterruptedException { } @Test - public void testGetNotebookJob() throws IOException, InterruptedException { + public void testGetNotebookJob() throws IOException, InterruptedException, + DuplicateNameException { LOG.info("testGetNotebookJob"); // Create note to run test. - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); assertNotNull("can't create new note", note); note.setName("note for run test"); Paragraph paragraph = note.addParagraph(); @@ -454,10 +447,11 @@ public void testGetNotebookJob() throws IOException, InterruptedException { } @Test - public void testRunParagraphWithParams() throws IOException, InterruptedException { + public void testRunParagraphWithParams() throws IOException, InterruptedException, + DuplicateNameException { LOG.info("testRunParagraphWithParams"); // Create note to run test. - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); assertNotNull("can't create new note", note); note.setName("note for run test"); Paragraph paragraph = note.addParagraph(); @@ -499,9 +493,9 @@ public void testRunParagraphWithParams() throws IOException, InterruptedExceptio } @Test - public void testCronJobs() throws InterruptedException, IOException{ + public void testCronJobs() throws InterruptedException, IOException, DuplicateNameException{ // create a note and a paragraph - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); note.setName("note for run test"); Paragraph paragraph = note.addParagraph(); @@ -549,8 +543,8 @@ public void testCronJobs() throws InterruptedException, IOException{ } @Test - public void testRegressionZEPPELIN_527() throws IOException { - Note note = ZeppelinServer.notebook.createNote(null); + public void testRegressionZEPPELIN_527() throws IOException, DuplicateNameException { + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); note.setName("note for run test"); Paragraph paragraph = note.addParagraph(); @@ -571,8 +565,8 @@ public void testRegressionZEPPELIN_527() throws IOException { } @Test - public void testInsertParagraph() throws IOException { - Note note = ZeppelinServer.notebook.createNote(null); + public void testInsertParagraph() throws IOException, DuplicateNameException { + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); String jsonRequest = "{\"title\": \"title1\", \"text\": \"text1\"}"; PostMethod post = httpPost("/notebook/" + note.getId() + "/paragraph", jsonRequest); @@ -611,8 +605,8 @@ public void testInsertParagraph() throws IOException { } @Test - public void testGetParagraph() throws IOException { - Note note = ZeppelinServer.notebook.createNote(null); + public void testGetParagraph() throws IOException, DuplicateNameException { + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); Paragraph p = note.addParagraph(); p.setTitle("hello"); @@ -640,8 +634,8 @@ public void testGetParagraph() throws IOException { } @Test - public void testMoveParagraph() throws IOException { - Note note = ZeppelinServer.notebook.createNote(null); + public void testMoveParagraph() throws IOException, DuplicateNameException { + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); Paragraph p = note.addParagraph(); p.setTitle("title1"); @@ -672,8 +666,8 @@ public void testMoveParagraph() throws IOException { } @Test - public void testDeleteParagraph() throws IOException { - Note note = ZeppelinServer.notebook.createNote(null); + public void testDeleteParagraph() throws IOException, DuplicateNameException { + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); Paragraph p = note.addParagraph(); p.setTitle("title1"); @@ -693,7 +687,7 @@ public void testDeleteParagraph() throws IOException { } @Test - public void testSearch() throws IOException { + public void testSearch() throws IOException, DuplicateNameException { Map body; GetMethod getSecurityTicket = httpGet("/security/ticket"); @@ -705,12 +699,12 @@ public void testSearch() throws IOException { String username = body.get("principal"); getSecurityTicket.releaseConnection(); - Note note1 = ZeppelinServer.notebook.createNote(null); + Note note1 = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); String jsonRequest = "{\"title\": \"title1\", \"text\": \"ThisIsToTestSearchMethodWithPermissions 1\"}"; PostMethod postNotebookText = httpPost("/notebook/" + note1.getId() + "/paragraph", jsonRequest); postNotebookText.releaseConnection(); - Note note2 = ZeppelinServer.notebook.createNote(null); + Note note2 = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); jsonRequest = "{\"title\": \"title1\", \"text\": \"ThisIsToTestSearchMethodWithPermissions 2\"}"; postNotebookText = httpPost("/notebook/" + note2.getId() + "/paragraph", jsonRequest); postNotebookText.releaseConnection(); @@ -757,8 +751,8 @@ public void testSearch() throws IOException { } @Test - public void testTitleSearch() throws IOException { - Note note = ZeppelinServer.notebook.createNote(null); + public void testTitleSearch() throws IOException, DuplicateNameException { + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); String jsonRequest = "{\"title\": \"testTitleSearchOfParagraph\", \"text\": \"ThisIsToTestSearchMethodWithTitle \"}"; PostMethod postNotebookText = httpPost("/notebook/" + note.getId() + "/paragraph", jsonRequest); postNotebookText.releaseConnection(); diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java index 5084ae73719..dede5979761 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java @@ -25,6 +25,7 @@ import java.util.Map; import org.apache.commons.io.FileUtils; +import org.apache.zeppelin.exception.DuplicateNameException; import org.apache.zeppelin.interpreter.InterpreterResult; import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.notebook.Note; @@ -43,6 +44,7 @@ */ public class ZeppelinSparkClusterTest extends AbstractTestRestApi { Gson gson = new Gson(); + private final String EMPTY_STRING = ""; @BeforeClass public static void init() throws Exception { @@ -67,9 +69,9 @@ private void waitForFinish(Paragraph p) { } @Test - public void basicRDDTransformationAndActionTest() throws IOException { + public void basicRDDTransformationAndActionTest() throws IOException, DuplicateNameException { // create new note - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); // run markdown paragraph, again Paragraph p = note.addParagraph(); @@ -85,9 +87,9 @@ public void basicRDDTransformationAndActionTest() throws IOException { } @Test - public void sparkSQLTest() throws IOException { + public void sparkSQLTest() throws IOException, DuplicateNameException { // create new note - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); int sparkVersion = getSparkVersionNumber(note); // DataFrame API is available from spark 1.3 if (sparkVersion >= 13) { @@ -136,9 +138,9 @@ public void sparkSQLTest() throws IOException { } @Test - public void sparkRTest() throws IOException { + public void sparkRTest() throws IOException, DuplicateNameException { // create new note - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); int sparkVersion = getSparkVersionNumber(note); if (isSparkR() && sparkVersion >= 14) { // sparkr supported from 1.4.0 @@ -175,9 +177,9 @@ public void sparkRTest() throws IOException { } @Test - public void pySparkTest() throws IOException { + public void pySparkTest() throws IOException, DuplicateNameException { // create new note - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); note.setName("note"); int sparkVersion = getSparkVersionNumber(note); @@ -265,9 +267,9 @@ public void pySparkTest() throws IOException { } @Test - public void pySparkAutoConvertOptionTest() throws IOException { + public void pySparkAutoConvertOptionTest() throws IOException, DuplicateNameException { // create new note - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); note.setName("note"); int sparkVersionNumber = getSparkVersionNumber(note); @@ -295,9 +297,9 @@ public void pySparkAutoConvertOptionTest() throws IOException { } @Test - public void zRunTest() throws IOException { + public void zRunTest() throws IOException, DuplicateNameException { // create new note - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); Paragraph p0 = note.addParagraph(); Map config0 = p0.getConfig(); config0.put("enabled", true); @@ -327,9 +329,9 @@ public void zRunTest() throws IOException { } @Test - public void pySparkDepLoaderTest() throws IOException { + public void pySparkDepLoaderTest() throws IOException, DuplicateNameException { // create new note - Note note = ZeppelinServer.notebook.createNote(null); + Note note = ZeppelinServer.notebook.createNote(null, EMPTY_STRING); int sparkVersionNumber = getSparkVersionNumber(note); if (isPyspark() && sparkVersionNumber >= 14) { diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java index 01a24e2e1de..fb1d1bbca65 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java @@ -21,6 +21,7 @@ import org.apache.zeppelin.display.AngularObject; import org.apache.zeppelin.display.AngularObjectBuilder; import org.apache.zeppelin.display.AngularObjectRegistry; +import org.apache.zeppelin.exception.DuplicateNameException; import org.apache.zeppelin.interpreter.InterpreterGroup; import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry; @@ -92,9 +93,9 @@ public void checkInvalidOrigin(){ } @Test - public void testMakeSureNoAngularObjectBroadcastToWebsocketWhoFireTheEvent() throws IOException { + public void testMakeSureNoAngularObjectBroadcastToWebsocketWhoFireTheEvent() throws IOException, DuplicateNameException { // create a notebook - Note note1 = notebook.createNote(null); + Note note1 = notebook.createNote(null, ""); // get reference to interpreterGroup InterpreterGroup interpreterGroup = null; @@ -148,7 +149,7 @@ public void testMakeSureNoAngularObjectBroadcastToWebsocketWhoFireTheEvent() thr } @Test - public void testImportNotebook() throws IOException { + public void testImportNotebook() throws IOException, DuplicateNameException { String msg = "{\"op\":\"IMPORT_NOTE\",\"data\":" + "{\"notebook\":{\"paragraphs\": [{\"text\": \"Test " + "paragraphs import\",\"config\":{},\"settings\":{}}]," + diff --git a/zeppelin-web/src/components/noteName-create/note-name-dialog.html b/zeppelin-web/src/components/noteName-create/note-name-dialog.html index 1f90085a491..f2afb2bcae0 100644 --- a/zeppelin-web/src/components/noteName-create/note-name-dialog.html +++ b/zeppelin-web/src/components/noteName-create/note-name-dialog.html @@ -29,11 +29,12 @@ id="noteName" ng-model="note.notename" ng-enter="notenamectrl.handleNameEnter()"/> Use '/' to create folders. Example: /NoteDirA/Notebook1 +
Please provide a unique Notebook name