From dfba1870eb86d2e2667c7f5c912cfd6a159064e2 Mon Sep 17 00:00:00 2001 From: Matthew Matz <19737272+MatzElectronics@users.noreply.github.com> Date: Mon, 10 Dec 2018 16:52:54 -0800 Subject: [PATCH 01/69] Fix UI button to pretty-format prop C code --- src/main/webapp/editor/blocklyc.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/editor/blocklyc.jsp b/src/main/webapp/editor/blocklyc.jsp index 28c92282..ad828a92 100644 --- a/src/main/webapp/editor/blocklyc.jsp +++ b/src/main/webapp/editor/blocklyc.jsp @@ -152,7 +152,7 @@     - + From 0335399bae0f620086b58c471b32957f2f24020c Mon Sep 17 00:00:00 2001 From: Matthew Matz <19737272+MatzElectronics@users.noreply.github.com> Date: Mon, 10 Dec 2018 16:54:54 -0800 Subject: [PATCH 02/69] Made update in the wrong branch - reverting --- src/main/webapp/editor/blocklyc.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/editor/blocklyc.jsp b/src/main/webapp/editor/blocklyc.jsp index ad828a92..28c92282 100644 --- a/src/main/webapp/editor/blocklyc.jsp +++ b/src/main/webapp/editor/blocklyc.jsp @@ -152,7 +152,7 @@     - + From c0a49a94578cca03778f5e7e6cecf1e34cc7cd57 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 30 Jan 2019 00:21:02 -0800 Subject: [PATCH 03/69] #1638 - Replaced local URI with URL to help system on learn.parallax.com. --- src/main/webapp/editor/blocklyc.jsp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/webapp/editor/blocklyc.jsp b/src/main/webapp/editor/blocklyc.jsp index 6f7a599c..b65cc454 100644 --- a/src/main/webapp/editor/blocklyc.jsp +++ b/src/main/webapp/editor/blocklyc.jsp @@ -171,7 +171,8 @@

  • -
  • +
  • +

  • From d072cfd3b2b8f690cc3f23e266f94d4a9b0dff55 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 30 Jan 2019 00:27:30 -0800 Subject: [PATCH 04/69] Issue #1638 - Update Help URI to point to help system hosted on learn.parallax.com. --- src/main/webapp/editor/blocklyc.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/editor/blocklyc.jsp b/src/main/webapp/editor/blocklyc.jsp index 6f7a599c..7fd53794 100644 --- a/src/main/webapp/editor/blocklyc.jsp +++ b/src/main/webapp/editor/blocklyc.jsp @@ -171,7 +171,7 @@

  • -
  • +

  • From 424b39df082a6da6bd52607689bb0a5902f14b52 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 30 Jan 2019 01:02:47 -0800 Subject: [PATCH 05/69] #1641 - Depricated the embedded help system. Plan to remove the related source file in version 1.3. --- .../parallax/server/blocklyprop/config/ServletsModule.java | 5 ----- .../com/parallax/server/blocklyprop/config/SetupConfig.java | 5 ----- .../server/blocklyprop/servlets/HelpSearchServlet.java | 4 ++++ .../parallax/server/blocklyprop/servlets/HelpServlet.java | 3 +++ .../server/blocklyprop/utils/HelpFileInitializer.java | 3 +++ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/config/ServletsModule.java b/src/main/java/com/parallax/server/blocklyprop/config/ServletsModule.java index 5ff2e753..66eca1ea 100644 --- a/src/main/java/com/parallax/server/blocklyprop/config/ServletsModule.java +++ b/src/main/java/com/parallax/server/blocklyprop/config/ServletsModule.java @@ -27,8 +27,6 @@ import com.parallax.server.blocklyprop.servlets.PrivacyPolicyServlet; import com.parallax.server.blocklyprop.servlets.ConfirmRequestServlet; import com.parallax.server.blocklyprop.servlets.ConfirmServlet; -import com.parallax.server.blocklyprop.servlets.HelpSearchServlet; -import com.parallax.server.blocklyprop.servlets.HelpServlet; import com.parallax.server.blocklyprop.servlets.NewOAuthUserServlet; import com.parallax.server.blocklyprop.servlets.OAuthGoogleServlet; import com.parallax.server.blocklyprop.servlets.PasswordResetRequestServlet; @@ -130,9 +128,6 @@ protected void configureServlets() { serve("/public/clientinstructions").with(TextileClientInstructionsServlet.class); serve("/public/changelog").with(TextileChangeLogServlet.class); - // Help - serve("/public/help").with(HelpServlet.class); - serve("/public/helpsearch").with(HelpSearchServlet.class); // OAuth serve("/oauth/newuser").with(NewOAuthUserServlet.class); diff --git a/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java b/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java index f4e28f13..10bafdf9 100644 --- a/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java +++ b/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java @@ -29,7 +29,6 @@ import com.parallax.server.blocklyprop.SessionData; import com.parallax.server.blocklyprop.jsp.Properties; import com.parallax.server.blocklyprop.monitoring.Monitor; -import com.parallax.server.blocklyprop.utils.HelpFileInitializer; import java.sql.Driver; import java.sql.DriverManager; import java.util.Enumeration; @@ -40,9 +39,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -// import java.sql.SQLException; -// import ch.qos.logback.classic.LoggerContext; - /** * @@ -73,7 +69,6 @@ protected void configure() { bind(SessionData.class); bind(Properties.class).asEagerSingleton(); - bind(HelpFileInitializer.class).asEagerSingleton(); bind(Monitor.class).asEagerSingleton(); // Configure the backend data store diff --git a/src/main/java/com/parallax/server/blocklyprop/servlets/HelpSearchServlet.java b/src/main/java/com/parallax/server/blocklyprop/servlets/HelpSearchServlet.java index 0d5b1ba7..644048f5 100644 --- a/src/main/java/com/parallax/server/blocklyprop/servlets/HelpSearchServlet.java +++ b/src/main/java/com/parallax/server/blocklyprop/servlets/HelpSearchServlet.java @@ -33,7 +33,11 @@ /** * * @author Michel + * + * @deprecated The help system has been moved to learn.parallax.com + * */ +@Deprecated @Singleton public class HelpSearchServlet extends HttpServlet { diff --git a/src/main/java/com/parallax/server/blocklyprop/servlets/HelpServlet.java b/src/main/java/com/parallax/server/blocklyprop/servlets/HelpServlet.java index cf1f42e8..55a85432 100644 --- a/src/main/java/com/parallax/server/blocklyprop/servlets/HelpServlet.java +++ b/src/main/java/com/parallax/server/blocklyprop/servlets/HelpServlet.java @@ -23,7 +23,10 @@ /** * * @author Michel + * + * @deprecated Help system is now hosted on learn.parallax.com */ +@Deprecated @Singleton public class HelpServlet extends HttpServlet { diff --git a/src/main/java/com/parallax/server/blocklyprop/utils/HelpFileInitializer.java b/src/main/java/com/parallax/server/blocklyprop/utils/HelpFileInitializer.java index efbdfede..256707bb 100644 --- a/src/main/java/com/parallax/server/blocklyprop/utils/HelpFileInitializer.java +++ b/src/main/java/com/parallax/server/blocklyprop/utils/HelpFileInitializer.java @@ -37,7 +37,10 @@ /** * * @author Michel + * + * @deprecated The help system has been moved to learn.parallax.com */ +@Deprecated @Singleton public class HelpFileInitializer { From c135b92195f891279f0ee0373106cbf5707e12a3 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 30 Jan 2019 01:03:46 -0800 Subject: [PATCH 06/69] Corrected an error in the HTML declaration. --- src/main/webapp/my/projects.jsp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/webapp/my/projects.jsp b/src/main/webapp/my/projects.jsp index 1f4af0d9..303cec9a 100644 --- a/src/main/webapp/my/projects.jsp +++ b/src/main/webapp/my/projects.jsp @@ -5,8 +5,7 @@ --%> <%@page contentType="text/html" pageEncoding="UTF-8"%> <%@ include file="/WEB-INF/includes/include.jsp"%> - - + From 8ae7a5305f08d76643757740b0e4d8ed9e2bf59d Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 1 Feb 2019 11:26:42 -0800 Subject: [PATCH 07/69] Clean up various warning messages. --- .../WEB-INF/includes/pageparts/menu.jsp | 106 +++++++++++------- src/main/webapp/WEB-INF/servlet/index.jsp | 29 ++++- 2 files changed, 91 insertions(+), 44 deletions(-) diff --git a/src/main/webapp/WEB-INF/includes/pageparts/menu.jsp b/src/main/webapp/WEB-INF/includes/pageparts/menu.jsp index efd2df01..91605ca3 100644 --- a/src/main/webapp/WEB-INF/includes/pageparts/menu.jsp +++ b/src/main/webapp/WEB-INF/includes/pageparts/menu.jsp @@ -1,7 +1,30 @@ +<%-- + ~ Copyright (c) 2019 Parallax Inc. + ~ + ~ Permission is hereby granted, free of charge, to any person obtaining a copy of this software + ~ and associated documentation files (the “Software”), to deal in the Software without + ~ restriction, including without limitation the rights to use, copy, modify, merge, publish, + ~ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + ~ Software is furnished to do so, subject to the following conditions: + ~ + ~ The above copyright notice and this permission notice shall be included in all copies or + ~ substantial portions of the Software. + ~ + ~ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ~ SOFTWARE. + --%> + <%-- Document : menu Created on : 4-nov-2015, 20:39:22 Author : Michel + + Display the horizontal menu across the top of the banner --%> <%@page contentType="text/html" pageEncoding="UTF-8"%> <%@ include file="/WEB-INF/includes/include.jsp"%> @@ -18,51 +41,53 @@ - - <%@ include file="/WEB-INF/includes/pageparts/footer.jsp"%> - \ No newline at end of file diff --git a/src/main/webapp/my/projects.jsp b/src/main/webapp/my/projects.jsp index 303cec9a..c98da1c8 100644 --- a/src/main/webapp/my/projects.jsp +++ b/src/main/webapp/my/projects.jsp @@ -6,6 +6,7 @@ <%@page contentType="text/html" pageEncoding="UTF-8"%> <%@ include file="/WEB-INF/includes/include.jsp"%> + @@ -29,32 +30,25 @@ + My Projects - <%@ include file="/WEB-INF/includes/pageparts/menu.jsp"%> -
    -

    - -
    - - <%@ include file="/WEB-INF/includes/pageparts/footer.jsp"%> - \ No newline at end of file diff --git a/src/main/webapp/projects.jsp b/src/main/webapp/projects.jsp index 991e687f..ed6df55a 100644 --- a/src/main/webapp/projects.jsp +++ b/src/main/webapp/projects.jsp @@ -1,5 +1,5 @@ <%-- - ~ Copyright (c) 2018 Parallax Inc. + ~ Copyright (c) 2019 Parallax Inc. ~ ~ Permission is hereby granted, free of charge, to any person obtaining a copy of this software ~ and associated documentation files (the “Software”), to deal in the Software without @@ -20,9 +20,11 @@ --%> <%-- - Document : projects + Document : projects.jsp Created on : 24-mei-2015, 18:41:02 Author : Michel + Notes : Display the community projects in a table format + TODO: UI-Convert to client side content with project data sourced from server endpoint. --%> <%@page contentType="text/html" pageEncoding="UTF-8"%> From 74c132b631fcfbb232c829824cb6621ea5910d3e Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:18:15 -0800 Subject: [PATCH 21/69] Minor changes to documentation and license date. Refactor variable req to request to improve code readability. --- .../services/AuthenticationService.java | 33 ++++++++-- .../impl/AuthenticationServiceImpl.java | 6 +- .../servlets/AuthenticationServlet.java | 61 ++++++++----------- 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/services/AuthenticationService.java b/src/main/java/com/parallax/server/blocklyprop/services/AuthenticationService.java index 2d5fbfbc..61b4faab 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/AuthenticationService.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/AuthenticationService.java @@ -1,18 +1,43 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.services; import com.parallax.client.cloudsession.objects.User; /** + * Interface for user authentication * * @author Michel */ public interface AuthenticationService { - public User authenticate(String username, String password); + /** + * Process a user authentication request + * + * @param username - user email address + * @param password - password submitted in the request + * + * @return - a user profile object if successful, otherwise return a null + */ + User authenticate(String username, String password); } diff --git a/src/main/java/com/parallax/server/blocklyprop/services/impl/AuthenticationServiceImpl.java b/src/main/java/com/parallax/server/blocklyprop/services/impl/AuthenticationServiceImpl.java index 56bfb8ec..c7d44811 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/impl/AuthenticationServiceImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/impl/AuthenticationServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Parallax Inc. + * Copyright (c) 2019 Parallax Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the “Software”), to deal in the Software without @@ -25,18 +25,22 @@ import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.inject.persist.Transactional; + import com.parallax.client.cloudsession.CloudSessionUserService; import com.parallax.client.cloudsession.exceptions.ServerException; import com.parallax.client.cloudsession.exceptions.UnknownUserException; import com.parallax.client.cloudsession.objects.User; import com.parallax.server.blocklyprop.services.AuthenticationService; import com.parallax.server.blocklyprop.services.TokenGeneratorService; + import javax.servlet.http.HttpSession; import org.apache.commons.configuration.Configuration; + import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/parallax/server/blocklyprop/servlets/AuthenticationServlet.java b/src/main/java/com/parallax/server/blocklyprop/servlets/AuthenticationServlet.java index 91670fe6..ab1f9f18 100644 --- a/src/main/java/com/parallax/server/blocklyprop/servlets/AuthenticationServlet.java +++ b/src/main/java/com/parallax/server/blocklyprop/servlets/AuthenticationServlet.java @@ -27,11 +27,9 @@ import com.parallax.client.cloudsession.objects.User; import com.parallax.server.blocklyprop.services.AuthenticationService; import java.io.IOException; -import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.configuration.Configuration; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.web.util.SavedRequest; import org.apache.shiro.web.util.WebUtils; @@ -50,39 +48,16 @@ @Singleton public class AuthenticationServlet extends HttpServlet { - /** - * Handle for any logging activity - */ + // Handle for any logging activity private final Logger LOG = LoggerFactory.getLogger(AuthenticationServlet.class); - - /** - * Application configuration settings - */ - private Configuration configuration; - - - /** - * An instance of this class - */ + //An instance of this class private AuthenticationService authenticationService; - - /** - * Initialize the application configuration - * - * @param configuration - */ - @Inject - public void setConfiguration(Configuration configuration) { - this.configuration = configuration; - } - - /** * Initialize an instance of the Authentication service * - * @param authenticationService + * @param authenticationService - inject an authentication service object */ @Inject public void setAuthenticationService(AuthenticationService authenticationService) { @@ -91,17 +66,25 @@ public void setAuthenticationService(AuthenticationService authenticationService } - + /** + * Process the authentication post request + * + * @param request - Http request object + * @param resp - Http response returned to the caller + * + * @throws IOException - an I/O error was detected + */ @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { + protected void doPost(HttpServletRequest request, HttpServletResponse resp) + throws IOException { LOG.info("REST:/authenticate/ Post request received"); + // Set the content type of the Http response resp.setContentType("application/json"); - String username = req.getParameter("username"); - String password = req.getParameter("password"); + String username = request.getParameter("username"); + String password = request.getParameter("password"); LOG.info("Authenticating user '{}'", username); @@ -120,14 +103,21 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) } if (user != null) { - SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(req); + // Authentication succeeded + + /* A SavedRequest object maintains request data for a request that was + * redirected, so that after authentication the user can be redirected + * to the originally requested page. + */ + SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request); + if (savedRequest != null) { LOG.info("Redirecting to third-part authenticator"); resp.sendRedirect(savedRequest.getRequestUrl()); } else { - JsonObject response = new JsonObject(); response.addProperty("success", true); + JsonObject userJson = new JsonObject(); userJson.addProperty("id-user", user.getId()); userJson.addProperty("screenname", user.getScreenname()); @@ -153,5 +143,4 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) resp.getWriter().write(response.toString()); } } - } From 3aab6b371751483a845447652ef3c7919a74f913 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:21:00 -0800 Subject: [PATCH 22/69] Add inline documentation. Expand SQL statements to improve readability. --- .../server/blocklyprop/db/dao/UserDao.java | 47 ++++++-- .../blocklyprop/db/dao/impl/UserDaoImpl.java | 100 +++++++++++++----- 2 files changed, 114 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/UserDao.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/UserDao.java index e199c047..779246b9 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/UserDao.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/UserDao.java @@ -18,9 +18,24 @@ public interface UserDao { @Deprecated UserRecord create(Long idCloudSession); - + + /** + * Update the blockly user screen name + * + * @param idUser - is the long integer id for the blockly user record + * @param screenName - is ghe new screen name to store in the user record + */ + void updateScreenName(Long idUser, String screenName); + + /** + * + * @param idCloudSession + * @param screenName + * @return + */ UserRecord create(Long idCloudSession, String screenName); + /** * Retrieve a BP user record * @@ -38,16 +53,34 @@ public interface UserDao { */ UserRecord getUser(Long idCloudSession, String screenName); - + /** + * + * @return + */ List getAll(); + /** + * + * @param idUser + * @param roles + */ void setRoles(Long idUser, Set roles); + /** + * + * @param idUser + * @return + */ List getRoles(Long idUser); - Long getUserIdForCloudSessionUserId(Long id); - - @Deprecated - public void updateScreenname(Long idUser, String screenname); - + /** + * Obtain the blocklyprop user ID from the supplied cloud session user id + * + * @param idCloudSession + * The user profile ID + * + * @return + * Returns a Long integer blocklyprop user ID if successful, otherwise returns zero + */ + Long getUserIdForCloudSessionUserId(Long idCloudSession); } diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/UserDaoImpl.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/UserDaoImpl.java index ef0e7814..be056238 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/UserDaoImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/UserDaoImpl.java @@ -125,18 +125,29 @@ public UserRecord create(Long idCloudSession, String screenName) { return record; } - @Override public List getAll() { - return create.selectFrom(Tables.USER).fetch(); + return create + .selectFrom(Tables.USER) + .fetch(); } + /** + * Get a Blockly UserRecord + * + * @param idUser + * Long integer ID of the user record to retrieve + * + * @return + * Returns a UserRecord object if successful, otherwise returns a null + */ @Override public UserRecord getUser(Long idUser) { return create .selectFrom(Tables.USER) - .where(Tables.USER.ID.equal(idUser)) + .where(Tables.USER.ID + .equal(idUser)) .fetchOne(); } @@ -145,8 +156,10 @@ public UserRecord getUser(Long idCloudSession, String screenName) { // Obtain the BP user id from the CS user id return create .selectFrom(Tables.USER) - .where(Tables.USER.IDCLOUDSESSION.eq(idCloudSession)) - .and(Tables.USER.SCREENNAME.eq(screenName)) + .where(Tables.USER.IDCLOUDSESSION + .eq(idCloudSession)) + .and(Tables.USER.SCREENNAME + .eq(screenName)) .fetchOne(); } @@ -167,30 +180,45 @@ public void setRoles(Long idUser, Set roles) { if (!roles.contains(roleRecord.getName())) { create .delete(Tables.SEC_USER_ROLE) - .where(Tables.SEC_USER_ROLE.ID_USER.equal(idUser)) - .and(Tables.SEC_USER_ROLE.ID_ROLE.equal(roleRecord.getId())) + .where(Tables.SEC_USER_ROLE.ID_USER + .equal(idUser)) + .and(Tables.SEC_USER_ROLE.ID_ROLE + .equal(roleRecord.getId())) .execute(); } } for (Role role : roles) { if (!currentAssignedRoles.getValues(Tables.SEC_ROLE.NAME).contains(role)) { - Long idRole = create.select(Tables.SEC_ROLE.ID).from(Tables.SEC_ROLE).where(Tables.SEC_ROLE.NAME.equal(role)).fetchOne(Tables.SEC_ROLE.ID); + Long idRole = create + .select(Tables.SEC_ROLE.ID) + .from(Tables.SEC_ROLE) + .where(Tables.SEC_ROLE.NAME.equal(role)) + .fetchOne(Tables.SEC_ROLE.ID); + if (idRole == null || idRole == 0) { SecRoleRecord roleRecord = createRole(role); idRole = roleRecord.getId(); } - create.insertInto(Tables.SEC_USER_ROLE, Tables.SEC_USER_ROLE.ID_USER, Tables.SEC_USER_ROLE.ID_ROLE) - .values(idUser, idRole).execute(); + create.insertInto(Tables.SEC_USER_ROLE, Tables.SEC_USER_ROLE.ID_USER, Tables.SEC_USER_ROLE.ID_ROLE) + .values(idUser, idRole) + .execute(); } } } private Result getRawRoles(Long idUser) { - Result currentAssignedRoles = create.select(Tables.SEC_ROLE.ID, Tables.SEC_ROLE.NAME).from(Tables.SEC_ROLE) - .join(Tables.SEC_USER_ROLE).on(Tables.SEC_USER_ROLE.ID_ROLE.equal(Tables.SEC_ROLE.ID)) - .where(Tables.SEC_USER_ROLE.ID_USER.equal(idUser)).fetch().into(Tables.SEC_ROLE); + Result currentAssignedRoles = create + .select(Tables.SEC_ROLE.ID, Tables.SEC_ROLE.NAME) + .from(Tables.SEC_ROLE) + .join(Tables.SEC_USER_ROLE) + .on(Tables.SEC_USER_ROLE.ID_ROLE + .equal(Tables.SEC_ROLE.ID)) + .where(Tables.SEC_USER_ROLE.ID_USER + .equal(idUser)) + .fetch() + .into(Tables.SEC_ROLE); return currentAssignedRoles; } @@ -201,8 +229,11 @@ public List getRoles(Long idUser) { } private SecRoleRecord createRole(Role role) { - SecRoleRecord record = create.insertInto(Tables.SEC_ROLE, Tables.SEC_ROLE.NAME) - .values(role).returning().fetchOne(); + SecRoleRecord record = create + .insertInto(Tables.SEC_ROLE, Tables.SEC_ROLE.NAME) + .values(role) + .returning() + .fetchOne(); return record; } @@ -216,14 +247,14 @@ private SecRoleRecord createRole(Role role) { * @return The BP user id */ @Override - @Deprecated public Long getUserIdForCloudSessionUserId(Long id) { // Obtain the BP user id from the CS user id Long idUser = create .select(Tables.USER.ID) .from(Tables.USER) - .where(Tables.USER.IDCLOUDSESSION.eq(id)) + .where(Tables.USER.IDCLOUDSESSION + .eq(id)) .fetchOneInto(Long.class); if (idUser == null) { @@ -234,23 +265,40 @@ public Long getUserIdForCloudSessionUserId(Long id) { } } + + /** + * Replace the blockly user screen name + * + * @param idUser - is the long integer id for the blockly user record + * @param screenName - is ghe new screen name to store in the user record + */ @Override - @Deprecated - public void updateScreenname(Long idUser, String screenname) { + public void updateScreenName(Long idUser, String screenName) { LOG.info("Attempting to update screen name for user: {} ", idUser); - - UserRecord user = create.selectFrom(Tables.USER) - .where(Tables.USER.ID.eq(idUser)) + + // Fetch the blockly user record + UserRecord user = create + .selectFrom(Tables.USER) + .where(Tables.USER.ID + .eq(idUser)) .fetchOne(); if (user != null) { - if ( ! Objects.equals(user.getScreenname(), screenname)) { - LOG.info("Changing screen name from {} to {}", user.getScreenname(), screenname); + // Compare the existing screen name with the proposed screen name + if ( ! Objects.equals(user.getScreenname(), screenName)) { + LOG.info("Changing screen name from {} to {}", user.getScreenname(), screenName); - user.setScreenname(screenname); - user.update(); + create.update(Tables.USER) + .set(Tables.USER.SCREENNAME, screenName) + .where(Tables.USER.ID.eq(idUser)) + .execute(); + + LOG.info("The screen name is now {}", user.getScreenname()); } } + else { + LOG.warn("Unable to locate a blockly user record for blockly id {}", idUser); + } } } From a90999e7e9a4f8471c92dd3981cb826152c93d4f Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:26:11 -0800 Subject: [PATCH 23/69] Add inline documentation. Insert copyright notice. --- .../servlets/PrivacyPolicyServlet.java | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/servlets/PrivacyPolicyServlet.java b/src/main/java/com/parallax/server/blocklyprop/servlets/PrivacyPolicyServlet.java index 7ca31a87..5a1e7deb 100644 --- a/src/main/java/com/parallax/server/blocklyprop/servlets/PrivacyPolicyServlet.java +++ b/src/main/java/com/parallax/server/blocklyprop/servlets/PrivacyPolicyServlet.java @@ -1,40 +1,53 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.servlets; import com.google.inject.Singleton; import java.io.IOException; -import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; + /** + * Return the privacy policy page * * @author developer */ @Singleton public class PrivacyPolicyServlet extends HttpServlet { - - /** - * Application logging system access - */ - private static Logger LOG = LoggerFactory.getLogger(PrivacyPolicyServlet.class); - - // + // Application logging system access + private static final Logger LOG = LoggerFactory.getLogger(PrivacyPolicyServlet.class); + /** * Handles the HTTP GET method. * * @param request servlet request * @param response servlet response - * * @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs */ @@ -43,10 +56,10 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { LOG.debug("Requesting child privacy page"); - + request.getRequestDispatcher( "WEB-INF/servlet/coppa/privacy-policy.jsp") - .forward(request, response); + .forward(request, response); } /** @@ -57,6 +70,5 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) @Override public String getServletInfo() { return "Parallax COPPA policy page"; - }// - + } } From 16f07420f5db0371fc83659762d66ce00b75fb11 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:30:01 -0800 Subject: [PATCH 24/69] Insert copyright notice. Minor code refactor to validate user list before iterating through the list. --- .../server/blocklyprop/rest/RestUser.java | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestUser.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestUser.java index 4d7656aa..ad881022 100644 --- a/src/main/java/com/parallax/server/blocklyprop/rest/RestUser.java +++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestUser.java @@ -1,17 +1,35 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.rest; import com.cuubez.visualizer.annotation.Detail; import com.cuubez.visualizer.annotation.Group; import com.cuubez.visualizer.annotation.HttpCode; import com.cuubez.visualizer.annotation.Name; + import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.inject.Inject; + import com.parallax.server.blocklyprop.converter.UserConverter; import com.parallax.server.blocklyprop.db.generated.tables.pojos.User; import com.parallax.server.blocklyprop.db.generated.tables.records.UserRecord; @@ -27,6 +45,7 @@ /** + * Respond to REST /user endpoint requests * * @author Michel */ @@ -35,9 +54,9 @@ @HttpCode("500>Internal Server Error,200>Success Response") public class RestUser { + // Logger handle private static final Logger LOG = LoggerFactory.getLogger(RestUser.class); - private UserService userService; @Inject @@ -45,20 +64,29 @@ public void setUserService(UserService userService) { this.userService = userService; } + + /** + * List of all user objects + * + * @return + * Returns a list of all user objects. + */ @GET @Path("/") @Detail("Get all users") @Name("Get all users") @Produces("application/json") public Response get() { + //FixMe: Endpoint /rest/user/ returns a list of ALL users. This needs to be regulated. LOG.info("REST:/rest/user/ Get request received"); - + JsonArray result = new JsonArray(); List users = userService.getAllUsers(); - JsonArray result = new JsonArray(); - for (UserRecord user : users) { - result.add(UserConverter.toJson(user)); + if (users != null) { + for (UserRecord user : users) { + result.add(UserConverter.toJson(user)); + } } return Response.ok(result.toString()).build(); From 2f691c4637f7c8fd42111fd6114ff2f6b4deb58b Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:39:53 -0800 Subject: [PATCH 25/69] Insert missing copyright notice. Deprecate method that uses the screen name as a search key. Created new method to obtain screen name directly from user profile. --- .../blocklyprop/services/UserService.java | 41 +++++- .../services/impl/UserServiceImpl.java | 134 ++++++++++++++++-- 2 files changed, 158 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/services/UserService.java b/src/main/java/com/parallax/server/blocklyprop/services/UserService.java index 8a975e48..78f58d7a 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/UserService.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/UserService.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.services; import com.parallax.server.blocklyprop.db.generated.tables.pojos.User; @@ -16,12 +32,27 @@ public interface UserService { User getUser(Long idUser); - + + @Deprecated User getUser(Long idCloudSessionUser, String screenName); + /** + * Get the blockly user id from the user profile cloud session id + * + * @param idCloudSession user profile primary key + * + * @return the blockly user id + */ + Long getIdUser(Long idCloudSession); + + + List getAllUsers(); - public String getUserScreenName(Long idUser); + String getUserScreenName(Long idUser); + + void setScreenName(Long idUser, String screenName); + void setLocale(String locale); diff --git a/src/main/java/com/parallax/server/blocklyprop/services/impl/UserServiceImpl.java b/src/main/java/com/parallax/server/blocklyprop/services/impl/UserServiceImpl.java index bcb0dffa..c3198dd1 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/impl/UserServiceImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/impl/UserServiceImpl.java @@ -1,21 +1,40 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.services.impl; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.persist.Transactional; + import com.parallax.client.cloudsession.CloudSessionUserService; import com.parallax.client.cloudsession.exceptions.ServerException; import com.parallax.client.cloudsession.exceptions.UnknownUserIdException; + import com.parallax.server.blocklyprop.db.dao.UserDao; import com.parallax.server.blocklyprop.db.generated.tables.pojos.User; import com.parallax.server.blocklyprop.db.generated.tables.records.UserRecord; import com.parallax.server.blocklyprop.security.BlocklyPropSecurityUtils; import com.parallax.server.blocklyprop.services.UserService; + import java.util.List; import org.apache.commons.configuration.Configuration; import org.slf4j.Logger; @@ -33,26 +52,47 @@ public class UserServiceImpl implements UserService { private static UserService USER_SERVICE; - private Configuration configuration; - private UserDao userDao; + /** + * User profile + */ private CloudSessionUserService userService; public UserServiceImpl() { UserServiceImpl.USER_SERVICE = this; } + /** + * BlocklyProp user record + */ + private UserDao userDao; + @Inject public void setUserDao(UserDao userDao) { this.userDao = userDao; } + /** + * Application configuration object + */ + private Configuration configuration; + @Inject public void setConfiguration(Configuration configuration) { this.configuration = configuration; userService = new CloudSessionUserService(configuration.getString("cloudsession.baseurl")); } + + /** + * Locate a blocklyprop user record from the bp user ID + * + * @param idUser + * The blocklyprop user ID + * + * @return + * A populated blocklyprop user instance + */ @Override public User getUser(Long idUser) { if (userDao != null) { @@ -62,9 +102,34 @@ public User getUser(Long idUser) { LOG.error("UserDAO is not initialized before first use!"); return null; } - } - + + + /** + * Look up the blockly user id from the user profile cloud session id + * + * @param idCloudSession + * The user profile key id + * + * @return + * Returns a Long integer representing the blockly record primary key id + */ + public Long getIdUser(Long idCloudSession) { + return userDao.getUserIdForCloudSessionUserId(idCloudSession); + } + + + /** + * Locate a blocklyprop user record from the user profile ID and screen name + * + * @param idCloudSessionUser + * + * @param screenName + * + * @return + * + */ + @Deprecated @Override public User getUser(Long idCloudSessionUser, String screenName) { if (userDao != null) { @@ -76,6 +141,13 @@ public User getUser(Long idCloudSessionUser, String screenName) { } } + + /** + * Get a list of blocklyprop user objects + * + * @return + * Returns a list of blocklyprop user objects + */ @Override public List getAllUsers() { if (userDao != null) { @@ -87,6 +159,17 @@ public List getAllUsers() { } } + + /** + * Get the blocklyprop user screen name + * + * @param idUser + * Provide the blocklyprop user ID + * + * @return + * Returns a string containing the user's screen name or an empty string + * if the blocklyprop user record was not found + */ @Override public String getUserScreenName(Long idUser) { @@ -101,21 +184,31 @@ public String getUserScreenName(Long idUser) { } } catch (NullPointerException ex) { - LOG.error("Error retreiving name for userID: {}", idUser); + LOG.error("Error retrieving name for userID: {}", idUser); } return name; } @Override public void setLocale(String locale) { + LOG.info("Setting locale {}", locale); + if (SecurityServiceImpl.getSessionData() != null) { + LOG.info("Retrieved SessionData object"); + LOG.info("SessionData {}",SecurityServiceImpl.getSessionData().toString() ); + if (SecurityServiceImpl.getSessionData().getUser() != null) { + LOG.info("Retrieved SessionData User object"); try { + LOG.info("Loading user profile to update locale"); com.parallax.client.cloudsession.objects.User user = BlocklyPropSecurityUtils.getUserInfo(); - if (!user.getLocale().equals(locale)) { - LOG.info("Setting user locale: {} - {}", user.getId(), locale); - user = userService.changeUserLocale(user.getId(), locale); - BlocklyPropSecurityUtils.setUserInfo(user); + + if (user != null) { + if (!user.getLocale().equals(locale)) { + LOG.info("Setting user locale: {} - {}", user.getId(), locale); + user = userService.changeUserLocale(user.getId(), locale); + BlocklyPropSecurityUtils.setUserInfo(user); + } } } catch (UnknownUserIdException uuie) { LOG.error("Unknown user id", uuie); @@ -128,6 +221,23 @@ public void setLocale(String locale) { } } + // + + /** + * Update the user screen name stored in the blockyprop.users table + * + * @param idUser is the blocklyprop user primary key + * + * @param screenName is the new screen name text to store + */ + public void setScreenName(Long idUser, String screenName) { + userDao.updateScreenName(idUser, screenName); + + // TODO: Set session screen name attribute + + } + + public static UserService getUserService() { return UserServiceImpl.USER_SERVICE; } From ebeee338f0a330e7bbc673df5bb815d8de47fa8a Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:42:22 -0800 Subject: [PATCH 26/69] Add inline documentation. Insert copyright notice. --- .../blocklyprop/services/SessionService.java | 23 +++++++++++--- .../services/impl/SessionServiceImpl.java | 31 ++++++++++++++----- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/services/SessionService.java b/src/main/java/com/parallax/server/blocklyprop/services/SessionService.java index b61606a6..dd70a989 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/SessionService.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/SessionService.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.services; import com.parallax.server.blocklyprop.db.generated.tables.records.SessionRecord; @@ -23,5 +39,4 @@ public interface SessionService { void deleteSession(String idSession); Collection getActiveSessions(); - } diff --git a/src/main/java/com/parallax/server/blocklyprop/services/impl/SessionServiceImpl.java b/src/main/java/com/parallax/server/blocklyprop/services/impl/SessionServiceImpl.java index caaab48f..45ead890 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/impl/SessionServiceImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/impl/SessionServiceImpl.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.services.impl; import com.google.inject.Inject; @@ -11,7 +27,6 @@ import com.parallax.server.blocklyprop.db.dao.SessionDao; import com.parallax.server.blocklyprop.db.generated.tables.records.SessionRecord; import com.parallax.server.blocklyprop.services.SessionService; -//import java.util.Arrays; import java.util.Collection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,7 +45,7 @@ public class SessionServiceImpl implements SessionService { // Retain session state private static SessionService sessionService; - // Session database acceess object + // Session database access object private SessionDao sessionDao; public SessionServiceImpl() { @@ -44,8 +59,8 @@ public void setSessionDao(SessionDao sessionDao) { @Override public void create(SessionRecord session) { - log.info("Creating a new user session with timeout set to {}", session.getTimeout()); - + log.debug("Creating a new user session"); + //TODO: Verify session attributes element has data when saving. sessionDao.create(session); } @@ -77,10 +92,10 @@ public Collection getActiveSessions() { return sessionDao.getActiveSessions(); } + public static SessionService getSessionService() { log.debug("Get current session service instance"); return sessionService; } - } From 88e6598635834fc470838c1e2b175dedbe29a06f Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:45:06 -0800 Subject: [PATCH 27/69] Add inline documentation. Insert copyright notice. Minor refactor to ensure a valid user object before accessing object methods. --- .../blocklyprop/servlets/ProfileServlet.java | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/servlets/ProfileServlet.java b/src/main/java/com/parallax/server/blocklyprop/servlets/ProfileServlet.java index ae769816..bd5119b1 100644 --- a/src/main/java/com/parallax/server/blocklyprop/servlets/ProfileServlet.java +++ b/src/main/java/com/parallax/server/blocklyprop/servlets/ProfileServlet.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.servlets; import com.google.common.base.Strings; @@ -55,8 +71,12 @@ public class ProfileServlet extends HttpServlet { @Inject public void setConfiguration(Configuration configuration) { this.configuration = configuration; - cloudSessionLocalUserService = new CloudSessionLocalUserService(configuration.getString("cloudsession.server"), configuration.getString("cloudsession.baseurl")); - cloudSessionUserService = new CloudSessionUserService(configuration.getString("cloudsession.baseurl")); + cloudSessionLocalUserService = new CloudSessionLocalUserService( + configuration.getString("cloudsession.server"), + configuration.getString("cloudsession.baseurl")); + + cloudSessionUserService = new CloudSessionUserService( + configuration.getString("cloudsession.baseurl")); } @Inject @@ -75,6 +95,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se LOG.info("REST:/profile/ Get request received"); User user = BlocklyPropSecurityUtils.getUserInfo(); + if (user == null) { + req.setAttribute("base-error", "Unknown user"); + req.getRequestDispatcher("WEB-INF/servlet/profile/profile.jsp").forward(req, resp); + } req.setAttribute("id", user.getId()); req.setAttribute("email", user.getEmail()); req.setAttribute("screenname", user.getScreenname()); From 37ebec629cebe63708d0717006d5dcc4f98eaeac Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:46:24 -0800 Subject: [PATCH 28/69] Update copyright notice. --- .../com/parallax/server/blocklyprop/config/SetupConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java b/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java index 10bafdf9..49aae174 100644 --- a/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java +++ b/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Parallax Inc. + * Copyright (c) 2019 Parallax Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the “Software”), to deal in the Software without @@ -64,6 +64,7 @@ protected Injector getInjector() { @Override protected void configure() { + bind(Configuration.class).toInstance(configuration); bind(SessionData.class); From d4a53d21f280210fc02242b9886ddd3ab8c0db9f Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:48:22 -0800 Subject: [PATCH 29/69] Insert copyright notice. Minor code refactoring to correct compiler warning. --- .../db/dao/impl/SessionDaoImpl.java | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/SessionDaoImpl.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/SessionDaoImpl.java index 42bf8faa..edab7453 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/SessionDaoImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/SessionDaoImpl.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.db.dao.impl; import com.google.inject.Inject; @@ -62,7 +78,8 @@ public void setConfiguration(Configuration configuration) { */ @Override public void create(SessionRecord session) { - LOG.info("Create a session. Timeout set to: {}", session.getTimeout()); + + LOG.debug("Create a session. Timeout set to: {}", session.getTimeout()); // Log session details if the configuration file permits it printSessionInfo("create", session); @@ -98,7 +115,9 @@ public void create(SessionRecord session) { */ @Override public SessionRecord readSession(String idSession) throws NullPointerException { - LOG.debug("Getting session details"); + + LOG.debug("Getting session {} details", idSession); + SessionRecord sessionRecord = null; try { @@ -112,9 +131,8 @@ public SessionRecord readSession(String idSession) throws NullPointerException { catch (org.jooq.exception.DataAccessException sqex) { LOG.error("Database exception {}", sqex.getMessage()); } - finally { - return sessionRecord; - } + + return sessionRecord; } /** @@ -124,6 +142,7 @@ public SessionRecord readSession(String idSession) throws NullPointerException { */ @Override public void updateSession(SessionRecord session) throws NullPointerException { + LOG.debug("Update a session"); try { @@ -158,8 +177,12 @@ public void updateSession(SessionRecord session) throws NullPointerException { */ @Override public void deleteSession(String idSession) { + LOG.info("Deleting session {}", idSession); - create.deleteFrom(Tables.SESSION).where(Tables.SESSION.IDSESSION.eq(idSession)).execute(); + + create.deleteFrom(Tables.SESSION) + .where(Tables.SESSION.IDSESSION.eq(idSession)) + .execute(); } /** From 180d91c11150972cca8a2e2a811089bab60fa745 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:52:26 -0800 Subject: [PATCH 30/69] Add inline documentation. Insert copyright notice. Add code to support updates to session attributes when needed. --- .../services/impl/SecurityServiceImpl.java | 270 ++++++++++-------- 1 file changed, 156 insertions(+), 114 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/services/impl/SecurityServiceImpl.java b/src/main/java/com/parallax/server/blocklyprop/services/impl/SecurityServiceImpl.java index 1a939325..1c4f22a7 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/impl/SecurityServiceImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/impl/SecurityServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Parallax Inc. + * Copyright (c) 2019 Parallax Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the “Software”), to deal in the Software without @@ -21,16 +21,10 @@ package com.parallax.server.blocklyprop.services.impl; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.inject.Inject; -import com.google.inject.Provider; -import com.google.inject.Singleton; -import com.google.inject.persist.Transactional; - import com.parallax.client.cloudsession.CloudSessionAuthenticateService; import com.parallax.client.cloudsession.CloudSessionRegisterService; import com.parallax.client.cloudsession.CloudSessionUserService; +import com.parallax.client.cloudsession.objects.User; import com.parallax.client.cloudsession.exceptions.EmailNotConfirmedException; import com.parallax.client.cloudsession.exceptions.InsufficientBucketTokensException; import com.parallax.client.cloudsession.exceptions.NonUniqueEmailException; @@ -42,19 +36,25 @@ import com.parallax.client.cloudsession.exceptions.UnknownUserIdException; import com.parallax.client.cloudsession.exceptions.UserBlockedException; import com.parallax.client.cloudsession.exceptions.WrongAuthenticationSourceException; -import com.parallax.client.cloudsession.objects.User; - import com.parallax.server.blocklyprop.SessionData; -import com.parallax.server.blocklyprop.db.dao.UserDao; import com.parallax.server.blocklyprop.services.SecurityService; - +import com.parallax.server.blocklyprop.services.SessionService; +import com.parallax.server.blocklyprop.db.dao.UserDao; import com.parallax.server.blocklyprop.db.generated.tables.records.UserRecord; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.google.inject.persist.Transactional; import java.util.Calendar; -import java.util.Set; + + import org.apache.commons.configuration.Configuration; import org.apache.commons.validator.routines.EmailValidator; import org.apache.shiro.SecurityUtils; +// import org.apache.shiro.session.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,56 +67,51 @@ @Transactional public class SecurityServiceImpl implements SecurityService { - /** - * Handle to logging facility - */ + // Handle to logging facility for the SecurityServiceImpl class private static final Logger LOG = LoggerFactory.getLogger(SecurityServiceImpl.class); - - /** - * - */ - private static SecurityServiceImpl instance; - /** - * Web client session details - */ - private Provider sessionData; + // A static instance of this object + private static SecurityServiceImpl instance; - /** - * Application configuration settings - */ + // Application configuration settings private Configuration configuration; - - /** - * - */ - private EmailValidator emailValidator = EmailValidator.getInstance(); - /** - * Interface to the Cloud Session user account registration service - */ + // Interface to the Cloud Session user account registration service private CloudSessionRegisterService registerService; - - /** - * Interface to the Cloud Session user authentication service - */ + + // Interface to the Cloud Session user authentication service private CloudSessionAuthenticateService authenticateService; - - /** - * Interface to the Cloud Session user account/profile services - */ + + // Interface to the Cloud Session user account/profile services private CloudSessionUserService userService; + // Injects an instance of SessionData here + private Provider sessionData; + + // Access to the BlocklyProp user details + private UserDao userDao; + + private SessionService sessionService; + + @Inject + public void setSessionService(SessionService sessionService) { + this.sessionService = sessionService; + } + /** - * Access to the BlocklyProp user details + * Get a static instance of the EmailValidator object. + * + * @implNote + * The default configuration for the validator is to not allow local and TLDs */ - private UserDao userDao; - + private EmailValidator emailValidator = EmailValidator.getInstance(); + + /** * Constructor - * */ public SecurityServiceImpl() { + // // TODO: Correct the 'this' construct in the constructor // // Notes from: https://www.securecoding.cert.org/confluence/display/java/TSM01-J.+Do+not+let+the+this+reference+escape+during+object+construction @@ -127,38 +122,46 @@ public SecurityServiceImpl() { instance = this; } + /** - * Set the session's data provider + * Implements the Providers setSessionDataProvider interface * * This is a callback used by the Shiro package to provide a connection * between the application and the Shiro session management services. * - * @param sessionDataProvider + * @param sessionDataProvider + * is a class that models the session data + * */ @Inject public void setSessionDataProvider(Provider sessionDataProvider) { this.sessionData = sessionDataProvider; } + /** * Set the session's user database object in the blocklyprop system. * - * @param userDao + * @param userDao + * is the DAO interface to User data instance store */ @Inject public void setUserDao(UserDao userDao) { this.userDao = userDao; } + /** * Configure cloud session service endpoints * - * @param configuration + * @param configuration + * A application configuration object */ - @Inject public void setConfiguration(Configuration configuration) { + LOG.debug("Setting cloud session configuration"); + this.configuration = configuration; // Set the source for the cloud session registration services @@ -177,6 +180,7 @@ public void setConfiguration(Configuration configuration) { configuration.getString("cloudsession.baseurl")); } + /** * Validate new user data and create a new user account * @@ -194,12 +198,23 @@ public void setConfiguration(Configuration configuration) { * @param birthYear int Year component of the user's birthday. COPPA field * @param parentEmail String sponsor email address. COPPA field * @param parentEmailSource int Sponsor classification. COPPA - * @return + * + * @return The cloud session user ID if successful or zero upon failure + * * @throws NonUniqueEmailException + * A user account with the provided email address already exists + * * @throws PasswordVerifyException + * The two password values provided do not match + * * @throws PasswordComplexityException + * The provided password does not meet the requirements for a secure password + * * @throws ScreennameUsedException - * @throws IllegalStateException + * A user account with the provided screen name already exists + * + * @throws IllegalStateException + * */ @Override public Long register( @@ -217,71 +232,60 @@ public Long register( ScreennameUsedException, IllegalStateException{ + LOG.info("Registering a new user: {}({})", email, screenname); + + // Instantiate a new cloud session user profile object User user = new User(); - // Log a few things - LOG.debug("In register: parameter screen name: {}", screenname); - LOG.debug("In register: parameter email: {}", email); - LOG.debug("In register: parameter month: {}", birthMonth); - LOG.debug("In register: parameter year: {}", birthYear); - LOG.debug("In register: parameter sponsor email: {}", parentEmail); - LOG.debug("In register: parameter sponsor type selection: {}", parentEmailSource); - // Perform basic sanity checks on inputs // Throws NullPointerException if screenname is null - LOG.debug("Resgistering new user: {}", screenname); Preconditions.checkNotNull(screenname, "ScreenNameNull"); // User email address is required and must be reasonably valid - LOG.debug("Verifying email address has been supplied"); Preconditions.checkNotNull(email, "UserEmailNull"); - - LOG.debug("Verifying email address is reasonable"); Preconditions.checkState( emailValidator.isValid(email), "Email address format is incorrect"); - LOG.debug("Verifying that a password was provided"); + // The password fields must contain something Preconditions.checkNotNull(password, "PasswordIsNull"); - - LOG.debug("Verify that second copy of password was provided"); Preconditions.checkNotNull(passwordConfirm, "PasswordConfirmIsNull"); - + // Verify that we have valid COPPA data before continuing // Birth month Preconditions.checkNotNull(birthMonth, "BirthMonthNull"); - LOG.debug("Verify that month is provided: {}", birthMonth); Preconditions.checkState((birthMonth != 0), "BirthMonthNotSet"); // Birth year Preconditions.checkNotNull(birthYear, "BirthYearNull"); - LOG.debug("Verify that year is provided: {}", birthYear); Preconditions.checkState( (Calendar.getInstance().get(Calendar.YEAR) != birthYear), "BirthYearNotSet"); // Get additional information if the registrant is under 13 years old if (user.isCoppaEligible(birthMonth, birthYear)) { - LOG.debug("User is subject to COPPA regulations"); - // We must have a sponsor email address for COPPA eligible users Preconditions.checkNotNull( parentEmail, "SponsorEmailNull"); - + // Verify that the sponsor email address is reasonable if (parentEmail != null && parentEmail.length() > 0) { - LOG.debug("Verify that optional user email address is reasonable"); Preconditions.checkState( - emailValidator.isValid(parentEmail), - "SponsorEmail"); + emailValidator.isValid(parentEmail), + "SponsorEmail"); } + + LOG.info("User is COPPA restricted"); + LOG.info("Sponsor email address is: {}", parentEmail); } + /* ------------------------------------------------------------------ + * Attempt to register the user account data with the cloud session + * service. If successful, the method call will return a cloud + * session user id for the newly created account + * -----------------------------------------------------------------*/ try { - // Attempt to register the user account data with the cloud session - // service. If successful, the method call will return a cloud - // session user id for the newly created account LOG.info("Registering user account with cloud-service"); Long idCloudSessionUser = registerService.registerUser( email, @@ -296,6 +300,7 @@ public Long register( // Create a BlocklyProp user account record if (idCloudSessionUser > 0) { + LOG.info("Creating matching blocklyprop user record for {}", screenname); userDao.create(idCloudSessionUser, screenname); } @@ -341,7 +346,7 @@ public static User authenticateLocalUserStatic( } /** - * Authenticate a user from the provided userID + * Get an instance of an authenticated user object * * @param idUser * @@ -389,7 +394,6 @@ public User authenticateLocalUser(String email, String password) throws // Query Cloud Session interface User user = authenticateService.authenticateLocalUser(email, password); - LOG.info("User authenticated"); return user; } catch (UnknownUserException uue) { @@ -412,7 +416,7 @@ public User authenticateLocalUser(String email, String password) throws throw wase; } catch (NullPointerException npe) { - LOG.error("Authetication threw Null Pointer Exception"); + LOG.error("Authentication threw Null Pointer Exception"); throw npe; } catch (ServerException se) { @@ -442,7 +446,9 @@ public User authenticateLocalUser(Long idUser) throws UnknownUserIdException, UserBlockedException, EmailNotConfirmedException { - + + // FixMe: UserBlockledException is never thrown in client.cloudsession. + try { User user = userService.getUser(idUser); LOG.info("User authenticated"); @@ -461,20 +467,26 @@ public User authenticateLocalUser(Long idUser) throws * Return user session data * * @return SessionData object containing user session details or null + * + * @implNote + * The SessionData object stores three attributes: + * user - A cloud session user profile object + * idUser - the blocklprop user primary key ID + * locale - the locale string used for this session */ public static SessionData getSessionData() { LOG.debug("Getting user session data"); - + SessionData sessionData = instance.sessionData.get(); - + if (sessionData == null) { LOG.warn("Error obtaining session data"); + return null; } - - LOG.debug("Session data - {}", sessionData.toString()); - + // Check for a BP user id if (sessionData.getIdUser() == null) { + LOG.debug("No user ID is associated with the current session"); // No BP user id found, is the user in this session authenticated? if (SecurityUtils.getSubject().isAuthenticated()) { @@ -482,59 +494,87 @@ public static SessionData getSessionData() { // The user identified by this session is authenticated. Perform // a fun exercise to locate the BP user id for this authenticated // user. - LOG.debug("Session data missing a valid BP id for an authenticated user"); + LOG.debug("Obtaining session data for authenticated user"); try { // Getting a user record using the account email address String principal = (String) SecurityUtils.getSubject().getPrincipal(); // Display the user's email address - LOG.debug("Getting pricipal: {}", principal ); + LOG.debug("Principal is: {}", principal ); // Get the user account/profile record + String emailAddress = (String) SecurityUtils.getSubject().getPrincipal(); + LOG.debug("Getting user profile for {}", emailAddress); + + // Retrieve a blocky user record using an email address User user = instance.userService.getUser( (String) SecurityUtils.getSubject().getPrincipal()); - + // Did we get a user account object if (user != null) { - LOG.debug("Session User: {}", user.getScreenname()); - LOG.debug("Session UserId: {}", user.getId()); - LOG.debug("Session locale: {}", user.getLocale()); - + LOG.debug("User Profile: {}({}), ID: {}", + user.getEmail(), + user.getScreenname(), + user.getId()); + LOG.debug("Session Locale is: {}",sessionData.getLocale()); + // Yes, User account local may have changed if (!Strings.isNullOrEmpty(sessionData.getLocale())) { if (!sessionData.getLocale().equals(user.getLocale())) { try { // User locale changed. Let's update the user // account with new locale - LOG.info("Changing user {} locale", user.getScreenname()); + LOG.debug("Changing user {} locale", user.getScreenname()); user = instance.userService.changeUserLocale( user.getId(), sessionData.getLocale()); + } catch (UnknownUserIdException ex) { LOG.error("UnknownUserId exception detected. {}", ex.getMessage()); } } } - - LOG.debug("Setting session user data for {}", user.getScreenname()); - sessionData.setUser(user); - - LOG.debug("Getting BP user id"); - UserRecord bpUser = instance.userDao.getUser(user.getId(), user.getScreenname()); + + // Store the user profile into the session + sessionData.setUser(user); + + LOG.debug("Checking {}", sessionData.getUser().getScreenname()); + + // Getting the blocklyprop user record + Long idBlocklyUser = instance.userDao.getUserIdForCloudSessionUserId(user.getId()); + + LOG.debug("Obtained BlocklyProp user id {} from cloud session id {} ", + idBlocklyUser, + user.getId() ); + + UserRecord bpUser = instance.userDao.getUser(idBlocklyUser); + if (bpUser != null) { - LOG.debug("Setting BP user id to: {}", bpUser.getId()); - sessionData.setIdUser(bpUser.getId()); + LOG.debug("Retrieved blockly user record: bpID: {}, csID: {}, Name: {}", + bpUser.getId(), + bpUser.getIdcloudsession(), + bpUser.getScreenname() ); + + + // Verify that the screen name matches in both databases + if (! bpUser.getScreenname().equals(user.getScreenname())) { + LOG.info("Updating bp screen name from {} to {}", + bpUser.getScreenname(), + user.getScreenname()); + +// instance.userDao.updateScreenName( +// bpUser.getId(), +// user.getScreenname()); + } }else{ LOG.warn("Warning! Setting BP user id to zero"); sessionData.setIdUser(0L); } - /* - * This should never be necessary until the user profile page - * offers the capability to change the user's screen name - */ -// instance.userDao.updateScreenname( -// sessionData.getIdUser(), -// user.getScreenname()); + sessionData.setIdUser(idBlocklyUser); + sessionData.setLocale(user.getLocale()); + + + //TODO: Persist the updated sessionData } } catch (UnknownUserException ex) { LOG.error("Unknown user ID. {}", ex); @@ -543,6 +583,8 @@ public static SessionData getSessionData() { } } } + + LOG.debug("Returning session data"); return sessionData; } } From d623f7db01f645e66efb4556f97b4befdc384111 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:53:49 -0800 Subject: [PATCH 31/69] Insert copyright notice. Add entry point logging. --- .../CloudSessionCredentialsMatcher.java | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/security/CloudSessionCredentialsMatcher.java b/src/main/java/com/parallax/server/blocklyprop/security/CloudSessionCredentialsMatcher.java index eb9e6aaa..13ef2ba6 100644 --- a/src/main/java/com/parallax/server/blocklyprop/security/CloudSessionCredentialsMatcher.java +++ b/src/main/java/com/parallax/server/blocklyprop/security/CloudSessionCredentialsMatcher.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.security; import java.util.Arrays; @@ -10,14 +26,29 @@ import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + /** * * @author Michel */ public class CloudSessionCredentialsMatcher extends SimpleCredentialsMatcher { + // Get a handle to a logger for this class + private static Logger LOG = LoggerFactory.getLogger(CloudSessionCredentialsMatcher.class); + + /** + * + * @param tokenCredentials + * @param accountCredentials + * @return + */ @Override protected boolean equals(Object tokenCredentials, Object accountCredentials) { + LOG.info("Testing for equivalent credentials"); + if (isByteSource(tokenCredentials) && isByteSource(accountCredentials)) { byte[] tokenBytes = toBytes(tokenCredentials); byte[] accountBytes = toBytes(accountCredentials); @@ -27,11 +58,17 @@ protected boolean equals(Object tokenCredentials, Object accountCredentials) { } } + /** + * + * @param token + * @param info + * @return + */ @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { + LOG.info("Testing auth token against auth information"); Object tokenCredentials = getCredentials(token); Object accountCredentials = getCredentials(info); return equals(tokenCredentials, accountCredentials); } - } From 8d155f3d5f745e15179a1d28e12d1993bfac14d1 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:57:06 -0800 Subject: [PATCH 32/69] Update copyright notice. Replace null return with a new thrown exception to force caller to handle authentication failure. --- .../CloudSessionAuthenticationRealm.java | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/security/CloudSessionAuthenticationRealm.java b/src/main/java/com/parallax/server/blocklyprop/security/CloudSessionAuthenticationRealm.java index c4d242b4..7a4e4db6 100644 --- a/src/main/java/com/parallax/server/blocklyprop/security/CloudSessionAuthenticationRealm.java +++ b/src/main/java/com/parallax/server/blocklyprop/security/CloudSessionAuthenticationRealm.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Parallax Inc. + * Copyright (c) 2019 Parallax Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the “Software”), to deal in the Software without @@ -66,11 +66,10 @@ */ public class CloudSessionAuthenticationRealm extends AuthorizingRealm { - /** - * Class logging handle - */ + // Get a handle to a logger for this class private static Logger LOG = LoggerFactory.getLogger(CloudSessionAuthenticationRealm.class); + /** * Convenience implementation that returns * getAuthenticationTokenClass().isAssignableFrom( token.getClass() ); @@ -90,6 +89,7 @@ public boolean supports(AuthenticationToken token) { return true; } + /** * Retrieves the AuthorizationInfo for the given principals from the * underlying data store. @@ -112,6 +112,7 @@ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal return authorizationInfo; } + /** * Retrieves authentication data from an implementation-specific data source * (RDBMS, LDAP, etc) for the given authentication token. @@ -141,12 +142,11 @@ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { - /* - * Any leading and/or trailing white space contained in the credentials + LOG.info("Obtaining authentication info"); + + /* Any leading and/or trailing white space contained in the credentials * (password) has been stripped out before it gets here. */ - LOG.info("Obtaining authentication info"); - try { if (token instanceof OAuthToken) { // Principal = email @@ -157,7 +157,6 @@ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) token.getCredentials(), "CloudSession"); } else { - LOG.info("Authentication is using local login authority"); // Principal = login @@ -166,6 +165,8 @@ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) // Credentials = password String credentials = new String((char[]) token.getCredentials()); + LOG.info("Authenticating user '{}'", principal); + // Thia can throw a NullPointerException User user = SecurityServiceImpl.authenticateLocalUserStatic( principal, @@ -176,6 +177,8 @@ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) return null; } + LOG.info("User {} is authenticated", principal); + try { return new SimpleAccount( token.getPrincipal(), @@ -185,32 +188,34 @@ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) LOG.error("Unexpected exception creating account object", t); } } - return null; - } catch (UnknownUserException ex) { + throw new AuthenticationException("Unable to authenticate token"); + } + catch (UnknownUserException ex) { LOG.warn("Authentication failed. Message: {}", ex.getMessage()); throw new AuthenticationException(ex.getMessage()); - - } catch (UserBlockedException ex) { + } + catch (UserBlockedException ex) { LOG.warn("Blocked user {}", ex); throw new AuthenticationException(ex.getMessage()); - - } catch (EmailNotConfirmedException ex) { + } + catch (EmailNotConfirmedException ex) { LOG.warn("Authentication failed. Message: {}", ex.getMessage()); throw new AuthenticationException("EmailNotConfirmed"); - - } catch (InsufficientBucketTokensException ex) { + } + catch (InsufficientBucketTokensException ex) { LOG.info("Insufficient bucket tokens: {}", ex.getMessage()); throw new AuthenticationException(ex.getMessage()); - - } catch (NullPointerException npe) { + } + catch (NullPointerException npe) { LOG.warn("NullPointer", npe); throw new AuthenticationException(npe.getMessage()); - - } catch (Throwable t) { + } + catch (Throwable t) { // This is a catchall exception handler that kicks the can back // to the caller LOG.warn("Throwable", t); } + return null; } From 969d1f2656030b349c7af1beae77b49c864cd216 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 01:59:45 -0800 Subject: [PATCH 33/69] Insert copyright notice. Add code to trap a null user record. --- .../security/BlocklyPropSecurityUtils.java | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/security/BlocklyPropSecurityUtils.java b/src/main/java/com/parallax/server/blocklyprop/security/BlocklyPropSecurityUtils.java index a17d1e27..17538a5b 100644 --- a/src/main/java/com/parallax/server/blocklyprop/security/BlocklyPropSecurityUtils.java +++ b/src/main/java/com/parallax/server/blocklyprop/security/BlocklyPropSecurityUtils.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.security; import com.parallax.client.cloudsession.objects.User; @@ -10,17 +26,36 @@ import com.parallax.server.blocklyprop.services.impl.SecurityServiceImpl; import org.apache.shiro.SecurityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * * @author Michel */ public class BlocklyPropSecurityUtils extends SecurityUtils { + // Get a logger instance + private static final Logger LOG = LoggerFactory.getLogger(BlocklyPropSecurityUtils.class); + + /** + * + * @return + */ public static Long getCurrentUserId() { - SessionData sessionData = SecurityServiceImpl.getSessionData(); - if (sessionData != null) { - return sessionData.getIdUser(); + + try { + SessionData sessionData = SecurityServiceImpl.getSessionData(); + if (sessionData != null) { + return sessionData.getIdUser(); + } } + catch (Exception ex) { + LOG.info("Exception trapped. Message is: {}.", ex.getMessage()); + } + + LOG.info("Session data not found"); + return null; } From f0add68d21e91bd0ff980473423f2ad8c5f91458 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 02:02:34 -0800 Subject: [PATCH 34/69] Add lots of inline documentation. Update copyright notice. Updated settings to group them into relevant groupings. --- src/main/resources/shiro.ini | 111 +++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 36 deletions(-) diff --git a/src/main/resources/shiro.ini b/src/main/resources/shiro.ini index 22d40217..23a16291 100644 --- a/src/main/resources/shiro.ini +++ b/src/main/resources/shiro.ini @@ -1,5 +1,5 @@ # -# Copyright (c) 2018 Parallax Inc. +# Copyright (c) 2019 Parallax Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy of this software # and associated documentation files (the “Software”), to deal in the Software without @@ -21,64 +21,98 @@ [main] -# Supports direct (plain) comparison for credentials of type byte[], char[], -# and Strings, and if the arguments do not match these types, then reverts back -# to simple Object.equals comparison. -credentialsMatcher = org.apache.shiro.authc.credential.SimpleCredentialsMatcher +# --------------- +# Session Manager +# --------------- +# Web-application capable SessionManager implementation. This provides a +# handler for HttpRequest and HttpResponse integrated with the Shiro system. +# The security manager will use the DefaultWebSessionManager interface. +# -------------------------------------------------------------------------- +sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager +securityManager.sessionManager = $sessionManager +# ------------------------- +# Enable session ID cookies +# ------------------------- +securityManager.sessionManager.sessionIdCookieEnabled = true -# A credentials matcher that always returns true when matching credentials no matter -# what arguments are passed in. This can be used for testing or when credentials are -# implicitly trusted for a particular Realm. -allow_all_credentialsMatcher = org.apache.shiro.authc.credential.AllowAllCredentialsMatcher +# -------------------------------------------------------------------------- +# Disable session management when operating within a multi-host environment +# -------------------------------------------------------------------------- +securityManager.sessionManager.deleteInvalidSessions = false +securityManager.sessionManager.sessionValidationSchedulerEnabled = false +securityManager.sessionManager.globalSessionTimeout = 28800000 -# cloudSessionMatcher = com.parallax.server.blocklyprop.security.CloudSessionCredentialsMatcher +# Attach the default session validation scheduler +sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler +securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler +# Run once per day +securityManager.sessionManager.sessionValidationScheduler.interval = 86400000 -# Define interface to the backend storage to hold user credentials -cloudsessionRealm = com.parallax.server.blocklyprop.security.CloudSessionAuthenticationRealm +# ---------------- +# Backing Storage +# ---------------- +# +# Configure the SessionDao to use the BlocklyPropSessionDao class to implement +# the SessionDao interface. Configure the Security Manager to use this interface +# to persist session data to a backend EIS +# ------------------------------------------------------------------------------- +sessionDao = com.parallax.server.blocklyprop.security.BlocklyPropSessionDao +securityManager.sessionManager.sessionDAO = $sessionDao -# Tell the Shiro security manager to use the CloudSession Realm -securityManager.realms = $cloudsessionRealm -# Configure the session manager -# Web-application capable SessionManager implementation. -sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager -securityManager.sessionManager = $sessionManager +# Security Manager Backend Storage (User Accounts) +# ------------------------------------------------- +# Define interface to the backend storage to hold user credentials. This is the +# Cloud Session interface for user authentication +cloudsessionRealm = com.parallax.server.blocklyprop.security.CloudSessionAuthenticationRealm +securityManager.realms = $cloudsessionRealm -# Configure a SessionDAO and then set it: -sessionDao = com.parallax.server.blocklyprop.security.BlocklyPropSessionDao -securityManager.sessionManager.sessionDAO = $sessionDao -securityManager.sessionManager.sessionIdCookieEnabled = true -# Attach the default session validation scheduler -sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler +# Credential Matching +# ------------------- +# Supports direct (plain) comparison for credentials of type byte[], char[], +# and Strings, and if the arguments do not match these types, then reverts back +# to simple Object.equals comparison. +credentialsMatcher = org.apache.shiro.authc.credential.SimpleCredentialsMatcher -# Run once per day -sessionValidationScheduler.interval = 86400000 +#============================================ +# TODO: Determine if this should be wired up +#============================================ +# This appears to be implemented and not referenced in this configuration file +cloudSessionMatcher = com.parallax.server.blocklyprop.security.CloudSessionCredentialsMatcher -# TESTING - Run every 30 seconds -# sessionValidationScheduler.interval = 300000 -securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler +####### TESTING AND DEV SETTINGS ######## +# A credentials matcher that always returns true when matching credentials no matter +# what arguments are passed in. This can be used for testing or when credentials are +# implicitly trusted for a particular Realm. +# ----------------------------------------------------------------------------------- +# allow_all_credentialsMatcher = org.apache.shiro.authc.credential.AllowAllCredentialsMatcher +#-########################################################################################## -# -------------------------------------------------------------------------- -# Disable session management when operating within a multi-host environment -# -------------------------------------------------------------------------- -securityManager.sessionManager.deleteInvalidSessions = false -securityManager.sessionManager.sessionValidationSchedulerEnabled = false -# Set global default session timeout to eight hours -securityManager.sessionManager.globalSessionTimeout = 28800000 ssl.enabled = false shiro.loginUrl = /login.jsp +# Static user accounts go here +# ---------------------------- +[users] + +# Authorization through roles is implemented here +# ----------------------------------------------- +[roles] + + +# Manager access to urls and url groups +# -------------------------------------- [urls] # # A list of accessable URLs @@ -102,7 +136,10 @@ shiro.loginUrl = /login.jsp # Public pages / = anon, ssl /index = anon, ssl + +# Display community projects /projects.jsp = anon, ssl + /public/** = anon /ping = anon /sessionapi = anon @@ -130,3 +167,5 @@ shiro.loginUrl = /login.jsp /** = authc, user, ssl #Testing + + From d7d29353bef6bf7fe0d7518be3c2df07ef01fa1b Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 02:04:32 -0800 Subject: [PATCH 35/69] Increment build number for deployment to Demo --- .../blocklyprop/internationalization/translations.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/com/parallax/server/blocklyprop/internationalization/translations.properties b/src/main/resources/com/parallax/server/blocklyprop/internationalization/translations.properties index 66a1ce1d..1cd5310a 100644 --- a/src/main/resources/com/parallax/server/blocklyprop/internationalization/translations.properties +++ b/src/main/resources/com/parallax/server/blocklyprop/internationalization/translations.properties @@ -28,7 +28,7 @@ footer.clientdownloadlink = BlocklyProp-client # Application version numbers. application.major = 1 application.minor = 1 -application.build = 455 +application.build = 456 html.content_missing = Content missing From 47816e712cab1f42aa0801039a9779bdd0b939d9 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 08:59:50 -0800 Subject: [PATCH 36/69] Add inline documentation. Insert copyright notice. --- .../server/blocklyprop/TableOrder.java | 25 +++++++++++++---- .../server/blocklyprop/TableSort.java | 27 +++++++++++++++---- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/TableOrder.java b/src/main/java/com/parallax/server/blocklyprop/TableOrder.java index bce5651d..3b597529 100644 --- a/src/main/java/com/parallax/server/blocklyprop/TableOrder.java +++ b/src/main/java/com/parallax/server/blocklyprop/TableOrder.java @@ -1,17 +1,32 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop; /** + * Enumberate possible sort orders for project listings * * @author Michel */ public enum TableOrder { - asc, desc - } diff --git a/src/main/java/com/parallax/server/blocklyprop/TableSort.java b/src/main/java/com/parallax/server/blocklyprop/TableSort.java index c7991bbe..c4b4520e 100644 --- a/src/main/java/com/parallax/server/blocklyprop/TableSort.java +++ b/src/main/java/com/parallax/server/blocklyprop/TableSort.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop; import com.parallax.server.blocklyprop.db.generated.Tables; @@ -10,6 +26,7 @@ import org.jooq.TableField; /** + * A list of the possible fields on which to sort project data * * @author Michel */ @@ -21,14 +38,14 @@ public enum TableSort { user(Tables.PROJECT.ID_USER), modified(Tables.PROJECT.MODIFIED); + // Map this enum to a Field in the JooQ ProjectRecord class private final TableField field; - private TableSort(TableField field) { + TableSort(TableField field) { this.field = field; } public TableField getField() { return field; } - } From 4105aae08bb2db91a325cdd9b47df9c4af7978ef Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 09:07:23 -0800 Subject: [PATCH 37/69] Add inline documentation. --- .../server/blocklyprop/SessionData.java | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/SessionData.java b/src/main/java/com/parallax/server/blocklyprop/SessionData.java index 1b36dcef..0ef95882 100644 --- a/src/main/java/com/parallax/server/blocklyprop/SessionData.java +++ b/src/main/java/com/parallax/server/blocklyprop/SessionData.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop; import com.google.inject.servlet.SessionScoped; @@ -13,7 +29,8 @@ * User session details. * * This class contains the fields used to manage the client's session with - * the application. + * the application. The @SessionScoped decorator will enforce a single + * instance per session. * * @author Michel */ @@ -59,6 +76,11 @@ public void setLocale(String locale) { this.locale = locale; } + /** + * Override the default toString method to enumerate all fields + * + * @return string representation of SessionData fields + */ @Override public String toString() { return "SessionData{" + "user=" + user + ", idUser=" + idUser + ", locale=" + locale + '}'; From 37b2cc45469b54b447d3c38808a82f56a1eab8f5 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 10:24:40 -0800 Subject: [PATCH 38/69] Re-enable code to update user screen name when a mismatch is detected. --- .../blocklyprop/services/impl/SecurityServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/services/impl/SecurityServiceImpl.java b/src/main/java/com/parallax/server/blocklyprop/services/impl/SecurityServiceImpl.java index 1c4f22a7..04a34c5d 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/impl/SecurityServiceImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/impl/SecurityServiceImpl.java @@ -561,9 +561,9 @@ public static SessionData getSessionData() { bpUser.getScreenname(), user.getScreenname()); -// instance.userDao.updateScreenName( -// bpUser.getId(), -// user.getScreenname()); + instance.userDao.updateScreenName( + bpUser.getId(), + user.getScreenname()); } }else{ LOG.warn("Warning! Setting BP user id to zero"); From 707ba881eacf81c803a7a474d8460bdf58047ae6 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 12:58:39 -0800 Subject: [PATCH 39/69] Add a settings field for the user profile profile table. --- db-updates/0014-add-project-settings.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/db-updates/0014-add-project-settings.sql b/db-updates/0014-add-project-settings.sql index b5b2a0d0..7c2bc37e 100644 --- a/db-updates/0014-add-project-settings.sql +++ b/db-updates/0014-add-project-settings.sql @@ -1,15 +1,15 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ /** * Author: Jim Ewald * Created: Aug 27, 2018 * * Add a field to the project table to store a JSON encoded group of project * settings. + * + * Add a field to the user profile to store JSON encoded settings related to + * specific user. */ ALTER TABLE blocklyprop.project ADD settings TEXT NULL; +ALTER TABLE cloudsession.user ADD settings TEXT NULL; + From 93195708712ca8e385c920c66c01b95b9f2c0ede Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 13:01:17 -0800 Subject: [PATCH 40/69] Insert copyright notice. --- .../blocklyprop/db/dao/impl/UserDaoImpl.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/UserDaoImpl.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/UserDaoImpl.java index be056238..f3e7702d 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/UserDaoImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/UserDaoImpl.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.db.dao.impl; import com.google.inject.Inject; @@ -24,6 +40,7 @@ import org.slf4j.LoggerFactory; /** + * Blockly user * * @author Michel */ From 8089bdbe753c593fe7a39b820acf58beca5bb49e Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 13:24:25 -0800 Subject: [PATCH 41/69] Add inline documentation. Insert copyright notice. --- .../server/blocklyprop/db/dao/ProjectDao.java | 220 +++++++++++++++++- 1 file changed, 209 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java index ee4be6e2..6c32b43d 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.db.dao; import com.parallax.server.blocklyprop.TableOrder; @@ -17,11 +33,11 @@ * @author Michel * * Fields: - * id: Unique record number within the table. + * id: Unique record number within the table * - * id_user: Link to primary key in the blocklyprop.user table. + * id_user: Link to primary key in the blocklyprop.user table * - * id_clouduser: Link to the primary key in the cloudsession.user table. + * id_clouduser: Link to the primary key in the cloudsession.user table * * name: Project name * @@ -29,18 +45,18 @@ * * description_html:Project description formatted in HTML * - * code: XML content that hold the project block structure. + * code: XML content that holds the project block structure * * type: Project source language (SPIN or PROPC) * - * board: Descriptor for the target device the project will use. + * board: Descriptor for the target device the project will use * * private: Flag to indicate if the project is visible to anyone but - * the project owner.This flag is mutually exclusive with the + * the project owner. This flag is mutually exclusive with the * 'shared' flag. * * shared: Flag to indicate if the project is available for viewing by - * anyone.This flag is mutually exclusive with the 'private' + * anyone. This flag is mutually exclusive with the 'private' * flag. * * created: Timestamp indicating when the project record was created. @@ -53,8 +69,52 @@ */ public interface ProjectDao { + /** + * Retrieve a project based on the supplied project id + * + * @param idProject - unique project key id + * + * @return - a ProjectRecord object if the project is available or + * a null if the project was not found or is not accessible to the + * the user in the current session. + */ ProjectRecord getProject(Long idProject); + + + /** + * Create a new project record from the supplied details + * + * @param name + * Project name + * + * @param description + * Project description formatted in plain text + * + * @param descriptionHtml + * Project description formatted in HTML + * + * @param code + * XML content that holds the project block structure + * + * @param type + * Project source language (SPIN or PROPC) + * + * @param board + * Descriptor for the target device the project will use + * + * @param privateProject + * Flag to indicate if the project is visible to anyone but the project owner + * + * @param sharedProject + * Flag to indicate if the project is available for viewing by anyone + * + * @param idProjectBasedOn + * The id from the project that is the parent of the current project record + * + * @return + * a ProjectRecord object if the project is available, otherwise return a null + */ ProjectRecord createProject( String name, String description, @@ -66,6 +126,19 @@ ProjectRecord createProject( boolean sharedProject, Long idProjectBasedOn); + + + /** + * + * @param name + * @param description + * @param descriptionHtml + * @param type + * @param board + * @param privateProject + * @param sharedProject + * @return + */ ProjectRecord createProject( String name, String description, @@ -75,6 +148,18 @@ ProjectRecord createProject( boolean privateProject, boolean sharedProject); + + + /** + * + * @param idProject + * @param name + * @param description + * @param descriptionHtml + * @param privateProject + * @param sharedProject + * @return + */ ProjectRecord updateProject( Long idProject, String name, @@ -83,6 +168,19 @@ ProjectRecord updateProject( boolean privateProject, boolean sharedProject); + + + /** + * + * @param idProject + * @param name + * @param description + * @param descriptionHtml + * @param code + * @param privateProject + * @param sharedProject + * @return + */ ProjectRecord updateProject( Long idProject, String name, @@ -92,10 +190,33 @@ ProjectRecord updateProject( boolean privateProject, boolean sharedProject); + + + /** + * Update the code in the selected project + * TODO: The IDE is reporting that this method is never used. Verify and deprecate as needed. + * + * @param idProject + * @param code + * @return + */ ProjectRecord saveCode( Long idProject, String code); + + + /** + * List community projects owned by a selected user + * TODO: This appears to do the same work as the getSharedProjectsByUser method. + * Identify a need for both to exist or deprecate one of the methods. + * @param idUser + * @param sort + * @param order + * @param limit + * @param offset + * @return + */ List getUserProjects( Long idUser, TableSort sort, @@ -103,13 +224,37 @@ List getUserProjects( Integer limit, Integer offset); - // Return a list of community projects + + + /** + * Return a list of community projects + * + * @param sort + * @param order + * @param limit + * @param offset + * @return + */ List getSharedProjects( TableSort sort, TableOrder order, Integer limit, Integer offset); + + + /** + * List community projects owned by a selected user + * TODO: This appears to do the same work as the overloaded getUserProjects. + * Figure it out and remove one. + * + * @param sort + * @param order + * @param limit + * @param offset + * @param idUser + * @return + */ List getSharedProjectsByUser( TableSort sort, TableOrder order, @@ -117,20 +262,73 @@ List getSharedProjectsByUser( Integer offset, Long idUser); + + + /** + * + * @param idUser + * @return + */ int countUserProjects(Long idUser); + + + /** + * + * @param idUser + * @return + */ int countSharedProjects(Long idUser); + + + /** + * + * @param idUser + * @return + */ int countSharedProjectsByUser(Long idUser); + + + /** + * + * @param idProject + * @return + */ ProjectRecord cloneProject(Long idProject); + + + /** + * + * @param idProject + * @return + */ boolean deleteProject(Long idProject); + + + /** + * + * @param idProject + * @param code + * @return + */ ProjectRecord updateProjectCode( Long idProject, String code); + + + /** + * + * @param idProject + * @param code + * @param newName + * @param newBoard + * @return + */ ProjectRecord saveProjectCodeAs( Long idProject, String code, From a9adb38f66626d5449a2463fc289a8671a24359c Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 14:19:51 -0800 Subject: [PATCH 42/69] Add support for a project settings field in a project. This is designed to hold a JSON formatted string that contains settings as defined by the client UI. --- .../server/blocklyprop/db/dao/ProjectDao.java | 50 ++++++ .../db/dao/impl/ProjectDaoImpl.java | 160 ++++++++++++++++-- .../db/generated/tables/Project.java | 7 +- .../db/generated/tables/pojos/Project.java | 17 +- .../tables/records/ProjectRecord.java | 63 +++++-- 5 files changed, 271 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java index 6c32b43d..ab8a85df 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java @@ -81,6 +81,56 @@ public interface ProjectDao { ProjectRecord getProject(Long idProject); + /** + * Create a new project record from the supplied details + * + * @param name + * Project name + * + * @param description + * Project description formatted in plain text + * + * @param descriptionHtml + * Project description formatted in HTML + * + * @param code + * XML content that holds the project block structure + * + * @param type + * Project source language (SPIN or PROPC) + * + * @param board + * Descriptor for the target device the project will use + * + * @param privateProject + * Flag to indicate if the project is visible to anyone but the project owner + * + * @param sharedProject + * Flag to indicate if the project is available for viewing by anyone + * + * @param idProjectBasedOn + * The id from the project that is the parent of the current project record + * + * @param projectSettings + * A JSON formatted string containing project settings. + * + * @return + * a ProjectRecord object if the project is available, otherwise return a null + */ + + ProjectRecord createProject( + String name, + String description, + String descriptionHtml, + String code, + ProjectType type, + String board, + boolean privateProject, + boolean sharedProject, + Long idProjectBasedOn, + String projectSettings); + + /** * Create a new project record from the supplied details diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java index 93c7308a..ac5afbce 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java @@ -43,7 +43,7 @@ public class ProjectDaoImpl implements ProjectDao { * The absolute lowest number of bytes that can exist in an * un-populated project. */ - private static final int Min_BlocklyCodeSize = 48; + private static final int MIN_BLOCKLY_CODE_SIZE = 48; /** @@ -79,6 +79,8 @@ public void setDSLContext(DSLContext dsl) { private ProjectSharingService projectSharingService; + + @Inject public void setProjectSharingContext(ProjectSharingService projectSharingService) { this.projectSharingService = projectSharingService; @@ -86,7 +88,6 @@ public void setProjectSharingContext(ProjectSharingService projectSharingService /** - * * Retrieve a new project record based from an existing project. * * Note: There are private getProject methods that retrieve a project record @@ -131,7 +132,117 @@ record = create return alterReadRecord(record); } - + + /** + * Create a complete project record. All parameters are supplied by the caller. + * + * @param name + * Project name + * + * @param description + * Project description formatted in plain text + * + * @param descriptionHtml + * Project description formatted in HTML + * + * @param code + * XML content that holds the project block structure + * + * @param type + * Project source language (SPIN or PROPC) + * + * @param board + * Descriptor for the target device the project will use + * + * @param privateProject + * Flag to indicate if the project is visible to anyone but the project owner + * + * @param sharedProject + * Flag to indicate if the project is available for viewing by anyone + * + * @param idProjectBasedOn + * The id from the project that is the parent of the current project record + * + * @param projectSettings + * A JSON formatted string containing project settings. + * + * @return + */ + public ProjectRecord createProject( + String name, + String description, + String descriptionHtml, + String code, + ProjectType type, + String board, + boolean privateProject, + boolean sharedProject, + Long idProjectBasedOn, + String projectSettings) { + + ProjectRecord record = null; + + // Get the logged in user's userID + Long idUser = BlocklyPropSecurityUtils.getCurrentUserId(); + if (idUser == null) { + LOG.error("Null BP UserID"); + return null; + } + + // Get the cloud session user id from the current authenticated session + Long idCloudUser = BlocklyPropSecurityUtils.getCurrentSessionUserId(); + if (idCloudUser == null) { + LOG.error("Null cloud user ID"); + return null; + } + + try { + record = create + .insertInto(Tables.PROJECT, + Tables.PROJECT.ID_USER, + Tables.PROJECT.ID_CLOUDUSER, + Tables.PROJECT.NAME, + Tables.PROJECT.DESCRIPTION, + Tables.PROJECT.DESCRIPTION_HTML, + Tables.PROJECT.CODE, + Tables.PROJECT.CODE_BLOCK_VERSION, + Tables.PROJECT.TYPE, + Tables.PROJECT.BOARD, + Tables.PROJECT.PRIVATE, + Tables.PROJECT.SHARED, + Tables.PROJECT.BASED_ON, + Tables.PROJECT.SETTINGS) + .values(idUser, + idCloudUser, + name, + description, + descriptionHtml, + code, + BLOCKLY_LIBRARY_VERSION, + type, + board, + privateProject, + sharedProject, + idProjectBasedOn, + projectSettings) + .returning() + .fetchOne(); + } + catch (org.jooq.exception.DataAccessException sqex) { + LOG.error("Database error encountered {}", sqex.getMessage()); + return null; + } catch (Exception ex) { + LOG.error("Unexpected exception creating a project record"); + LOG.error("Error Message: {}", ex.getMessage()); + return null; + } + + return record; + + } + + + /** * * Create a new project with supplied code. @@ -161,6 +272,11 @@ public ProjectRecord createProject( boolean sharedProject, Long idProjectBasedOn) { + return createProject( + name, description, descriptionHtml, code, type, board, + privateProject, sharedProject, idProjectBasedOn, null); + +/* LOG.info("Creating a new project with existing code."); ProjectRecord record = null; @@ -219,8 +335,11 @@ record = create } return record; + */ } + + /** * * Create a new project with a blank canvas (no blocks). @@ -436,7 +555,7 @@ public List getSharedProjects( Integer limit, Integer offset) { - LOG.info("Retreive shared projects."); + LOG.info("Retrieve shared projects."); SortField orderField = sort == null ? Tables.PROJECT.NAME.asc() : sort.getField().asc(); if (TableOrder.desc == order) { @@ -718,6 +837,8 @@ public ProjectRecord saveProjectCodeAs(Long idProject, String code, String newNa return null; } + + // Private over-ride of the public getProject() // // @@ -750,9 +871,17 @@ private ProjectRecord getProject(Long idProject, boolean toEdit) { return alterReadRecord(record); } + + + /** + * + * @param original + * @return + */ private ProjectRecord doProjectClone(ProjectRecord original) { // TODO: Add based_on parameter as last argument + // TODO: Add parameter to support project settings ProjectRecord cloned = createProject( original.getName(), original.getDescription(), @@ -768,7 +897,7 @@ private ProjectRecord doProjectClone(ProjectRecord original) { // cloned.setBasedOn(original.getId()); // cloned.update(); - // WHAT IS THIS DOING? + // TODO WHAT IS THIS DOING? create.update(Tables.PROJECT) .set(Tables.PROJECT.BASED_ON, original.getId()) .where(Tables.PROJECT.ID.equal(cloned.getId())); @@ -777,7 +906,13 @@ private ProjectRecord doProjectClone(ProjectRecord original) { } - // Produce a current timestamp + + + /** + * Produce a current timestamp + * + * @return a GregorianCalendar time stamp + */ private GregorianCalendar getCurrentTimestamp() { GregorianCalendar cal = new GregorianCalendar(); cal.setTime(new java.util.Date()); @@ -787,9 +922,7 @@ private GregorianCalendar getCurrentTimestamp() { - - - + // Evaluate project code and replace any deprecated or updated blocks // // Return a ProjectRecord object. The code field may be altered to correct @@ -798,7 +931,6 @@ private GregorianCalendar getCurrentTimestamp() { // horribly wrong with the string conversions. // private ProjectRecord alterReadRecord(ProjectRecord record) { - String currentCode, newCode; @@ -825,7 +957,7 @@ private ProjectRecord alterReadRecord(ProjectRecord record) { return record; } - if (currentCode.length() < Min_BlocklyCodeSize ) { + if (currentCode.length() < MIN_BLOCKLY_CODE_SIZE) { LOG.warn("Project code appears to be empty. Code size:{}",currentCode.length()); return record; } @@ -849,7 +981,9 @@ private ProjectRecord alterReadRecord(ProjectRecord record) { return record; } - /** + + + /** * * Create a random string to use as a blockID. * @@ -867,6 +1001,8 @@ private String randomString(int len) { return sb.toString(); } + + // Correct depricated block details related to Spin blocks @Deprecated private String fixSpinProjectBlocks(String newCode) { diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Project.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Project.java index 8c201861..da28e09f 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Project.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Project.java @@ -38,7 +38,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Project extends TableImpl { - private static final long serialVersionUID = -1048994704; + private static final long serialVersionUID = -1976592299; /** * The reference instance of blocklyprop.project @@ -128,6 +128,11 @@ public Class getRecordType() { */ public final TableField BASED_ON = createField("based_on", org.jooq.impl.SQLDataType.BIGINT, this, ""); + /** + * The column blocklyprop.project.settings. + */ + public final TableField SETTINGS = createField("settings", org.jooq.impl.SQLDataType.CLOB, this, ""); + /** * Create a blocklyprop.project table reference */ diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Project.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Project.java index 4cded530..b84c7b66 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Project.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Project.java @@ -25,7 +25,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Project implements Serializable { - private static final long serialVersionUID = -1074694014; + private static final long serialVersionUID = 1473532278; private Long id; private Long idUser; @@ -42,6 +42,7 @@ public class Project implements Serializable { private GregorianCalendar created; private GregorianCalendar modified; private Long basedOn; + private String settings; public Project() {} @@ -61,6 +62,7 @@ public Project(Project value) { this.created = value.created; this.modified = value.modified; this.basedOn = value.basedOn; + this.settings = value.settings; } public Project( @@ -78,7 +80,8 @@ public Project( Boolean shared, GregorianCalendar created, GregorianCalendar modified, - Long basedOn + Long basedOn, + String settings ) { this.id = id; this.idUser = idUser; @@ -95,6 +98,7 @@ public Project( this.created = created; this.modified = modified; this.basedOn = basedOn; + this.settings = settings; } public Long getId() { @@ -217,6 +221,14 @@ public void setBasedOn(Long basedOn) { this.basedOn = basedOn; } + public String getSettings() { + return this.settings; + } + + public void setSettings(String settings) { + this.settings = settings; + } + @Override public String toString() { StringBuilder sb = new StringBuilder("Project ("); @@ -236,6 +248,7 @@ public String toString() { sb.append(", ").append(created); sb.append(", ").append(modified); sb.append(", ").append(basedOn); + sb.append(", ").append(settings); sb.append(")"); return sb.toString(); diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/ProjectRecord.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/ProjectRecord.java index ce0e1357..1fc51c93 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/ProjectRecord.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/ProjectRecord.java @@ -13,8 +13,8 @@ import org.jooq.Field; import org.jooq.Record1; -import org.jooq.Record15; -import org.jooq.Row15; +import org.jooq.Record16; +import org.jooq.Row16; import org.jooq.impl.UpdatableRecordImpl; @@ -29,9 +29,9 @@ comments = "This class is generated by jOOQ" ) @SuppressWarnings({ "all", "unchecked", "rawtypes" }) -public class ProjectRecord extends UpdatableRecordImpl implements Record15 { +public class ProjectRecord extends UpdatableRecordImpl implements Record16 { - private static final long serialVersionUID = 902114783; + private static final long serialVersionUID = 83268425; /** * Setter for blocklyprop.project.id. @@ -243,6 +243,20 @@ public Long getBasedOn() { return (Long) getValue(14); } + /** + * Setter for blocklyprop.project.settings. + */ + public void setSettings(String value) { + setValue(15, value); + } + + /** + * Getter for blocklyprop.project.settings. + */ + public String getSettings() { + return (String) getValue(15); + } + // ------------------------------------------------------------------------- // Primary key information // ------------------------------------------------------------------------- @@ -256,23 +270,23 @@ public Record1 key() { } // ------------------------------------------------------------------------- - // Record15 type implementation + // Record16 type implementation // ------------------------------------------------------------------------- /** * {@inheritDoc} */ @Override - public Row15 fieldsRow() { - return (Row15) super.fieldsRow(); + public Row16 fieldsRow() { + return (Row16) super.fieldsRow(); } /** * {@inheritDoc} */ @Override - public Row15 valuesRow() { - return (Row15) super.valuesRow(); + public Row16 valuesRow() { + return (Row16) super.valuesRow(); } /** @@ -395,6 +409,14 @@ public Field field15() { return Project.PROJECT.BASED_ON; } + /** + * {@inheritDoc} + */ + @Override + public Field field16() { + return Project.PROJECT.SETTINGS; + } + /** * {@inheritDoc} */ @@ -515,6 +537,14 @@ public Long value15() { return getBasedOn(); } + /** + * {@inheritDoc} + */ + @Override + public String value16() { + return getSettings(); + } + /** * {@inheritDoc} */ @@ -654,7 +684,16 @@ public ProjectRecord value15(Long value) { * {@inheritDoc} */ @Override - public ProjectRecord values(Long value1, Long value2, Long value3, String value4, String value5, String value6, String value7, Short value8, ProjectType value9, String value10, Boolean value11, Boolean value12, GregorianCalendar value13, GregorianCalendar value14, Long value15) { + public ProjectRecord value16(String value) { + setSettings(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public ProjectRecord values(Long value1, Long value2, Long value3, String value4, String value5, String value6, String value7, Short value8, ProjectType value9, String value10, Boolean value11, Boolean value12, GregorianCalendar value13, GregorianCalendar value14, Long value15, String value16) { value1(value1); value2(value2); value3(value3); @@ -670,6 +709,7 @@ public ProjectRecord values(Long value1, Long value2, Long value3, String value4 value13(value13); value14(value14); value15(value15); + value16(value16); return this; } @@ -687,7 +727,7 @@ public ProjectRecord() { /** * Create a detached, initialised ProjectRecord */ - public ProjectRecord(Long id, Long idUser, Long idClouduser, String name, String description, String descriptionHtml, String code, Short codeBlockVersion, ProjectType type, String board, Boolean private_, Boolean shared, GregorianCalendar created, GregorianCalendar modified, Long basedOn) { + public ProjectRecord(Long id, Long idUser, Long idClouduser, String name, String description, String descriptionHtml, String code, Short codeBlockVersion, ProjectType type, String board, Boolean private_, Boolean shared, GregorianCalendar created, GregorianCalendar modified, Long basedOn, String settings) { super(Project.PROJECT); setValue(0, id); @@ -705,5 +745,6 @@ public ProjectRecord(Long id, Long idUser, Long idClouduser, String name, String setValue(12, created); setValue(13, modified); setValue(14, basedOn); + setValue(15, settings); } } From 13c146f5a0c7035c233e505cf5609c05b01016e6 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 20 Feb 2019 14:20:04 -0800 Subject: [PATCH 43/69] Add inline documentation. Insert copyright notice. --- .../parallax/server/blocklyprop/db/generated/tables/Motd.java | 2 +- .../server/blocklyprop/db/generated/tables/pojos/Motd.java | 2 +- .../blocklyprop/db/generated/tables/records/MotdRecord.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Motd.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Motd.java index b7b64129..d64066da 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Motd.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Motd.java @@ -160,4 +160,4 @@ public Motd as(String alias) { public Motd rename(String name) { return new Motd(name, null); } -} \ No newline at end of file +} diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Motd.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Motd.java index 1ef5b542..65d052bc 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Motd.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Motd.java @@ -173,4 +173,4 @@ public String toString() { sb.append(")"); return sb.toString(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/MotdRecord.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/MotdRecord.java index b6e83213..00d16202 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/MotdRecord.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/MotdRecord.java @@ -500,4 +500,4 @@ public MotdRecord(Long id, String messageText, String messageHtml, String notes, setValue(8, messageEnableTime); setValue(9, messageDisableTime); } -} \ No newline at end of file +} From 0753132548d8a395db61695febd6633257311174 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 21 Feb 2019 10:55:27 -0800 Subject: [PATCH 44/69] Implement project settings support. Arrange methods based on functional groups. Added to documentation effort. --- .../db/dao/impl/ProjectDaoImpl.java | 660 +++++++++--------- 1 file changed, 336 insertions(+), 324 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java index ac5afbce..6999ef3a 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java @@ -39,54 +39,51 @@ @Singleton public class ProjectDaoImpl implements ProjectDao { - /** - * The absolute lowest number of bytes that can exist in an - * un-populated project. - */ + // The absolute lowest number of bytes that can exist in an un-populated project. private static final int MIN_BLOCKLY_CODE_SIZE = 48; - /** - * Application logging facility - */ + // Application logging facility private static final Logger LOG = LoggerFactory.getLogger(ProjectDao.class); - /** - * Database connection - */ + // Database connection private DSLContext create; - // Used by the randomString function - static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#()*-+./:;=@[]^_`{|}~"; - static Random rnd = new Random(); - - // Constants to clarify the edit flag in method calls static final boolean EDIT_MODE_OFF = false; static final boolean EDIT_MODE_ON = true; + // Constant to identify the current version of the blockly block library; public static final short BLOCKLY_LIBRARY_VERSION = 1; - + + // Inject a database connection @Inject public void setDSLContext(DSLContext dsl) { this.create = dsl; } + // Injecting the project sharing service + // TODO: Figure out why the projected sharing service is being injected into + // the DAO layer. That is just weird private ProjectSharingService projectSharingService; - - @Inject public void setProjectSharingContext(ProjectSharingService projectSharingService) { this.projectSharingService = projectSharingService; } + /************************************************************************** + * + * GET methods + * + **************************************************************************/ + /** * Retrieve a new project record based from an existing project. * @@ -122,6 +119,7 @@ record = create } catch (org.jooq.exception.DataAccessException sqex) { LOG.error("Database error encountered {}", sqex.getMessage()); return null; + } catch (Exception ex) { LOG.error("Unexpected exception retreiving a project record"); LOG.error("Error Message: {}", ex.getMessage()); @@ -133,6 +131,124 @@ record = create } + /** + * TODO: add details. + * + * @param idUser + * @param sort + * @param order + * @param limit + * @param offset + * @return + */ + @Override + public List getUserProjects( + Long idUser, + TableSort sort, + TableOrder order, + Integer limit, + Integer offset) { + + LOG.info("Retreive projects for user {}.", idUser); + + SortField orderField = Tables.PROJECT.NAME.asc(); + if (TableOrder.desc == order) { + orderField = Tables.PROJECT.NAME.desc(); + } + + return create + .selectFrom(Tables.PROJECT) + .where(Tables.PROJECT.ID_USER.equal(idUser)) + .orderBy(orderField).limit(limit).offset(offset) + .fetch(); + } + + /** + * Return a list of community projects constrained by the parameters + * + * @param sort + * @param order + * @param limit + * @param offset + + * @return + * Returns a list of ProjectRecord objects corresponding to the projects + * matching the selection creiteria + */ + @Override + public List getSharedProjects( + TableSort sort, + TableOrder order, + Integer limit, + Integer offset) { + + LOG.info("Retrieve shared projects."); + + SortField orderField = sort == null ? Tables.PROJECT.NAME.asc() : sort.getField().asc(); + + if (TableOrder.desc == order) { + orderField = sort == null ? Tables.PROJECT.NAME.desc() : sort.getField().desc(); + } + + // Search for community projects + Condition conditions = Tables.PROJECT.SHARED.eq(Boolean.TRUE); + + return create + .selectFrom(Tables.PROJECT) + .where(conditions) + .orderBy(orderField).limit(limit).offset(offset) + .fetch(); + } + + + + /** + * TODO: add details. + * + * @param sort + * @param order + * @param limit + * @param offset + * @param idUser + * @return + */ + @Override + public List getSharedProjectsByUser( + TableSort sort, + TableOrder order, + Integer limit, + Integer offset, + Long idUser) { + + LOG.info("Retreive shared projects."); + + SortField orderField = sort == null ? Tables.PROJECT.NAME.asc() : sort.getField().asc(); + + if (TableOrder.desc == order) { + orderField = sort == null ? Tables.PROJECT.NAME.desc() : sort.getField().desc(); + } + + Condition conditions = Tables.PROJECT.SHARED.eq(Boolean.TRUE); + + if (idUser != null) { + conditions = conditions.and(Tables.PROJECT.ID_USER.eq(idUser)); + } + + return create + .selectFrom(Tables.PROJECT) + .where(conditions) + .orderBy(orderField).limit(limit).offset(offset) + .fetch(); + } + + + + /************************************************************************** + * + * POST methods + * + **************************************************************************/ + /** * Create a complete project record. All parameters are supplied by the caller. * @@ -167,7 +283,9 @@ record = create * A JSON formatted string containing project settings. * * @return + * Returns a ProjectRecord object, including the new project settings string */ + @Override public ProjectRecord createProject( String name, String description, @@ -244,7 +362,6 @@ record = create /** - * * Create a new project with supplied code. * * @param name Project name @@ -259,6 +376,8 @@ record = create * from another project * * @return a fully formed ProjectRecord or a null if an error is detected. + * + * @implNote This is an override of the base createProject class. */ @Override public ProjectRecord createProject( @@ -276,66 +395,6 @@ public ProjectRecord createProject( name, description, descriptionHtml, code, type, board, privateProject, sharedProject, idProjectBasedOn, null); -/* - LOG.info("Creating a new project with existing code."); - - ProjectRecord record = null; - - // Get the logged in user's userID - Long idUser = BlocklyPropSecurityUtils.getCurrentUserId(); - if (idUser == null) { - LOG.error("Null BP UserID"); - return null; - } - - // Get the cloud session user id from the current authenticated session - Long idCloudUser = BlocklyPropSecurityUtils.getCurrentSessionUserId(); - if (idCloudUser == null) { - LOG.error("Null cloud user ID"); - return null; - } - - try { - record = create - .insertInto(Tables.PROJECT, - Tables.PROJECT.ID_USER, - Tables.PROJECT.ID_CLOUDUSER, - Tables.PROJECT.NAME, - Tables.PROJECT.DESCRIPTION, - Tables.PROJECT.DESCRIPTION_HTML, - Tables.PROJECT.CODE, - Tables.PROJECT.CODE_BLOCK_VERSION, - Tables.PROJECT.TYPE, - Tables.PROJECT.BOARD, - Tables.PROJECT.PRIVATE, - Tables.PROJECT.SHARED, - Tables.PROJECT.BASED_ON) - .values(idUser, - idCloudUser, - name, - description, - descriptionHtml, - code, - BLOCKLY_LIBRARY_VERSION, - type, - board, - privateProject, - sharedProject, - idProjectBasedOn) - .returning() - .fetchOne(); - } - catch (org.jooq.exception.DataAccessException sqex) { - LOG.error("Database error encountered {}", sqex.getMessage()); - return null; - } catch (Exception ex) { - LOG.error("Unexpected exception creating a project record"); - LOG.error("Error Message: {}", ex.getMessage()); - return null; - } - - return record; - */ } @@ -344,9 +403,6 @@ record = create * * Create a new project with a blank canvas (no blocks). * - * This is an overload of the base createProject method that omits - * the code block. - * * @param name * @param description * @param descriptionHtml @@ -355,6 +411,9 @@ record = create * @param privateProject * @param sharedProject * @return + * + * @implNote + * This is an overload of the base createProject method that omits the code block. */ @Override public ProjectRecord createProject( @@ -366,21 +425,50 @@ public ProjectRecord createProject( boolean privateProject, boolean sharedProject) { - LOG.info("Creating a new, empty project from existing project."); + LOG.info("Creating a new, empty project."); // TODO: Add based_on field to end of argument list return createProject( - name, - description, - descriptionHtml, - "", - type, - board, - privateProject, - sharedProject, - null); + name, description, descriptionHtml, "", type, board, + privateProject, sharedProject, null); } + /** + * TODO: add details. + * + * @param idProject + * @return + */ + @Override + public ProjectRecord cloneProject(Long idProject) { + + LOG.info("Clone existing project {} to a new project.", idProject); + + // Retrieve the project to be copied + ProjectRecord projectRecord = getProject(idProject); + + if (projectRecord == null) { + throw new NullPointerException("Project doesn't exist"); + } + + // Allow a copy only if the project is shared (public) or if the + // currently logged in user owns the project + Long idUser = BlocklyPropSecurityUtils.getCurrentUserId(); + + if (projectRecord.getIdUser().equals(idUser) || projectRecord.getShared()) { + return doProjectClone(projectRecord); + } + + return null; + } + + + /************************************************************************** + * + * UPDATE methods + * + **************************************************************************/ + /** * @@ -409,20 +497,27 @@ public ProjectRecord updateProject( LOG.info("Update project {}.", idProject); ProjectRecord record = getProject(idProject, EDIT_MODE_ON); + if (record == null) { LOG.warn("Unable to locate project {} to update it.", idProject); return null; } + // Update the fields with data from the caller record.setName(name); record.setDescription(description); record.setDescriptionHtml(descriptionHtml); record.setPrivate(privateProject); record.setShared(sharedProject); + + // Add some housekeeping meta data record.setModified(getCurrentTimestamp()); record.setCodeBlockVersion(BLOCKLY_LIBRARY_VERSION); + + // Persist the updated record record.update(); + return record; } @@ -454,166 +549,153 @@ public ProjectRecord updateProject( LOG.info("Update project {}.", idProject); ProjectRecord record = getProject(idProject, EDIT_MODE_ON); - if (record != null) { - record.setName(name); - record.setDescription(description); - record.setDescriptionHtml(descriptionHtml); - record.setCode(code); - record.setPrivate(privateProject); - record.setShared(sharedProject); - record.setModified(getCurrentTimestamp()); - record.setCodeBlockVersion(BLOCKLY_LIBRARY_VERSION); - record.update(); - - return record; + + if (record == null) { + LOG.warn("Unable to update project {}", idProject); + return null; } - LOG.warn("Unable to update project {}", idProject); - return null; - } + record.setCode(code); + + record.setName(name); + record.setDescription(description); + record.setDescriptionHtml(descriptionHtml); + record.setPrivate(privateProject); + record.setShared(sharedProject); + + record.setModified(getCurrentTimestamp()); + record.setCodeBlockVersion(BLOCKLY_LIBRARY_VERSION); + + record.update(); + + return record; + } /** * Update the code blocks for a project * - * @param idProject - * @param code - * @return + * @param idProject the project primary key ID for the project that will receive the update + * + * @param code the code blocks to update within the project + * + * @return an updated ProjectRecord that contains the new code blocks */ @Override public ProjectRecord saveCode(Long idProject, String code) { LOG.info("Saving code for project {}.", idProject); ProjectRecord record = getProject(idProject, EDIT_MODE_ON); - if (record != null) { - // Update project record details - record.setCode(code); - record.setCodeBlockVersion(BLOCKLY_LIBRARY_VERSION); - record.setModified(getCurrentTimestamp()); - - // Update the record - ProjectRecord returningRecord = create - .update(Tables.PROJECT) - .set(record) - .returning() - .fetchOne(); + + if (record == null) { + LOG.error("Unable to save code for project {}", idProject); + return null; + } + + // Update project record details + record.setCode(code); + record.setCodeBlockVersion(BLOCKLY_LIBRARY_VERSION); + record.setModified(getCurrentTimestamp()); - // Return a copy of the updated project record - return returningRecord; + // Update the record and then return the updated record + return create + .update(Tables.PROJECT) + .set(record) + .returning() + .fetchOne(); } - - LOG.error("Unable to save code for project {}", idProject); - return null; - } + + /** - * TODO: add details. + * Update the code block in the specified project + * + * @param idProject + * @param code * - * @param idUser - * @param sort - * @param order - * @param limit - * @param offset * @return + * Returns the specified project record, otherwise it returns a null if + * the current user does not own the project and the project is not shared + * or public, or the requested project record was not found. + * + * @implNote This method will actually create a new project record based on the + * existing project under specific conditions. Since this is an update record method, + * the creation of a new project my be unexpected at higher layers of the application. */ @Override - public List getUserProjects( - Long idUser, - TableSort sort, - TableOrder order, - Integer limit, - Integer offset) { - - LOG.info("Retreive projects for user {}.", idUser); + public ProjectRecord updateProjectCode(Long idProject, String code) { - SortField orderField = Tables.PROJECT.NAME.asc(); - if (TableOrder.desc == order) { - orderField = Tables.PROJECT.NAME.desc(); - } + LOG.info("Update code for project {}.", idProject); - return create.selectFrom(Tables.PROJECT) - .where(Tables.PROJECT.ID_USER.equal(idUser)) - .orderBy(orderField).limit(limit).offset(offset) - .fetch(); - } + // Retrieve the specified project + ProjectRecord record = create.selectFrom(Tables.PROJECT) + .where(Tables.PROJECT.ID.equal(idProject)) + .fetchOne(); - /** - * Return a list of community projects constrained by the parameters - * - * @param sort - * @param order - * @param limit - * @param offset + if (record != null) { + // Found the project. Verify that the current user owns it + Long idUser = BlocklyPropSecurityUtils.getCurrentUserId(); - * @return - * Returns a list of ProjectRecord objects corresponding to the projects - * matching the selection creiteria - */ - @Override - public List getSharedProjects( - TableSort sort, - TableOrder order, - Integer limit, - Integer offset) { - - LOG.info("Retrieve shared projects."); + if (idUser == 0) { + LOG.error("User is not logged in. Current user ID is zero for project {}", idProject); + return null; + } - SortField orderField = sort == null ? Tables.PROJECT.NAME.asc() : sort.getField().asc(); - if (TableOrder.desc == order) { - orderField = sort == null ? Tables.PROJECT.NAME.desc() : sort.getField().desc(); - } + /* ------------------------------------------------------------------------ + // Is the user ID in the current project zero. This should never happen + // but if it does, the project is orphaned. This can happen if the project + // owner's user profile has been removed incorrectly. The orphaned project + // should be either removed or converted to a community project. + // -----------------------------------------------------------------------*/ + if (record.getIdUser() == 0) { + LOG.error("Detected project user ID is zero for project {}", idProject); + return null; + } - // Search for community projects - Condition conditions = Tables.PROJECT.SHARED.eq(Boolean.TRUE); - - return create.selectFrom(Tables.PROJECT) - .where(conditions) - .orderBy(orderField).limit(limit).offset(offset) - .fetch(); - } + // Get a timestamp used to update the modified field of the project record + GregorianCalendar cal = new GregorianCalendar(); + cal.setTime(new java.util.Date()); - /** - * TODO: add details. - * - * @param sort - * @param order - * @param limit - * @param offset - * @param idUser - * @return - */ - @Override - public List getSharedProjectsByUser( - TableSort sort, - TableOrder order, - Integer limit, - Integer offset, - Long idUser) { - - LOG.info("Retreive shared projects."); + // Update the project if the current user owns it + if (record.getIdUser().equals(idUser)) { + record.setCode(code); + record.setModified(cal); + record.setCodeBlockVersion(BLOCKLY_LIBRARY_VERSION); + record.update(); + return record; + } else { + // If the project is a shared project, allow the current user + // to clone the project into their library + if (record.getShared()) { + ProjectRecord cloned = doProjectClone(record); + cloned.setCode(code); + cloned.setModified(cal); + cloned.setCodeBlockVersion(BLOCKLY_LIBRARY_VERSION); + cloned.setIdUser(idUser); // The logged in user owns this copy of the project + cloned.update(); + return cloned; + } - SortField orderField = sort == null ? Tables.PROJECT.NAME.asc() : sort.getField().asc(); - if (TableOrder.desc == order) { - orderField = sort == null ? Tables.PROJECT.NAME.desc() : sort.getField().desc(); - } - Condition conditions = Tables.PROJECT.SHARED.eq(Boolean.TRUE); - if (idUser != null) { - conditions = conditions.and(Tables.PROJECT.ID_USER.eq(idUser)); + LOG.error("User {} tried and failed to update project {}.", idUser, idProject); + throw new UnauthorizedException(); + } + } else { + LOG.warn("Unable to project {}. Unknown reason.", idProject); + return null; } - return create.selectFrom(Tables.PROJECT) - .where(conditions) - .orderBy(orderField).limit(limit).offset(offset) - .fetch(); } + + /** - * TODO: add details. + * Get the number of projects owned by a specified userId * - * @param idUser - * @return + * @param idUser the primary key ID of the user who onws the projects to be counted + * + * @return the number of rows found to be owned by the specified user */ @Override public int countUserProjects(Long idUser) { - LOG.info("Count project for user {}.", idUser); + LOG.debug("Count project for user {}.", idUser); return create.fetchCount(Tables.PROJECT, Tables.PROJECT.ID_USER.equal(idUser)); } @@ -654,118 +736,44 @@ public int countSharedProjectsByUser(Long idUser) { LOG.info("Count shared projects for user {}.", idUser); Condition conditions = Tables.PROJECT.SHARED.equal(Boolean.TRUE); + if (idUser != null) { conditions = conditions.and(Tables.PROJECT.ID_USER.eq(idUser)); } + return create.fetchCount(Tables.PROJECT, conditions); } - /** - * TODO: add details. - * - * @param idProject - * @return - */ - @Override - public ProjectRecord cloneProject(Long idProject) { - LOG.info("Clone existing project {} to a new project.", idProject); - ProjectRecord original = getProject(idProject); - if (original == null) { - throw new NullPointerException("Project doesn't exist"); - } - Long idUser = BlocklyPropSecurityUtils.getCurrentUserId(); - if (original.getIdUser().equals(idUser) || original.getShared()) { // TODO check if friends - return doProjectClone(original); - } - return null; - } + + + + /************************************************************************** + * + * DELETE methods + * + **************************************************************************/ /** - * TODO: add details. + * Delete a project * * @param idProject + * The project primary key if + * * @return + * True if the project was successfully removed, otherwise false. */ @Override public boolean deleteProject(Long idProject) { + LOG.info("Delete project {}.", idProject); + return create.deleteFrom(Tables.PROJECT) .where(Tables.PROJECT.ID.equal(idProject)) .execute() > 0; } - /** - * Update the code block in the specified project - * - * @param idProject - * @param code - * - * @return - * Returns the specified project record, otherwise it returns a null if - * the current user does not own the project and the project is not shared - * or public, or the requested project record was not found. - * - * @implNote This method will actually create a new project record based on the - * existing project under specific conditions. Since this is an update record method, - * the creation of a new project my be unexpected at higher layers of the application. - */ - @Override - public ProjectRecord updateProjectCode(Long idProject, String code) { - LOG.info("Update code for project {}.", idProject); - - // Retrieve the specified project - ProjectRecord record = create.selectFrom(Tables.PROJECT) - .where(Tables.PROJECT.ID.equal(idProject)) - .fetchOne(); - - // Get a timestamp used to update the modified field of the project record - GregorianCalendar cal = new GregorianCalendar(); - cal.setTime(new java.util.Date()); - - if (record != null) { - // Found the project. Verify that the current user owns it - Long idUser = BlocklyPropSecurityUtils.getCurrentUserId(); - - // TODO: Detecting a zero user id - if (idUser == 0) { - LOG.error("Detected current user ID is zero for project {}", idProject); - return null; - } - - if (record.getIdUser() == 0) { - LOG.error("Detected project user ID is zero for project {}", idProject); - return null; - } - - // Update the project if the current user owns it - if (record.getIdUser().equals(idUser)) { - record.setCode(code); - record.setModified(cal); - record.setCodeBlockVersion(BLOCKLY_LIBRARY_VERSION); - record.update(); - return record; - } else { - // If the project is a shared project, allow the current user - // to clone the project into their library - if (record.getShared()) { - ProjectRecord cloned = doProjectClone(record); - cloned.setCode(code); - cloned.setModified(cal); - cloned.setCodeBlockVersion(BLOCKLY_LIBRARY_VERSION); - cloned.setIdUser(idUser); // The logged in user owns this copy of the project - cloned.update(); - return cloned; - } - LOG.error("User {} tried and failed to update project {}.", idUser, idProject); - throw new UnauthorizedException(); - } - } else { - LOG.warn("Unable to project {}. Unknown reason.", idProject); - return null; - } - } @@ -837,6 +845,11 @@ public ProjectRecord saveProjectCodeAs(Long idProject, String code, String newNa return null; } + /************************************************************************** + * + * PRIVATE methods + * + **************************************************************************/ // Private over-ride of the public getProject() @@ -874,15 +887,18 @@ private ProjectRecord getProject(Long idProject, boolean toEdit) { /** + * Create a copy of an existing project and add it to the current user's library + * + * @param original The source project to copy into a new project * - * @param original * @return + * A copy of the original project with ownership updated to the current user */ private ProjectRecord doProjectClone(ProjectRecord original) { - - // TODO: Add based_on parameter as last argument - // TODO: Add parameter to support project settings - ProjectRecord cloned = createProject( + + // Create a new project using the details from the project record + // that was passed in. + return createProject( original.getName(), original.getDescription(), original.getDescriptionHtml(), @@ -891,23 +907,13 @@ private ProjectRecord doProjectClone(ProjectRecord original) { original.getBoard(), original.getPrivate(), original.getShared(), - original.getId() // set the parent project id + original.getId(), // set the parent project id (idProjectBasedOn) + original.getSettings() ); - -// cloned.setBasedOn(original.getId()); -// cloned.update(); - - // TODO WHAT IS THIS DOING? - create.update(Tables.PROJECT) - .set(Tables.PROJECT.BASED_ON, original.getId()) - .where(Tables.PROJECT.ID.equal(cloned.getId())); - - return cloned; } - /** * Produce a current timestamp * @@ -922,7 +928,6 @@ private GregorianCalendar getCurrentTimestamp() { - // Evaluate project code and replace any deprecated or updated blocks // // Return a ProjectRecord object. The code field may be altered to correct @@ -994,7 +999,14 @@ private ProjectRecord alterReadRecord(ProjectRecord record) { * @return */ private String randomString(int len) { + + // Used by the randomString function + final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#()*-+./:;" + + "=@[]^_`{|}~"; + + Random rnd = new Random(); StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { sb.append(AB.charAt(rnd.nextInt(AB.length()))); } From 46bc01b5bf16f806834337d7269ab2312bf8856c Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 21 Feb 2019 12:27:08 -0800 Subject: [PATCH 45/69] Add inline documentation. Update delete() method to remove outer try block. --- .../blocklyprop/servlets/ProjectServlet.java | 108 ++++++++++++------ 1 file changed, 76 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/servlets/ProjectServlet.java b/src/main/java/com/parallax/server/blocklyprop/servlets/ProjectServlet.java index b6bfbb25..3a2cbeb9 100644 --- a/src/main/java/com/parallax/server/blocklyprop/servlets/ProjectServlet.java +++ b/src/main/java/com/parallax/server/blocklyprop/servlets/ProjectServlet.java @@ -31,74 +31,118 @@ public class ProjectServlet extends HttpServlet { // Get a logger instance private static final Logger LOG = LoggerFactory.getLogger(ProjectServlet.class); + // Object to store the injected project service object private ProjectService projectService; - + + /** + * Inject project services access + * + * @param projectService - object to hold injected service class + */ @Inject public void setProjectService(ProjectService projectService) { this.projectService = projectService; } + /** - * - * @param req - * @param resp - * @throws ServletException - * @throws IOException + * Process an HTTP GET request + * + * @param request is an HttpServlet Request object + * @param response is an HttpServlet Response object + * + * @throws ServletException panic if something goes wrong in the servlet code + * @throws IOException - Really panic if there is an I/O issue. */ @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { LOG.info("REST:/project/ Get request received"); - - String clone = req.getParameter("clone"); + + /* ----------------------------------------------------- + * The request object holds parameters that indicate the + * action that is to be taken in this call. Each key is + * paired with a value that contains the id of the project + * to at upon. + * + * 'clone' - create a copy of the project specified + * 'delete' - destroy the project specified. Note that this + * is only allowed if the currently logged in + * user owns the project to be destroyed. + * ------------------------------------------------------*/ + String clone = request.getParameter("clone"); + if (!Strings.isNullOrEmpty(clone)) { - clone(Long.parseLong(clone), req, resp); + clone(Long.parseLong(clone), request, response); } - String delete = req.getParameter("delete"); + String delete = request.getParameter("delete"); + if (!Strings.isNullOrEmpty(delete)) { - delete(Long.parseLong(delete), req, resp); + delete(Long.parseLong(delete), request, response); } } - private void clone(Long idProject, HttpServletRequest req, HttpServletResponse resp) + + /** + * Create a copy of an existing project and assign it to the currently logged in user + * + * @param idProject The primary key id of the project to copy + * @param request is an HttpServlet Request object + * @param response is an HttpServlet Response object + * + * @throws ServletException panic if something goes wrong in the servlet code + * @throws IOException Really panic if there is an I/O issue. + */ + private void clone(Long idProject, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { LOG.info("Cloning project {}", idProject); try { ProjectRecord clonedProject = projectService.cloneProject(idProject); + if (clonedProject == null) { - req.getRequestDispatcher("WEB-INF/servlet/project/not-authorized.jsp").forward(req, resp); + request.getRequestDispatcher( + "WEB-INF/servlet/project/not-authorized.jsp").forward(request, response); } else { - resp.sendRedirect("my/projects.jsp#" + clonedProject.getId()); + response.sendRedirect("my/projects.jsp#" + clonedProject.getId()); } } catch (NullPointerException npe) { - req.getRequestDispatcher("WEB-INF/servlet/project/not-found.jsp").forward(req, resp); + request.getRequestDispatcher("WEB-INF/servlet/project/not-found.jsp").forward(request, response); } } - private void delete(Long idProject, HttpServletRequest req, HttpServletResponse resp) + + /** + * Destroy an existing project only when the currently logged in user owns the target project + * + * @param idProject The primary key id of the project to copy + * @param request is an HttpServlet Request object + * @param response is an HttpServlet Response object + * @throws ServletException panic if something goes wrong in the servlet code + * @throws IOException Really panic if there is an I/O issue. + */ + private void delete(Long idProject, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { LOG.info("Deleting project {}", idProject); - try { - try { - ProjectRecord project = projectService.getProjectOwnedByThisUser(idProject); - if (project == null) { - req.getRequestDispatcher("WEB-INF/servlet/project/not-found.jsp").forward(req, resp); - } - projectService.deleteProject(idProject); - resp.sendRedirect("my/projects.jsp"); - } catch (UnauthorizedException ue) { - req.getRequestDispatcher("WEB-INF/servlet/project/not-authorized.jsp").forward(req, resp); - } - } catch (NullPointerException npe) { - req.getRequestDispatcher("WEB-INF/servlet/project/not-found.jsp").forward(req, resp); - } - } + try { + ProjectRecord project = projectService.getProjectOwnedByThisUser(idProject); + + if (project == null) { + request.getRequestDispatcher("WEB-INF/servlet/project/not-found.jsp").forward(request, response); + } + projectService.deleteProject(idProject); + response.sendRedirect("my/projects.jsp"); + + } + catch (UnauthorizedException ue) { + request.getRequestDispatcher("WEB-INF/servlet/project/not-authorized.jsp").forward(request, response); + } + } } From a41e2868a4e3246ba39cde1f06a7df440fd7a2d6 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 21 Feb 2019 15:02:49 -0800 Subject: [PATCH 46/69] Correct issue where idUser was used before null check. Refactor redundant code into a private method. Add inline documentation. --- .../server/blocklyprop/rest/RestProject.java | 110 +++++++++++------- 1 file changed, 70 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestProject.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestProject.java index 04d5ff19..9d490bd1 100644 --- a/src/main/java/com/parallax/server/blocklyprop/rest/RestProject.java +++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestProject.java @@ -38,6 +38,8 @@ import com.parallax.server.blocklyprop.db.generated.tables.records.ProjectRecord; import com.parallax.server.blocklyprop.security.BlocklyPropSecurityUtils; import com.parallax.server.blocklyprop.services.ProjectService; + +import java.util.Arrays; import java.util.List; import javax.ws.rs.FormParam; import javax.ws.rs.GET; @@ -48,7 +50,6 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; -import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.AuthorizationException; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -66,28 +67,20 @@ @HttpCode("500>Internal Server Error,200>Success Response") public class RestProject { - /** - * Get a logger instance - */ + // Get a logger instance private static final Logger LOG = LoggerFactory.getLogger(RestProject.class); - /** - * Connector to project services object - */ + // Connector to project services object private ProjectService projectService; - /** - * Connector to project converter object - */ + //Connector to project converter object private ProjectConverter projectConverter; - /** - * Limit the number of records that can be returned in list functions - */ - final int REQUEST_LIMIT = 100; + //Limit the number of records that can be returned in list functions + private static final int REQUEST_LIMIT = 100; /** @@ -135,7 +128,7 @@ public void setProjectConverter(ProjectConverter projectConverter) { */ @GET @Path("/list") - @Detail("Get all projects for the authenticated user") + @Detail("Retrieve a list of projects for the authenticated user") @Name("ListProjects") @Produces("application/json") public Response get( @@ -145,23 +138,20 @@ public Response get( @QueryParam("offset") @ParameterDetail("Offset to next row returned") @M() Integer offset) { String endPoint = "REST:/rest/project/list/"; - LOG.info("{} Get request received", endPoint); -// RestProjectUtils restProjectUtils = new RestProjectUtils(); try { + LOG.debug("Requesting blockly user id"); + // Get the logged in user id for the current session - LOG.info("Requesting blockly user id from BlocklyPropSecurityUtils.getCurrentUserId()"); Long idUser = BlocklyPropSecurityUtils.getCurrentUserId(); -// LOG.info("Getting subject: {} ", SecurityUtils.getSubject()); - - // Return FORBIDDEN if we cannot identify the current user. This could // mean that the user is not logged in or that some underlying issue // is causing the authentication system to fail. LOG.info("Received blockly user id: {}", idUser); - if (idUser == 0) { + + if (idUser == null || idUser == 0) { // Current session is not logged in. return Response.status(Response.Status.FORBIDDEN).build(); } @@ -217,8 +207,7 @@ public Response get( /** * Retreive a project based on the supplied project ID * - * @param idProject - * The project key ID + * @param idProject the project key ID * * @return * Return a string representation of the project in Json format if successful, otherwise @@ -265,12 +254,12 @@ public Response get(@PathParam("id") @ParameterDetail("Project identifier") Long * * This assumes that the project already exists. * - * @param idProject - * @param code + * @param idProject The project key ID + * @param code the project blocks code string + * * @return * Returns a Json string containing the project details if the update was successful * or an error message upon failure - * */ @POST @Path("/code") @@ -294,14 +283,12 @@ public Response saveProjectCode( ProjectRecord savedProject = projectService.saveProjectCode(idProject, code); LOG.debug("Code for project {} has been saved", idProject); - +/* JsonObject result = projectConverter.toJson(savedProject,false); - - result.addProperty("success", true); - return Response.ok(result.toString()).build(); - +*/ + return Response.ok(buildConvertedResponse(savedProject)).build(); } catch (AuthorizationException ae) { LOG.warn("Project code not saved. Not Authorized"); return Response.status(Response.Status.UNAUTHORIZED).build(); @@ -314,12 +301,16 @@ public Response saveProjectCode( /** + * Create a new project from an existing project + * + * @param idProject The project key ID + * @param code the project blocks code string + * @param newName the name to assign to the newly created project + * @param newBoard the board type assigned to the new project * - * @param idProject - * @param code - * @param newName - * @param newBoard * @return + * Returns a Json string containing the project details if the update was successful + * or an error message upon failure */ @POST @Path("/code-as") @@ -342,14 +333,15 @@ public Response saveProjectCodeAs( code, newName, newBoard); + LOG.debug("Code for project {} has been saved as {}", idProject, newName); - +/* JsonObject result = projectConverter.toJson(savedProject,false); LOG.debug("Returning JSON: {}", result); - result.addProperty("success", true); - return Response.ok(result.toString()).build(); +*/ + return Response.ok(buildConvertedResponse(savedProject)).build(); } catch (AuthorizationException ae) { LOG.warn("Project code not saved. Not Authorized"); @@ -357,11 +349,27 @@ public Response saveProjectCodeAs( } catch (Exception ex) { LOG.error("General exception encountered. Message is: ", ex.getMessage()); - LOG.error("Error: {}", ex.getStackTrace().toString()); + LOG.error("Error: {}", Arrays.toString(ex.getStackTrace())); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } } + + /** + * Update the details of an existing project + * + * @param idProject the project key ID + * @param name the name assigned to the project + * @param description a text description of the project + * @param descriptionHtml the same project description expressed in HTML + * @param projectSharing a boolean flag indicating the public accessibility of the project + * @param type is the classification of the project's language (c or spin) + * @param board is the type of hardware associated with the project + * + * @return + * Returns a Json string containing the project details if the update was successful + * or an error message upon failure + */ @POST @Path("/") @Detail("Save project") @@ -415,6 +423,8 @@ public Response saveProject( } } + + /** * Iterate a list of projects into an array of Json objects * @@ -442,4 +452,24 @@ private String returnProjectsJson(@NotNull List projects, int pro return result.toString(); } + + + /** + * Convert a ProjectRecord to a Json string + * + * @param project is the project record to convert + * + * @return a Json string representing the project contents and the operation results message + */ + private String buildConvertedResponse(ProjectRecord project) { + + /* Convert the project record to a Json object */ + JsonObject result = projectConverter.toJson(project,false); + + /* Add in a results message */ + result.addProperty("success", true); + + return result.toString(); + } + } From 27d3dcf77f957ba940db6a205be35e53bd16e95d Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 27 Feb 2019 15:03:02 -0800 Subject: [PATCH 47/69] Refactor common code constructs into separate methods. Add code to handle missing data in project fields. Improve documentation. --- .../converter/ProjectConverter.java | 177 +++++++++++------- 1 file changed, 109 insertions(+), 68 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/converter/ProjectConverter.java b/src/main/java/com/parallax/server/blocklyprop/converter/ProjectConverter.java index ce6fa7d9..727db630 100644 --- a/src/main/java/com/parallax/server/blocklyprop/converter/ProjectConverter.java +++ b/src/main/java/com/parallax/server/blocklyprop/converter/ProjectConverter.java @@ -29,9 +29,7 @@ public class ProjectConverter { - /** - * Application logging facility - */ + // Application logging facility private static final Logger LOG = LoggerFactory.getLogger(ProjectConverter.class); private ProjectDao projectDao; @@ -41,7 +39,6 @@ public class ProjectConverter { // Internal flag to enable/disable parent project details private Boolean includeParentProjectDetails = false; - @Inject public void setProjectDao(ProjectDao projectDao) { this.projectDao = projectDao; @@ -61,48 +58,45 @@ public void setProjectSharingService(ProjectSharingService projectSharingService /** * Convert a ProjectRecord to a JSON object * - * @param project - * @return + * @param project is the ProjectRecord object to convert + * + * @return a JsonObject. The JsonObject may be empty if the source project was null */ public JsonObject toListJson(ProjectRecord project) { + LOG.debug("Converting a ProjectRecord to a Json object"); - - JsonObject result = new JsonObject(); - if (project != null) { - result.addProperty("id", project.getId()); - result.addProperty("name", project.getName()); - result.addProperty("description", project.getDescription()); - result.addProperty("type", project.getType().name()); - result.addProperty("board", project.getBoard()); - result.addProperty("private", project.getPrivate()); - result.addProperty("shared", project.getShared()); - result.addProperty("created", DateConversion.toDateTimeString(project.getCreated().getTime())); - result.addProperty("modified", DateConversion.toDateTimeString(project.getModified().getTime())); - - boolean isYours = project.getIdUser().equals(BlocklyPropSecurityUtils.getCurrentUserId()); - result.addProperty("yours", isYours); + if (project == null) { + return new JsonObject(); + } - // Get user screen name only if it's a registered user - if (project.getId() > 0) { - // Get the project owner's screen name - String screenName = userService.getUserScreenName(project.getIdUser()); - result.addProperty("user",(screenName == "" ? "unknown" : screenName)); + // Load the core project details + JsonObject result = addProjectRecordProperties(project); - // Add the project user's BP user id - result.addProperty("id-user", project.getIdUser()); - } - else { - result.addProperty("user", "anonymous"); - result.addProperty("id-user", 0); - } + boolean isYours = project.getIdUser().equals(BlocklyPropSecurityUtils.getCurrentUserId()); + result.addProperty("yours", isYours); + + // Get user screen name only if it's a registered user + if (project.getId() > 0) { + // Get the project owner's screen name + String screenName = userService.getUserScreenName(project.getIdUser()); + result.addProperty("user",(screenName == "" ? "unknown" : screenName)); + + // Add the project user's BP user id + result.addProperty("id-user", project.getIdUser()); + } + else { + result.addProperty("user", "anonymous"); + result.addProperty("id-user", 0); } + return result; } + + // TODO: Refactor code to eliminate the parent project details. We don't use it - - + // Overrride method to provide option to turn off parent project details public JsonObject toJson(ProjectRecord project, Boolean includeParentDetails) { includeParentProjectDetails = includeParentDetails; @@ -119,38 +113,29 @@ public JsonObject toJson(Project project, Boolean includeParentDetails) { /** * Convert a project record to a JSON payload * - * @param project - * @return + * @param project is te source ProjectRecord object to convert + * + * @return A Json object that contains a set of properties that represent + * the fields contained in the ProjectRecord object */ public JsonObject toJson(ProjectRecord project) { LOG.debug("Converting a ProjectRecord object to JSON"); - JsonObject result = null; - if (project == null) { - LOG.error("Recieved a null project. Unable to convert it to JSON"); - return null; + LOG.error("Received a null project. Unable to convert it to JSON"); + return new JsonObject(); } - + + JsonObject result = null; + try { - result = new JsonObject(); - - result.addProperty("id", project.getId()); - result.addProperty("name", project.getName()); - result.addProperty("description", project.getDescription()); - result.addProperty("description-html", project.getDescriptionHtml()); - result.addProperty("type", project.getType().name()); - result.addProperty("board", project.getBoard()); - result.addProperty("private", project.getPrivate()); - result.addProperty("shared", project.getShared()); - result.addProperty("created", DateConversion.toDateTimeString(project.getCreated().getTime())); - result.addProperty("modified", DateConversion.toDateTimeString(project.getModified().getTime())); - - LOG.debug("Evaluating project owner"); - + // Load the project fields into the Json object + result = addProjectRecordProperties(project); + + // Does current user own this project? + LOG.debug("Evaluating project owner"); boolean isYours = project.getIdUser().equals(BlocklyPropSecurityUtils.getCurrentUserId()); - LOG.debug("isYours = {}", isYours); result.addProperty("yours", isYours); @@ -224,17 +209,10 @@ public JsonObject toJson(ProjectRecord project) { private JsonObject toJson(Project project) { - JsonObject result = new JsonObject(); - result.addProperty("id", project.getId()); - result.addProperty("name", project.getName()); - result.addProperty("description", project.getDescription()); - result.addProperty("description-html", project.getDescriptionHtml()); - result.addProperty("type", project.getType().name()); - result.addProperty("board", project.getBoard()); - result.addProperty("private", project.getPrivate()); - result.addProperty("shared", project.getShared()); - result.addProperty("modified", DateConversion.toDateTimeString(project.getModified().getTime())); + boolean isYours = project.getIdUser().equals(BlocklyPropSecurityUtils.getCurrentUserId()); + JsonObject result = addProperties(project); + result.addProperty("yours", isYours); result.addProperty("user", userService.getUserScreenName(project.getIdUser())); @@ -258,4 +236,67 @@ private JsonObject toJson(Project project) { return result; } + + /** + * Create a Json object representing a Project object. + * + * @param project is the Project object to translate + * + * @return a Json object that contains a collection of properties representing + * the fields in the source Project object + */ + private JsonObject addProperties(Project project) { + + JsonObject result = new JsonObject(); + + try { + result.addProperty("id", project.getId()); + result.addProperty("name", project.getName()); + result.addProperty("description", project.getDescription()); + result.addProperty("description-html", project.getDescriptionHtml()); + result.addProperty("type", project.getType().name()); + result.addProperty("board", project.getBoard()); + result.addProperty("private", project.getPrivate()); + result.addProperty("shared", project.getShared()); + result.addProperty("modified", DateConversion.toDateTimeString(project.getModified().getTime())); + result.addProperty("settings", project.getSettings()); + } + catch (Exception ex) { + LOG.error("Exception {} detected.", ex.toString()); + } + + return result; + } + + + /** + * Create a Json string representing a ProjectRecord object + * + * @param projectRecord is the ProjectRecord object to translate + * + * @return a Json object that contains a collection of properties representing + * the fields in the ProjectRecord object + */ + private JsonObject addProjectRecordProperties( ProjectRecord projectRecord) { + + JsonObject result = new JsonObject(); + + try { + result.addProperty("id", projectRecord.getId()); + result.addProperty("name", projectRecord.getName()); + result.addProperty("description", projectRecord.getDescription()); + result.addProperty("type", projectRecord.getType().name()); + result.addProperty("board", projectRecord.getBoard()); + result.addProperty("private", projectRecord.getPrivate()); + result.addProperty("shared", projectRecord.getShared()); + result.addProperty("created", DateConversion.toDateTimeString(projectRecord.getCreated().getTime())); + result.addProperty("modified", DateConversion.toDateTimeString(projectRecord.getModified().getTime())); + result.addProperty("settings", projectRecord.getSettings()); + } + catch (Exception ex) { + LOG.error("Exception {} detected.", ex.toString()); + } + + return result; + } } From 468f5f9b3f832815d9a5c31577ebcfd0e38cbd6d Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 27 Feb 2019 22:38:52 -0800 Subject: [PATCH 48/69] Create a Project V2 API draft. --- .../blocklyprop/rest/RestV2Project.java | 718 ++++++++++++++++++ 1 file changed, 718 insertions(+) create mode 100644 src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java new file mode 100644 index 00000000..5eec0a6d --- /dev/null +++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java @@ -0,0 +1,718 @@ +/* + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.parallax.server.blocklyprop.rest; + +import com.cuubez.visualizer.annotation.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.inject.Inject; + +import com.parallax.server.blocklyprop.TableOrder; +import com.parallax.server.blocklyprop.TableSort; +import com.parallax.server.blocklyprop.converter.ProjectConverter; +import com.parallax.server.blocklyprop.db.enums.ProjectType; +import com.parallax.server.blocklyprop.db.generated.tables.records.ProjectRecord; +import com.parallax.server.blocklyprop.security.BlocklyPropSecurityUtils; +import com.parallax.server.blocklyprop.services.ProjectService; +import com.parallax.server.blocklyprop.utils.RestProjectUtils; + +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.authz.UnauthorizedException; +import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.Arrays; +import java.util.List; + + +/** + * REST endpoints for project persistence + * Version 2 + * + * @author Michel, J. Ewald + * + * @implNote + * + * Supported endpoints: + * [POST] /project/ Save current project + * [GET] /project/list List projects + * [GET] /project/get/{id} Retrieve a project + * [POST] /project/code Update project code + * [POST] /project/code-as Create new project from existing project + * + * Version 2 supported endpoints + * CRUD.. + * + * CREATE + * [POST] /v2/project/ Creates a new project and returns it in the response body + * [POST] /v2/project/code Create a new project based on details on the request body + * + * RETRIEVE + * [GET] /v2/project/ Returns a list of projects; parameters in request body + * [GET] /v2/project/{id} Returns the specific project if authorized + * + * UPDATE + * [PUT] /v2/project/{id} Updates s specific project. Project details are in the request body + * [PUT] /v2/project/code/{id} Updates the code block of a specific project + * + * DELETE + * [DELETE] /v2/project/{id} Destroys the project specified by {id} + */ + +@Path("/v2/project") +@Group(name = "/project", title = "Project management") +@HttpCode("500>Internal Server Error,200>Success Response") +public class RestV2Project { + + // Get a logger instance + private static final Logger LOG = LoggerFactory.getLogger(RestProject.class); + + + // Connector to project services object + private ProjectService projectService; + + + //Connector to project converter object + private ProjectConverter projectConverter; + + + //Limit the number of records that can be returned in list functions + private static final int REQUEST_LIMIT = 100; + + + /** + * Connect to the project service object + * + * @param projectService + * An instance of the ProjectService object + */ + @Inject + public void setProjectService(ProjectService projectService) { + this.projectService = projectService; + } + + + /** + * Connect to the project converter object + * + * @param projectConverter + * An instance of the ProjectConverter object + */ + @Inject + public void setProjectConverter(ProjectConverter projectConverter) { + this.projectConverter = projectConverter; + } + + + + /** + * Test endpoint to verify that the class is reachable + * + * @return a Json reply, 'Pong' + */ + @GET + @Path("/ping") + @Detail("Testing for life on Mars") + @Name("PingProjects") + @Produces("application/json") + public Response get() { + return Response.ok("{\"result\": \"Pong\"}").build(); + } + + + /** + * Create a new project + * + * + * @return + * Returns a Json string containing the project details, including the new project ID if successful + * or an error message upon failure + */ + @POST + @Detail("Create a new project") + @Name("Create project") + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Produces(MediaType.APPLICATION_JSON) + public Response createProject( + @FormParam("name") String projectName, + @FormParam("description") String description, + @FormParam("description-html") String descriptionHtml, + @FormParam("code") String code, + @FormParam("sharing") String projectSharing, + @FormParam("type") ProjectType type, + @FormParam("board") String board, + @FormParam("settings") String settings) { + + LOG.info("REST:/rest/v2/project/ POST request received to create a new project '{}'", projectName); + + try { + // Required fields + Validate.notEmpty(projectName, "A project name is required."); + Validate.notEmpty(description, "A project description is required."); + Validate.notEmpty(code, "A project code block is required."); + Validate.notNull(type, "The project type is required."); + Validate.notEmpty(board, "The project board type is required."); + + boolean privateProject = false; + boolean sharedProject = false; + + if ("private".equalsIgnoreCase(projectSharing)) { + privateProject = true; + } else if ("shared".equalsIgnoreCase(projectSharing)) { + sharedProject = true; + } + + LOG.info("Saving the project"); + ProjectRecord savedProject = projectService.saveProject( + null, projectName, description, descriptionHtml, code, + privateProject, sharedProject, type, board, settings); + + if (savedProject == null) { + LOG.warn("Unable to create a new project record"); + } + + JsonObject result = projectConverter.toJson(savedProject,false); + LOG.info("Returning JSON: {}", result); + + result.addProperty("success", true); + + return Response.ok(result.toString()).build(); + } + catch (NullPointerException ex) { + LOG.warn("Null pointer exception detected. {}", ex.getMessage()); + return Response.status(Response.Status.NOT_ACCEPTABLE).build(); + } + catch (IllegalArgumentException aex) { + LOG.warn("Illegal argument exception detected. {}", aex.getMessage()); + + return Response.status(Response.Status.NOT_ACCEPTABLE).build(); + } + catch (AuthorizationException ae) { + LOG.warn("Project not saved. Not Authorized"); + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + catch (Exception ex) { + LOG.error("General exception encountered. Message is: ", ex.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + + + + + + + + + + + /** + * Return a list of projects owned by the currently authenticated user. + * + * @param sort + * The project field used to evaluate the sort + * + * @param order + * Specify the sort order - ascending or descending + * + * @param limit + * Specify the maximum number of rows to return + * + * @param offset + * Specify the beginning row to return + * + * @return + * Return a response object that contains either the data requested + * or a JSON string containing the error details + * + * @implNote + * + * VERB URI Notes: + * ------- ---------------------- -------------------------------------- + * [GET] /v2/project/ Returns a list of projects; parameters + * + * Sample output: + * { + * "rows": [ + * { + * "id": 16, + * "name": "XBee Sandbox", + * "description": "Testing XBee blocks", + * "type": "PROPC", + * "board": "activity-board", + * "private": false, + * "shared": true, + * "created": "2017/04/12 06:01", + * "modified": "2018/10/18 04:06", + * "settings": null, + * "yours": true, + * "user": "demo-998", + * "id-user": 1 + * }, + * { + * "id": 13, + * "name": "TestIssue#886", + * "description": "Testing pin dropdown.", + * "type": "PROPC", + * "board": "activity-board", + * "private": true, + * "shared": false, + * "created": "2017/01/17 23:25", + * "modified": "2017/01/17 23:25", + * "settings": null, + * "yours": true, + * "user": "demo-998", + * "id-user": 1 + * } + * ], + * "total": 41 + * } + * --------------------------------------------------------------------------------- + */ + @GET + @Detail("Retrieve a list of projects for the authenticated user") + @Name("ListProjects") + @Produces("application/json") + public Response get( + @QueryParam("sort") @ParameterDetail("Sort detail") @M() TableSort sort, + @QueryParam("order") @ParameterDetail("Sort order") @M() TableOrder order, + @QueryParam("limit") @ParameterDetail("Number of rows to return") @M() Integer limit, + @QueryParam("offset") @ParameterDetail("Offset to next row returned") @M() Integer offset) { + + String endPoint = "REST:/rest/v2/project/list/"; + LOG.info("{} Get request received", endPoint); + + try { + LOG.debug("Requesting blockly user id"); + + // Get the logged in user id for the current session + Long idUser = BlocklyPropSecurityUtils.getCurrentUserId(); + + // Return UNAUTHORIZED if we cannot identify the current user. This could + // mean that the user is not logged in or that some underlying issue + // is causing the authentication system to fail. + LOG.info("Received blockly user id: {}", idUser); + + if (idUser == null || idUser == 0) { + // Current session is not logged in. + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + + //Sanity checks - is the request reasonable + + // Sort flag evaluation + if (!RestProjectUtils.ValidateSortType(sort)) { + LOG.warn("{} Sort parameter failed. Defaulting to sort by project name", endPoint); + sort = TableSort.name; + } + + // Sort order evaluation + if (!RestProjectUtils.ValidateSortOrder(order)) { + LOG.warn("{} Sort order parameter failed. Defaulting to ascending order", endPoint); + order = TableOrder.asc; + } + + // Limit result set value + if ( (limit == null) || (limit > REQUEST_LIMIT)) { + LOG.info("{} Limit throttle to {} entries", endPoint, REQUEST_LIMIT); + limit = REQUEST_LIMIT; + } + + // Check offset from the beginning of the record set + if ((offset == null) || (offset < 0)) { + offset = 0; + } + + // Obtain a list of the user's projects + List userProjects = projectService.getUserProjects(idUser, sort, order, limit, offset); + + // Tell the caller that there is nothing to see here + if (userProjects == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return Response.ok( + returnProjectsJson( + userProjects, + projectService.countUserProjects(idUser))) + .build(); + } + catch(UnauthorizedException ex) { + LOG.warn("Unauthorized access attempted"); + return Response.status(Response.Status.FORBIDDEN).build(); + } + catch(Exception ex) { + LOG.warn("Unable to process REST request."); + LOG.warn("Error is {}", ex.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + + + /** + * Retreive a project based on the supplied project ID + * + * @param idProject the project key ID + * + * @return + * Return a string representation of the project in Json format if successful, otherwise + * return a Json string containing an error status message + * + * @implNote + * + * VERB URI Notes: + * ------- ---------------------- -------------------------------------- + * [GET] /v2/project/{id} Returns a single projects based on the + * project ID received and the user is + * owner of the project. + * + * Sample output: + * + * { + * "id": 20, + * "name": "AB-Drive 360 Error", + * "description": "Testing a linking error that appears when the AB360 library is invoked.\nTesting search engine bits a little more.", + * "type": "PROPC", + * "board": "activity-board", + * "private": true, + * "shared": false, + * "created": "2017/10/27 16:50", + * "modified": "2018/10/17 19:22", + * "settings": "{\"settings\": {\"setting_1\": \"Always-ON\", \"setting-2\": \"false\", \"setting-3\": \"/cdn/bootloader\"}}", + * "yours": true, + * "user": "demo-998", + * "id-user": 1 + * } + * + * ---------------------------------------------------------------------------------- + * + */ + @GET + @Path("/{id}") + @Detail("Get project by id") + @Name("Get project by id") + @Produces("application/json") + public Response get(@PathParam("id") @ParameterDetail("Project identifier") Long idProject) { + + LOG.info("REST:/rest/project/get/ Get request received for project '{}'", idProject); + + try { + ProjectRecord project = projectService.getProject(idProject); + + if (project != null) { + // Verify that the current user owns the requested project + if (!project.getIdUser().equals(BlocklyPropSecurityUtils.getCurrentUserId())) { + LOG.info("User not authorized to get project {}", idProject); + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + } else { + LOG.info("Project {} was not found", idProject); + return Response.status(Response.Status.NOT_FOUND).build(); + } + + // The current user owns this project + JsonObject result = projectConverter.toJson(project,false); + result.addProperty("id-user", project.getIdUser()); + + return Response.ok(result.toString()).build(); + } + + catch(Exception ex) { + LOG.warn("Unable to process REST request."); + LOG.warn("Error is {}", ex.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + + + + + + + + + + + + + + + + + + + + + + + + + /** + * Update the code in an existing project. + * + * This assumes that the project already exists. + * + * @param idProject The project key ID + * @param code the project blocks code string + * + * @return + * Returns a Json string containing the project details if the update was successful + * or an error message upon failure + */ + @POST + @Path("/code") + @Detail("Save project code") + @Name("UpdateProjectCode") + @Produces("application/json") + public Response saveProjectCode( + @FormParam("id") @ParameterDetail("Project identifier") @M() Long idProject, + @FormParam("code") @ParameterDetail("Project code") @M() String code) { + + LOG.info("REST:/rest/project/code/ POST request received for project '{}'", idProject); + + try { + + /* WARNING: + * ================================================================================= + * This call can create a new project record under specific circumstances and does + * not appear to provide any notification that this has occurred. + * ================================================================================= + */ + ProjectRecord savedProject = projectService.saveProjectCode(idProject, code); + + LOG.debug("Code for project {} has been saved", idProject); +/* + JsonObject result = projectConverter.toJson(savedProject,false); + result.addProperty("success", true); + return Response.ok(result.toString()).build(); +*/ + return Response.ok(buildConvertedResponse(savedProject)).build(); + } catch (AuthorizationException ae) { + LOG.warn("Project code not saved. Not Authorized"); + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + catch (Exception ex) { + LOG.error("General exception encountered. Message is: ", ex.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + + + + + /** + * Create a new project from an existing project + * + * @param idProject The project key ID + * @param code the project blocks code string + * @param newName the name to assign to the newly created project + * @param newBoard the board type assigned to the new project + * + * @return + * Returns a Json string containing the project details if the update was successful + * or an error message upon failure + */ + @POST + @Path("/code-as") + @Detail("Save project code") + @Name("Save project code") + @Produces("application/json") + public Response saveProjectCodeAs( + @FormParam("id") Long idProject, + @FormParam("code") String code, + @FormParam("name") String newName, + @FormParam("board") String newBoard) { + + LOG.info("REST:/rest/project/code-as/ POST request received for project '{}'", idProject); + + try { + LOG.info("Saving project '{}', '{}' as a new project", idProject, newName); + + ProjectRecord savedProject = projectService.saveProjectCodeAs( + idProject, + code, + newName, + newBoard); + + LOG.debug("Code for project {} has been saved as {}", idProject, newName); +/* + JsonObject result = projectConverter.toJson(savedProject,false); + LOG.debug("Returning JSON: {}", result); + result.addProperty("success", true); + return Response.ok(result.toString()).build(); +*/ + return Response.ok(buildConvertedResponse(savedProject)).build(); + } + catch (AuthorizationException ae) { + LOG.warn("Project code not saved. Not Authorized"); + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + catch (Exception ex) { + LOG.error("General exception encountered. Message is: ", ex.getMessage()); + LOG.error("Error: {}", Arrays.toString(ex.getStackTrace())); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + + + + + + /*-------------------------------------------------------------------------+ + * VERB URI Notes: | + * ------- ---------------------- -------------------------------------- | + * [POST] /v2/project/ Creates a new project. The project | + * details, including the new project ID, | + * are returned to the caller. | + *-------------------------------------------------------------------------*/ + + + + + + + + /* + * Update the details of an existing project V1 implementation + * + * @param idProject the project key ID + * @param name the name assigned to the project + * @param description a text description of the project + * @param descriptionHtml the same project description expressed in HTML + * @param projectSharing a boolean flag indicating the public accessibility of the project + * @param type is the classification of the project's language (c or spin) + * @param board is the type of hardware associated with the project + * + * @return + * Returns a Json string containing the project details if the update was successful + * or an error message upon failure + */ + +/* + @POST +// @Path("/") + @Detail("Save project") + @Name("Save project") + @Produces("application/json") + public Response saveProject( + @FormParam("id") Long idProject, + @FormParam("name") String name, + @FormParam("description") String description, + @FormParam("description-html") String descriptionHtml, + @FormParam("sharing") String projectSharing, + @FormParam("type") ProjectType type, + @FormParam("board") String board) { + + LOG.info("REST:/rest/project/ POST request received for project '{}'", idProject); + + try { + boolean privateProject = false; + boolean sharedProject = false; + + if ("private".equalsIgnoreCase(projectSharing)) { + privateProject = true; + } else if ("shared".equalsIgnoreCase(projectSharing)) { + sharedProject = true; + } + + ProjectRecord savedProject = projectService.saveProject( + idProject, + name, + description, + descriptionHtml, + privateProject, + sharedProject, + type, + board); + LOG.debug("Project {} has been saved.", idProject); + + JsonObject result = projectConverter.toJson(savedProject,false); + LOG.debug("Returning JSON: {}", result); + + result.addProperty("success", true); + + return Response.ok(result.toString()).build(); + } catch (AuthorizationException ae) { + LOG.warn("Project not saved. Not Authorized"); + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + catch (Exception ex) { + LOG.error("General exception encountered. Message is: ", ex.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + +*/ + + /** + * Iterate a list of projects into an array of Json objects + * + * @param projects + * A List of ProjectRecord objects + * + * @param projectCount + * The number of projects available. This may not be the same value as + * the number of records contained in the passed list of ProjectRecords. + * + * @return + * A String containing the array of the converted Json objects + */ + private String returnProjectsJson(@NotNull List projects, int projectCount) { + JsonObject result = new JsonObject(); + JsonArray jsonProjects = new JsonArray(); + + for (ProjectRecord project : projects) { + jsonProjects.add(projectConverter.toListJson(project)); + } + + result.add("rows", jsonProjects); + result.addProperty("total", projectCount); + + return result.toString(); + } + + + + /** + * Convert a ProjectRecord to a Json string + * + * @param project is the project record to convert + * + * @return a Json string representing the project contents and the operation results message + */ + private String buildConvertedResponse(ProjectRecord project) { + + /* Convert the project record to a Json object */ + JsonObject result = projectConverter.toJson(project,false); + + /* Add in a results message */ + result.addProperty("success", true); + + return result.toString(); + } + +} From 5f3f8953894d4da6bed08d24afe37bccac1ba92d Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 27 Feb 2019 23:03:04 -0800 Subject: [PATCH 49/69] Add inline documentation for the POST / endpoint. --- .../blocklyprop/rest/RestV2Project.java | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java index 5eec0a6d..a2f6dcce 100644 --- a/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java +++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java @@ -147,10 +147,68 @@ public Response get() { /** * Create a new project * + * @param projectName + * is a required string parameter containing the project name + * + * @param description + * is a required string parameter containing the project description + * + * @param descriptionHtml + * is an optional parameter containing the HTML representation of the + * project description + * + * @param code + * is a required parameter containing the XML representation of the + * project's code blocks + * + * @param projectSharing + * is an optional parameter containing one of two possible strings; + * 'private' or 'shared. + * + * The project will be marked as a public or community project only + * if the 'shared' keyword value is supplied. Otherwise, if the + * 'private' keyword is supplied or no keyword is supplied, the + * project will be configured as a private project. + * + * @param type + * is a required parameter indicating the project's source language, either + * SPIN or PROPC. + * + * @param board + * is a required parameter indicating the type of board used for the project. + * + * @param settings + * is an optional parameter containing a Json encoded string of various + * custom project settings. * * @return - * Returns a Json string containing the project details, including the new project ID if successful - * or an error message upon failure + * Returns a Json string containing the project details, including the new + * project ID if successful or an error message upon failure + * + * @implNote + * + * VERB URI Notes: + * ------- ---------------------- ---------------------------------------------- + * [POST] /v2/project/ Create a new project from the data provided. + * The service returns a Json string containing + * the new project details. + * + * Return value in response body: + * { + * "id": 66, + * "name": "Chocolate Factory IV", + * "description": "Willie Wonka and the factory", + * "type": "PROPC", + * "board": "heb", + * "private": true, + * "shared": false, + * "created": "2019/02/28 06:36", + * "modified": "2019/02/28 06:36", + * "settings": null, + * "yours": true, + * "user": "demo-998", + * "success": true + * } */ @POST @Detail("Create a new project") From 001f03482190cc516c29bc45c323f55df2222714 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 28 Feb 2019 03:35:46 -0800 Subject: [PATCH 50/69] Improve inline documentation. Add exception to trap case where logged in user was attempting to load a private project that was not theirs. --- .../server/blocklyprop/rest/RestProject.java | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestProject.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestProject.java index 9d490bd1..2cb71f1d 100644 --- a/src/main/java/com/parallax/server/blocklyprop/rest/RestProject.java +++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestProject.java @@ -27,9 +27,11 @@ import com.cuubez.visualizer.annotation.Name; import com.cuubez.visualizer.annotation.M; import com.cuubez.visualizer.annotation.ParameterDetail; + import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.inject.Inject; + import com.parallax.server.blocklyprop.TableOrder; import com.parallax.server.blocklyprop.TableSort; import com.parallax.server.blocklyprop.utils.RestProjectUtils; @@ -51,6 +53,7 @@ import javax.ws.rs.core.Response; import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.authz.UnauthorizedException; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,6 +63,20 @@ * REST endpoints for project persistence * * @author Michel + * + * @implNote + * + * Supported endpoints: + * [POST] /project/ Save current project + * [GET] /project/list List projects + * [GET] /project/get/{id} Retrieve a project + * [POST] /project/code Update project code + * [POST] /project/code-as Create new project from existing project + * + * The code block is not returned with most of these calls because it is only used when + * the user is viewing or editing a project. This eliminates the unnecessary overhead of + * moving a code block that could easily dwarf the size of the other elements in the project + * record. */ @Path("/project") @@ -153,7 +170,7 @@ public Response get( if (idUser == null || idUser == 0) { // Current session is not logged in. - return Response.status(Response.Status.FORBIDDEN).build(); + return Response.status(Response.Status.UNAUTHORIZED).build(); } //Sanity checks - is the request reasonable @@ -176,7 +193,7 @@ public Response get( limit = REQUEST_LIMIT; } - // Check ofset from the beginning of the record set + // Check offset from the beginning of the record set if ((offset == null) || (offset < 0)) { offset = 0; } @@ -189,13 +206,19 @@ public Response get( return Response.status(Response.Status.NOT_FOUND).build(); } + LOG.info("Returning {} projects for user {}", userProjects.size(), idUser); + return Response.ok( returnProjectsJson( userProjects, projectService.countUserProjects(idUser))) .build(); } - + catch(UnauthorizedException ex) { + // User is logged in but attempting to access a secured resource + LOG.warn("Unauthorized access attempted"); + return Response.status(Response.Status.FORBIDDEN).build(); + } catch(Exception ex) { LOG.warn("Unable to process REST request."); LOG.warn("Error is {}", ex.getMessage()); @@ -276,18 +299,14 @@ public Response saveProjectCode( /* WARNING: * ================================================================================= - * This call can create a new project record under specific circumstances and does - * not appear to provide any notification that this has occurred. + * The call to the saveProjectCode() method can create a new project record under + * specific circumstances and does not appear to provide any notification that this + * has occurred. * ================================================================================= */ ProjectRecord savedProject = projectService.saveProjectCode(idProject, code); LOG.debug("Code for project {} has been saved", idProject); -/* - JsonObject result = projectConverter.toJson(savedProject,false); - result.addProperty("success", true); - return Response.ok(result.toString()).build(); -*/ return Response.ok(buildConvertedResponse(savedProject)).build(); } catch (AuthorizationException ae) { LOG.warn("Project code not saved. Not Authorized"); @@ -371,7 +390,6 @@ public Response saveProjectCodeAs( * or an error message upon failure */ @POST - @Path("/") @Detail("Save project") @Name("Save project") @Produces("application/json") @@ -439,15 +457,19 @@ public Response saveProject( * A String containing the array of the converted Json objects */ private String returnProjectsJson(@NotNull List projects, int projectCount) { + JsonObject result = new JsonObject(); - JsonArray jsonProjects = new JsonArray(); - for (ProjectRecord project : projects) { - jsonProjects.add(projectConverter.toListJson(project)); - } + if (projects.size() > 0) { + JsonArray jsonProjects = new JsonArray(); + + for (ProjectRecord project : projects) { + jsonProjects.add(projectConverter.toListJson(project)); + } - result.add("rows", jsonProjects); - result.addProperty("total", projectCount); + result.add("rows", jsonProjects); + result.addProperty("total", projectCount); + } return result.toString(); } From 614dc23431aab3eebbbc7cd12855c538a8bdc0c5 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 28 Feb 2019 12:21:22 -0800 Subject: [PATCH 51/69] Add inline documentation. Add static method to validate ProjectType parameter. --- .../blocklyprop/utils/RestProjectUtils.java | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/utils/RestProjectUtils.java b/src/main/java/com/parallax/server/blocklyprop/utils/RestProjectUtils.java index 014dbe67..69e7f113 100644 --- a/src/main/java/com/parallax/server/blocklyprop/utils/RestProjectUtils.java +++ b/src/main/java/com/parallax/server/blocklyprop/utils/RestProjectUtils.java @@ -23,15 +23,23 @@ import com.parallax.server.blocklyprop.TableOrder; import com.parallax.server.blocklyprop.TableSort; +import com.parallax.server.blocklyprop.db.enums.ProjectType; -public class RestProjectUtils { +/** + * Project REST endpoint static utility methods + */ +public class RestProjectUtils { /** + * Validate that the provided value represents a valid column name on which to sort + * a list of projects + * + * @param sort is the column name to sort on. Note that not all project columns are + * sortable. * - * @param sort - * @return - * Return true if the provided sort is a valid item, otherwise return false + * @return a boolean true if the supplied value matches a sortable column, otherwise + * return a boolean false value */ public static boolean ValidateSortType(TableSort sort) { @@ -46,21 +54,45 @@ public static boolean ValidateSortType(TableSort sort) { return false; } + /** + * Validate the provided sort order. + * + * @param order Project sort order + * + * @return true if the sort order is valid, otherwise false + * + */ public static boolean ValidateSortOrder(TableOrder order) { - boolean parametersValid = false; - if (order != null) { for (TableOrder t : TableOrder.values()) { if (order == t) { - parametersValid = true; - break; + return true; } } } - return parametersValid; + return false; } -} + /** + * Validate the project language + * + * @param type is a ProjectType that indicates the underlying language used for the project + * + * @return true if the project type is a known type, otherwise return false + */ + public static boolean ValidateProjectType(ProjectType type) { + + if (type != null) { + for (ProjectType p : ProjectType.values()) { + if (type == p) { + return true; + } + } + } + + return false; + } +} From ac738049024ffad816a2cd688d07f1132ab0c25e Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 1 Mar 2019 09:38:39 -0800 Subject: [PATCH 52/69] Refactor readSession to reduce code paths. Add inline documentation. --- .../db/dao/impl/SessionDaoImpl.java | 94 ++++++++++++------- 1 file changed, 58 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/SessionDaoImpl.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/SessionDaoImpl.java index edab7453..968a2bf9 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/SessionDaoImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/SessionDaoImpl.java @@ -40,6 +40,7 @@ /** + * This is the session database layer. * * @author Michel */ @@ -48,37 +49,42 @@ public class SessionDaoImpl implements SessionDao { // Get a logger instance private static final Logger LOG = LoggerFactory.getLogger(SessionDaoImpl.class); - /** - * - */ + // Database connection private DSLContext create; - /** - * An instance of the application configuration settings - */ + //An instance of the application configuration settings private Configuration configuration; + + /** + * Get a copy of the database context + * + * @param dsl is the context provided by JooQ + */ @Inject public void setDSLContext(DSLContext dsl) { this.create = dsl; } + /** - * - * @param configuration + * Get a copy of the application configuration + * + * @param configuration is the context provided by the configuration manager */ @Inject public void setConfiguration(Configuration configuration) { this.configuration = configuration; } + /** - * - * @param session + * Create a new session + * + * @param session is a initialized SessrionRecord object */ @Override public void create(SessionRecord session) { - LOG.debug("Create a session. Timeout set to: {}", session.getTimeout()); // Log session details if the configuration file permits it @@ -108,49 +114,53 @@ public void create(SessionRecord session) { } /** - * - * @param idSession - * @return - * @throws NullPointerException + * Retrieve a session + * + * @param idSession is the unique identifier for the requested session + * + * @return a SessionRecord, otherwise throws a NullPointerException + * + * @throws NullPointerException raise an NPE if the session is not found, + * otherwise the caller expects to receive a valid session record */ @Override public SessionRecord readSession(String idSession) throws NullPointerException { - LOG.debug("Getting session {} details", idSession); - SessionRecord sessionRecord = null; - try { - sessionRecord = create.selectFrom(Tables.SESSION) + SessionRecord sessionRecord = create + .selectFrom(Tables.SESSION) .where(Tables.SESSION.IDSESSION.eq(idSession)) .fetchOne(); // Log session details if the configuration file permits it printSessionInfo("read", sessionRecord); + return sessionRecord; } catch (org.jooq.exception.DataAccessException sqex) { LOG.error("Database exception {}", sqex.getMessage()); + throw new NullPointerException("Session not found"); } - - return sessionRecord; } /** - * - * @param session - * @throws NullPointerException + * Update an existing session + * + * @param session is the unique identifier for the requested session + * + * @throws NullPointerException raise an NPE if the session yo be updated + * is not found */ @Override public void updateSession(SessionRecord session) throws NullPointerException { - - LOG.debug("Update a session"); + LOG.debug("Update session {}", session.getIdsession()); try { // Get the current session record SessionRecord dbRecord = readSession(session.getIdsession()); if (dbRecord == null) { - throw new NullPointerException("Session not found"); + throw new NullPointerException("Invalid session"); } dbRecord.setStarttimestamp(session.getStarttimestamp()); @@ -167,17 +177,18 @@ public void updateSession(SessionRecord session) throws NullPointerException { } catch (org.jooq.exception.DataAccessException sqex) { LOG.error("Database exception {}", sqex.getMessage()); - throw new NullPointerException("Database error"); + throw new NullPointerException("Session not found"); } } + /** - * - * @param idSession + * Remove a session record from the backing store + * + * @param idSession is the unique identifier for the requested session */ @Override public void deleteSession(String idSession) { - LOG.info("Deleting session {}", idSession); create.deleteFrom(Tables.SESSION) @@ -185,16 +196,25 @@ public void deleteSession(String idSession) { .execute(); } + /** - * - * @return + * Get a list of all active (unexpired) sessions + * + * @return a collection of session records */ @Override public Collection getActiveSessions() { return Arrays.asList(create.selectFrom(Tables.SESSION).fetchArray()); } - + + /** + * Provide detailed logging for the specified session + * + * @param action is a message that is embedded in the log record + * + * @param session is the session record to to log. + */ private void printSessionInfo(String action, SessionRecord session) { if (configuration.getBoolean("debug.session", false)) { try { @@ -206,9 +226,11 @@ private void printSessionInfo(String action, SessionRecord session) { HashMap attributes = (HashMap) in.readObject(); LOG.info("Session info: {}:{}", action, attributes); } - } catch (IOException ex) { + } + catch (IOException ex) { LOG.error("I/O error. {}",ex.getMessage()); - } catch (ClassNotFoundException cnfe) { + } + catch (ClassNotFoundException cnfe) { LOG.error("Class not found. {}", cnfe.getMessage()); } } From 496ca5884962bea6845c5f61e3977acb027effcc Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Mar 2019 10:43:30 -0800 Subject: [PATCH 53/69] Add version 2 Project REST endpoints. Work is still ongoing. --- .../server/blocklyprop/db/dao/ProjectDao.java | 14 + .../db/dao/impl/ProjectDaoImpl.java | 135 ++++++++- .../server/blocklyprop/rest/RestUser.java | 2 +- .../blocklyprop/rest/RestV2Project.java | 198 ++++++++++++- .../blocklyprop/services/ProjectService.java | 269 +++++++++++++++++- .../services/impl/ProjectServiceImpl.java | 233 +++++++++++++-- 6 files changed, 792 insertions(+), 59 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java index ee4be6e2..a6661a10 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java @@ -55,6 +55,20 @@ public interface ProjectDao { ProjectRecord getProject(Long idProject); + // V2 implementation + ProjectRecord createProject( + String name, + String description, + String descriptionHtml, + String code, + ProjectType type, + String board, + boolean privateProject, + boolean sharedProject, + Long idProjectBasedOn, + String settings); + + @Deprecated ProjectRecord createProject( String name, String description, diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java index 93c7308a..e31fe08b 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java @@ -65,7 +65,7 @@ public class ProjectDaoImpl implements ProjectDao { // Constants to clarify the edit flag in method calls static final boolean EDIT_MODE_OFF = false; - static final boolean EDIT_MODE_ON = true; + private static final boolean EDIT_MODE_ON = true; // Constant to identify the current version of the blockly block library; public static final short BLOCKLY_LIBRARY_VERSION = 1; @@ -103,7 +103,7 @@ public void setProjectSharingContext(ProjectSharingService projectSharingService */ @Override public ProjectRecord getProject(Long idProject) { - LOG.info("Retreiving data for project #{}", idProject); + LOG.info("Retrieving data for project #{}", idProject); ProjectRecord record = null; @@ -122,15 +122,108 @@ record = create LOG.error("Database error encountered {}", sqex.getMessage()); return null; } catch (Exception ex) { - LOG.error("Unexpected exception retreiving a project record"); + LOG.error("Unexpected exception retrieving a project record"); LOG.error("Error Message: {}", ex.getMessage()); return null; } - // Return the project after checking if for depricated blocks - return alterReadRecord(record); + // Return the project after checking if for deprecated blocks + LOG.info("Cleaning project {} blocks", record.getId()); + + ProjectRecord prjRecord = alterReadRecord(record); + + if (prjRecord == null) { + LOG.info("Hmm, Something broke the project record"); + return null; + } + + LOG.info("Returning project {}.", prjRecord.getId()); + + return prjRecord; } + + + // V2 implementations + @Override + public ProjectRecord createProject( + String name, + String description, + String descriptionHtml, + String code, + ProjectType type, + String board, + boolean privateProject, + boolean sharedProject, + Long idProjectBasedOn, + String settings) { + + LOG.info("Creating a new project with existing code."); + + ProjectRecord record = null; + + // Get the logged in user's userID + Long idUser = BlocklyPropSecurityUtils.getCurrentUserId(); + if (idUser == null) { + LOG.error("Null BP UserID"); + return null; + } + + // Get the cloud session user id from the current authenticated session + Long idCloudUser = BlocklyPropSecurityUtils.getCurrentSessionUserId(); + if (idCloudUser == null) { + LOG.error("Null cloud user ID"); + return null; + } + + try { + record = create + .insertInto(Tables.PROJECT, + Tables.PROJECT.ID_USER, + Tables.PROJECT.ID_CLOUDUSER, + Tables.PROJECT.NAME, + Tables.PROJECT.DESCRIPTION, + Tables.PROJECT.DESCRIPTION_HTML, + Tables.PROJECT.CODE, + Tables.PROJECT.CODE_BLOCK_VERSION, + Tables.PROJECT.TYPE, + Tables.PROJECT.BOARD, + Tables.PROJECT.PRIVATE, + Tables.PROJECT.SHARED, + Tables.PROJECT.BASED_ON, + Tables.PROJECT.SETTINGS) + .values(idUser, + idCloudUser, + name, + description, + descriptionHtml, + code, + BLOCKLY_LIBRARY_VERSION, + type, + board, + privateProject, + sharedProject, + idProjectBasedOn, + settings) + .returning() + .fetchOne(); + } + catch (org.jooq.exception.DataAccessException sqex) { + LOG.error("Database error encountered {}", sqex.getMessage()); + return null; + } catch (Exception ex) { + LOG.error("Unexpected exception creating a project record"); + LOG.error("Error Message: {}", ex.getMessage()); + return null; + } + + return record; + + } + + + + /** * @@ -149,6 +242,7 @@ record = create * * @return a fully formed ProjectRecord or a null if an error is detected. */ + @Deprecated @Override public ProjectRecord createProject( String name, @@ -404,16 +498,23 @@ public List getUserProjects( Integer limit, Integer offset) { - LOG.info("Retreive projects for user {}.", idUser); + LOG.info("Retrieve projects for user {}.", idUser); - SortField orderField = Tables.PROJECT.NAME.asc(); + SortField orderField; + + // Set sort order of the result if (TableOrder.desc == order) { orderField = Tables.PROJECT.NAME.desc(); + } else { + orderField = Tables.PROJECT.NAME.asc(); } - return create.selectFrom(Tables.PROJECT) + return create + .selectFrom(Tables.PROJECT) .where(Tables.PROJECT.ID_USER.equal(idUser)) - .orderBy(orderField).limit(limit).offset(offset) + .orderBy(orderField) + .limit(limit) + .offset(offset) .fetch(); } @@ -436,7 +537,7 @@ public List getSharedProjects( Integer limit, Integer offset) { - LOG.info("Retreive shared projects."); + LOG.info("List shared projects."); SortField orderField = sort == null ? Tables.PROJECT.NAME.asc() : sort.getField().asc(); if (TableOrder.desc == order) { @@ -494,9 +595,11 @@ public List getSharedProjectsByUser( */ @Override public int countUserProjects(Long idUser) { - LOG.info("Count project for user {}.", idUser); - return create.fetchCount(Tables.PROJECT, Tables.PROJECT.ID_USER.equal(idUser)); + int rows = create.fetchCount(Tables.PROJECT, Tables.PROJECT.ID_USER.equal(idUser)); + LOG.info("Found a total of {} projects for user: {}.",rows, idUser); + + return rows; } /** @@ -552,10 +655,13 @@ public ProjectRecord cloneProject(Long idProject) { LOG.info("Clone existing project {} to a new project.", idProject); ProjectRecord original = getProject(idProject); + if (original == null) { throw new NullPointerException("Project doesn't exist"); } + Long idUser = BlocklyPropSecurityUtils.getCurrentUserId(); + if (original.getIdUser().equals(idUser) || original.getShared()) { // TODO check if friends return doProjectClone(original); } @@ -762,13 +868,14 @@ private ProjectRecord doProjectClone(ProjectRecord original) { original.getBoard(), original.getPrivate(), original.getShared(), - original.getId() // set the parent project id + original.getId(), // set the parent project id + original.getSettings() ); // cloned.setBasedOn(original.getId()); // cloned.update(); - // WHAT IS THIS DOING? + // TODO: URGENT - Evaluate query to verify that it is updating only one record create.update(Tables.PROJECT) .set(Tables.PROJECT.BASED_ON, original.getId()) .where(Tables.PROJECT.ID.equal(cloned.getId())); diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestUser.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestUser.java index ad881022..ed35eb13 100644 --- a/src/main/java/com/parallax/server/blocklyprop/rest/RestUser.java +++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestUser.java @@ -72,7 +72,7 @@ public void setUserService(UserService userService) { * Returns a list of all user objects. */ @GET - @Path("/") +// @Path("/") @Detail("Get all users") @Name("Get all users") @Produces("application/json") diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java index a2f6dcce..6cb098cb 100644 --- a/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java +++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java @@ -38,7 +38,6 @@ import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.UnauthorizedException; import org.apache.commons.lang.Validate; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,16 +47,19 @@ import java.util.Arrays; import java.util.List; +import org.jetbrains.annotations.NotNull; + /** - * REST endpoints for project persistence - * Version 2 + * Version 2 REST endpoints for private project persistence. Access is limited to + * authenticated users. Refer to the /shared/project/* endpoints for + * public access to projects. * * @author Michel, J. Ewald * * @implNote * - * Supported endpoints: + * Version 1 Supported endpoints: * [POST] /project/ Save current project * [GET] /project/list List projects * [GET] /project/get/{id} Retrieve a project @@ -65,11 +67,11 @@ * [POST] /project/code-as Create new project from existing project * * Version 2 supported endpoints - * CRUD.. - * + * ----------------------------------------------------------------------------------------------------- * CREATE * [POST] /v2/project/ Creates a new project and returns it in the response body - * [POST] /v2/project/code Create a new project based on details on the request body + * [POST] /v2/project/{id} Creates a new project using the contents of the provided + * project id * * RETRIEVE * [GET] /v2/project/ Returns a list of projects; parameters in request body @@ -77,10 +79,10 @@ * * UPDATE * [PUT] /v2/project/{id} Updates s specific project. Project details are in the request body - * [PUT] /v2/project/code/{id} Updates the code block of a specific project * * DELETE * [DELETE] /v2/project/{id} Destroys the project specified by {id} + * ----------------------------------------------------------------------------------------------------- */ @Path("/v2/project") @@ -144,8 +146,9 @@ public Response get() { } + /** - * Create a new project + * Create a new project. Access is limited to authenticated users. * * @param projectName * is a required string parameter containing the project name @@ -209,6 +212,22 @@ public Response get() { * "user": "demo-998", * "success": true * } + * + * @apiNote + * + * This endpoint could also handle the use case where the client wishes to + * create a new copy of an existing project. There are two possible branches + * in this expanded specification; + * + * 1) The existing project belongs to the current user. + * + * 2) The existing project is a) a community project or b) a shared project. + * + * In both cases, a new project is created from the contents of the existing + * project. The new project is designated as 'private' and it's parent field + * is set to the project id of the source project. + * + * TODO: Monitor hits/second from a single user to prevent malicious spamming */ @POST @Detail("Create a new project") @@ -244,7 +263,12 @@ public Response createProject( sharedProject = true; } - LOG.info("Saving the project"); + // Validate the project type + if (! RestProjectUtils.ValidateProjectType(type)) { + return Response.status(Response.Status.NOT_ACCEPTABLE).build(); + } + + LOG.info("Parameters are valid. Saving the project"); ProjectRecord savedProject = projectService.saveProject( null, projectName, description, descriptionHtml, code, privateProject, sharedProject, type, board, settings); @@ -254,8 +278,6 @@ public Response createProject( } JsonObject result = projectConverter.toJson(savedProject,false); - LOG.info("Returning JSON: {}", result); - result.addProperty("success", true); return Response.ok(result.toString()).build(); @@ -266,7 +288,6 @@ public Response createProject( } catch (IllegalArgumentException aex) { LOG.warn("Illegal argument exception detected. {}", aex.getMessage()); - return Response.status(Response.Status.NOT_ACCEPTABLE).build(); } catch (AuthorizationException ae) { @@ -283,7 +304,155 @@ public Response createProject( + /** + * Create a new project based on an existing project. The resulting new + * project will be placed in the logged in user's library as a private + * project. + * + * @param idProject is the identifier of the project to use as a + * source for the new project + * @param projectName + * is a required string parameter containing the project name + * + * @param description + * is a required string parameter containing the project description + * + * @param descriptionHtml + * is an optional parameter containing the HTML representation of the + * project description + * + * @param code + * is a required parameter containing the XML representation of the + * project's code blocks + * + * @param type + * is a required parameter indicating the project's source language, either + * SPIN or PROPC. + * + * @param board + * is a required parameter indicating the type of board used for the project. + * + * @param settings + * is an optional parameter containing a Json encoded string of various + * custom project settings. + * + * @return + * Returns a Json string containing the project details, including the new + * project ID if successful or an error message upon failure + * + * @implNote + * + * VERB URI Notes: + * ------- ---------------------- ---------------------------------------------- + * [POST] /v2/project/ Create a new project from the data provided. + * The service returns a Json string containing + * the new project details. + * + * Return value in response body: + * { + * "id": 66, + * "name": "Chocolate Factory IV", + * "description": "Willie Wonka and the factory", + * "type": "PROPC", + * "board": "heb", + * "private": true, + * "shared": false, + * "created": "2019/02/28 06:36", + * "modified": "2019/02/28 06:36", + * "settings": null, + * "yours": true, + * "user": "demo-998", + * "success": true + * } + * + * @apiNote + * + * This endpoint could also handle the use case where the client wishes to + * create a new copy of an existing project. There are two possible branches + * in this expanded specification; + * + * 1) The existing project belongs to the current user. + * + * 2) The existing project is a) a community project or b) a shared project. + * + * In both cases, a new project is created from the contents of the existing + * project. The new project is designated as 'private' and it's parent field + * is set to the project id of the source project. + * + * TODO: Monitor hits/second from a single user to prevent malicious spamming + */ + @POST + @Path("/{id}") + @Detail("Create a new copy of an existing project") + @Name("Create project copy") + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Produces(MediaType.APPLICATION_JSON) + public Response createProjectCopy( + @PathParam("id") @ParameterDetail("Project identifier") Long idProject, + @FormParam("name") String projectName, + @FormParam("description") String description, + @FormParam("description-html") String descriptionHtml, + @FormParam("code") String code, + @FormParam("type") ProjectType type, + @FormParam("board") String board, + @FormParam("settings") String settings) { + + LOG.info("REST:/rest/v2/project/id POST request received to create a new project '{}'", projectName); + + /** + * This method will retrieve the specified project, verify that it is either a + * public (community) project or if it is a private project, is the project available + * as a shared project. + * + * TODO: If we are cloning a shared project, should we require the shared project key? + * It look like we're creating a separate endpoint to accept a shared project token as + * the source project identifier. + * + */ + try { + // Required fields + Validate.notNull(idProject, "A parent project id is required."); + + /* + * All of these parameters are optional. IF they are not provided, + * the copy operation will take the settings from the original project + * + */ + + LOG.info("Parameters are valid. Saving the project"); + + ProjectRecord project = projectService.createProjectCopy( + idProject, projectName, description, descriptionHtml, + code, type, board, settings); + + LOG.info("Project created?"); + if (project == null) { + LOG.warn("Unable to create a new project record"); + } + + JsonObject result = projectConverter.toJson(project,false); + result.addProperty("success", true); + + return Response.ok(result.toString()).build(); + } + catch (NullPointerException ex) { + LOG.warn("Null pointer exception detected. {}", ex.getMessage()); + return Response.status(Response.Status.NOT_ACCEPTABLE).build(); + } + catch (IllegalArgumentException aex) { + LOG.warn("Illegal argument exception detected. {}", aex.getMessage()); + return Response.status(Response.Status.NOT_ACCEPTABLE).build(); + } + catch (AuthorizationException ae) { + LOG.warn("Project not saved. Not Authorized"); + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + catch (Exception ex) { + LOG.error("General exception encountered. Message is: ", ex.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } @@ -772,5 +941,6 @@ private String buildConvertedResponse(ProjectRecord project) { return result.toString(); } - } + + diff --git a/src/main/java/com/parallax/server/blocklyprop/services/ProjectService.java b/src/main/java/com/parallax/server/blocklyprop/services/ProjectService.java index 5f2b9369..de536eca 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/ProjectService.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/ProjectService.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.services; import com.parallax.server.blocklyprop.TableOrder; @@ -12,38 +28,263 @@ import java.util.List; /** + * Interface definition for the project services layer * * @author Michel */ public interface ProjectService { - ProjectRecord getProjectOwnedByThisUser(Long idProject); + //------------- + // Getters + //------------- - // Return a project + /** + * Retrieve a project record keyed on the unique project id + * + * @param idProject Primary key id for the requested project + * @return a ProjectRecord object if successful, otherwise null + */ ProjectRecord getProject(Long idProject); - List getUserProjects(Long idUser, TableSort tablesSort, TableOrder order, Integer limit, Integer offset); - List getSharedProjects(TableSort tablesSort, TableOrder order, Integer limit, Integer offset); + /** + * Retrieve a project record keyed on the unique project id. + * The requested record must be owned by the current user + * + * @param idProject Primary key id for the requested project + * @return a ProjectRecord object if successful, otherwise null + * + * @implNote This method appears to be used only within the context of deleting + * a project. In the current implementation, there is no concept of + * an admin or superuser account or role that can delete projects on + * a global scale. + */ + ProjectRecord getProjectOwnedByThisUser(Long idProject); + + + /** + * Retrieve a list of projects owned by a specific user + * + * @param idUser + * @param tablesSort + * @param order + * @param limit + * @param offset + * + * @return + */ + List getUserProjects( + Long idUser, + TableSort tablesSort, + TableOrder order, + Integer limit, + Integer offset); + + + /** + * Retrieve a list of public projects + * + * @param tablesSort + * @param order + * @param limit + * @param offset + * + * @return + */ + List getSharedProjects( + TableSort tablesSort, + TableOrder order, + Integer limit, + Integer offset); + + + /** + * Retrieve a list of public projects owned by a specific user + * + * @param tablesSort + * @param order + * @param limit + * @param offset + * @param idUser + * @return + */ + List getSharedProjectsByUser( + TableSort tablesSort, + TableOrder order, + Integer limit, + Integer offset, + Long idUser); + + + //------------- + // Create + //------------- + + /** + * Create a new project + * + * @param name + * @param description + * @param descriptionHtml + * @param privateProject + * @param sharedProject + * @param type + * @param board + * @return + */ + ProjectRecord createProject( + String name, + String description, + String descriptionHtml, + boolean privateProject, + boolean sharedProject, + ProjectType type, + String board); + + - List getSharedProjectsByUser(TableSort tablesSort, TableOrder order, Integer limit, Integer offset, Long idUser); + ProjectRecord createProject( + String name, + String description, + String descriptionHtml, + String projectCode, + boolean privateProject, + boolean sharedProject, + ProjectType type, + String board, + String settings); + + + // Create a copy of an existing project overriding project elements + // with those provided in the parameters supplied + // ----------------------------------------------------------------- + ProjectRecord createProjectCopy( + Long idSourceProject, + String name, + String description, + String descriptionHtml, + String projectCode, + ProjectType type, + String board, + String settings); + + //------------- + // Count + //------------- + + /** + * Count the number of projects owned by a specific user + * + * @param idUser + * @return + */ int countUserProjects(Long idUser); + // TODO: Merge this method with the countSharedProjectsByUser method + + /** + * Count the number of public projects + * + * @return + */ int countSharedProjects(); - + + + /** + * Count the number of public projects oned by a specific user + * + * @param idUser + * @return + */ int countSharedProjectsByUser(Long idUser); - ProjectRecord saveProject(Long idProject, String name, String description, String descriptionHtml, boolean privateProject, boolean sharedProject, ProjectType type, String board); - ProjectRecord cloneProject(Long idProject); + //------------- + // Update + //------------- - ProjectRecord createProject(String name, String description, String descriptionHtml, boolean privateProject, boolean sharedProject, ProjectType type, String board); + /** + * Update an existing project + * + * @param idProject + * @param name + * @param description + * @param descriptionHtml + * @param privateProject + * @param sharedProject + * @param type + * @param board + * + * @return + */ + @Deprecated + ProjectRecord saveProject( + Long idProject, + String name, + String description, + String descriptionHtml, + boolean privateProject, + boolean sharedProject, + ProjectType type, + String board); - boolean deleteProject(Long idProject); + ProjectRecord saveProject( + Long idProject, + String name, + String description, + String descriptionHtml, + String projectCode, + boolean privateProject, + boolean sharedProject, + ProjectType type, + String board, + String settings); + + /** + * Save the code blocks for a specified project + * + * @param idProject + * @param code + * + * @return + */ ProjectRecord saveProjectCode(Long idProject, String code); - public ProjectRecord saveProjectCodeAs(Long idProject, String code, String newName, String newBoard); + /** + * Create a new project owned by the current user that is a copy of the original project + * + * @param idProject + * @param code + * @param newName + * @param newBoard + * + * @return + */ + ProjectRecord saveProjectCodeAs(Long idProject, String code, String newName, String newBoard); + + + /** + * Create a copy of the existing project into the current user's library + * + * @param idProject + * + * @return + */ + ProjectRecord cloneProject(Long idProject); + + + //------------- + // Delete + //------------- + + /** + * Destroy the specified project only if the project is owned by the current user + * + * @param idProject + * @return + */ + boolean deleteProject(Long idProject); } diff --git a/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java b/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java index ad3ada24..abeefbee 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java @@ -20,29 +20,55 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.shiro.authz.UnauthorizedException; +import org.apache.commons.lang.StringUtils; /** + * Implementation of project services layer * * @author Michel + * + * @implNote + * Any method or class marked with the @Transactional decorator will be considered for + * transactionality. Consult the documentation on https://github.com/google/guice/wiki/GuicePersist + * for detailed semantics. Marking a method @Transactional will start a new transaction before + * the method executes and commit it after the method returns. + * + * If the method throws an exception, the transaction will be rolled back unless you have specifically + * requested not to in the ignore() clause. + * + * Similarly, the set of exceptions that will trigger a rollback can be defined in the rollbackOn() + * clause. By default, only unchecked exceptions trigger a rollback. */ @Singleton @Transactional public class ProjectServiceImpl implements ProjectService { + //Application logging facility + private static final Logger LOG = LoggerFactory.getLogger(ProjectServiceImpl.class); + + // Object to store database layer access private ProjectDao projectDao; + + // Object to store project sharing service access private ProjectSharingService projectSharingService; - + /** - * Application logging facility + * Inject support for DAO layer access + * + * @param projectDao object to store injected database layer access */ - private static final Logger LOG = LoggerFactory.getLogger(ProjectServiceImpl.class); - @Inject public void setProjectDao(ProjectDao projectDao) { this.projectDao = projectDao; } + + /** + * Inject support for access to the project sharing services + * + * @param projectSharingService object to store injected project sharing service access + */ @Inject public void setProjectSharingService(ProjectSharingService projectSharingService) { this.projectSharingService = projectSharingService; @@ -51,7 +77,9 @@ public void setProjectSharingService(ProjectSharingService projectSharingService /** * Create a new project record - * + *

    + * This method signature is deprecated. + * * @param name * @param description * @param descriptionHtml @@ -61,12 +89,17 @@ public void setProjectSharingService(ProjectSharingService projectSharingService * @param board * @return */ + @Deprecated @Override public ProjectRecord createProject( - String name, String description, String descriptionHtml, - boolean privateProject, boolean sharedProject, ProjectType type, + String name, + String description, + String descriptionHtml, + boolean privateProject, + boolean sharedProject, + ProjectType type, String board) { - + // Calling saveProject with a null project id will force underlying code // to create a new project return saveProject( @@ -74,6 +107,133 @@ public ProjectRecord createProject( sharedProject, type, board); } + + /** + * + * @param name + * @param description + * @param descriptionHtml + * @param projectCode + * @param privateProject + * @param sharedProject + * @param type + * @param board + * @param settings + * @return + */ + @Override + public ProjectRecord createProject( + String name, + String description, + String descriptionHtml, + String projectCode, + boolean privateProject, + boolean sharedProject, + ProjectType type, + String board, + String settings) { + + return saveProject( + null, + name, + description, + descriptionHtml, + projectCode, + privateProject, + sharedProject, + type, + board, + settings); + // FIXME: Add project code blocks string + + } + + + + @Override + public ProjectRecord createProjectCopy( + Long idSourceProject, + String name, + String description, + String descriptionHtml, + String code, + ProjectType type, + String board, + String settings) { + + + // local project details + String projectName = null; + String projectDescription = null; + String projectDescriptionHtml = null; + String projectCode = null; + ProjectType projectType = null; + String projectBoard = null; + String projectSettings = null; + + + // Obtain the source project + LOG.info("Service retrieving project {} from database", idSourceProject); + ProjectRecord sourceProject = projectDao.getProject(idSourceProject); + + if (sourceProject == null) { + LOG.info("Project retrieval failed."); + return null; + } + + LOG.info("Got project #{}", sourceProject.getId()); + + // Prepare date for new project + try { + projectName = (name != null && !name.trim().isEmpty()) ? + name : sourceProject.getName(); + + projectDescription = (description != null && !description.trim().isEmpty()) ? + description : sourceProject.getDescription(); + + projectDescriptionHtml = (descriptionHtml != null && !descriptionHtml.trim().isEmpty()) ? + descriptionHtml : + sourceProject.getDescriptionHtml(); + + projectCode = (code != null && !code.trim().isEmpty()) ? + code : sourceProject.getCode(); + + projectType = (type != null) ? type : sourceProject.getType(); + + projectBoard = (board != null && !board.trim().isEmpty()) ? + board : sourceProject.getBoard(); + + projectSettings = (settings != null && !settings.trim().isEmpty()) ? + settings : sourceProject.getSettings(); + + } + catch (NullPointerException ex) { + LOG.info("Something is null and not a good thing"); + } + + LOG.info("Creating the new project copy"); + + + // Create a new project record + ProjectRecord newProject = projectDao.createProject( + projectName, + projectDescription, + projectDescriptionHtml, + projectCode, + projectType, + projectBoard, + true, // Make it a private project + false, // which means it is not shared + idSourceProject, // parent project is the source project + projectSettings); + + + LOG.info("Returning the new project"); + return newProject; + } + + + /** * Update an existing project or create a new project. * @@ -93,6 +253,7 @@ public ProjectRecord createProject( * @param board * @return */ + @Deprecated @Override public ProjectRecord saveProject( Long idProject, String name, String description, @@ -112,10 +273,34 @@ public ProjectRecord saveProject( } - + @Override + public ProjectRecord saveProject( + Long idProject, String name, String description, String descriptionHtml, + String code, + boolean privateProject, + boolean sharedProject, ProjectType type, String board, String settings) { + + // Check if project is from the current user, if not, unset idProject and create new + if (idProject != null) { + return projectDao.updateProject( + idProject, name, description, descriptionHtml, + privateProject, sharedProject); + } else { + return projectDao.createProject( + name, description, descriptionHtml, + code, + type, board, privateProject, sharedProject, + null, + settings); + } + } + + @Override public ProjectRecord getProjectOwnedByThisUser(Long idProject) { + ProjectRecord projectRecord = projectDao.getProject(idProject); + if (projectRecord != null) { if (projectRecord.getIdUser().equals(BlocklyPropSecurityUtils.getCurrentUserId())) { return projectRecord; @@ -208,7 +393,13 @@ public List getSharedProjects( } @Override - public List getSharedProjectsByUser(TableSort sort, TableOrder order, Integer limit, Integer offset, Long idUser) { + public List getSharedProjectsByUser( + TableSort sort, + TableOrder order, + Integer limit, + Integer offset, + Long idUser) { + return projectDao.getSharedProjectsByUser(sort, order, limit, offset, idUser); } @@ -267,15 +458,25 @@ public ProjectRecord saveProjectCode(Long idProject, String code) { /** + * Create a new project, specifying a new project name and board type, based on an existing project * - * @param idProject - * @param code - * @param newName - * @param newBoard - * @return + * @param idProject the primary key ID of the source project + * + * @param code is the code blocks to add to the new project + * + * @param newName is the nae that will be assigned to the new project + * + * @param newBoard is the new board type that will be assigned to the new project + * + * @return a ProjectRecord representing the newly created project */ @Override - public ProjectRecord saveProjectCodeAs(Long idProject, String code, String newName, String newBoard) { + public ProjectRecord saveProjectCodeAs( + Long idProject, + String code, + String newName, + String newBoard) { + return projectDao.saveProjectCodeAs(idProject, code, newName, newBoard); } From b65209ac9f2aa923da225f0cfa7a3443a058d554 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Mar 2019 10:45:21 -0800 Subject: [PATCH 54/69] Jooq generated updates for adding project settings to the application. --- .../blocklyprop/db/generated/tables/Motd.java | 2 +- .../db/generated/tables/Project.java | 7 ++- .../db/generated/tables/pojos/Motd.java | 2 +- .../db/generated/tables/pojos/Project.java | 17 ++++- .../generated/tables/records/MotdRecord.java | 2 +- .../tables/records/ProjectRecord.java | 63 +++++++++++++++---- 6 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Motd.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Motd.java index b7b64129..d64066da 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Motd.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Motd.java @@ -160,4 +160,4 @@ public Motd as(String alias) { public Motd rename(String name) { return new Motd(name, null); } -} \ No newline at end of file +} diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Project.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Project.java index 8c201861..da28e09f 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Project.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/Project.java @@ -38,7 +38,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Project extends TableImpl { - private static final long serialVersionUID = -1048994704; + private static final long serialVersionUID = -1976592299; /** * The reference instance of blocklyprop.project @@ -128,6 +128,11 @@ public Class getRecordType() { */ public final TableField BASED_ON = createField("based_on", org.jooq.impl.SQLDataType.BIGINT, this, ""); + /** + * The column blocklyprop.project.settings. + */ + public final TableField SETTINGS = createField("settings", org.jooq.impl.SQLDataType.CLOB, this, ""); + /** * Create a blocklyprop.project table reference */ diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Motd.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Motd.java index 1ef5b542..65d052bc 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Motd.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Motd.java @@ -173,4 +173,4 @@ public String toString() { sb.append(")"); return sb.toString(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Project.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Project.java index 4cded530..b84c7b66 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Project.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/pojos/Project.java @@ -25,7 +25,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Project implements Serializable { - private static final long serialVersionUID = -1074694014; + private static final long serialVersionUID = 1473532278; private Long id; private Long idUser; @@ -42,6 +42,7 @@ public class Project implements Serializable { private GregorianCalendar created; private GregorianCalendar modified; private Long basedOn; + private String settings; public Project() {} @@ -61,6 +62,7 @@ public Project(Project value) { this.created = value.created; this.modified = value.modified; this.basedOn = value.basedOn; + this.settings = value.settings; } public Project( @@ -78,7 +80,8 @@ public Project( Boolean shared, GregorianCalendar created, GregorianCalendar modified, - Long basedOn + Long basedOn, + String settings ) { this.id = id; this.idUser = idUser; @@ -95,6 +98,7 @@ public Project( this.created = created; this.modified = modified; this.basedOn = basedOn; + this.settings = settings; } public Long getId() { @@ -217,6 +221,14 @@ public void setBasedOn(Long basedOn) { this.basedOn = basedOn; } + public String getSettings() { + return this.settings; + } + + public void setSettings(String settings) { + this.settings = settings; + } + @Override public String toString() { StringBuilder sb = new StringBuilder("Project ("); @@ -236,6 +248,7 @@ public String toString() { sb.append(", ").append(created); sb.append(", ").append(modified); sb.append(", ").append(basedOn); + sb.append(", ").append(settings); sb.append(")"); return sb.toString(); diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/MotdRecord.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/MotdRecord.java index b6e83213..00d16202 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/MotdRecord.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/MotdRecord.java @@ -500,4 +500,4 @@ public MotdRecord(Long id, String messageText, String messageHtml, String notes, setValue(8, messageEnableTime); setValue(9, messageDisableTime); } -} \ No newline at end of file +} diff --git a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/ProjectRecord.java b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/ProjectRecord.java index ce0e1357..1fc51c93 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/ProjectRecord.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/generated/tables/records/ProjectRecord.java @@ -13,8 +13,8 @@ import org.jooq.Field; import org.jooq.Record1; -import org.jooq.Record15; -import org.jooq.Row15; +import org.jooq.Record16; +import org.jooq.Row16; import org.jooq.impl.UpdatableRecordImpl; @@ -29,9 +29,9 @@ comments = "This class is generated by jOOQ" ) @SuppressWarnings({ "all", "unchecked", "rawtypes" }) -public class ProjectRecord extends UpdatableRecordImpl implements Record15 { +public class ProjectRecord extends UpdatableRecordImpl implements Record16 { - private static final long serialVersionUID = 902114783; + private static final long serialVersionUID = 83268425; /** * Setter for blocklyprop.project.id. @@ -243,6 +243,20 @@ public Long getBasedOn() { return (Long) getValue(14); } + /** + * Setter for blocklyprop.project.settings. + */ + public void setSettings(String value) { + setValue(15, value); + } + + /** + * Getter for blocklyprop.project.settings. + */ + public String getSettings() { + return (String) getValue(15); + } + // ------------------------------------------------------------------------- // Primary key information // ------------------------------------------------------------------------- @@ -256,23 +270,23 @@ public Record1 key() { } // ------------------------------------------------------------------------- - // Record15 type implementation + // Record16 type implementation // ------------------------------------------------------------------------- /** * {@inheritDoc} */ @Override - public Row15 fieldsRow() { - return (Row15) super.fieldsRow(); + public Row16 fieldsRow() { + return (Row16) super.fieldsRow(); } /** * {@inheritDoc} */ @Override - public Row15 valuesRow() { - return (Row15) super.valuesRow(); + public Row16 valuesRow() { + return (Row16) super.valuesRow(); } /** @@ -395,6 +409,14 @@ public Field field15() { return Project.PROJECT.BASED_ON; } + /** + * {@inheritDoc} + */ + @Override + public Field field16() { + return Project.PROJECT.SETTINGS; + } + /** * {@inheritDoc} */ @@ -515,6 +537,14 @@ public Long value15() { return getBasedOn(); } + /** + * {@inheritDoc} + */ + @Override + public String value16() { + return getSettings(); + } + /** * {@inheritDoc} */ @@ -654,7 +684,16 @@ public ProjectRecord value15(Long value) { * {@inheritDoc} */ @Override - public ProjectRecord values(Long value1, Long value2, Long value3, String value4, String value5, String value6, String value7, Short value8, ProjectType value9, String value10, Boolean value11, Boolean value12, GregorianCalendar value13, GregorianCalendar value14, Long value15) { + public ProjectRecord value16(String value) { + setSettings(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public ProjectRecord values(Long value1, Long value2, Long value3, String value4, String value5, String value6, String value7, Short value8, ProjectType value9, String value10, Boolean value11, Boolean value12, GregorianCalendar value13, GregorianCalendar value14, Long value15, String value16) { value1(value1); value2(value2); value3(value3); @@ -670,6 +709,7 @@ public ProjectRecord values(Long value1, Long value2, Long value3, String value4 value13(value13); value14(value14); value15(value15); + value16(value16); return this; } @@ -687,7 +727,7 @@ public ProjectRecord() { /** * Create a detached, initialised ProjectRecord */ - public ProjectRecord(Long id, Long idUser, Long idClouduser, String name, String description, String descriptionHtml, String code, Short codeBlockVersion, ProjectType type, String board, Boolean private_, Boolean shared, GregorianCalendar created, GregorianCalendar modified, Long basedOn) { + public ProjectRecord(Long id, Long idUser, Long idClouduser, String name, String description, String descriptionHtml, String code, Short codeBlockVersion, ProjectType type, String board, Boolean private_, Boolean shared, GregorianCalendar created, GregorianCalendar modified, Long basedOn, String settings) { super(Project.PROJECT); setValue(0, id); @@ -705,5 +745,6 @@ public ProjectRecord(Long id, Long idUser, Long idClouduser, String name, String setValue(12, created); setValue(13, modified); setValue(14, basedOn); + setValue(15, settings); } } From 00853c31885e814faf17919236c9e43b2ea59edd Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Mar 2019 10:47:07 -0800 Subject: [PATCH 55/69] Add support for version 2 Project REST endpoints. --- .../server/blocklyprop/config/RestModule.java | 5 +++ .../blocklyprop/config/SetupConfig.java | 37 ++++++++----------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/config/RestModule.java b/src/main/java/com/parallax/server/blocklyprop/config/RestModule.java index 04e11b84..999523c3 100644 --- a/src/main/java/com/parallax/server/blocklyprop/config/RestModule.java +++ b/src/main/java/com/parallax/server/blocklyprop/config/RestModule.java @@ -25,10 +25,13 @@ import com.parallax.server.blocklyprop.rest.RestMotd; import com.parallax.server.blocklyprop.rest.RestProfile; import com.parallax.server.blocklyprop.rest.RestProject; +import com.parallax.server.blocklyprop.rest.RestV2Project; import com.parallax.server.blocklyprop.rest.RestSharedProject; import com.parallax.server.blocklyprop.rest.RestUser; + import com.sun.jersey.guice.JerseyServletModule; import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; + import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.MessageBodyWriter; import org.codehaus.jackson.jaxrs.JacksonJsonProvider; @@ -45,6 +48,8 @@ protected void configureServlets() { bind(RestCompile.class); bind(RestUser.class); bind(RestProject.class); + bind(RestV2Project.class); + bind(RestSharedProject.class); bind(RestProfile.class); bind(RestMotd.class); diff --git a/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java b/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java index 49aae174..fd830cdb 100644 --- a/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java +++ b/src/main/java/com/parallax/server/blocklyprop/config/SetupConfig.java @@ -26,11 +26,12 @@ import com.google.inject.Injector; import com.google.inject.servlet.GuiceServletContextListener; -import com.parallax.server.blocklyprop.SessionData; import com.parallax.server.blocklyprop.jsp.Properties; import com.parallax.server.blocklyprop.monitoring.Monitor; + import java.sql.Driver; import java.sql.DriverManager; +import java.sql.SQLException; import java.util.Enumeration; import javax.servlet.ServletContextEvent; import org.apache.commons.configuration.Configuration; @@ -40,22 +41,25 @@ import org.slf4j.LoggerFactory; + /** * * @author Michel */ public class SetupConfig extends GuiceServletContextListener { - /** - * Application-specific configuration options - */ + // Application logging connector + private final Logger LOG = LoggerFactory.getLogger(SetupConfig.class); + + // Application-specific configuration options private Configuration configuration; - + + /** - * Application logging connector + * Create a Guice injector object + * + * @return a Guice injector object */ - private final Logger LOG = LoggerFactory.getLogger(SetupConfig.class); - @Override protected Injector getInjector() { readConfiguration(); @@ -65,9 +69,9 @@ protected Injector getInjector() { @Override protected void configure() { + LOG.info("Binding Configuration class"); bind(Configuration.class).toInstance(configuration); - bind(SessionData.class); bind(Properties.class).asEagerSingleton(); bind(Monitor.class).asEagerSingleton(); @@ -81,7 +85,6 @@ protected void configure() { install(new ServletsModule()); install(new RestModule()); } - }); } @@ -119,26 +122,16 @@ public void contextDestroyed(ServletContextEvent servletContextEvent) { // This manually deregisters JDBC driver, which prevents Tomcat 7 from // complaining about memory leaks into this class -/* while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); try { DriverManager.deregisterDriver(driver); - LOG.info("deregistering jdbc driver: {}",driver); + LOG.info("Deregister the jdbc driver: {}",driver); } catch (SQLException sqlE) { - LOG.error("Error deregistering driver %s", driver); + LOG.error("Unable to deregister the jdbc driver {}", driver.toString()); LOG.error("{}", sqlE.getSQLState()); } - - } - - // Shut down the loggers. Assume SLF4J is bound to logback-classic - // in the current environment - LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); - if (loggerContext != null) { - loggerContext.stop(); } -*/ } } From 4c1730a949576add7840dcf0161b16876467d82a Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Mar 2019 12:15:25 -0800 Subject: [PATCH 56/69] Add code to support Project Update REST endpoint. --- .../server/blocklyprop/db/dao/ProjectDao.java | 12 ++ .../db/dao/impl/ProjectDaoImpl.java | 39 ++++++ .../blocklyprop/rest/RestV2Project.java | 112 ++++++++++++++++-- .../blocklyprop/services/ProjectService.java | 3 + .../services/impl/ProjectServiceImpl.java | 22 +++- 5 files changed, 180 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java index a6661a10..14875aea 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectDao.java @@ -8,6 +8,7 @@ import com.parallax.server.blocklyprop.TableOrder; import com.parallax.server.blocklyprop.TableSort; import com.parallax.server.blocklyprop.db.enums.ProjectType; +import com.parallax.server.blocklyprop.db.generated.tables.Project; import com.parallax.server.blocklyprop.db.generated.tables.records.ProjectRecord; import java.util.List; @@ -106,6 +107,17 @@ ProjectRecord updateProject( boolean privateProject, boolean sharedProject); + ProjectRecord updateProject( + Long idProject, + String name, + String description, + String descriptionHtml, + String code, + boolean privateProject, + boolean sharedProject, + String settings); + + ProjectRecord saveCode( Long idProject, String code); diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java index e31fe08b..d33fc33b 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java @@ -447,6 +447,45 @@ public ProjectRecord updateProject( return null; } + + + + @Override + public ProjectRecord updateProject( + Long idProject, + String name, + String description, + String descriptionHtml, + String code, + boolean privateProject, + boolean sharedProject, + String settings) { + + LOG.info("Update project {}.", idProject); + + ProjectRecord record = getProject(idProject, EDIT_MODE_ON); + if (record != null) { + record.setName(name); + record.setDescription(description); + record.setDescriptionHtml(descriptionHtml); + record.setCode(code); + record.setPrivate(privateProject); + record.setShared(sharedProject); + record.setSettings(settings); + record.setModified(getCurrentTimestamp()); + record.setCodeBlockVersion(BLOCKLY_LIBRARY_VERSION); + record.update(); + + return record; + } + + LOG.warn("Unable to update project {}", idProject); + return null; + } + + + + /** * Update the code blocks for a project * diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java index 6cb098cb..3ee1d771 100644 --- a/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java +++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java @@ -69,13 +69,13 @@ * Version 2 supported endpoints * ----------------------------------------------------------------------------------------------------- * CREATE - * [POST] /v2/project/ Creates a new project and returns it in the response body - * [POST] /v2/project/{id} Creates a new project using the contents of the provided + * [POST] /v2/project/ [X] Creates a new project and returns it in the response body + * [POST] /v2/project/{id} [X] Creates a new project using the contents of the provided * project id * * RETRIEVE - * [GET] /v2/project/ Returns a list of projects; parameters in request body - * [GET] /v2/project/{id} Returns the specific project if authorized + * [GET] /v2/project/ [X] Returns a list of projects; parameters in request body + * [GET] /v2/project/{id} [X] Returns the specific project if authorized * * UPDATE * [PUT] /v2/project/{id} Updates s specific project. Project details are in the request body @@ -302,8 +302,6 @@ public Response createProject( - - /** * Create a new project based on an existing project. The resulting new * project will be placed in the logged in user's library as a private @@ -645,7 +643,7 @@ public Response get( @Produces("application/json") public Response get(@PathParam("id") @ParameterDetail("Project identifier") Long idProject) { - LOG.info("REST:/rest/project/get/ Get request received for project '{}'", idProject); + LOG.info("REST:/rest/v2/project/get/ Get request received for project '{}'", idProject); try { ProjectRecord project = projectService.getProject(idProject); @@ -678,7 +676,70 @@ public Response get(@PathParam("id") @ParameterDetail("Project identifier") Long + @PUT + @Path("/{id}") + @Detail("Update voluble elements of an existing project") + @Name("Update Project by id") + @Produces("application/json") + public Response update( + @PathParam("id") @ParameterDetail("Project identifier") Long idProject, + @FormParam("name") String projectName, + @FormParam("description") String description, + @FormParam("description-html") String descriptionHtml, + @FormParam("code") String code, + @FormParam("type") ProjectType type, + @FormParam("board") String board, + @FormParam("settings") String settings) { + + LOG.info("REST:/rest/v2/project/{} PUT request received for project", idProject, idProject); + + ProjectRecord project; + + // Get the specified project + try { + project = projectService.getProject(idProject); + + if (project != null) { + // Verify that the current user owns the requested project + if (!project.getIdUser().equals(BlocklyPropSecurityUtils.getCurrentUserId())) { + LOG.info("User not authorized to get project {}", idProject); + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + } else { + LOG.info("Project {} was not found", idProject); + return Response.status(Response.Status.NOT_FOUND).build(); + } + } + catch (Exception ex) { + LOG.warn("An unexpected exception has occurred. Message: {}", ex.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + + // Update fields with passed in parameters. Parameters that are null will + // not update the record. However, parameters that are empty strings will + // update the corresponding field in the project record + // ----------------------------------------------------------------------- + project = updateProjectRecordFields( + project, projectName, description, descriptionHtml, + code, type, board, settings); + + // Save the record + ProjectRecord revisedProject = projectService.saveProject(project); + if (revisedProject == null) { + LOG.warn("Unable to update project {}", idProject); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + + // Return the saved record + + + JsonObject result = projectConverter.toJson(project,false); + result.addProperty("result", "success"); + result.addProperty("message", "Update project"); + return Response.ok(result.toString()).build(); + + } @@ -941,6 +1002,43 @@ private String buildConvertedResponse(ProjectRecord project) { return result.toString(); } + + + private ProjectRecord updateProjectRecordFields( + ProjectRecord project, String projectName, String description, String descriptionHtml, + String code, ProjectType type, String board, String settings) { + + if (projectName != null) { + project.setName(projectName); + } + + if (description != null) { + project.setDescription(description); + } + + if (descriptionHtml != null) { + project.setDescriptionHtml(descriptionHtml); + } + + if (code != null) { + project.setCode(code); + } + + if (type != null) { + project.setType(type); + } + + if (board != null) { + project.setBoard(board); + } + + if (settings != null) { + project.setSettings(settings); + } + + return project; + + } } diff --git a/src/main/java/com/parallax/server/blocklyprop/services/ProjectService.java b/src/main/java/com/parallax/server/blocklyprop/services/ProjectService.java index de536eca..e7695a20 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/ProjectService.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/ProjectService.java @@ -242,6 +242,9 @@ ProjectRecord saveProject( String settings); + // Update an existing project + ProjectRecord saveProject(ProjectRecord project); + /** * Save the code blocks for a specified project * diff --git a/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java b/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java index abeefbee..57c5d816 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java @@ -20,7 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.shiro.authz.UnauthorizedException; -import org.apache.commons.lang.StringUtils; +// import org.apache.commons.lang.StringUtils; /** * Implementation of project services layer @@ -38,6 +38,12 @@ * * Similarly, the set of exceptions that will trigger a rollback can be defined in the rollbackOn() * clause. By default, only unchecked exceptions trigger a rollback. + * + * Public Methods: + * createProject() + * createProjectCopy() + * saveProject() + * */ @Singleton @Transactional @@ -296,6 +302,20 @@ public ProjectRecord saveProject( } + // Update a project, using the existing project record. + @Override + public ProjectRecord saveProject( ProjectRecord project) { + return projectDao.updateProject( + project.getId(), + project.getName(), + project.getDescription(), + project.getDescriptionHtml(), + project.getCode(), + project.getPrivate(), + project.getShared(), + project.getSettings()); + } + @Override public ProjectRecord getProjectOwnedByThisUser(Long idProject) { From 1d73749b9d81892ec20b62312b47e1fda5c29a2c Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Mar 2019 15:19:32 -0800 Subject: [PATCH 57/69] Add inline documentation. --- .../blocklyprop/db/dao/impl/ProjectDaoImpl.java | 2 +- .../blocklyprop/services/impl/ProjectServiceImpl.java | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java index d33fc33b..15b5ad65 100644 --- a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java @@ -711,7 +711,7 @@ public ProjectRecord cloneProject(Long idProject) { * TODO: add details. * * @param idProject - * @return + * @return boolean true if record was deleted, otherwise false. */ @Override public boolean deleteProject(Long idProject) { diff --git a/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java b/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java index 57c5d816..1b5830cd 100644 --- a/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java +++ b/src/main/java/com/parallax/server/blocklyprop/services/impl/ProjectServiceImpl.java @@ -455,11 +455,11 @@ public ProjectRecord cloneProject(Long idProject) { @Override public boolean deleteProject(Long idProject) { - LOG.info("Deleting project {}", idProject); - // Remove the project shared key if it exists. + LOG.info("Deleting project {} shared link", idProject); projectSharingService.deleteSharedProject(idProject); + LOG.info("Deleting project {}", idProject); return projectDao.deleteProject(idProject); } @@ -467,9 +467,10 @@ public boolean deleteProject(Long idProject) { /** * Update the code block in the specified project * - * @param idProject - * @param code - * @return + * @param idProject is the primary key ID of the source project + * @param code is the code blocks to add to the new project + * + * @return a ProjectRecord representing the updated project */ @Override public ProjectRecord saveProjectCode(Long idProject, String code) { From 4d235e154bc362efd7c84277b9d74fa53aeff5d5 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Mar 2019 15:20:29 -0800 Subject: [PATCH 58/69] Implement project REST DELETE /{id} endpoint. --- .../blocklyprop/rest/RestV2Project.java | 313 ++++++------------ 1 file changed, 107 insertions(+), 206 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java index 3ee1d771..e85aca9f 100644 --- a/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java +++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java @@ -44,7 +44,6 @@ import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.util.Arrays; import java.util.List; import org.jetbrains.annotations.NotNull; @@ -69,13 +68,13 @@ * Version 2 supported endpoints * ----------------------------------------------------------------------------------------------------- * CREATE - * [POST] /v2/project/ [X] Creates a new project and returns it in the response body - * [POST] /v2/project/{id} [X] Creates a new project using the contents of the provided + * [POST] /v2/project/ Creates a new project and returns it in the response body + * [POST] /v2/project/{id} Creates a new project using the contents of the provided * project id * * RETRIEVE - * [GET] /v2/project/ [X] Returns a list of projects; parameters in request body - * [GET] /v2/project/{id} [X] Returns the specific project if authorized + * [GET] /v2/project/ Returns a list of projects; parameters in request body + * [GET] /v2/project/{id} Returns the specific project if authorized * * UPDATE * [PUT] /v2/project/{id} Updates s specific project. Project details are in the request body @@ -106,6 +105,7 @@ public class RestV2Project { private static final int REQUEST_LIMIT = 100; + /** * Connect to the project service object * @@ -118,6 +118,7 @@ public void setProjectService(ProjectService projectService) { } + /** * Connect to the project converter object * @@ -131,6 +132,7 @@ public void setProjectConverter(ProjectConverter projectConverter) { + // GET /v2/project/ping /** * Test endpoint to verify that the class is reachable * @@ -147,6 +149,7 @@ public Response get() { + // POST /v2/project/ /** * Create a new project. Access is limited to authenticated users. * @@ -302,6 +305,7 @@ public Response createProject( + // POST /v2/project/{id} /** * Create a new project based on an existing project. The resulting new * project will be placed in the logged in user's library as a private @@ -342,7 +346,7 @@ public Response createProject( * * VERB URI Notes: * ------- ---------------------- ---------------------------------------------- - * [POST] /v2/project/ Create a new project from the data provided. + * [POST] /v2/project/{id} Create a new project from the data provided. * The service returns a Json string containing * the new project details. * @@ -454,7 +458,7 @@ public Response createProjectCopy( - + // GET /v2/project/ /** * Return a list of projects owned by the currently authenticated user. * @@ -598,6 +602,7 @@ public Response get( } + // GET /v2/project/{id} /** * Retreive a project based on the supplied project ID * @@ -676,6 +681,41 @@ public Response get(@PathParam("id") @ParameterDetail("Project identifier") Long + // PUT /v2/project/{id} + /** + * Update an existing project + * + * @param idProject is the identifier of the project to use as a + * source for the new project + * @param projectName + * is a required string parameter containing the project name + * + * @param description + * is a required string parameter containing the project description + * + * @param descriptionHtml + * is an optional parameter containing the HTML representation of the + * project description + * + * @param code + * is a required parameter containing the XML representation of the + * project's code blocks + * + * @param type + * is a required parameter indicating the project's source language, either + * SPIN or PROPC. + * + * @param board + * is a required parameter indicating the type of board used for the project. + * + * @param settings + * is an optional parameter containing a Json encoded string of various + * custom project settings. + * + * @return + * Returns a Json string containing the project details, including the new + * project ID if successful or an error message upon failure + */ @PUT @Path("/{id}") @Detail("Update voluble elements of an existing project") @@ -691,7 +731,7 @@ public Response update( @FormParam("board") String board, @FormParam("settings") String settings) { - LOG.info("REST:/rest/v2/project/{} PUT request received for project", idProject, idProject); + LOG.info("REST:/rest/v2/project/{} PUT request received for project", idProject); ProjectRecord project; @@ -715,6 +755,7 @@ public Response update( return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } + // ----------------------------------------------------------------------- // Update fields with passed in parameters. Parameters that are null will // not update the record. However, parameters that are empty strings will // update the corresponding field in the project record @@ -731,230 +772,72 @@ public Response update( } // Return the saved record - - JsonObject result = projectConverter.toJson(project,false); result.addProperty("result", "success"); - result.addProperty("message", "Update project"); return Response.ok(result.toString()).build(); - } - - - - - - - - - - - - - - - - - + // DELETE /v2/project/{id} /** - * Update the code in an existing project. - * - * This assumes that the project already exists. - * - * @param idProject The project key ID - * @param code the project blocks code string + * Delete a project only if the project is owned by the currently logged in user. * - * @return - * Returns a Json string containing the project details if the update was successful - * or an error message upon failure - */ - @POST - @Path("/code") - @Detail("Save project code") - @Name("UpdateProjectCode") - @Produces("application/json") - public Response saveProjectCode( - @FormParam("id") @ParameterDetail("Project identifier") @M() Long idProject, - @FormParam("code") @ParameterDetail("Project code") @M() String code) { - - LOG.info("REST:/rest/project/code/ POST request received for project '{}'", idProject); - - try { - - /* WARNING: - * ================================================================================= - * This call can create a new project record under specific circumstances and does - * not appear to provide any notification that this has occurred. - * ================================================================================= - */ - ProjectRecord savedProject = projectService.saveProjectCode(idProject, code); - - LOG.debug("Code for project {} has been saved", idProject); -/* - JsonObject result = projectConverter.toJson(savedProject,false); - result.addProperty("success", true); - return Response.ok(result.toString()).build(); -*/ - return Response.ok(buildConvertedResponse(savedProject)).build(); - } catch (AuthorizationException ae) { - LOG.warn("Project code not saved. Not Authorized"); - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - catch (Exception ex) { - LOG.error("General exception encountered. Message is: ", ex.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); - } - } - - - + * @param idProject is the identifier of the project to use as a + * source for the new project - /** - * Create a new project from an existing project - * - * @param idProject The project key ID - * @param code the project blocks code string - * @param newName the name to assign to the newly created project - * @param newBoard the board type assigned to the new project - * - * @return - * Returns a Json string containing the project details if the update was successful - * or an error message upon failure + * @return a Json formatted string containing "success" otherwise returns an + * error code indicating the type of failure encountered. */ - @POST - @Path("/code-as") - @Detail("Save project code") - @Name("Save project code") + @DELETE + @Path("/{id}") + @Detail("Delete a project by the project id") + @Name("Delete Project by id") @Produces("application/json") - public Response saveProjectCodeAs( - @FormParam("id") Long idProject, - @FormParam("code") String code, - @FormParam("name") String newName, - @FormParam("board") String newBoard) { - - LOG.info("REST:/rest/project/code-as/ POST request received for project '{}'", idProject); - - try { - LOG.info("Saving project '{}', '{}' as a new project", idProject, newName); - - ProjectRecord savedProject = projectService.saveProjectCodeAs( - idProject, - code, - newName, - newBoard); - - LOG.debug("Code for project {} has been saved as {}", idProject, newName); -/* - JsonObject result = projectConverter.toJson(savedProject,false); - LOG.debug("Returning JSON: {}", result); - result.addProperty("success", true); - return Response.ok(result.toString()).build(); -*/ - return Response.ok(buildConvertedResponse(savedProject)).build(); - } - catch (AuthorizationException ae) { - LOG.warn("Project code not saved. Not Authorized"); - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - catch (Exception ex) { - LOG.error("General exception encountered. Message is: ", ex.getMessage()); - LOG.error("Error: {}", Arrays.toString(ex.getStackTrace())); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); - } - } - - - - - - /*-------------------------------------------------------------------------+ - * VERB URI Notes: | - * ------- ---------------------- -------------------------------------- | - * [POST] /v2/project/ Creates a new project. The project | - * details, including the new project ID, | - * are returned to the caller. | - *-------------------------------------------------------------------------*/ - - - - - - - - /* - * Update the details of an existing project V1 implementation - * - * @param idProject the project key ID - * @param name the name assigned to the project - * @param description a text description of the project - * @param descriptionHtml the same project description expressed in HTML - * @param projectSharing a boolean flag indicating the public accessibility of the project - * @param type is the classification of the project's language (c or spin) - * @param board is the type of hardware associated with the project - * - * @return - * Returns a Json string containing the project details if the update was successful - * or an error message upon failure - */ + public Response delete( + @PathParam("id") @ParameterDetail("Project identifier") Long idProject) { -/* - @POST -// @Path("/") - @Detail("Save project") - @Name("Save project") - @Produces("application/json") - public Response saveProject( - @FormParam("id") Long idProject, - @FormParam("name") String name, - @FormParam("description") String description, - @FormParam("description-html") String descriptionHtml, - @FormParam("sharing") String projectSharing, - @FormParam("type") ProjectType type, - @FormParam("board") String board) { + LOG.info("REST:/rest/v2/project/{} DELETE request received for project", idProject); - LOG.info("REST:/rest/project/ POST request received for project '{}'", idProject); + ProjectRecord project; + // Get the specified project try { - boolean privateProject = false; - boolean sharedProject = false; + project = projectService.getProject(idProject); - if ("private".equalsIgnoreCase(projectSharing)) { - privateProject = true; - } else if ("shared".equalsIgnoreCase(projectSharing)) { - sharedProject = true; + if (project == null) { + LOG.info("Project {} was not found", idProject); + return Response.status(Response.Status.NOT_FOUND).build(); } - ProjectRecord savedProject = projectService.saveProject( - idProject, - name, - description, - descriptionHtml, - privateProject, - sharedProject, - type, - board); - LOG.debug("Project {} has been saved.", idProject); + // Verify that the current user owns the requested project + if (!project.getIdUser().equals(BlocklyPropSecurityUtils.getCurrentUserId())) { + LOG.info("User not authorized to get project {}", idProject); + return Response.status(Response.Status.UNAUTHORIZED).build(); + } - JsonObject result = projectConverter.toJson(savedProject,false); - LOG.debug("Returning JSON: {}", result); + LOG.info("Project {} is ready to be deleted."); - result.addProperty("success", true); + // Delete the record + if (!projectService.deleteProject(idProject)) { + LOG.warn("Unable to delete project {}", idProject); + return Response.status(Response.Status.NOT_MODIFIED).build(); + } + JsonObject result = new JsonObject(); + result.addProperty("result", "success"); return Response.ok(result.toString()).build(); - } catch (AuthorizationException ae) { - LOG.warn("Project not saved. Not Authorized"); - return Response.status(Response.Status.UNAUTHORIZED).build(); + } catch (Exception ex) { - LOG.error("General exception encountered. Message is: ", ex.getMessage()); + LOG.warn("An unexpected exception has occurred. Message: {}", ex.getMessage()); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } } -*/ + + + /** * Iterate a list of projects into an array of Json objects @@ -992,7 +875,7 @@ private String returnProjectsJson(@NotNull List projects, int pro * * @return a Json string representing the project contents and the operation results message */ - private String buildConvertedResponse(ProjectRecord project) { + private String buildConvertedResponse(@NotNull ProjectRecord project) { /* Convert the project record to a Json object */ JsonObject result = projectConverter.toJson(project,false); @@ -1004,8 +887,27 @@ private String buildConvertedResponse(ProjectRecord project) { } + /** + * Update the fields in a ProjectRecord object + * + * @param project is a ProjectRecord object that will be updated with the date + * passed in the other parameters of this call. + * @param projectName is a string parameter containing the new project name + * @param description is a string parameter containing the project description + * @param descriptionHtml is a string containing the HTML representation of the + * project description + * @param code is a string containing the XML representation of the project's code blocks + * @param type is a ProjectType enumberation indicating the project's source language, + * currently either SPIN or PROPC + * @param board is a string indicating the type of board used for the project. + * @param settings is an optional parameter containing a Json encoded string of various + * custom project settings. + * + * @return an updated ProjectRecord object. + */ private ProjectRecord updateProjectRecordFields( - ProjectRecord project, String projectName, String description, String descriptionHtml, + @NotNull ProjectRecord project, + String projectName, String description, String descriptionHtml, String code, ProjectType type, String board, String settings) { if (projectName != null) { @@ -1037,7 +939,6 @@ private ProjectRecord updateProjectRecordFields( } return project; - } } From ed0b7f8245e1e9bdc2a4b37e0ce8c638e6f2b64c Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Mar 2019 20:40:53 -0800 Subject: [PATCH 59/69] Add URL path for version 2 project REST API interface. --- src/main/resources/shiro.ini | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/resources/shiro.ini b/src/main/resources/shiro.ini index 23a16291..05c06763 100644 --- a/src/main/resources/shiro.ini +++ b/src/main/resources/shiro.ini @@ -157,7 +157,17 @@ shiro.loginUrl = /login.jsp # REST api and api documentation /apidoc = anon /rest/shared/** = anon, ssl -/rest/** = authc, ssl + +# Version 2 of the Blockly API +# /rest/v2/** = authc, ssl +/rest/v2/** = anon, ssl + +# Version 1 of the Blockly API +#/rest/** = authc, ssl +/rest/** = anon, ssl + + + # Authorized projects /createproject = authc, noSessionCreation, ssl From ea55d89b5c6a88c487cfa87f9406c80967d657ae Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Mar 2019 21:20:15 -0800 Subject: [PATCH 60/69] Corrected error in v2 REST API access level. --- src/main/resources/shiro.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/shiro.ini b/src/main/resources/shiro.ini index 05c06763..dddfe00e 100644 --- a/src/main/resources/shiro.ini +++ b/src/main/resources/shiro.ini @@ -159,12 +159,12 @@ shiro.loginUrl = /login.jsp /rest/shared/** = anon, ssl # Version 2 of the Blockly API -# /rest/v2/** = authc, ssl -/rest/v2/** = anon, ssl +/rest/v2/** = authc, ssl +# /rest/v2/** = anon, ssl # Version 1 of the Blockly API -#/rest/** = authc, ssl -/rest/** = anon, ssl +/rest/** = authc, ssl +# /rest/** = anon, ssl From dff8dd30a3418b34b28859559b2715a952a27ca1 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 8 Mar 2019 13:12:03 -0800 Subject: [PATCH 61/69] Add code to evaluate User object returned from the Cloud Session server. --- .../servlets/PublicProfileServlet.java | 61 ++++++++++++++----- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/parallax/server/blocklyprop/servlets/PublicProfileServlet.java b/src/main/java/com/parallax/server/blocklyprop/servlets/PublicProfileServlet.java index f5a1e44e..09052bd5 100644 --- a/src/main/java/com/parallax/server/blocklyprop/servlets/PublicProfileServlet.java +++ b/src/main/java/com/parallax/server/blocklyprop/servlets/PublicProfileServlet.java @@ -1,8 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2019 Parallax Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + package com.parallax.server.blocklyprop.servlets; import com.google.common.base.Strings; @@ -12,7 +28,6 @@ import com.parallax.client.cloudsession.exceptions.EmailNotConfirmedException; import com.parallax.client.cloudsession.exceptions.ServerException; import com.parallax.client.cloudsession.exceptions.UnknownUserIdException; -import com.parallax.client.cloudsession.exceptions.EmailNotConfirmedException; import com.parallax.server.blocklyprop.db.generated.tables.pojos.User; import com.parallax.server.blocklyprop.services.UserService; import com.parallax.server.blocklyprop.services.impl.SecurityServiceImpl; @@ -50,7 +65,9 @@ public void setConfiguration(Configuration configuration) { } @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws + ServletException, IOException { + String idUserString = req.getParameter("id-user"); Long idUser = null; @@ -61,14 +78,14 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se if (SecurityUtils.getSubject().isAuthenticated()) { idUser = SecurityServiceImpl.getSessionData().getIdUser(); } else { - LOG.info("Getting current user while not authenticated"); + LOG.warn("Getting current user while not authenticated"); resp.sendError(404); } } else { idUser = Long.parseLong(idUserString); } } catch (NumberFormatException nfe) { - LOG.info("id-user is not a valid number: {}", idUserString); + LOG.warn("id-user is not a valid number: {}", idUserString); resp.sendError(500); } try { @@ -77,27 +94,39 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se if (user == null) { LOG.info("Get public profile for user {} (Does not exist!)", idUser); resp.sendError(404); - return; } - LOG.info("Get public profile for user {}: Cloud-session user: {}", idUser, user.getIdcloudsession()); - com.parallax.client.cloudsession.objects.User cloudSessionUser = cloudSessionUserService.getUser(user.getIdcloudsession()); + // It is possible to receive an empty, non-null object + if (cloudSessionUser == null) { + LOG.warn("User object is null"); + resp.sendError(404, "User profile is unavailable"); + } + if (cloudSessionUser.getScreenname() == null ) { + LOG.warn("Unable to decode result from Cloud Session call"); + resp.sendError(404, "User object is empty"); + } + req.setAttribute("screenname", cloudSessionUser.getScreenname()); req.getRequestDispatcher("/WEB-INF/servlet/public-profile.jsp").forward(req, resp); - } catch (EmailNotConfirmedException ex) { - LOG.info("User not known in cloud-session"); + } + catch (EmailNotConfirmedException ex) { + LOG.info("User email is unconfirmed cloud-session"); resp.sendError(404); - } catch (UnknownUserIdException ex) { + } + catch (UnknownUserIdException ex) { LOG.info("User not known in cloud-session"); resp.sendError(404); - } catch (ServerException ex) { + } + catch (NullPointerException ex) { + LOG.warn("Unexpected Null Pointer Exception encountered. Message is: {}", ex.getMessage()); + resp.sendError(404, "NPE error. User not found"); + } + catch (ServerException ex) { LOG.error("Communication problem with Cloud-session", ex); resp.sendError(500); } - } - } From 2e6705d27df0f7bd923cab81e012877de30406f2 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 8 Mar 2019 13:22:46 -0800 Subject: [PATCH 62/69] bump up version build number from 456 to 457. --- .../blocklyprop/internationalization/translations.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/com/parallax/server/blocklyprop/internationalization/translations.properties b/src/main/resources/com/parallax/server/blocklyprop/internationalization/translations.properties index 1cd5310a..f33fc16c 100644 --- a/src/main/resources/com/parallax/server/blocklyprop/internationalization/translations.properties +++ b/src/main/resources/com/parallax/server/blocklyprop/internationalization/translations.properties @@ -28,7 +28,7 @@ footer.clientdownloadlink = BlocklyProp-client # Application version numbers. application.major = 1 application.minor = 1 -application.build = 456 +application.build = 457 html.content_missing = Content missing From b556c6be886a7a734a60e8a2e39dce9dbbb6ec3c Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 24 Apr 2019 06:34:29 -0700 Subject: [PATCH 63/69] Include MIME type for javascript files. These are not required in HTML-5 but are necessary for current browsers using HTML 4.01. --- src/main/webapp/WEB-INF/servlet/index.jsp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/webapp/WEB-INF/servlet/index.jsp b/src/main/webapp/WEB-INF/servlet/index.jsp index 2973256f..ab06fed9 100644 --- a/src/main/webapp/WEB-INF/servlet/index.jsp +++ b/src/main/webapp/WEB-INF/servlet/index.jsp @@ -46,9 +46,9 @@ " /> " /> BlocklyProp - - - + + + <%@ include file="/WEB-INF/includes/pageparts/menu.jsp"%> @@ -78,7 +78,7 @@ <%@ include file="/WEB-INF/includes/pageparts/project-login-dialog.jsp"%> - + <%@ include file="/WEB-INF/includes/pageparts/footer.jsp"%> From e390dc16817a51b214b81326be0c70a2a678343b Mon Sep 17 00:00:00 2001 From: Matthew Matz <19737272+MatzElectronics@users.noreply.github.com> Date: Fri, 17 May 2019 21:45:08 -0700 Subject: [PATCH 64/69] CSS/Styling improvement Makes XML view consistent with other buttons - will match changes in CDN CSS --- src/main/webapp/editor/blocklyc.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/editor/blocklyc.jsp b/src/main/webapp/editor/blocklyc.jsp index 28c92282..4fd6004b 100644 --- a/src/main/webapp/editor/blocklyc.jsp +++ b/src/main/webapp/editor/blocklyc.jsp @@ -160,7 +160,7 @@ - +