diff --git a/.gitignore b/.gitignore
index 7c438576..bf6ce5ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,9 @@ nb*
# /src/main/java/com/parallax/server/blocklyprop/db/generated/*
#
+*.sh
+
+
#################
## NetBeans
#################
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;
+
diff --git a/pom.xml b/pom.xml
index 6fc0a3f6..2656d85a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -549,5 +549,12 @@
4.12test
-
+
+
+
+ org.jetbrains
+ annotations
+ 17.0.0
+
+
\ No newline at end of file
diff --git a/src/main/java/com/parallax/server/blocklyprop/SessionData.java b/src/main/java/com/parallax/server/blocklyprop/SessionData.java
index 978b0894..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,15 +29,21 @@
* 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
*/
@SessionScoped
public class SessionData implements Serializable {
+ // A cloud session user profile object
private User user;
+
+ // A cloud session user profile primary key ID
private Long idUser;
+
+ // A locale string for this user
private String locale;
/**
@@ -54,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 + '}';
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;
}
-
}
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/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..fd830cdb 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
@@ -26,12 +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 com.parallax.server.blocklyprop.utils.HelpFileInitializer;
+
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,8 +40,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-// import java.sql.SQLException;
-// import ch.qos.logback.classic.LoggerContext;
/**
@@ -50,16 +48,18 @@
*/
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();
@@ -68,12 +68,12 @@ 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(HelpFileInitializer.class).asEagerSingleton();
bind(Monitor.class).asEagerSingleton();
// Configure the backend data store
@@ -85,7 +85,6 @@ protected void configure() {
install(new ServletsModule());
install(new RestModule());
}
-
});
}
@@ -123,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();
}
-*/
}
}
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;
+ }
}
diff --git a/src/main/java/com/parallax/server/blocklyprop/converter/UserConverter.java b/src/main/java/com/parallax/server/blocklyprop/converter/UserConverter.java
index 48a5f892..ad43b9de 100644
--- a/src/main/java/com/parallax/server/blocklyprop/converter/UserConverter.java
+++ b/src/main/java/com/parallax/server/blocklyprop/converter/UserConverter.java
@@ -10,6 +10,7 @@
import com.parallax.server.blocklyprop.db.generated.tables.records.UserRecord;
/**
+ * Convert user object to a Json string
*
* @author Michel
*/
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..383c9f2b 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
@@ -13,108 +13,133 @@
/**
* Interface for the blocklyprop.project table
- *
+ *
* @author Michel
- *
+ *
* Fields:
* id: Unique record number within the table.
- *
+ *
* id_user: Link to primary key in the blocklyprop.user table.
- *
+ *
* id_clouduser: Link to the primary key in the cloudsession.user table.
- *
+ *
* name: Project name
- *
+ *
* description: Project description formatted in plain text
- *
+ *
* description_html:Project description formatted in HTML
- *
+ *
* code: XML content that hold the project block structure.
- *
- * type: Project source language (SPIN or PROPC)
- *
+ *
+ * type: Project source language (SPIN or PROPC)
+ *
* 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
* 'shared' flag.
- *
+ *
* shared: Flag to indicate if the project is available for viewing by
* anyone.This flag is mutually exclusive with the 'private'
* flag.
- *
+ *
* created: Timestamp indicating when the project record was created.
- *
+ *
* modified: Timestamp indicating when the project record was last changed.
- *
+ *
* based_on: The id from the project that is the parent of the current
* project record.
- *
+ *
*/
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,
+ 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,
+ String descriptionHtml,
+ String code,
+ ProjectType type,
+ String board,
+ boolean privateProject,
boolean sharedProject,
Long idProjectBasedOn);
ProjectRecord createProject(
- String name,
- String description,
- String descriptionHtml,
- ProjectType type,
- String board,
- boolean privateProject,
+ String name,
+ String description,
+ String descriptionHtml,
+ ProjectType type,
+ String board,
+ boolean privateProject,
boolean sharedProject);
ProjectRecord updateProject(
- Long idProject,
- String name,
- String description,
- String descriptionHtml,
- boolean privateProject,
+ Long idProject,
+ String name,
+ String description,
+ String descriptionHtml,
+ boolean privateProject,
boolean sharedProject);
ProjectRecord updateProject(
- Long idProject,
- String name,
- String description,
- String descriptionHtml,
- String code,
- boolean privateProject,
+ Long idProject,
+ String name,
+ String description,
+ String descriptionHtml,
+ String code,
+ 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,
+ Long idProject,
String code);
List getUserProjects(
- Long idUser,
- TableSort sort,
- TableOrder order,
- Integer limit,
+ Long idUser,
+ TableSort sort,
+ TableOrder order,
+ Integer limit,
Integer offset);
// Return a list of community projects
List getSharedProjects(
- TableSort sort,
- TableOrder order,
- Integer limit,
+ TableSort sort,
+ TableOrder order,
+ Integer limit,
Integer offset);
List getSharedProjectsByUser(
- TableSort sort,
- TableOrder order,
- Integer limit,
- Integer offset,
+ TableSort sort,
+ TableOrder order,
+ Integer limit,
+ Integer offset,
Long idUser);
int countUserProjects(Long idUser);
@@ -128,13 +153,13 @@ List getSharedProjectsByUser(
boolean deleteProject(Long idProject);
ProjectRecord updateProjectCode(
- Long idProject,
+ Long idProject,
String code);
ProjectRecord saveProjectCodeAs(
- Long idProject,
- String code,
+ Long idProject,
+ String code,
String newName,
String newBoard);
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectSharingDao.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectSharingDao.java
index b613d88d..4d74e827 100644
--- a/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectSharingDao.java
+++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/ProjectSharingDao.java
@@ -5,6 +5,7 @@
*/
package com.parallax.server.blocklyprop.db.dao;
+import com.parallax.server.blocklyprop.db.generated.tables.ProjectSharing;
import com.parallax.server.blocklyprop.db.generated.tables.records.ProjectSharingRecord;
import java.util.List;
@@ -14,17 +15,64 @@
*/
public interface ProjectSharingDao {
+ /**
+ * Retrieve a project
+ * @param idProject
+ * @param accessKey
+ * @return
+ */
ProjectSharingRecord getProject(Long idProject, String accessKey);
+
+ /**
+ * Share an existing project
+ * @param idProject
+ * @param shareKey
+ * @return
+ */
ProjectSharingRecord shareProject(Long idProject, String shareKey);
+
+ /**
+ * Disable the shared link to a project
+ * @param idProject
+ * @return
+ */
int revokeSharing(Long idProject);
- public List getSharingInfo(Long idProject);
+
+ /**
+ * Get a project sharing record
+ * @param idProject
+ * @return
+ */
+ List getSharingInfo(Long idProject);
// Set the active flag in an existing shared project record
- public ProjectSharingRecord activateProject(Long idProject);
+
+ /**
+ * Enable the project sharing link
+ *
+ * @param idProject
+ * @return
+ */
+ ProjectSharingRecord activateProject(Long idProject);
// Remove a project sharing link record
- public boolean deleteProjectSharingRecord(Long idProject);
+
+ /**
+ * Delete a project sharing record
+ *
+ * @param idProject
+ * @return
+ */
+ boolean deleteProjectSharingRecord(Long idProject);
+
+
+ /**
+ * Is the project sharing feature enabled for a project
+ * @param idProject
+ * @return
+ */
+ boolean isProjectSharingActive(Long idProject);
}
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/ProjectDaoImpl.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectDaoImpl.java
index 2dbbaf5f..ce20502b 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
@@ -1,7 +1,22 @@
/*
- * 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;
@@ -13,10 +28,14 @@
import com.parallax.server.blocklyprop.db.generated.tables.records.ProjectRecord;
import com.parallax.server.blocklyprop.security.BlocklyPropSecurityUtils;
+//import com.parallax.server.blocklyprop.services.impl.ProjectSharingServiceImpl;
+import com.parallax.server.blocklyprop.services.ProjectSharingService;
+
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.GregorianCalendar;
import java.util.List;
+
import org.apache.shiro.authz.UnauthorizedException;
import org.jooq.Condition;
import org.jooq.DSLContext;
@@ -28,7 +47,7 @@
/**
* DAO interface to the blocklyprop.project table.
- *
+ *
* @author Michel
*
*/
@@ -41,38 +60,46 @@ public class ProjectDaoImpl implements ProjectDao {
*/
private static final int Min_BlocklyCodeSize = 48;
-
+
/**
* Application logging facility
*/
private static final Logger LOG = LoggerFactory.getLogger(ProjectDao.class);
-
+
/**
* 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;
-
+ 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;
-
-
+
+
@Inject
public void setDSLContext(DSLContext dsl) {
this.create = dsl;
}
-
+
+ private ProjectSharingService projectSharingService;
+
+ @Inject
+ public void setProjectSharingContext(ProjectSharingService projectSharingService) {
+ this.projectSharingService = projectSharingService;
+ }
+
+
/**
*
* Retrieve a new project record based from an existing project.
@@ -86,12 +113,12 @@ public void setDSLContext(DSLContext dsl) {
*
* @param idProject The id for the project that will be the source for the
* new project record
- *
+ *
* @return ProjectRecord object containing a copy of the original project
*/
@Override
public ProjectRecord getProject(Long idProject) {
- LOG.info("Retreiving data for project #{}", idProject);
+ LOG.info("Retrieving data for project #{}", idProject);
ProjectRecord record = null;
@@ -105,21 +132,114 @@ record = create
LOG.warn("Unable to retreive project {}", idProject);
return null;
}
-
+
} 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("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;
+
+ }
+
+
+
+
+
/**
*
* Create a new project with supplied code.
@@ -134,23 +254,24 @@ record = create
* @param sharedProject Flag to indicate if the project is a community project
* @param idProjectBasedOn Parent project id if the new project is cloned
* from another project
- *
+ *
* @return a fully formed ProjectRecord or a null if an error is detected.
*/
+ @Deprecated
@Override
- public ProjectRecord createProject(
+ public ProjectRecord createProject(
String name,
String description,
String descriptionHtml,
String code,
- ProjectType type,
- String board,
+ ProjectType type,
+ String board,
boolean privateProject,
boolean sharedProject,
Long idProjectBasedOn) {
LOG.info("Creating a new project with existing code.");
-
+
ProjectRecord record = null;
// Get the logged in user's userID
@@ -159,43 +280,43 @@ public ProjectRecord createProject(
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();
+ .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());
@@ -205,7 +326,7 @@ record = create
LOG.error("Error Message: {}", ex.getMessage());
return null;
}
-
+
return record;
}
@@ -236,16 +357,16 @@ public ProjectRecord createProject(
boolean sharedProject) {
LOG.info("Creating a new, empty project from existing project.");
-
+
// TODO: Add based_on field to end of argument list
- return createProject(
- name,
- description,
- descriptionHtml,
- "",
- type,
- board,
- privateProject,
+ return createProject(
+ name,
+ description,
+ descriptionHtml,
+ "",
+ type,
+ board,
+ privateProject,
sharedProject,
null);
}
@@ -341,6 +462,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
*
@@ -358,18 +518,18 @@ public ProjectRecord saveCode(Long idProject, String code) {
record.setCode(code);
record.setCodeBlockVersion(BLOCKLY_LIBRARY_VERSION);
record.setModified(getCurrentTimestamp());
-
+
// Update the record
ProjectRecord returningRecord = create
.update(Tables.PROJECT)
.set(record)
.returning()
.fetchOne();
-
+
// Return a copy of the updated project record
return returningRecord;
}
-
+
LOG.error("Unable to save code for project {}", idProject);
return null;
}
@@ -386,22 +546,29 @@ public ProjectRecord saveCode(Long idProject, String code) {
*/
@Override
public List getUserProjects(
- Long idUser,
- TableSort sort,
- TableOrder order,
- Integer limit,
+ Long idUser,
+ TableSort sort,
+ TableOrder order,
+ Integer limit,
Integer offset) {
-
- LOG.info("Retreive projects for user {}.", idUser);
- SortField orderField = Tables.PROJECT.NAME.asc();
+ LOG.info("Retrieve projects for user {}.", idUser);
+
+ 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();
}
@@ -412,17 +579,18 @@ public List getUserProjects(
* @param order
* @param limit
* @param offset
- * @param idUser
* @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,
+ TableSort sort,
+ TableOrder order,
+ 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) {
@@ -431,7 +599,7 @@ public List getSharedProjects(
// 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)
@@ -450,12 +618,12 @@ public List getSharedProjects(
*/
@Override
public List getSharedProjectsByUser(
- TableSort sort,
- TableOrder order,
- Integer limit,
- Integer offset,
+ 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();
@@ -480,9 +648,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;
}
/**
@@ -497,7 +667,7 @@ public int countSharedProjects(Long idUser) {
LOG.info("Count shared projects for user {}.", idUser);
Condition conditions = Tables.PROJECT.SHARED.equal(Boolean.TRUE);
-
+
// Shared projects are really community projects. We should not include
// the logged in user's projects in the community listing. There is a
// separate listing available for the logged in user's private projects.
@@ -538,10 +708,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);
}
@@ -552,7 +725,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) {
@@ -563,24 +736,49 @@ public boolean deleteProject(Long idProject) {
}
/**
- * TODO: add details.
+ * 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);
@@ -588,14 +786,18 @@ public ProjectRecord updateProjectCode(Long idProject, String code) {
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();
}
@@ -605,6 +807,8 @@ public ProjectRecord updateProjectCode(Long idProject, String code) {
}
}
+
+
/**
* Save the current project as a new project
*
@@ -616,29 +820,40 @@ public ProjectRecord updateProjectCode(Long idProject, String code) {
*/
@Override
public ProjectRecord saveProjectCodeAs(Long idProject, String code, String newName, String newBoard) {
-
- LOG.info("Saving project code as '{}'", newName);
+
+ LOG.info("Saving project code from project {} as '{}'", idProject, newName);
// Retreive the source project
ProjectRecord original = getProject(idProject);
+
if (original == null) {
LOG.error("Original project {} is missing. Unable to save code as...", idProject);
throw new NullPointerException("Project doesn't exist");
- } else if (newBoard == null) {
+ }
+
+ // Use the board type from the parent project if it was not provided
+ if (newBoard == null) {
newBoard = original.getBoard();
}
-
- // Obtain the current bp user record.
+
+
+ // Obtain the current bp user record.
Long idUser = BlocklyPropSecurityUtils.getCurrentUserId();
-
+
if (idUser != null) {
// Create a copy of the source project is the source project is owned
// by the current user OR if the source project is designated as a
// shared or community project
// --------------------------------------------------------------------
- if (original.getIdUser().equals(idUser) || original.getShared()) {
+ boolean sharedStatus = projectSharingService.isProjectShared(idProject);
+ LOG.info("Project shared status: {}", sharedStatus);
+
+ if (original.getIdUser().equals(idUser) || // Project is owned by currently logged in user
+ sharedStatus || // Project is shared
+ (!original.getPrivate())) { // Project is public
+
ProjectRecord cloned = createProject(
- newName,
+ newName,
original.getDescription(),
original.getDescriptionHtml(),
code,
@@ -648,7 +863,13 @@ public ProjectRecord saveProjectCodeAs(Long idProject, String code, String newNa
false, // Set project unshared
original.getId());
+ if (cloned == null) {
+ LOG.warn("Unable to create a copy of the project.");
+ }
return cloned;
+ } else {
+ LOG.warn("Unable to copy the project. UID: {}, PUID: {}, Shared: {}",
+ idUser, original.getIdUser(), original.getShared());
}
} else {
LOG.info("Unable to retreive BP user id");
@@ -658,7 +879,7 @@ public ProjectRecord saveProjectCodeAs(Long idProject, String code, String newNa
// Private over-ride of the public getProject()
//
- //
+ //
private ProjectRecord getProject(Long idProject, boolean toEdit) {
LOG.info("Retreiving project {}.", idProject);
ProjectRecord record = create
@@ -689,7 +910,7 @@ private ProjectRecord getProject(Long idProject, boolean toEdit) {
}
private ProjectRecord doProjectClone(ProjectRecord original) {
-
+
// TODO: Add based_on parameter as last argument
ProjectRecord cloned = createProject(
original.getName(),
@@ -700,34 +921,35 @@ private ProjectRecord doProjectClone(ProjectRecord original) {
original.getBoard(),
original.getPrivate(),
original.getShared(),
- original.getId()
+ 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()));
-
+
return cloned;
}
-
+
// Produce a current timestamp
private GregorianCalendar getCurrentTimestamp() {
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(new java.util.Date());
-
+
return cal;
}
-
-
-
-
+
+
+
+
// Evaluate project code and replace any deprecated or updated blocks
//
// Return a ProjectRecord object. The code field may be altered to correct
@@ -736,7 +958,7 @@ private GregorianCalendar getCurrentTimestamp() {
// horribly wrong with the string conversions.
//
private ProjectRecord alterReadRecord(ProjectRecord record) {
-
+
String currentCode, newCode;
@@ -750,11 +972,11 @@ private ProjectRecord alterReadRecord(ProjectRecord record) {
LOG.info("Bypassing project block evaluation");
return record;
}
-
+
LOG.info("Verify project {} block version {} characteristics",
record.getId(),
record.getCodeBlockVersion());
-
+
currentCode = record.getCode();
// Return immediately if there is no code to adjust
@@ -762,7 +984,7 @@ private ProjectRecord alterReadRecord(ProjectRecord record) {
LOG.warn("Project () code block is empty.", record.getId());
return record;
}
-
+
if (currentCode.length() < Min_BlocklyCodeSize ) {
LOG.warn("Project code appears to be empty. Code size:{}",currentCode.length());
return record;
@@ -787,7 +1009,7 @@ private ProjectRecord alterReadRecord(ProjectRecord record) {
return record;
}
- /**
+ /**
*
* Create a random string to use as a blockID.
*
@@ -829,20 +1051,20 @@ private String fixSpinProjectBlocks(String newCode) {
newCode = newCode.replaceAll(
"block type=\"math_number\"",
"block type=\"spin_integer\"");
-
+
return newCode;
}
/**
* Find and replace deprecated project code blocks
- *
+ *
* @param originalCode is the project code that will be evaluated.
* @param projType
- * @return
+ * @return
*/
private String fixPropcProjectBlocks(String originalCode, ProjectType projType) {
LOG.info("Looking for depricated PropC blocks.");
-
+
// Copy the original project code into a working variable
String newCode = originalCode;
@@ -929,32 +1151,32 @@ private String fixPropcProjectBlocks(String originalCode, ProjectType projType)
newCode = newCode.replaceAll(
"block type=\"logic_boolean_negate\"",
"block type=\"logic_negate\"");
-
+
newCode = newCode.replaceAll( "_000 / ", "000 / ");
// Fix a small issue with calling the wrong project type.
newCode = newCode.replaceAll(
"block type=\"spin_integer\"",
"block type=\"math_number\"");
-
+
if (!newCode.contains("block type=\"math_number\"") && projType == ProjectType.SPIN) {
// Change all math number blocks to the same kind
newCode = newCode.replaceAll(
"block type=\"math_int_angle\"",
"block type=\"math_number\"");
-
+
newCode = newCode.replaceAll(
"block type=\"math_integer\"",
"block type=\"math_number\"");
-
+
newCode = newCode.replaceAll(
"block type=\"scribbler_random_number\"",
"block type=\"math_random\"");
-
+
newCode = newCode.replaceAll(
"field name=\"INT_VALUE\"",
"field name=\"NUM\"");
-
+
newCode = newCode.replaceAll(
"field name=\"ANGLE_VALUE\"",
"field name=\"NUM\"");
@@ -962,70 +1184,70 @@ private String fixPropcProjectBlocks(String originalCode, ProjectType projType)
newCode = newCode.replaceAll(
"block type=\"digital_input\"",
"block type=\"check_pin\"");
-
+
newCode = newCode.replaceAll(
"block type=\"digital_output\"",
"block type=\"make_pin\"");
-
+
newCode = newCode.replaceAll(
"block type=\"scribbler_servo\"",
"block type=\"servo_move\"");
-
+
newCode = newCode.replaceAll(
"field name=\"SERVO_PIN\"",
"field name=\"PIN\"");
-
+
newCode = newCode.replaceAll(
"field name=\"SERVO_ANGLE\"",
"field name=\"ANGLE\"");
-
+
newCode = newCode.replaceAll(
"1000<",
"field name=\"TIMESCALE\">Z1<");
-
+
newCode = newCode.replaceAll(
"field name=\"TIMESCALE\">1<",
"field name=\"TIMESCALE\">Z1000<");
-
+
newCode = newCode.replaceAll(
"field name=\"TIMESCALE\">10<",
"field name=\"TIMESCALE\">100<");
-
+
newCode = newCode.replaceAll(
"field name=\"TIMESCALE\">Z",
"field name=\"TIMESCALE\">");
-
+
newCode = newCode.replaceAll("Scribbler#CS","256");
newCode = newCode.replaceAll("Scribbler#NL","10");
newCode = newCode.replaceAll("Scribbler#LF","13");
newCode = newCode.replaceAll("Scribbler#BS","127");
-
+
newCode = newCode.replaceAll(
"block type=\"scribbler_loop\"",
"block type=\"controls_repeat\"");
-
+
newCode = newCode.replaceAll(
"statement name=\"LOOP\"",
"statement name=\"DO\"");
-
+
newCode = newCode.replaceAll(
"(.*)",
- "TIMES$2");
+ "TIMES$2");
}
-
+
// Replace the Robot init block with two blocks, need to generate unique 20-digit blockID:
if (!newCode.contains("block type=\"ab_drive_ramping\"")) {
newCode = newCode.replaceAll(
- "",
+ "",
"");
}
-
+
return newCode;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectSharingDaoImpl.java b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectSharingDaoImpl.java
index 4a0804a7..b962e71c 100644
--- a/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectSharingDaoImpl.java
+++ b/src/main/java/com/parallax/server/blocklyprop/db/dao/impl/ProjectSharingDaoImpl.java
@@ -7,9 +7,11 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
+
import com.parallax.server.blocklyprop.db.dao.ProjectSharingDao;
import com.parallax.server.blocklyprop.db.generated.Tables;
import com.parallax.server.blocklyprop.db.generated.tables.records.ProjectSharingRecord;
+
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -61,7 +63,8 @@ public ProjectSharingRecord getProject(Long idProject, String accessKey) {
/**
- *
+ * Create a project sharing record
+ *
* @param idProject
* @param shareKey
* @return
@@ -178,4 +181,32 @@ public boolean deleteProjectSharingRecord(Long idProject) {
}
+
+ /**
+ * Determine the on/off state of the project's shared link URL
+ *
+ * @param idProject
+ * @return
+ */
+ @Override
+ public boolean isProjectSharingActive(Long idProject) {
+
+ LOG.info("Retrieving sharing record for project {}", idProject);
+
+ ProjectSharingRecord project = create
+ .selectFrom(Tables.PROJECT_SHARING)
+ .where((Tables.PROJECT_SHARING.ID_PROJECT
+ .equal(idProject)))
+ .fetchOne();
+
+ if (project == null) {
+ LOG.info("The sharing record for project {} was not found", idProject);
+ // Record not found
+ return false;
+ }
+
+ LOG.info("Project {} sharing is {}", idProject, project.getActive());
+ return project.getActive();
+ }
+
}
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..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
@@ -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 @@
/**
+ * This is the session database layer.
*
* @author Michel
*/
@@ -32,37 +49,43 @@ 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.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);
@@ -91,47 +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");
- SessionRecord sessionRecord = null;
-
+ LOG.debug("Getting session {} details", idSession);
+
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());
- }
- finally {
- return sessionRecord;
+ throw new NullPointerException("Session not found");
}
}
/**
- *
- * @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());
@@ -148,30 +177,44 @@ 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).where(Tables.SESSION.IDSESSION.eq(idSession)).execute();
+
+ create.deleteFrom(Tables.SESSION)
+ .where(Tables.SESSION.IDSESSION.eq(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 {
@@ -183,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());
}
}
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..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
*/
@@ -125,18 +142,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 +173,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 +197,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 +246,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 +264,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 +282,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);
+
+ create.update(Tables.USER)
+ .set(Tables.USER.SCREENNAME, screenName)
+ .where(Tables.USER.ID.eq(idUser))
+ .execute();
- user.setScreenname(screenname);
- user.update();
+ LOG.info("The screen name is now {}", user.getScreenname());
}
}
+ else {
+ LOG.warn("Unable to locate a blockly user record for blockly id {}", idUser);
+ }
}
}
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);
}
}
diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestCompile.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestCompile.java
index 8a3c3ec5..ac04be78 100644
--- a/src/main/java/com/parallax/server/blocklyprop/rest/RestCompile.java
+++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestCompile.java
@@ -18,28 +18,18 @@
import com.parallax.client.cloudcompiler.objects.CompilationResult;
import com.parallax.client.cloudcompiler.objects.CompileAction;
import com.parallax.client.cloudsession.CloudSessionBucketService;
-import com.parallax.client.cloudsession.exceptions.EmailNotConfirmedException;
-import com.parallax.client.cloudsession.exceptions.InsufficientBucketTokensException;
-import com.parallax.client.cloudsession.exceptions.ServerException;
-import com.parallax.client.cloudsession.exceptions.UnknownBucketTypeException;
-import com.parallax.client.cloudsession.exceptions.UnknownUserIdException;
-import com.parallax.client.cloudsession.exceptions.UserBlockedException;
+import com.parallax.client.cloudsession.exceptions.*;
import com.parallax.server.blocklyprop.rest.typewrappers.CompileActionTypeWrapper;
import com.parallax.server.blocklyprop.services.impl.SecurityServiceImpl;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.FormParam;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
import org.apache.commons.configuration.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
/**
*
* @author Michel
@@ -72,12 +62,22 @@ public void setConfiguration(Configuration configuration) {
@Name("compile")
@Produces("text/plain")
public Response get(@QueryParam("test") String testString) {
-
LOG.info("REST:/rest/compile/ Get request received");
-
return Response.ok("Hello " + testString).build();
}
+/*
+ @GET
+ @Detail("Get Simple Libraries version")
+ @Name("version")
+ @Produces("text/plain")
+ public Response version() {
+ LOG.info("REST:/rest/compile/ Get request received");
+ return Response.ok("v0.0.0").build();
+ }
+*/
+
+
@POST
@Path("/spin/{action}")
@Detail("Spin compile")
diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestProfile.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestProfile.java
index aa065c82..c85ce814 100644
--- a/src/main/java/com/parallax/server/blocklyprop/rest/RestProfile.java
+++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestProfile.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.rest;
import com.cuubez.visualizer.annotation.Detail;
@@ -23,6 +39,9 @@
import com.parallax.client.cloudsession.exceptions.WrongAuthenticationSourceException;
import com.parallax.client.cloudsession.exceptions.EmailNotConfirmedException;
import com.parallax.client.cloudsession.objects.User;
+
+import com.parallax.server.blocklyprop.services.UserService;
+
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@@ -46,6 +65,7 @@ public class RestProfile {
private CloudSessionLocalUserService cloudSessionLocalUserService;
private CloudSessionUserService cloudSessionUserService;
private Configuration configuration;
+ private UserService userService;
@Inject
public void setConfiguration(Configuration configuration) {
@@ -54,6 +74,15 @@ public void setConfiguration(Configuration configuration) {
cloudSessionUserService = new CloudSessionUserService(configuration.getString("cloudsession.baseurl"));
}
+ /**
+ * Inject a user service object so we can update the screen name
+ * @param userService
+ */
+ @Inject
+ public void setUserService(UserService userService) {
+ this.userService = userService;
+ }
+
@POST
@Path("/base")
@Detail("Save base profile data")
@@ -68,14 +97,29 @@ public Response saveBase(
LOG.info("REST:/rest/profile/base/ Post request received");
JsonObject result = new JsonObject();
+
if (Strings.isNullOrEmpty(screenname)) {
result.addProperty("success", false);
- result.addProperty("message", "fields-missing");
+ result.addProperty("message", "screen-name--missing");
return Response.ok(result.toString()).build();
} else {
try {
+ // Contact the cloud session server to update the user profile
User user = cloudSessionUserService.changeUserInfo(id, screenname);
if (user != null) {
+ // The update was successful, update the screen name in the
+ // blocklyprop user table
+ // TODO: Update the screen name field in blocklyprop.user table
+
+ Long idUser = userService.getIdUser(user.getId());
+ if (idUser > 0) {
+ userService.setScreenName(idUser, screenname);
+ LOG.info("Screen name for {} has been changed to {}", username, screenname);
+ }
+ else {
+ LOG.warn("Unable to locate blockly user record for cloudSession id {}", user.getId());
+ }
+
result.addProperty("success", true);
result.addProperty("screenname", user.getScreenname());
return Response.ok(result.toString()).build();
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 e0c73742..2cb71f1d 100644
--- a/src/main/java/com/parallax/server/blocklyprop/rest/RestProject.java
+++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestProject.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.rest;
import com.cuubez.visualizer.annotation.Detail;
@@ -11,16 +27,21 @@
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;
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 java.util.Arrays;
import java.util.List;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
@@ -30,7 +51,10 @@
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
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;
@@ -39,52 +63,89 @@
* 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")
@Group(name = "/project", title = "Project management")
@HttpCode("500>Internal Server Error,200>Success Response")
public class RestProject {
+
// 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
+
+ //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
+ *
+ * @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
+ *
+ * @param projectConverter
+ * An instance of the ProjectConverter object
*/
@Inject
public void setProjectConverter(ProjectConverter projectConverter) {
this.projectConverter = projectConverter;
}
+
/**
* 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
- *
- * @return JSON formatted list of project details
+ * 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
*/
@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(
@@ -92,56 +153,89 @@ public Response get(
@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) {
-
- LOG.info("REST:/rest/project/list/ Get request received");
-
+
+ String endPoint = "REST:/rest/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();
-
- if (idUser == 0) {
+
+ // 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 == null || idUser == 0) {
// Current session is not logged in.
- return Response.status(Response.Status.NOT_FOUND).build();
+ return Response.status(Response.Status.UNAUTHORIZED).build();
}
//Sanity checks - is the request reasonable
- if (sort == null)
- sort = TableSort.modified;
-
- if (order == null)
+
+ // 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;
-
- if (limit == null)
- limit = 20;
-
- if (offset == null)
+ }
+
+ // 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;
+ }
- List userProjects =
- projectService.getUserProjects(idUser, sort, order, limit, offset);
-
- int projectCount = projectService.countUserProjects(idUser);
+ // Obtain a list of the user's projects
+ List userProjects = projectService.getUserProjects(idUser, sort, order, limit, offset);
- JsonObject result = new JsonObject();
- JsonArray jsonProjects = new JsonArray();
- for (ProjectRecord project : userProjects) {
- jsonProjects.add(projectConverter.toListJson(project));
+ // Tell the caller that there is nothing to see here
+ if (userProjects == null) {
+ return Response.status(Response.Status.NOT_FOUND).build();
}
- result.add("rows", jsonProjects);
- result.addProperty("total", projectCount);
+ LOG.info("Returning {} projects for user {}", userProjects.size(), idUser);
- return Response.ok(result.toString()).build();
+ 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());
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
+ */
@GET
@Path("/get/{id}")
@Detail("Get project by id")
@@ -177,14 +271,18 @@ public Response get(@PathParam("id") @ParameterDetail("Project identifier") Long
}
}
+
/**
* Update the code in an existing project.
*
* This assumes that the project already exists.
*
- * @param idProject
- * @param code
- * @return
+ * @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")
@@ -198,15 +296,18 @@ public Response saveProjectCode(
LOG.info("REST:/rest/project/code/ POST request received for project '{}'", idProject);
try {
- ProjectRecord savedProject = projectService.saveProjectCode(idProject, code);
- LOG.debug("Code for project {} has been saved", idProject);
- JsonObject result = projectConverter.toJson(savedProject,false);
- LOG.debug("Returning JSON: {}", result);
-
- result.addProperty("success", true);
+ /* WARNING:
+ * =================================================================================
+ * 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);
- return Response.ok(result.toString()).build();
+ LOG.debug("Code for project {} has been saved", idProject);
+ return Response.ok(buildConvertedResponse(savedProject)).build();
} catch (AuthorizationException ae) {
LOG.warn("Project code not saved. Not Authorized");
return Response.status(Response.Status.UNAUTHORIZED).build();
@@ -217,6 +318,19 @@ 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
+ *
+ * @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")
@@ -238,26 +352,44 @@ 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();
- } catch (AuthorizationException ae) {
+*/
+ 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();
}
}
+
+ /**
+ * 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")
@Name("Save project")
@Produces("application/json")
@@ -308,4 +440,58 @@ public Response saveProject(
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();
+
+ 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);
+ }
+
+ 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();
+ }
+
}
diff --git a/src/main/java/com/parallax/server/blocklyprop/rest/RestSharedProject.java b/src/main/java/com/parallax/server/blocklyprop/rest/RestSharedProject.java
index e053fa9f..c757d8cd 100644
--- a/src/main/java/com/parallax/server/blocklyprop/rest/RestSharedProject.java
+++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestSharedProject.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
@@ -16,7 +16,7 @@
* 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.
+ * SOFTWARE.
*/
package com.parallax.server.blocklyprop.rest;
@@ -28,11 +28,14 @@
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;
import com.parallax.server.blocklyprop.converter.ProjectConverter;
import com.parallax.server.blocklyprop.db.generated.tables.records.ProjectRecord;
import com.parallax.server.blocklyprop.services.ProjectService;
+
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
@@ -42,35 +45,74 @@
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
+import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
+ * Manage requests for public projects
*
* @author Michel
+ *
+ * NOTE:
+ * The concept of 'shared' projects has changed over time. A project
+ * can be private or public. A project can also be associated with a
+ * specific project sharing URL, regardless of its public/private status.
*/
@Path("/shared/project")
@Group(name = "/shared/project", title = "Project management")
@HttpCode("500>Internal Server Error,200>Success Response")
public class RestSharedProject {
+ /**
+ * Get a connection to the logging system
+ */
private static final Logger LOG = LoggerFactory.getLogger(RestSharedProject.class);
+
+ /**
+ * Get a handle to project services
+ */
private ProjectService projectService;
+
+ /**
+ * Get a handle to a project converter
+ */
private ProjectConverter projectConverter;
+
+ /**
+ * Limit the number of records that can be returned in list functions
+ */
+ final int REQUEST_LIMIT = 100;
+
+
+ /**
+ * Inject project services
+ *
+ * @param projectService
+ * An instance of the ProjectService object
+ */
@Inject
public void setProjectService(ProjectService projectService) {
this.projectService = projectService;
}
+
+ /**
+ * Inject project conversion services
+ * @param projectConverter
+ *
+ * An instance of the ProjectConverter object
+ */
@Inject
public void setProjectConverter(ProjectConverter projectConverter) {
this.projectConverter = projectConverter;
}
+
/**
* Return a list of community projects.
*
@@ -101,76 +143,68 @@ public Response get(
@QueryParam("limit") Integer limit,
@QueryParam("offset") Integer offset) {
- LOG.info("REST:/shared/project/list/ endpoint activated");
- LOG.debug("REST:/shared/project/list/ Sort parameter is '{}'", sort);
- LOG.debug("REST:/shared/project/list/ Sort parameter is '{}'", sort);
- boolean parametersValid = false;
-
+ String endPoint = "REST:/shared/project/list/";
+ LOG.info("{} endpoint activated", endPoint);
+
// Sort flag evaluation
- if (sort != null) {
- for (TableSort t : TableSort.values()) {
- LOG.debug("REST:/shared/project/list/ Sort test for '{}'", t);
-
- if (sort == t) {
- parametersValid = true;
- break;
- }
- }
-
- if (parametersValid == false) {
- LOG.warn("REST:/shared/project/list/ Sort parameter failed");
- return Response.status(Response.Status.NOT_ACCEPTABLE).build();
- }
+ if (!RestProjectUtils.ValidateSortType(sort)) {
+ LOG.warn("{} Sort parameter failed. Defaulting to sort by project name", endPoint);
+ sort = TableSort.name;
}
// Sort order evaluation
- if (order != null) {
- parametersValid = false;
- LOG.debug("REST:/shared/project/list/ Checking order");
-
- for (TableOrder t : TableOrder.values()) {
- if (order == t) {
- parametersValid = true;
- break;
- }
- }
-
- if (parametersValid == false) {
- return Response.status(Response.Status.NOT_ACCEPTABLE).build();
- }
+ 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 > 50)) {
- LOG.info("REST:/shared/project/list/ Limit throttle to 50 entries");
- limit = 50;
+ if ( (limit == null) || (limit > REQUEST_LIMIT)) {
+ LOG.info("{} Limit throttle to {} entries", endPoint, REQUEST_LIMIT);
+ limit = REQUEST_LIMIT;
}
// Check ofset from the beginning of the record set
if ((offset == null) || (offset < 0)) {
offset = 0;
}
-
- List projects
- = projectService.getSharedProjects(sort, order, limit, offset);
-
- // Obtain a count of the total number of community projects available
- int projectCount = projectService.countSharedProjects();
- JsonObject result = new JsonObject();
- JsonArray jsonProjects = new JsonArray();
-
- for (ProjectRecord project : projects) {
- jsonProjects.add(projectConverter.toListJson(project));
- }
+ // Get a block of projects
+ List projects = projectService.getSharedProjects(sort, order, limit, offset);
- result.add("rows", jsonProjects);
- result.addProperty("total", projectCount);
+ // Tell the caller that there is nothing to see here
+ if (projects == null) {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
- return Response.ok(result.toString()).build();
+ return Response.ok(
+ returnProjectsJson(
+ projects,
+ projectService.countSharedProjects()))
+ .build();
}
+
+ /**
+ * Get a list of projects owned by a specific 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
+ */
@GET
@Path("/list/user/{id}")
@Detail("Get shared projects by user")
@@ -183,25 +217,63 @@ public Response get(
@QueryParam("offset") Integer offset,
@PathParam("id") Long idUser) {
- LOG.info("REST:/shared/project/list/user/ Get request received for user '{}'", idUser);
+ RestProjectUtils restProjectUtils = new RestProjectUtils();
- List projects = projectService.getSharedProjectsByUser(sort, order, limit, offset, idUser);
- int projectCount = projectService.countSharedProjectsByUser(idUser);
+ String endPoint = "REST:/shared/project/list/user/";
+ LOG.info("{} Get request received for user '{}'", endPoint, idUser);
- JsonObject result = new JsonObject();
- JsonArray jsonProjects = new JsonArray();
-
- for (ProjectRecord project : projects) {
- jsonProjects.add(projectConverter.toListJson(project));
+ // Sort flag evaluation
+ if (!restProjectUtils.ValidateSortType(sort)) {
+ LOG.warn("{} Sort parameter failed", endPoint);
+ return Response.status(Response.Status.NOT_ACCEPTABLE).build();
}
- result.add("rows", jsonProjects);
- result.addProperty("total", projectCount);
+ // Sort order evaluation
+ if (!restProjectUtils.ValidateSortOrder(order)) {
+ LOG.warn("{} Sort order parameter failed", endPoint);
+ return Response.status(Response.Status.NOT_ACCEPTABLE).build();
+ }
+
+ // Limit result set value
+ if ( (limit == null) || (limit > REQUEST_LIMIT)) {
+ LOG.info("{} Limit throttle to {} entries", endPoint, REQUEST_LIMIT);
+ limit = REQUEST_LIMIT;
+ }
+
+ // Check ofset from the beginning of the record set
+ if ((offset == null) || (offset < 0)) {
+ offset = 0;
+ }
+
+ List projects = projectService.getSharedProjectsByUser(sort, order, limit, offset, idUser);
- return Response.ok(result.toString()).build();
+ // Tell the caller that there is nothing to see here
+ if (projects == null) {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+
+ return Response.ok(
+ returnProjectsJson(
+ projects,
+ projectService.countSharedProjectsByUser(idUser)))
+ .build();
}
+ /**
+ *
+ * @param authorization
+ * Authorization header token
+ *
+ * @param timestamp
+ * A timestamp
+ *
+ * @param idProject
+ * The project key ID
+ *
+ * @return
+ * Returns a Json string containing the project details
+ */
@GET
@Path("/get/{id}")
@Detail("Get project by id")
@@ -212,10 +284,10 @@ public Response get(
@HeaderParam("X-Timestamp") Long timestamp,
@PathParam("id") Long idProject) {
- LOG.info("REST:/rest/shared/project/get/ Get request received for projecet '{}'", idProject);
+ String endPoint = "REST:/rest/shared/project/get/";
+ LOG.info("{} Get request received for project '{}'", endPoint, idProject);
try {
- LOG.info("Getting project record.");
ProjectRecord project = projectService.getProject(idProject);
if (project == null) {
@@ -223,9 +295,11 @@ public Response get(
return Response.status(Response.Status.NOT_FOUND).build();
}
- LOG.info("Converting project to JSON string");
+ LOG.info("Converting project {} to JSON string", idProject);
+
JsonObject result = projectConverter.toJson(project, false);
- LOG.info("REST: /get/" + idProject.toString() + "/ returning project {}.", project.getId());
+
+ LOG.info("{}" + idProject.toString() + "/ returning project {}.", endPoint, project.getId());
return Response.ok(result.toString()).build();
}
@@ -235,6 +309,22 @@ public Response get(
}
}
+
+ /**
+ * Get project details, including the project code payload
+ *
+ * @param authorization
+ * Request authorization header
+ *
+ * @param timestamp
+ * A timestamp
+ *
+ * @param idProject
+ * The project key ID
+ *
+ * @return
+ * A string containing a Json object representing the requested project
+ */
@GET
@Path("/editor/{id}")
@Detail("Get project by id for editor")
@@ -267,4 +357,32 @@ public Response getEditor(
}
}
+
+ /**
+ * 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();
+ }
+
}
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..ed35eb13 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("/")
+// @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();
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..e85aca9f
--- /dev/null
+++ b/src/main/java/com/parallax/server/blocklyprop/rest/RestV2Project.java
@@ -0,0 +1,945 @@
+/*
+ * 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.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.List;
+
+import org.jetbrains.annotations.NotNull;
+
+
+/**
+ * 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
+ *
+ * Version 1 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
+ * -----------------------------------------------------------------------------------------------------
+ * 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
+ * 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
+ *
+ * UPDATE
+ * [PUT] /v2/project/{id} Updates s specific project. Project details are in the request body
+ *
+ * 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;
+ }
+
+
+
+ // GET /v2/project/ping
+ /**
+ * 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();
+ }
+
+
+
+ // POST /v2/project/
+ /**
+ * Create a new project. Access is limited to authenticated users.
+ *
+ * @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
+ *
+ * @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
+ @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;
+ }
+
+ // 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);
+
+ if (savedProject == null) {
+ LOG.warn("Unable to create a new project record");
+ }
+
+ JsonObject result = projectConverter.toJson(savedProject,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();
+ }
+ }
+
+
+
+ // 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
+ * 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/{id} 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();
+ }
+ }
+
+
+
+ // GET /v2/project/
+ /**
+ * 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();
+ }
+ }
+
+
+ // GET /v2/project/{id}
+ /**
+ * 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/v2/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();
+ }
+ }
+
+
+
+
+ // 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")
+ @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);
+
+ 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");
+ return Response.ok(result.toString()).build();
+ }
+
+
+ // DELETE /v2/project/{id}
+ /**
+ * Delete a project only if the project is owned by the currently logged in user.
+ *
+ * @param idProject is the identifier of the project to use as a
+ * source for the new project
+
+ * @return a Json formatted string containing "success" otherwise returns an
+ * error code indicating the type of failure encountered.
+ */
+ @DELETE
+ @Path("/{id}")
+ @Detail("Delete a project by the project id")
+ @Name("Delete Project by id")
+ @Produces("application/json")
+ public Response delete(
+ @PathParam("id") @ParameterDetail("Project identifier") Long idProject) {
+
+ LOG.info("REST:/rest/v2/project/{} DELETE request received for project", idProject);
+
+ ProjectRecord project;
+
+ // Get the specified project
+ try {
+ project = projectService.getProject(idProject);
+
+ if (project == null) {
+ LOG.info("Project {} was not found", idProject);
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+
+ // 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();
+ }
+
+ LOG.info("Project {} is ready to be deleted.");
+
+ // 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 (Exception ex) {
+ 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
+ *
+ * @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(@NotNull 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();
+ }
+
+
+ /**
+ * 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(
+ @NotNull 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/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;
}
diff --git a/src/main/java/com/parallax/server/blocklyprop/security/BlocklyPropSessionDao.java b/src/main/java/com/parallax/server/blocklyprop/security/BlocklyPropSessionDao.java
index 87282b2f..84eec6ac 100644
--- a/src/main/java/com/parallax/server/blocklyprop/security/BlocklyPropSessionDao.java
+++ b/src/main/java/com/parallax/server/blocklyprop/security/BlocklyPropSessionDao.java
@@ -7,18 +7,17 @@
import com.parallax.server.blocklyprop.db.generated.tables.records.SessionRecord;
import com.parallax.server.blocklyprop.services.impl.SessionServiceImpl;
+
import java.io.Serializable;
import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.UUID;
+import java.util.*;
+
import org.apache.commons.lang.SerializationUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.session.mgt.eis.SessionDAO;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,50 +25,101 @@
* Session persistence manager
*
* @author Michel
+ *
+ * @implNote
+ * Notes from the Shiro JavaDocs:
+ * Data Access Object design pattern specification to enable Session access to an EIS
+ * (Enterprise Information System). It provides your four typical CRUD methods:
+ * create(org.apache.shiro.session.Session),
+ * readSession(java.io.Serializable),
+ * update(org.apache.shiro.session.Session),
+ * and delete(org.apache.shiro.session.Session).
+ *
+ * The remaining getActiveSessions() method exists as a support mechanism to pre-emptively
+ * orphaned sessions, typically by ValidatingSessionManagers), and should be as efficient
+ * as possible, especially if there are thousands of active sessions. Large scale/high
+ * performance implementations will often return a subset of the total active sessions and
+ * perform validation a little more frequently, rather than return a massive set and
+ * infrequently validate.
*/
public class BlocklyPropSessionDao implements SessionDAO {
- // Get a logger instance
+
+ /**
+ * Get an instance of the logger initialized to this class
+ */
private static final Logger LOG = LoggerFactory.getLogger(SessionServiceImpl.class);
/**
- *
+ * Inserts a new Session record into the underling EIS (a relational database in this
+ * implementation).
+ *
* @param session
- * @return
+ * the Session object to create in the EIS.
+ *
+ * @return
+ * the EIS id (e.g. primary key) of the created Session object.
+ *
+ * @implNote
+ * After this method is invoked, the Session.getId() method executed on the argument
+ * must return a valid session identifier. That is, the following should always be
+ * true:
+ *
+ * Serializable id = create( session );
+ * id.equals( session.getId() ) == true
+ *
+ * Implementations are free to throw any exceptions that might occur due to integrity
+ * violation constraints or other EIS related errors.
*/
@Override
public Serializable create(Session session) {
- LOG.debug("Create BlocklyProp session");
+ LOG.trace("Create BlocklyProp session");
// Set session timeout for 8 hours
session.setTimeout(28800000);
SimpleSession simpleSession = (SimpleSession) session;
+
+ // Create a unique string and save into the session object
String uuid = UUID.randomUUID().toString();
-
simpleSession.setId(uuid);
+
+ // Get a reference to the static session service and create
+ // a session record from the session object and store it in the
+ // sessionDao backing store
SessionServiceImpl.getSessionService().create(convert(simpleSession));
- LOG.debug("Session timeout is: {}", simpleSession.getTimeout());
LOG.info("Creating session: {}", simpleSession.getId());
+ // Return a unique session identifier
return uuid;
}
/**
- *
+ * Retrieves the session from the EIS uniquely identified by the specified sessionId.
+ *
* @param sessionId
+ * the system-wide unique identifier of the Session object to retrieve from the EIS.
+ *
* @return
- * @throws UnknownSessionException
+ * the persisted session in the EIS identified by sessionId.
+ *
+ * @throws UnknownSessionException
+ * if there is no EIS record for any session with the specified sessionId
*/
@Override
public Session readSession(Serializable sessionId) throws UnknownSessionException {
- LOG.debug("Reading session: {}", sessionId);
+
+ LOG.trace("Reading session: {}", sessionId);
+
+ // Check parameter for sanity
+ if (sessionId == null) {
+ LOG.warn("Attempt to retrieve session with a null UUID parameter");
+ throw new UnknownSessionException();
+ }
try {
// Obtain an existing session object
SessionRecord sessionRecord
- = SessionServiceImpl
- .getSessionService()
- .readSession(sessionId.toString());
+ = SessionServiceImpl.getSessionService().readSession(sessionId.toString());
if (sessionRecord != null) {
return convert(sessionRecord);
@@ -84,13 +134,26 @@ public Session readSession(Serializable sessionId) throws UnknownSessionExceptio
}
/**
- *
+ * Updates (persists) data from a previously created Session instance in the EIS identified
+ * by {@link Session#getId() session.getId()}. This effectively propagates the data in the
+ * argument to the EIS record previously saved.
+ *
* @param session
- * @throws UnknownSessionException
+ * session - the Session to update
+ *
+ * @throws UnknownSessionException
+ * if no existing EIS session record exists with the identifier of session.getSessionId()
+ *
+ * @implNote
+ * In addition to UnknownSessionException, implementations are free to throw any other
+ * exceptions that might occur due to integrity violation constraints or other EIS related
+ * errors.
*/
@Override
public void update(Session session) throws UnknownSessionException {
- LOG.debug("Update session: {}", session.getId());
+
+ LOG.trace("Update session: {}", session.getId());
+
try {
// updateSession() can throw a NullPointerException if something goes wrong
SessionServiceImpl.getSessionService().updateSession(convert(session));
@@ -102,55 +165,116 @@ public void update(Session session) throws UnknownSessionException {
}
/**
- *
- * @param session
+ * Deletes the associated EIS record of the specified session. If there never existed a
+ * session EIS record with the identifier of session.getId(), then this method does nothing.
+ *
+ * @param session
+ * session - the session to delete.
*/
@Override
public void delete(Session session) {
- LOG.debug("Removing session {}", session.getId());
+
+ LOG.trace("Removing session {}", session.getId());
+
SessionServiceImpl.getSessionService().deleteSession(session.getId().toString());
}
/**
- *
- * @return
+ * Returns all sessions in the EIS that are considered active, meaning all sessions that
+ * have not been stopped or expired. This is primarily used to validate potential orphans.
+ *
+ * @return
+ * a Collection of Sessions that are considered active, or an empty collection or null if
+ * there are no active sessions.
+ *
+ * @implNote
+ * This method should be as efficient as possible, especially in larger systems where there
+ * might be thousands of active sessions. Large scale/high performance implementations will
+ * often return a subset of the total active sessions and perform validation a little more
+ * frequently, rather than return a massive set and validate infrequently. If efficient and
+ * possible, it would make sense to return the oldest unstopped sessions available, ordered
+ * by lastAccessTime.
+ *
+ * Ideally this method would only return active sessions that the EIS was certain should be
+ * invalided. Typically that is any session that is not stopped and where its
+ * lastAccessTimestamp is older than the session timeout. For example, if sessions were
+ * backed by a relational database or SQL-92 'query-able' enterprise cache, you might return
+ * something similar to the results returned by this query (assuming SimpleSessions were
+ * being stored):
+ *
+ * select *
+ * from sessions s
+ * where s.lastAccessTimestamp < ? and s.stopTimestamp is null
+ *
+ * where the ? parameter is a date instance equal to 'now' minus the session timeout
+ * (e.g. now - 30 minutes).
*/
@Override
public Collection getActiveSessions() {
- LOG.debug("Getting all active sessions");
+
+ LOG.trace("Getting all active sessions");
Collection sessionRecords = SessionServiceImpl.getSessionService().getActiveSessions();
- List sessions = new ArrayList();
+ List sessions = new ArrayList<>();
+
for (SessionRecord sessionRecord : sessionRecords) {
sessions.add(convert(sessionRecord));
}
+
return sessions;
}
- protected SessionRecord convert(Session session) {
- LOG.debug("Converting session {} to a SessionRecord object", session.getId());
-
+
+ /**
+ * Convert a Session object into a SessionRecord object
+ *
+ * @param session
+ * the session to convert into a SessionRecord
+ *
+ * @return
+ * a SessionRecord object containing the details necessary to persist the object
+ * into an EIS.
+ */
+ private SessionRecord convert(Session session) {
+ LOG.trace("Converting session {} to a SessionRecord object", session.getId());
+
+ // Cast the Session parameter into a SimpleSession reference
SimpleSession ssession = (SimpleSession) session;
+
SessionRecord sessionRecord = new SessionRecord();
sessionRecord.setIdsession(session.getId().toString());
sessionRecord.setStarttimestamp(new Timestamp(session.getStartTimestamp().getTime()));
sessionRecord.setLastaccesstime(new Timestamp(session.getLastAccessTime().getTime()));
sessionRecord.setTimeout(session.getTimeout());
sessionRecord.setHost(session.getHost());
+
+ // Gather the session attributes into a HashMap that can be persisted into the
+ // SessionRecord object
if (ssession.getAttributes() != null) {
HashMap