diff --git a/.env b/.env new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.env @@ -0,0 +1 @@ + diff --git a/.gitignore b/.gitignore index 2af7cefb..665c192c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ build/ nbbuild/ dist/ nbdist/ -.nb-gradle/ \ No newline at end of file +.nb-gradle/ +*.flattened-pom.xml diff --git a/muttley-admin-server.pom/.gitignore b/muttley-admin-server.pom/.gitignore new file mode 100644 index 00000000..549e00a2 --- /dev/null +++ b/muttley-admin-server.pom/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/muttley-admin-server.pom/.mvn/wrapper/MavenWrapperDownloader.java b/muttley-admin-server.pom/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..a45eb6ba --- /dev/null +++ b/muttley-admin-server.pom/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/muttley-admin-server.pom/.mvn/wrapper/maven-wrapper.jar b/muttley-admin-server.pom/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..2cc7d4a5 Binary files /dev/null and b/muttley-admin-server.pom/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-admin-server.pom/.mvn/wrapper/maven-wrapper.properties b/muttley-admin-server.pom/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..ffdc10e5 --- /dev/null +++ b/muttley-admin-server.pom/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/muttley-admin-server.pom/muttley-admin-server/.gitignore b/muttley-admin-server.pom/muttley-admin-server/.gitignore new file mode 100644 index 00000000..549e00a2 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/muttley-admin-server.pom/muttley-admin-server/.mvn/wrapper/MavenWrapperDownloader.java b/muttley-admin-server.pom/muttley-admin-server/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..a45eb6ba --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/muttley-admin-server.pom/muttley-admin-server/.mvn/wrapper/maven-wrapper.jar b/muttley-admin-server.pom/muttley-admin-server/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..2cc7d4a5 Binary files /dev/null and b/muttley-admin-server.pom/muttley-admin-server/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-admin-server.pom/muttley-admin-server/.mvn/wrapper/maven-wrapper.properties b/muttley-admin-server.pom/muttley-admin-server/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..ffdc10e5 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/muttley-admin-server.pom/muttley-admin-server/mvnw b/muttley-admin-server.pom/muttley-admin-server/mvnw new file mode 100755 index 00000000..a16b5431 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-admin-server.pom/muttley-admin-server/mvnw.cmd b/muttley-admin-server.pom/muttley-admin-server/mvnw.cmd new file mode 100644 index 00000000..c8d43372 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/muttley-admin-server.pom/muttley-admin-server/pom.xml b/muttley-admin-server.pom/muttley-admin-server/pom.xml new file mode 100644 index 00000000..482865bd --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/pom.xml @@ -0,0 +1,96 @@ + + + + br.com.muttley + muttley-admin-server.pom + ${revision} + + 4.0.0 + jar + + muttley-admin-server + + muttley-admin-server + Demo project for Spring Boot + + + + org.projectlombok + lombok + true + + + + org.hibernate + hibernate-validator + + + + br.com.muttley + muttley-feign + + + + br.com.muttley + muttley-jackson + + + + br.com.muttley + muttley-model + + + + br.com.muttley + muttley-mongo + + + + br.com.muttley + muttley-security + + + + br.com.muttley + muttley-exception + + + + br.com.muttley + muttley-rest + + + + br.com.muttley + muttley-domain-service + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.cloud + spring-cloud-starter-eureka + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/MuttleyAdminServerConfig.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/MuttleyAdminServerConfig.java new file mode 100644 index 00000000..b7d4d85e --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/MuttleyAdminServerConfig.java @@ -0,0 +1,36 @@ +package br.com.muttley.admin.server; + +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * @author Joel Rodrigues Moreira 22/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Configuration +//Packages onde existem entidades +@EntityScan(basePackages = {"br.com.muttley.model.admin"}) +@ComponentScan(basePackages = { + //Injeções internas do projeto + "br.com.muttley.admin.server", + //Configuração de serviços + "br.com.muttley.domain.service", + //Configurações de segurança para o gateway + "br.com.muttley.security.zuul.gateway.service", + //Configurações do serviço de cache + "br.com.muttley.redis.service", + //Configurações de exceptions + "br.com.muttley.exception.service", + //Configurações de serialização + "br.com.muttley.jackson.service", + "br.com.muttley.feign.service", + "br.com.muttley.rest" +}) +@EnableEurekaClient +@EnableFeignClients(basePackages = "br.com.muttley.security.feign") +public class MuttleyAdminServerConfig { +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/model/DocumentNameConfig.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/model/DocumentNameConfig.java new file mode 100644 index 00000000..9e5b58de --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/model/DocumentNameConfig.java @@ -0,0 +1,81 @@ +package br.com.muttley.admin.server.config.model; + + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +/** + * @author Joel Rodrigues Moreira on 30/04/20. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Configuration(value = "documentNameConfig") +@Getter +public class DocumentNameConfig { + private final String nameCollectionAdminOwner; + private final String nameCollectionOwner; + private final String nameCollectionUser; + private final String nameCollectionPassword; + private final String nameCollectionAdminUserBase; + private final String nameCollectionUserBase; + private final String nameCollectionAccessPlan; + private final String nameCollectionUserPreferences; + private final String nameCollectionUserTokensNotification; + private final String nameCollectionAdminPassaport; + private final String nameCollectionPassaport; + private final String nameCollectionWorkTeam; + + private final String nameCollectionXAPIToken; + + private final String nameCollectionAdminUserDataBinding; + private final String nameCollectionUserDataBinding; + private final String nameViewCollectionUser; + private final String nameViewCollectionPassaport; + private final String nameViewCollectionPassaportRolesUser; + + private final String nameViewCollectionWorkTeam; + + + public DocumentNameConfig( + @Value("${br.com.muttley.security.server.owner-document:muttley-admin-owners}") final String nameCollectionAdminOwner, + @Value("${br.com.muttley.security.server.owner-document:muttley-owners}") final String nameCollectionOwner, + @Value("${br.com.muttley.security.server.user-document:muttley-users}") final String nameCollectionUser, + @Value("${br.com.muttley.security.server.user-password-document:muttley-users-password}") final String nameCollectionPassword, + @Value("${br.com.muttley.security.server.user-base-document:muttley-admin-users-base}") final String nameCollectionAdminUserBase, + @Value("${br.com.muttley.security.server.user-base-document:muttley-users-base}") final String nameCollectionUserBase, + @Value("${br.com.muttley.security.server.access-plan-document:muttley-access-plans}") final String nameCollectionAccessPlan, + @Value("${br.com.muttley.security.server.user-preference-document:muttley-users-preferences}") final String nameCollectionUserPreferences, + @Value("${br.com.muttley.security.server.user-tokens-notification-document:muttley-users-tokens-notification}") final String nameCollectionUserTokensNotification, + @Value("${br.com.muttley.security.server.admin-passaport-document:muttley-admin-passaports}") final String nameCollectionAdminPassaport, + @Value("${br.com.muttley.security.server.passaport-document:muttley-passaports}") final String nameCollectionPassaport, + @Value("${br.com.muttley.security.server.work-team-document:muttley-work-teams}") final String nameCollectionWorkTeam, + @Value("${br.com.muttley.security.server.x-api-token-document:muttley-x-api-token}") final String nameCollectionXAPIToken, + @Value("${br.com.muttley.security.server.user-data-binding:muttley-admin-users-databinding}") final String nameCollectionAdminUserDataBinding, + @Value("${br.com.muttley.security.server.user-data-binding:muttley-users-databinding}") final String nameCollectionUserDataBinding, + @Value("${br.com.muttley.security.server.user-document-view:view-muttley-users}") final String nameViewCollectionUser, + @Value("${br.com.muttley.security.server.passaport-document-view:view-muttley-passaports}") final String nameViewCollectionPassaport, + @Value("${br.com.muttley.security.server.passaport-role-document-view:view-muttley-passaports-roles-user}") final String nameViewCollectionPassaportRolesUser, + @Value("${br.com.muttley.security.server.work-team-document-view:view-muttley-work-teams}") final String nameViewCollectionWorkTeam + ) { + this.nameCollectionAdminOwner = nameCollectionAdminOwner; + this.nameCollectionOwner = nameCollectionOwner; + this.nameCollectionUser = nameCollectionUser; + this.nameCollectionPassword = nameCollectionPassword; + this.nameCollectionAdminUserBase = nameCollectionAdminUserBase; + this.nameCollectionUserBase = nameCollectionUserBase; + this.nameCollectionAccessPlan = nameCollectionAccessPlan; + this.nameCollectionUserPreferences = nameCollectionUserPreferences; + this.nameCollectionUserTokensNotification = nameCollectionUserTokensNotification; + this.nameCollectionAdminPassaport = nameCollectionAdminPassaport; + this.nameCollectionPassaport = nameCollectionPassaport; + this.nameCollectionWorkTeam = nameCollectionWorkTeam; + this.nameCollectionXAPIToken = nameCollectionXAPIToken; + this.nameCollectionAdminUserDataBinding = nameCollectionAdminUserDataBinding; + this.nameCollectionUserDataBinding = nameCollectionUserDataBinding; + this.nameViewCollectionUser = nameViewCollectionUser; + this.nameViewCollectionPassaport = nameViewCollectionPassaport; + this.nameViewCollectionPassaportRolesUser = nameViewCollectionPassaportRolesUser; + this.nameViewCollectionWorkTeam = nameViewCollectionWorkTeam; + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/mongo/MongoConfig.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/mongo/MongoConfig.java new file mode 100644 index 00000000..1dc8a6cd --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/mongo/MongoConfig.java @@ -0,0 +1,29 @@ +package br.com.muttley.admin.server.config.mongo; + + +import br.com.muttley.mongo.service.repository.impl.DocumentMongoRepositoryImpl; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +/** + * @author Joel Rodrigues Moreira on 22/04/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + *

+ * Realiza a configuração do mongo db + */ +@Configuration +@EnableMongoRepositories(basePackages = {"br.com.muttley.admin.server.repository"}, repositoryBaseClass = DocumentMongoRepositoryImpl.class) +public class MongoConfig extends br.com.muttley.mongo.service.MongoConfig { + + public MongoConfig(@Value("${spring.data.mongodb.database}") final String dataBaseName, + @Value("${spring.data.mongodb.host}") final String hostDataBase, + @Value("${spring.data.mongodb.port}") final String portDataBase, + @Value("${spring.data.mongodb.username}") final String userName, + @Value("${spring.data.mongodb.password}") final String password) { + super(dataBaseName, hostDataBase, portDataBase, userName, password); + } +} + + diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/postint/AccessPlanConfig.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/postint/AccessPlanConfig.java new file mode 100644 index 00000000..11abd825 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/postint/AccessPlanConfig.java @@ -0,0 +1,69 @@ +package br.com.muttley.admin.server.config.postint; + + +import br.com.muttley.model.security.AccessPlan; +import br.com.muttley.security.feign.AccessPlanServiceClient; +import br.com.muttley.security.feign.OwnerServiceClient; +import br.com.muttley.security.feign.PassaportServiceClient; +import br.com.muttley.security.feign.UserServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import java.util.Collection; + +import static java.util.Arrays.asList; + +/** + * @author Joel Rodrigues Moreira on 22/04/2021 + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + * Realiza a criação dos planos basico de utilização do sistema. + */ +@Component +public class AccessPlanConfig implements ApplicationListener { + private final AccessPlanServiceClient accessplanService; + private final OwnerServiceClient ownerService; + private final UserServiceClient userService; + private final PassaportServiceClient passaportService; + + @Autowired + public AccessPlanConfig(final AccessPlanServiceClient accessplanService, final OwnerServiceClient ownerService, final UserServiceClient userService, final PassaportServiceClient passaportService) { + this.accessplanService = accessplanService; + this.ownerService = ownerService; + this.userService = userService; + this.passaportService = passaportService; + } + + @Override + public void onApplicationEvent(final ApplicationReadyEvent event) { + /*new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + } + accessplanService.count(); + } + }).start();*/ + if (this.accessplanService.count() == 0) { + getDefaultPlanos() + .forEach(p -> accessplanService.save(p, "")); + } + } + + private final Collection getDefaultPlanos() { + return asList( + new AccessPlan() + .setName("Básico") + .setTotalUsers(10) + .setDescription("Plano de acesso básico"), + new AccessPlan() + .setName("Premium") + .setTotalUsers(10) + .setDescription("Plano de acesso premium") + ); + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/postint/UserConfig.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/postint/UserConfig.java new file mode 100644 index 00000000..ce294086 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/config/postint/UserConfig.java @@ -0,0 +1,130 @@ +package br.com.muttley.admin.server.config.postint; + +import br.com.muttley.admin.server.service.AdminUserBaseService; +import br.com.muttley.admin.server.service.NoSecurityAdminOwnerService; +import br.com.muttley.admin.server.service.NoSecurityAdminPassaportService; +import br.com.muttley.exception.throwables.MuttleyConflictException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.admin.AdminPassaport; +import br.com.muttley.model.admin.AdminUserBase; +import br.com.muttley.model.security.*; +import br.com.muttley.model.security.preference.Foto; +import br.com.muttley.security.feign.UserServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; + +import java.util.Arrays; +import java.util.Date; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class UserConfig implements ApplicationListener { + private final String defaultUser; + private final String userRead; + private final String passwdDefaultUser; + private final String passwdUserRead; + private final String nameOrganization; + private final UserServiceClient service; + private final AdminUserBaseService adminUserBaseService; + + private final NoSecurityAdminOwnerService ownerService; + private final NoSecurityAdminPassaportService passaportService; + + @Autowired + public UserConfig( + @Value("${muttley.admin-server.default-user}") String defaultUser, + @Value("${muttley.admin-server.user-read:#{null}}") String userRead, + @Value("${muttley.admin-server.passwd-default-user}") String passwdDefaultUser, + @Value("${muttley.admin-server.passwd-user-read:#{null}}") String passwdUserRead, + @Value("${muttley.admin-server.name-organization}") String nameOrganization, + UserServiceClient service, + AdminUserBaseService adminUserBaseService, + NoSecurityAdminOwnerService ownerService, + NoSecurityAdminPassaportService passaportService) { + this.defaultUser = defaultUser; + this.userRead = userRead; + this.passwdDefaultUser = passwdDefaultUser; + this.passwdUserRead = passwdUserRead; + this.nameOrganization = nameOrganization; + this.service = service; + this.adminUserBaseService = adminUserBaseService; + this.ownerService = ownerService; + this.passaportService = passaportService; + } + + @Override + public void onApplicationEvent(final ApplicationReadyEvent event) { + AdminOwner owner; + try { + owner = this.ownerService.findByName(this.nameOrganization); + } catch (MuttleyNotFoundException var7) { + User user = null; + + try { + user = this.service.save(new UserPayLoad("Admin", "Usuário para administrar todo o ecossistema", this.defaultUser, (String) null, (String) null, (Foto) null, (Set) null, this.passwdDefaultUser, (String) null, true, (String) null, (String) null, false), "true"); + } catch (MuttleyConflictException var6) { + user = this.service.findByUserName(this.defaultUser); + } + + owner = this.ownerService.save((AdminOwner) (new AdminOwner()).setName(this.nameOrganization).setDescription("Administrador unico do sistema").setUserMaster(user)); + this.adminUserBaseService.save(user, (AdminUserBase) (new AdminUserBase()).setOwner(owner).addUser((new UserBaseItem()).setUser(user).setAddedBy(user).setStatus(true).setDtCreate(new Date()))); + AdminPassaport var5 = this.passaportService.save((AdminPassaport) (new AdminPassaport()).setName("Grupo principal").setUserMaster(user).setOwner(owner).addRole(Role.ROLE_ROOT).setDescription("Não pode exister outro grupo no odin repository").addMember(user)); + } + + this.createUserRead(owner); + } + + private void createUserRead(Owner owner) { + if (!ObjectUtils.isEmpty(this.userRead) || !ObjectUtils.isEmpty(this.passwdUserRead)) { + try { + SecurityContext ctx = SecurityContextHolder.createEmptyContext(); + SecurityContextHolder.setContext(ctx); + ctx.setAuthentication(new AnonymousAuthenticationToken(owner.getUserMaster().getId(), owner.getUserMaster(), Arrays.asList(new GrantedAuthority() { + @Override + public String getAuthority() { + return Role.ROLE_ROOT.toString(); + } + }))); + + //Do what ever you want to do + + + User userRead = null; + + try { + userRead = this.service.findByUserName(this.userRead); + } catch (MuttleyNotFoundException var5) { + } + + if (userRead == null) { + try { + userRead = this.service.save(new UserPayLoad("AdminRead", "Usuário para consumir dados do ecossistema", this.userRead, (String) null, (String) null, (Foto) null, (Set) null, this.passwdUserRead, (String) null, true, (String) null, (String) null, false), "true"); + } catch (MuttleyConflictException var4) { + userRead = this.service.findByUserName(this.userRead); + } + + this.adminUserBaseService.update(owner.getUserMaster(), (AdminUserBase) ((AdminUserBase) this.adminUserBaseService.findFirst(owner.getUserMaster())).addUser((new UserBaseItem()).setUser(userRead).setAddedBy(userRead).setStatus(true).setDtCreate(new Date()))); + AdminPassaport var3 = this.passaportService.save((AdminPassaport) (new AdminPassaport()).setName("Grupo principal para leitura").setUserMaster(owner.getUserMaster()).setOwner(owner).addRole(Role.ROLE_ROOT).setDescription("Não pode exister outro grupo no odin repository").addMember(userRead)); + } + + } finally { + SecurityContextHolder.clearContext(); + } + } + } + +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/controller/AccessPlanController.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/controller/AccessPlanController.java new file mode 100644 index 00000000..6c2ff63c --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/controller/AccessPlanController.java @@ -0,0 +1,122 @@ +package br.com.muttley.admin.server.controller; + +import br.com.muttley.model.security.AccessPlan; +import br.com.muttley.rest.RestController; +import br.com.muttley.rest.RestResource; +import br.com.muttley.security.feign.AccessPlanServiceClient; +import br.com.muttley.security.infra.resource.PageableResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@org.springframework.web.bind.annotation.RestController +@RequestMapping(value = "/api/v1/access-plan", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) +public class AccessPlanController implements RestController, RestResource { + private final AccessPlanServiceClient client; + private final ApplicationEventPublisher eventPublisher; + + @Autowired + public AccessPlanController(final AccessPlanServiceClient client, final ApplicationEventPublisher eventPublisher) { + this.client = client; + this.eventPublisher = eventPublisher; + } + + @Override + @RequestMapping(method = POST, consumes = {APPLICATION_JSON_UTF8_VALUE}) + public ResponseEntity save(@RequestBody final AccessPlan value, final HttpServletResponse response, @RequestParam(required = false, value = "returnEntity", defaultValue = "") final String returnEntity) { + final AccessPlan record = client.save(value, returnEntity); + + publishCreateResourceEvent(this.eventPublisher, response, record); + + if (returnEntity != null && returnEntity.equals("true")) { + return ResponseEntity.status(HttpStatus.CREATED).body(record); + } + return ResponseEntity.status(HttpStatus.CREATED).build(); + + } + + @Override + @RequestMapping(value = "/{id}", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity update(@PathVariable("id") final String id, @RequestBody final AccessPlan model) { + return ResponseEntity.ok(client.update(id, model)); + } + + @Override + @RequestMapping(value = "/{id}", method = DELETE) + public ResponseEntity deleteById(@PathVariable("id") final String id) { + client.deleteById(id); + return ResponseEntity.ok().build(); + } + + @Override + @RequestMapping(value = "/{id}", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity findById(@PathVariable("id") final String id, final HttpServletResponse response) { + final AccessPlan value = client.findById(id); + + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + + return ResponseEntity.ok(value); + } + + @Override + @RequestMapping(value = "/reference/{id}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findReferenceById(@PathVariable("id") String id, HttpServletResponse response) { + final AccessPlan value = client.findReferenceById(id); + + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + + return ResponseEntity.ok(value); + } + + @Override + public ResponseEntity findByIds(final String[] strings, final HttpServletResponse httpServletResponse) { + throw new NotImplementedException(); + } + + @Override + @RequestMapping(value = "/first", method = GET, produces = APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity first(final HttpServletResponse response) { + final AccessPlan value = client.first(); + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(value); + } + + @Override + @RequestMapping(method = GET, produces = APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity list(final HttpServletResponse response, @RequestParam final Map allRequestParams) { + final PageableResource pageableResource = client.list(allRequestParams); + return ResponseEntity.ok(pageableResource); + } + + @Override + @RequestMapping(value = "/count", method = GET, produces = TEXT_PLAIN_VALUE) + public ResponseEntity count(@RequestParam final Map allRequestParams) { + return ResponseEntity.ok(String.valueOf(client.count(allRequestParams))); + } + +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/controller/OwnerController.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/controller/OwnerController.java new file mode 100644 index 00000000..64aeaa67 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/controller/OwnerController.java @@ -0,0 +1,146 @@ +package br.com.muttley.admin.server.controller; + +import br.com.muttley.admin.server.events.OwnerCreatedEvent; +import br.com.muttley.model.security.Owner; +import br.com.muttley.rest.RestController; +import br.com.muttley.rest.RestResource; +import br.com.muttley.security.feign.OwnerServiceClient; +import br.com.muttley.security.feign.PassaportServiceClient; +import br.com.muttley.security.infra.resource.PageableResource; +import org.apache.commons.lang.NotImplementedException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; + +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@org.springframework.web.bind.annotation.RestController +@RequestMapping(value = "/api/v1/owners", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) +public class OwnerController implements RestController, RestResource { + private final OwnerServiceClient client; + private final PassaportServiceClient passaportService; + private final ApplicationEventPublisher eventPublisher; + + + @Autowired + public OwnerController(final OwnerServiceClient client, final PassaportServiceClient passaportService, final ApplicationEventPublisher eventPublisher) { + this.client = client; + this.passaportService = passaportService; + this.eventPublisher = eventPublisher; + } + + @Override + @RequestMapping(method = POST, consumes = {APPLICATION_JSON_UTF8_VALUE}) + public ResponseEntity save(@RequestBody final Owner value, final HttpServletResponse response, @RequestParam(required = false, value = "returnEntity", defaultValue = "") final String returnEntity) { + + final Owner record = client.save(value, "true"); + + //disparando evento para informar que foi cria o owner + this.eventPublisher.publishEvent(new OwnerCreatedEvent(record)); + /*//criando o grupo de trabalho para vendedores + final WorkTeam vendedores = this.workTeamService.createWorkTeamFor( + record.getId(), new WorkTeam() + .setName(WORK_TEAM_NAME) + .setDescription("Grupo principal do sistema criado especificamente para dar autorização a vendedores mobile") + .setUserMaster(record.getUserMaster()) + .setUserMaster(record.getUserMaster()) + .setOwner(record) + .setRoles( + Role.getValues() + .stream() + .filter(it -> it.getRoleName().contains("MOBILE")) + .collect(toSet()) + ) + );*/ + + + publishCreateResourceEvent(this.eventPublisher, response, record); + + if (returnEntity != null && returnEntity.equals("true")) { + return ResponseEntity.status(HttpStatus.CREATED).body(record); + } + return ResponseEntity.status(HttpStatus.CREATED).build(); + + } + + @Override + @RequestMapping(value = "/{id}", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity update(@PathVariable("id") final String id, @RequestBody final Owner model) { + return ResponseEntity.ok(client.update(id, model)); + } + + @Override + @RequestMapping(value = "/{id}", method = DELETE) + public ResponseEntity deleteById(@PathVariable("id") final String id) { + client.deleteById(id); + return ResponseEntity.ok().build(); + } + + @Override + @RequestMapping(value = "/{id}", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity findById(@PathVariable("id") final String id, final HttpServletResponse response) { + final Owner value = client.findById(id); + + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + + return ResponseEntity.ok(value); + } + + @Override + @RequestMapping(value = "/reference/{id}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findReferenceById(@PathVariable("id") String id, HttpServletResponse response) { + final Owner value = client.findReferenceById(id); + + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + + return ResponseEntity.ok(value); + } + + @Override + public ResponseEntity findByIds(final String[] strings, final HttpServletResponse httpServletResponse) { + throw new NotImplementedException(); + } + + @Override + @RequestMapping(value = "/first", method = GET, produces = APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity first(final HttpServletResponse response) { + final Owner value = client.first(); + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(value); + } + + @Override + @RequestMapping(method = GET, produces = APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity list(final HttpServletResponse response, @RequestParam final Map allRequestParams) { + final PageableResource pageableResource = client.list(allRequestParams); + return ResponseEntity.ok(pageableResource); + } + + @Override + @RequestMapping(value = "/count", method = GET, produces = TEXT_PLAIN_VALUE) + public ResponseEntity count(@RequestParam final Map allRequestParams) { + return ResponseEntity.ok(String.valueOf(client.count(allRequestParams))); + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/events/OwnerCreatedEvent.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/events/OwnerCreatedEvent.java new file mode 100644 index 00000000..aaedf5d5 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/events/OwnerCreatedEvent.java @@ -0,0 +1,23 @@ +package br.com.muttley.admin.server.events; + +import br.com.muttley.model.security.Owner; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class OwnerCreatedEvent extends ApplicationEvent { + private final Owner owner; + + public OwnerCreatedEvent(final Owner owner) { + super(owner); + this.owner = owner; + } + + @Override + public Owner getSource() { + return this.owner; + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/listeners/OwnerCreatedEventListeners.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/listeners/OwnerCreatedEventListeners.java new file mode 100644 index 00000000..efd0b042 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/listeners/OwnerCreatedEventListeners.java @@ -0,0 +1,63 @@ +package br.com.muttley.admin.server.listeners; + +import br.com.muttley.admin.server.events.OwnerCreatedEvent; +import br.com.muttley.admin.server.service.AdminPassaportService; +import br.com.muttley.model.admin.AdminPassaport; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBase; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; + +import static br.com.muttley.model.security.Role.ROLE_OWNER; + +/** + * @author Joel Rodrigues Moreira 27/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +//@Component +public class OwnerCreatedEventListeners implements ApplicationListener { + final AdminPassaportService passaportService; + //final br.com.muttley.security.server.service.AdminUserBaseService + + @Autowired + public OwnerCreatedEventListeners(final AdminPassaportService passaportService) { + this.passaportService = passaportService; + } + + @Override + public void onApplicationEvent(final OwnerCreatedEvent event) { + final User userMaster = event.getSource().getUserMaster(); + + // Adicinando a base de usuário para esse novo owner cadastradao + final UserBase userBase = new UserBase(); + userBase.setOwner(event.getSource()) + .addUser(userMaster, userMaster); + + //this.userBaseService.save(currentUser, event.getSource(), userBase); + + + AdminPassaport passaport = (AdminPassaport) new AdminPassaport() + .setName("Master") + .setDescription("Esse é o grupo principal") + .setOwner(event.getSource()) + .setUserMaster(userMaster) + .addMember(userMaster) + .addRole(ROLE_OWNER); + userMaster.setCurrentOwner(passaport.getOwner()); + + passaport = this.passaportService.save(userMaster, passaport); + + /*Já que acabamos de criar um Owner, devemos verificar se o usuário master já tem algumas preferencias básicas + * tudo isso para evitar erros + */ + /*final UserPreferences preference = this.userService.loadPreference(userMaster); + if (!preference.contains(OWNER_PREFERENCE)) { + preference.set(OWNER_PREFERENCE, workTeam.getOwner()); + //salvando as alterções das preferencias + this.userService.save(userMaster, preference); + }*/ + + + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/repository/AdminOwnerRepository.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/repository/AdminOwnerRepository.java new file mode 100644 index 00000000..ee5f8fe0 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/repository/AdminOwnerRepository.java @@ -0,0 +1,15 @@ +package br.com.muttley.admin.server.repository; + +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.mongo.service.repository.DocumentMongoRepository; +import org.springframework.stereotype.Repository; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Repository +public interface AdminOwnerRepository extends DocumentMongoRepository { + public AdminOwner findByName(final String name); +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/repository/AdminPassaportRepository.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/repository/AdminPassaportRepository.java new file mode 100644 index 00000000..0eb27fd8 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/repository/AdminPassaportRepository.java @@ -0,0 +1,12 @@ +package br.com.muttley.admin.server.repository; + +import br.com.muttley.model.admin.AdminPassaport; +import br.com.muttley.mongo.service.repository.DocumentMongoRepository; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface AdminPassaportRepository extends DocumentMongoRepository { +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/repository/AdminUserDataBindingRepository.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/repository/AdminUserDataBindingRepository.java new file mode 100644 index 00000000..75c22be4 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/repository/AdminUserDataBindingRepository.java @@ -0,0 +1,26 @@ +package br.com.muttley.admin.server.repository; + +import br.com.muttley.model.admin.AdminUserDataBinding; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.mongo.service.repository.DocumentMongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 01/03/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Repository +public interface AdminUserDataBindingRepository extends DocumentMongoRepository { + + @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionOwner()}, '$id' : ?#{[0].getId()}}, 'user': {'$ref' : ?#{@documentNameConfig.getNameCollectionUser()}, '$id' : ?#{[1].getId()}}}") + List findByUser(final Owner owner, final User user); + + @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionOwner()}, '$id' : ?#{[0].getId()}}, 'user': {'$ref' : ?#{@documentNameConfig.getNameCollectionUser()}, '$id' : ?#{[1].getId()}}, 'key': '?2'}") + UserDataBinding findByUserAndKey(final Owner owner, final User user, final String key); +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/config/WebSecurityGatewayConfig.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/config/WebSecurityGatewayConfig.java new file mode 100644 index 00000000..63a9a7d6 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/config/WebSecurityGatewayConfig.java @@ -0,0 +1,48 @@ +package br.com.muttley.admin.server.security.config; + +import br.com.muttley.security.feign.UserServiceClient; +import br.com.muttley.security.infra.component.AuthenticationTokenFilterGateway; +import br.com.muttley.security.infra.component.UnauthorizedHandler; +import br.com.muttley.security.zuul.gateway.AbstractWebSecurityGateway; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class WebSecurityGatewayConfig extends AbstractWebSecurityGateway { + + @Autowired + public WebSecurityGatewayConfig( + @Value("${muttley.security.jwt.controller.loginEndPoint}") final String loginEndPoint, + @Value("${muttley.security.jwt.controller.refreshEndPoint}") final String refreshTokenEndPoin, + @Value("${muttley.security.jwt.controller.forgotPassword}") final String forgotPassword, + @Value("${muttley.security.jwt.controller.createEndPoint}") final String createEndPoint, + @Value("${muttley.security.jwt.controller.resetPassword}") final String resetPassword, + final UnauthorizedHandler unauthorizedHandler, + final AuthenticationTokenFilterGateway authenticationTokenFilterGateway, + final UserServiceClient userServiceClient) { + super(loginEndPoint, refreshTokenEndPoin, forgotPassword, createEndPoint, resetPassword, unauthorizedHandler, authenticationTokenFilterGateway, userServiceClient); + } + + @Override + protected String[] endPointPermitAllToGet() { + return new String[]{ + "/", + "/*.html", + "/**/*.{png,jpg,jpeg,svg.ico}", + "/**/*.{html,css,js,svg,woff,woff2}", + //endpoit padrão da aplicação + "/login", + "/create-user", + "/home/**"}; + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/controller/AuthenticationRestController.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/controller/AuthenticationRestController.java new file mode 100644 index 00000000..a1471086 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/controller/AuthenticationRestController.java @@ -0,0 +1,28 @@ +package br.com.muttley.admin.server.security.controller; + +import br.com.muttley.localcache.services.LocalUserAuthenticationService; +import br.com.muttley.security.feign.auth.AuthenticationRestServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@RestController +public class AuthenticationRestController extends br.com.muttley.security.infra.controller.AuthenticationRestController { + + @Autowired + public AuthenticationRestController( + @Value("${muttley.security.jwt.controller.tokenHeader:Authorization}") final String tokenHeader, + final AuthenticationManager authenticationManager, + final AuthenticationRestServiceClient authenticationRestServiceClient, + final ApplicationEventPublisher eventPublisher, + final LocalUserAuthenticationService localUserAuthenticationService) { + super(tokenHeader, authenticationManager, authenticationRestServiceClient, eventPublisher, localUserAuthenticationService); + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/controller/CreateUserController.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/controller/CreateUserController.java new file mode 100644 index 00000000..f6f291f1 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/controller/CreateUserController.java @@ -0,0 +1,18 @@ +package br.com.muttley.admin.server.security.controller; + +import br.com.muttley.security.feign.UserServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class CreateUserController extends br.com.muttley.security.infra.controller.CreateUserController { + @Autowired + public CreateUserController(final ApplicationEventPublisher eventPublisher, final UserServiceClient service) { + super(eventPublisher, service); + } +} + diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/controller/UserManagerController.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/controller/UserManagerController.java new file mode 100644 index 00000000..7491d8c1 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/controller/UserManagerController.java @@ -0,0 +1,20 @@ +package br.com.muttley.admin.server.security.controller; + +import br.com.muttley.security.feign.UserServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@RestController +public class UserManagerController extends br.com.muttley.security.infra.controller.UserManagerController { + + @Autowired + public UserManagerController(UserServiceClient service) { + super(service); + } + +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/listener/UserAfterCacheLoadListener.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/listener/UserAfterCacheLoadListener.java new file mode 100644 index 00000000..d5743929 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/security/listener/UserAfterCacheLoadListener.java @@ -0,0 +1,38 @@ +package br.com.muttley.admin.server.security.listener; + +import br.com.muttley.admin.server.service.AdminOwnerService; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.events.UserAfterCacheLoadEvent; +import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.security.feign.UserPreferenceServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class UserAfterCacheLoadListener implements ApplicationListener { + + private final UserPreferenceServiceClient preferenceServiceClient; + private final AdminOwnerService adminOwnerService; + + @Autowired + public UserAfterCacheLoadListener(final UserPreferenceServiceClient preferenceServiceClient, final AdminOwnerService adminOwnerService) { + this.preferenceServiceClient = preferenceServiceClient; + this.adminOwnerService = adminOwnerService; + } + + @Override + public void onApplicationEvent(final UserAfterCacheLoadEvent event) { + final User user = event.getUser(); + //carregando preferencias + final UserPreferences preferences = preferenceServiceClient.getUserPreferences(); + final String idPassaport = (String) preferences.get(UserPreferences.OWNER_PREFERENCE).getValue(); + user.setPreferences(preferences); + user.setCurrentOwner(adminOwnerService.findById1(user, idPassaport)); + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminOwnerService.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminOwnerService.java new file mode 100644 index 00000000..e4bb28ed --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminOwnerService.java @@ -0,0 +1,27 @@ +package br.com.muttley.admin.server.service; + +import br.com.muttley.domain.service.Service; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.security.User; +import org.springframework.security.access.prepost.PreAuthorize; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface AdminOwnerService extends Service { + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + " ) " + + "): " + + " true" + ) + AdminOwner findByName(final User user, final String name); + + AdminOwner findById1(final User user, final String id); +} + diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminPassaportService.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminPassaportService.java new file mode 100644 index 00000000..0fc02c9a --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminPassaportService.java @@ -0,0 +1,60 @@ +package br.com.muttley.admin.server.service; + +import br.com.muttley.domain.service.Service; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.admin.AdminPassaport; +import br.com.muttley.model.security.User; +import org.springframework.security.access.prepost.PreAuthorize; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface AdminPassaportService extends Service { + AdminPassaport findById1(final User user, final String id); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles())" + + " )" + + "): " + + " true" + ) + AdminPassaport findByName(final AdminOwner owner, final String nome); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + List loadAllPassaports(final User user); + + void removeUserFromAllPassaport(AdminOwner owner, User user); +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminService.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminService.java new file mode 100644 index 00000000..143cb1e5 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminService.java @@ -0,0 +1,12 @@ +package br.com.muttley.admin.server.service; + +import br.com.muttley.domain.service.Service; +import br.com.muttley.model.Document; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface AdminService extends Service { +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminUserBaseService.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminUserBaseService.java new file mode 100644 index 00000000..4055219b --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminUserBaseService.java @@ -0,0 +1,49 @@ +package br.com.muttley.admin.server.service; + +import br.com.muttley.model.admin.AdminUserBase; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBaseItem; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.UserView; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 26/11/2020. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface AdminUserBaseService extends AdminService { + AdminUserBase save(final User user, final AdminUserBase userBase); + + AdminUserBase update(final User user, final AdminUserBase userBase); + + AdminUserBase findFirst(final User user); + + AdminUserBase save(final User user, final OwnerData owner, final AdminUserBase userBase); + + boolean userNameIsAvaliableForUserName(final User user, final String userName, final Set userNames); + + boolean userNameIsAvaliable(final User user, final Set userNames); + + UserView findUserByEmailOrUserNameOrNickUser(final User user, final String emailOrUserName); + + void addUserItemIfNotExists(final User user, final User userForAdd); + + void addUserItemIfNotExists(final User user, final UserBaseItem userForAdd); + + void createNewUserAndAdd(final User user, final UserBaseItem item); + + void mergeUserItemIfExists(User user, final UserBaseItem item); + + void removeByUserName(User user, String userName); + + boolean hasBeenIncludedAnyGroup(final User user, UserData userForCheck); + + boolean hasBeenIncludedAnyGroup(UserData userForCheck); + + boolean hasBeenIncludedAnyGroup(final User user, final String userNameForCheck); + + boolean hasBeenIncludedAnyGroup(final String userNameForCheck); +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminUserDataBindingService.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminUserDataBindingService.java new file mode 100644 index 00000000..7d872612 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/AdminUserDataBindingService.java @@ -0,0 +1,257 @@ +package br.com.muttley.admin.server.service; + +import br.com.muttley.model.admin.AdminUserDataBinding; +import br.com.muttley.model.security.KeyUserDataBinding; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserData; +import org.springframework.security.access.prepost.PreAuthorize; + +import java.util.List; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 12/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface AdminUserDataBindingService { + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_CREATE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_CREATE.toString() " + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + AdminUserDataBinding save(final User user, final AdminUserDataBinding dataBinding); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_UPDATE.toString() " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_UPDATE.toString() " + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + AdminUserDataBinding update(final User user, final AdminUserDataBinding dataBinding); + + /** + * Lista os itens levando em consideração não o usuário da requisição, + * mas sim o userName informado + * + * @param user -> usuário da requisição corrente + * @param userName -> nome de usuário desejado + */ + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString()" + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + List listByUserName(final User user, final String userName); + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_READ.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_READ.toString() " + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + List listBy(final User user); + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString()" + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + AdminUserDataBinding saveByUserName(final User user, final String userName, final AdminUserDataBinding value); + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString()" + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + AdminUserDataBinding updateByUserName(final User user, final String userName, final AdminUserDataBinding dataBinding); + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString()" + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + void merge(final User user, final String userName, final AdminUserDataBinding dataBinding); + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString()" + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + void merge(final User user, final String userName, final Set dataBindings); + + AdminUserDataBinding getKey(final User user, final KeyUserDataBinding key); + + AdminUserDataBinding getKey(final User user, final String key); + + AdminUserDataBinding getKeyByUserName(final User user, final String userName, final KeyUserDataBinding key); + + AdminUserDataBinding getKeyByUserName(final User user, final String userName, final String key); + + boolean contains(final User user, final KeyUserDataBinding key); + + boolean contains(final User user, final String key); + + boolean containsByUserNameAndKey(final User user, final String userName, final KeyUserDataBinding key); + + boolean containsByUserNameAndKey(final User user, final String userName, final String key); + + /** + * Verifica se uma determina chave e valor já esta reservado para algum usuário + */ + boolean containsByKeyAndValue(final User user, final KeyUserDataBinding key, final String value); + + /** + * Verifica se uma determina chave e valor já esta reservado para algum usuário + */ + boolean containsByKeyAndValue(final User user, final String key, final String value); + + /** + * Verifica se uma determina chave e valor já esta reservado para algum usuário diferente do username informado + */ + boolean containsByKeyAndValueAndUserNameNotEq(final User user, final String userName, final KeyUserDataBinding key, final String value); + + /** + * Verifica se uma determina chave e valor já esta reservado para algum usuário diferente do username informado + */ + boolean containsByKeyAndValueAndUserNameNotEq(final User user, final String userName, final String key, final String value); + + UserData getUserBy(final User user, final KeyUserDataBinding key, final String value); + + UserData getUserBy(final User user, final String key, final String value); +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/NoSecurityAdminOwnerService.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/NoSecurityAdminOwnerService.java new file mode 100644 index 00000000..ab9b7375 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/NoSecurityAdminOwnerService.java @@ -0,0 +1,17 @@ +package br.com.muttley.admin.server.service; + +import br.com.muttley.model.admin.AdminOwner; + +/** + * Essa classe deve ser utilizada apenas para configuração automatica do sistema, + * A mesma não contem validação de negócio nem mesmo de segurança + * + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface NoSecurityAdminOwnerService { + AdminOwner findByName(final String nome); + + AdminOwner save(final AdminOwner owner); +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/NoSecurityAdminPassaportService.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/NoSecurityAdminPassaportService.java new file mode 100644 index 00000000..4a2b60be --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/NoSecurityAdminPassaportService.java @@ -0,0 +1,15 @@ +package br.com.muttley.admin.server.service; + +import br.com.muttley.model.admin.AdminPassaport; + +/** + * Essa classe deve ser utilizada apenas para configuração automatica do sistema, + * A mesma não contem validação de negócio nem mesmo de segurança + * + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface NoSecurityAdminPassaportService { + AdminPassaport save(final AdminPassaport passaport); +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AbstractNoSecurityService.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AbstractNoSecurityService.java new file mode 100644 index 00000000..99864dce --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AbstractNoSecurityService.java @@ -0,0 +1,35 @@ +package br.com.muttley.admin.server.service.impl; + +import br.com.muttley.domain.service.Validator; +import br.com.muttley.exception.throwables.MuttleyException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class AbstractNoSecurityService { + @Autowired + protected Validator validator; + + /** + * O método verifica se estamos executanto dentro de um contexto de requisição HTTP + *

+ * Caso estejamos em uma requisição, devemos lançar uma exception + */ + protected void validateContext() { + try { + //se esssa linha for executada sem lançar uma exception, é sinal que o serviço está trabalhando sobre um contexto http + final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + //lançando a exception necessária + throw new MuttleyException("ATENÇÃO, ESSE SERVIÇO NÃO PODE SER USADO EM CONTEXTO DE REQUISIÇÃO!"); + } catch (IllegalStateException ex) { + //se chegou aqui está tudo ok podemos proceguir + } + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminOwnerServiceImpl.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminOwnerServiceImpl.java new file mode 100644 index 00000000..63503a24 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminOwnerServiceImpl.java @@ -0,0 +1,41 @@ +package br.com.muttley.admin.server.service.impl; + +import br.com.muttley.admin.server.repository.AdminOwnerRepository; +import br.com.muttley.admin.server.service.AdminOwnerService; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.stereotype.Service; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class AdminOwnerServiceImpl extends AdminServiceImpl implements AdminOwnerService { + private final AdminOwnerRepository repository; + + @Autowired + public AdminOwnerServiceImpl(final AdminOwnerRepository repository, final MongoTemplate mongoTemplate) { + super(repository, mongoTemplate, AdminOwner.class); + this.repository = repository; + } + + @Override + public AdminOwner findByName(final User user, final String name) { + final AdminOwner owner = this.repository.findByName(name); + if (owner == null) { + throw new MuttleyNotFoundException(Owner.class, "name", "Registro não encontrado"); + } + return owner; + } + + @Override + public AdminOwner findById1(final User user, final String id) { + return super.findById(user, id); + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminPassaportServiceImpl.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminPassaportServiceImpl.java new file mode 100644 index 00000000..af7dcc29 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminPassaportServiceImpl.java @@ -0,0 +1,114 @@ +package br.com.muttley.admin.server.service.impl; + +import br.com.muttley.admin.server.config.model.DocumentNameConfig; +import br.com.muttley.admin.server.repository.AdminPassaportRepository; +import br.com.muttley.admin.server.service.AdminPassaportService; +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.admin.AdminPassaport; +import br.com.muttley.model.security.User; +import com.mongodb.BasicDBObject; +import com.mongodb.DBRef; +import org.bson.BsonDocument; +import org.bson.BsonElement; +import org.bson.BsonObjectId; +import org.bson.BsonString; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +import static java.util.Arrays.asList; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class AdminPassaportServiceImpl extends AdminServiceImpl implements AdminPassaportService { + private final DocumentNameConfig documentNameConfig; + + @Autowired + public AdminPassaportServiceImpl(AdminPassaportRepository repository, final MongoTemplate mongoTemplate, final DocumentNameConfig documentNameConfig) { + super(repository, mongoTemplate, AdminPassaport.class); + this.documentNameConfig = documentNameConfig; + } + + @Override + public AdminPassaport findById1(final User user, final String id) { + return super.findById(user, id); + } + + @Override + public AdminPassaport findByName(final AdminOwner owner, final String name) { + final AdminPassaport owt = this.mongoTemplate.findOne( + query( + where("owner.$id").is(owner.getId()).and("name").is(name) + ), AdminPassaport.class + ); + if (owt == null) { + throw new MuttleyNotFoundException(AdminPassaport.class, "name", "Registro não encontrado"); + } + return owt; + } + + @Override + public List loadAllPassaports(final User user) { + /* + db['odin-work-teams'].aggregate( + { + "$match":{ + "owner.$id": ObjectId("5a984453aa9f6634ac2e461c"), + "members": {"$in":[{"$ref":"users", "$id":ObjectId("5a984453aa9f6634ac2e461d")}]} + } + } + ) + */ + AggregationResults result = this.mongoTemplate.aggregate( + newAggregation( + match( + where("members") + .in(new BsonDocument( + asList( + new BsonElement("$ref", new BsonString("users")), + new BsonElement("$id", new BsonObjectId(new ObjectId(user.getId()))) + ) + ) + ) + ) + ), + this.documentNameConfig.getNameCollectionAdminPassaport(), + AdminPassaport.class); + + final List list = result.getMappedResults(); + if (CollectionUtils.isEmpty(list)) { + throw new MuttleyNoContentException(AdminPassaport.class, null, "Nenhum grupo de trabalho encontrado"); + } + return list; + } + + @Override + public void removeUserFromAllPassaport(final AdminOwner owner, final User user) { + this.mongoTemplate.updateMulti( + new Query( + where("owner.$id").is(owner.getObjectId()) + ), + new Update().pull("members", new BasicDBObject("$in", asList( + new DBRef(this.documentNameConfig.getNameCollectionUser(), user.getObjectId()) + ))), + AdminPassaport.class + ); + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminServiceImpl.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminServiceImpl.java new file mode 100644 index 00000000..1f05ea25 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminServiceImpl.java @@ -0,0 +1,18 @@ +package br.com.muttley.admin.server.service.impl; + +import br.com.muttley.admin.server.service.AdminService; +import br.com.muttley.domain.service.impl.ServiceImpl; +import br.com.muttley.model.Document; +import br.com.muttley.mongo.service.repository.DocumentMongoRepository; +import org.springframework.data.mongodb.core.MongoTemplate; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AdminServiceImpl extends ServiceImpl implements AdminService { + public AdminServiceImpl(final DocumentMongoRepository repository, final MongoTemplate mongoTemplate, final Class clazz) { + super(repository, mongoTemplate, clazz); + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminUserBaseServiceImpl.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminUserBaseServiceImpl.java new file mode 100644 index 00000000..b715951e --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminUserBaseServiceImpl.java @@ -0,0 +1,529 @@ +package br.com.muttley.admin.server.service.impl; + +import br.com.muttley.admin.server.config.model.DocumentNameConfig; +import br.com.muttley.admin.server.service.AdminPassaportService; +import br.com.muttley.admin.server.service.AdminUserBaseService; +import br.com.muttley.admin.server.service.AdminUserDataBindingService; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.model.BasicAggregateResultCount; +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.TimeZoneDocument; +import br.com.muttley.model.VersionDocument; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.admin.AdminUserBase; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBase; +import br.com.muttley.model.security.UserBaseItem; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.UserView; +import br.com.muttley.mongo.service.infra.AggregationUtils; +import br.com.muttley.mongo.service.infra.metadata.EntityMetaData; +import br.com.muttley.security.feign.UserServiceClient; +import com.mongodb.BasicDBObject; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import javax.validation.constraints.NotNull; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + +import static br.com.muttley.model.security.Role.ROLE_USER_BASE_CREATE; +import static br.com.muttley.utils.TimeZoneUtils.getTimezoneFromId; +import static java.util.Arrays.asList; +import static java.util.Objects.isNull; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.group; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.lookup; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Update.Position.FIRST; + +/** + * @author Joel Rodrigues Moreira on 27/04/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class AdminUserBaseServiceImpl extends AdminServiceImpl implements AdminUserBaseService { + private static final String[] basicRoles = new String[]{ROLE_USER_BASE_CREATE.getSimpleName()}; + private final UserServiceClient userService; + //private final AdminUserDataBindingService dataBindingService; + private final DocumentNameConfig documentNameConfig; + private final AdminPassaportService passaportService; + + + @Autowired + public AdminUserBaseServiceImpl( + final MongoTemplate template, + final UserServiceClient userService, + final AdminUserDataBindingService dataBindingService, + final DocumentNameConfig documentNameConfig, + final AdminPassaportService passaportService) { + super(null, template, AdminUserBase.class); + this.userService = userService; + // this.dataBindingService = dataBindingService; + this.documentNameConfig = documentNameConfig; + this.passaportService = passaportService; + } + + @Override + public String[] getBasicRoles() { + return basicRoles; + } + + @Override + public void checkPrecondictionSave(final User user, final AdminUserBase value) { + final AggregationResults result = this.mongoTemplate.aggregate( + newAggregation( + AggregationUtils.createAggregationsCount(EntityMetaData.of(AdminUserBase.class), null, + addOwnerQueryParam(value.getOwner(), null) + )), + this.clazz, ResultCount.class); + if ((result.getUniqueMappedResult() != null ? ((ResultCount) result.getUniqueMappedResult()).getCount() : 0) == 1) { + throw new MuttleyBadRequestException(UserBase.class, null, "Já existe uma base de usuário cadastrada no sistema"); + } + } + + @Override + public AdminUserBase save(final User user, final AdminUserBase value) { + //verificando se realmente está criando um novo registro + checkIdForSave(value); + //garantindo que o metadata ta preenchido + //this.createMetaData(user, value); + try { + this.metadataService.generateNewMetadataFor(user, value); + } catch (BeanCreationException ex) { + this.generateLocalMetadata(user, value); + } + //processa regra de negocio antes de qualquer validação + this.beforeSave(user, value); + //verificando precondições + this.checkPrecondictionSave(user, value); + //validando dados do objeto + this.validator.validate(value); + this.mongoTemplate.save(value, this.documentNameConfig.getNameCollectionAdminUserBase()); + final AdminUserBase otherValue = this.mongoTemplate.findOne(new Query(), AdminUserBase.class, this.documentNameConfig.getNameCollectionAdminUserBase()); + //realizando regras de enegocio depois do objeto ter sido salvo + this.afterSave(user, otherValue); + //valor salvo + return otherValue; + } + + @Override + public AdminUserBase update(User user, AdminUserBase value) { + //verificando se realmente está alterando um registro + this.checkIdForUpdate(value); + //verificando se o registro realmente existe + if (this.mongoTemplate.findById(value.getId(), AdminUserBase.class, documentNameConfig.getNameCollectionAdminUserBase()) == null) { + throw this.createNotFoundExceptionById(user, value.getId()); + //throw new MuttleyNotFoundException(clazz, "id", "Registro não encontrado"); + } + //gerando metadata de alteração0 + + + final AggregationResults result = mongoTemplate.aggregate( + newAggregation( + match( + where("_id").is(value.getObjectId()) + ), project().and("$metadata.timeZones").as("timeZones") + .and("$metadata.versionDocument").as("versionDocument") + .and("$metadata.historic").as("historic") + ), documentNameConfig.getNameCollectionAdminUserBase(), MetadataDocument.class); + + final MetadataDocument meta = result.getUniqueMappedResult() != null ? ((MetadataDocument) result.getUniqueMappedResult()) : null; + + + try { + this.metadataService.generateMetaDataUpdateFor(user, meta, value); + } catch (Throwable ex) { + ex.printStackTrace(); + } + //processa regra de negocio antes de qualquer validação + beforeUpdate(user, value); + //verificando precondições + checkPrecondictionUpdate(user, value); + //validando dados + this.validator.validate(value); + this.mongoTemplate.save(value, documentNameConfig.getNameCollectionAdminUserBase()); + final AdminUserBase otherValue = value; + //realizando regras de enegocio depois do objeto ter sido alterado + afterUpdate(user, value); + return otherValue; + } + + @Override + public AdminUserBase findFirst(User user) { + final AdminUserBase result = mongoTemplate.findOne(new Query(), AdminUserBase.class); + if (isNull(result)) { + throw this.createNotFoundExceptionById(user).setField("user"); + //*throw new MuttleyNotFoundException(clazz, "user", "Nenhum registro encontrado"); + } + return result; + } + + /** + * Gera o metadatalocal caso estejamos fora do contexto de requisição + */ + private void generateLocalMetadata(final User user, final AdminUserBase value) { + final MetadataDocument metadataDocument = new MetadataDocument(user); + final TimeZone tz = TimeZone.getDefault(); + final String timezone = getTimezoneFromId(tz.getID()); + metadataDocument.setTimeZones(new TimeZoneDocument(timezone, timezone, timezone, timezone)); + metadataDocument.setVersionDocument(new VersionDocument()); + value.setMetadata(metadataDocument); + } + + //@Override + public void checkPrecondictionSave(final User user, final OwnerData owner, final AdminUserBase value) { + /*if (this.count(user, owner, null) == 1) { + throw new MuttleyBadRequestException(UserBase.class, null, "Já existe uma base de usuário cadastrada no sistema"); + }*/ + if (owner != null) { + throw new MuttleyException(); + } + final AggregationResults result = this.mongoTemplate.aggregate( + newAggregation( + AggregationUtils.createAggregationsCount(EntityMetaData.of(AdminUserBase.class), null, + addOwnerQueryParam(owner, null) + )), + this.clazz, ResultCount.class); + + if ((result.getUniqueMappedResult() != null ? ((ResultCount) result.getUniqueMappedResult()).getCount() : 0) == 1) { + throw new MuttleyBadRequestException(AdminUserBase.class, null, "Já existe uma base de usuário cadastrada no sistema"); + } + } + + private final Map addOwnerQueryParam(final OwnerData owner, final Map queryParams) { + final Map query = new LinkedHashMap<>(1); + query.put("owner.$id.$is", owner.getObjectId().toString()); + if (queryParams != null) { + query.putAll(queryParams); + } + return query; + } + + + @Override + public void checkPrecondictionDelete(final User user, final String id) { + throw new MuttleyBadRequestException(Owner.class, "id", "Não é possível deletar a base de usuário"); + } + + @Override + public AdminUserBase save(final User user, final OwnerData owner, final AdminUserBase userBase) { + throw new NotImplementedException(); + } + + @Override + public boolean userNameIsAvaliableForUserName(final User user, final String userName, final Set userNames) { + //return this.userService.userNameIsAvaliableForUserName(userName, userNames); + throw new NotImplementedException(); + } + + @Override + public boolean userNameIsAvaliable(final User user, final Set userNames) { + return this.userService.userNameIsAvaliable(userNames); + } + + @Override + public UserView findUserByEmailOrUserNameOrNickUser(final User user, final String emailOrUserName) { + return new UserView(this.userService.findUserByEmailOrUserNameOrNickUsers(emailOrUserName, emailOrUserName, new HashSet<>(asList(emailOrUserName)))); + } + + @Override + public void addUserItemIfNotExists(final User user, final User userForAdd) { + this.addUserItemIfNotExists(user, new UserBaseItem(user, userForAdd, null, new Date(), true, null)); + } + + @Override + public void addUserItemIfNotExists(final User user, final UserBaseItem userForAdd) { + if (!this.hasBeenIncludedAnyGroup(user, userForAdd.getUser())) { + userForAdd.setAddedBy(user); + if (userForAdd.getDtCreate() == null) { + userForAdd.setDtCreate(new Date()); + } + if (userForAdd.getAddedBy() == null) { + userForAdd.setAddedBy(user); + } + this.validator.validate(userForAdd); + if (this.hasBeenIncludedAnyGroup(user, userForAdd.getUser())) { + throw new MuttleyBadRequestException(UserBase.class, "users", "Usuário já está presente na base"); + } + this.mongoTemplate.updateFirst( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + ), + new Update() + .push("users") + .atPosition(FIRST) + .each(userForAdd), + UserBase.class + ); + } else { + this.mongoTemplate.updateMulti( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("users.user.$id").is(new ObjectId(userForAdd.getUser().getId())) + ), + new Update().set("users.$.status", userForAdd.isStatus()), + UserBase.class + ); + } + } + + @Override + public void createNewUserAndAdd(final User user, final UserBaseItem item) { + final User userForSave = new User(item.getUserInfoForMerge()); + /*if (!item.dataBindingsIsEmpty()) { + item.getDataBindings().forEach(it -> { + if (it.getKey().isUnique()) { + if (this.dataBindingService.containsByKeyAndValueAndUserNameNotEq(user, userForSave.getUserName(), it.getKey(), it.getValue())) { + throw new MuttleyBadRequestException(UserDataBinding.class, "key", "Já existe um usuário que possui ligação com " + it.getKey().getDisplayKey() + " informado(a)"); + } + } + }); + }*/ + + final User salvedUser = userService.save(item.getUserInfoForMerge(), "true"); + + /*if (!item.dataBindingsIsEmpty()) { + this.dataBindingService.merge(user, salvedUser.getUserName(), item.getDataBindings().parallelStream().map(AdminUserDataBinding::new).collect(Collectors.toSet())); + }*/ + this.addUserItemIfNotExists(user, salvedUser); + } + + @Override + public void mergeUserItemIfExists(final User user, final UserBaseItem item) { + /* + item.setUser(userService.update(user, new User(item.getUserInfoForMerge()))); + if (!item.dataBindingsIsEmpty()) { + this.dataBindingService.merge(user, item.getUser().getUserName(), item.getDataBindings()); + } + this.addUserItemIfNotExists(user, item);*/ + throw new NotImplementedException(); + } + + @Override + public void removeByUserName(final User user, final String userName) { + final User userLoaded = this.userService.findByUserName(userName); + this.mongoTemplate.updateFirst( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + ), + new Update() + .pull("users", new BasicDBObject("user.$id", new ObjectId(userLoaded.getId()))), + UserBase.class + ); + this.passaportService.removeUserFromAllPassaport((AdminOwner) user.getCurrentOwner(), userLoaded); + } + + @Override + public boolean hasBeenIncludedAnyGroup(final User user, final UserData userForCheck) { + return this.mongoTemplate.exists( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("users.user.$id").is(new ObjectId(userForCheck.getId())) + ), UserBase.class + ); + } + + @Override + public boolean hasBeenIncludedAnyGroup(final UserData userForCheck) { + return this.mongoTemplate.exists( + new Query( + /*where("owner.$id").is(user.getCurrentOwner().getObjectId())*/ + where("users.user.$id").is(new ObjectId(userForCheck.getId())) + ), UserBase.class + ); + } + + @Override + public boolean hasBeenIncludedAnyGroup(final User user, final String userNameForCheck) { + /** + * db.getCollection("muttley-users-base").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6")}}, + * {$unwind:"$users"}, + * {$project:{user:{$objectToArray:"$users.user"}}}, + * {$project:{user:{$arrayElemAt:["$user.v",1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as:"user" + * }}, + * {$unwind:"$user"}, + * {$match:{ + * $or:[ + * {"user.userName":"asdfasd234asdfasdf"}, + * {"user.email":"df7899@gmail.com"}, + * {"user.nickUsers":{$in:["asdfas"]}}, + * ] + * }}, + * {$group:{_id:"$user"}}, + * {$count:"result"} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId())), + unwind("$users"), + project().and(context -> new BasicDBObject("$objectToArray", "$users.user")).as("user"), + project().and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup( + this.documentNameConfig.getNameCollectionUser(), + "user", + "_id", + "user" + ), + unwind("$user"), + match(new Criteria().orOperator( + where("user.userName").is(userNameForCheck), + where("user.email").is(userNameForCheck), + where("user.nickUsers").in(asList(userNameForCheck)) + )), + group("$user"), + Aggregation.count().as("result") + ), + this.documentNameConfig.getNameCollectionUserBase(), + BasicAggregateResultCount.class + ); + if (results == null || results.getUniqueMappedResult() == null || results.getUniqueMappedResult().getResult() == 0) { + return false; + } + if (results.getUniqueMappedResult().getResult() > 1) { + //não pode retornar mais de um usuário + throw new MuttleyException("Erro interno"); + } + + return true; + } + + @Override + public boolean hasBeenIncludedAnyGroup(final String userNameForCheck) { + /** + * db.getCollection("muttley-users-base").aggregate([ + * {$unwind:"$users"}, + * {$project:{user:{$objectToArray:"$users.user"}}}, + * {$project:{user:{$arrayElemAt:["$user.v",1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as:"user" + * }}, + * {$unwind:"$user"}, + * {$match:{ + * $or:[ + * {"user.userName":"asdfasd234asdfasdf"}, + * {"user.email":"df7899@gmail.com"}, + * {"user.nickUsers":{$in:["asdfas"]}}, + * ] + * }}, + * {$group:{_id:"$user"}}, + * {$count:"result"} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + unwind("$users"), + project().and(context -> new BasicDBObject("$objectToArray", "$users.user")).as("user"), + project().and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup( + this.documentNameConfig.getNameCollectionUser(), + "user", + "_id", + "user" + ), + unwind("$user"), + match(new Criteria().orOperator( + where("user.userName").is(userNameForCheck), + where("user.email").is(userNameForCheck), + where("user.nickUsers").in(asList(userNameForCheck)) + )), + group("$user"), + Aggregation.count().as("result") + ), + UserBase.class, + BasicAggregateResultCount.class + ); + if (results == null || results.getUniqueMappedResult() == null || results.getUniqueMappedResult().getResult() == 0) { + return false; + } + if (results.getUniqueMappedResult().getResult() > 1) { + //não pode retornar mais de um usuário + throw new MuttleyException("Erro interno"); + } + + return true; + } + + /* */ + + /** + * Verifica se o usuário já existe na base + *//* + private boolean userHasBeenIncluded(final User user, final User userForCheck) { + return this.userHasBeenIncluded(user, userForCheck.getId()); + } + + private boolean userHasBeenIncluded(final User user, final String id) { + return this.mongoTemplate.exists( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("users.user.$id").is(new ObjectId(id)) + ), UserBase.class + ); + }*/ + + @Getter + @Setter + @Accessors(chain = true) + private static class UserItemForAdd { + @DBRef + @NotNull(message = "Informe o usuário que está efetuando essa operação") + private User addedBy; + + @DBRef + @NotNull(message = "Informe o usuário participante da base") + private User user; + + @NotNull + private Date dtCreate; + + private boolean status; + } + + protected final class ResultCount { + private Long count; + + public Long getCount() { + return count; + } + + public ResultCount setCount(final Long count) { + this.count = count; + return this; + } + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminUserDataBindingServiceImpl.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminUserDataBindingServiceImpl.java new file mode 100644 index 00000000..770804d1 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/AdminUserDataBindingServiceImpl.java @@ -0,0 +1,628 @@ +package br.com.muttley.admin.server.service.impl; + +import br.com.muttley.admin.server.config.model.DocumentNameConfig; +import br.com.muttley.admin.server.repository.AdminUserDataBindingRepository; +import br.com.muttley.admin.server.service.AdminUserDataBindingService; +import br.com.muttley.domain.service.Validator; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyConflictException; +import br.com.muttley.headers.services.MetadataService; +import br.com.muttley.model.BasicAggregateResult; +import br.com.muttley.model.BasicAggregateResultCount; +import br.com.muttley.model.admin.AdminUserDataBinding; +import br.com.muttley.model.security.KeyUserDataBinding; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.events.UserResolverEvent; +import com.mongodb.BasicDBObject; +import io.jsonwebtoken.lang.Collections; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import static java.util.Arrays.asList; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.count; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.lookup; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * @author Joel Rodrigues Moreira 12/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class AdminUserDataBindingServiceImpl implements AdminUserDataBindingService { + @Autowired + protected MetadataService metadataService; + private final MongoTemplate mongoTemplate; + private final AdminUserDataBindingRepository repository; + private final DocumentNameConfig documentNameConfig; + private final Validator validator; + //private final UserService userService; + private final ApplicationEventPublisher eventPublisher; + @Value("${muttley.security.check-roles:false}") + private boolean checkRoles; + + //private final LocalDatabindingService localDatabindingService; + + @Autowired + public AdminUserDataBindingServiceImpl(final MongoTemplate mongoTemplate, final AdminUserDataBindingRepository repository, final DocumentNameConfig documentNameConfig, final Validator validator, final ApplicationEventPublisher eventPublisher /*final LocalDatabindingService localDatabindingService*/) { + this.mongoTemplate = mongoTemplate; + this.repository = repository; + this.documentNameConfig = documentNameConfig; + this.validator = validator; + this.eventPublisher = eventPublisher; + //this.localDatabindingService = localDatabindingService; + } + + public boolean isCheckRole() { + return checkRoles; + } + + @Override + public AdminUserDataBinding save(final User user, final AdminUserDataBinding dataBinding) { + if (dataBinding.getUser() == null) { + dataBinding.setUser(user); + } + checkBasicInfos(user, dataBinding); + checkPrecondictionSave(user, dataBinding); + final AdminUserDataBinding salved = repository.save(dataBinding); + //this.localDatabindingService.expireUserDataBindings(user); + return salved; + } + + public void checkPrecondictionSave(final User user, final AdminUserDataBinding dataBinding) { + if (!user.equals(dataBinding.getUser())) { + throw new MuttleyBadRequestException(AdminUserDataBinding.class, "user", "O usuário informado é diferente do da requisição!"); + } + //verificando se já não existe um registro com as informações + this.checkIndex(user, dataBinding); + this.validator.validate(dataBinding); + } + + @Override + public AdminUserDataBinding update(final User user, final AdminUserDataBinding dataBinding) { + if (dataBinding.getUser() == null) { + dataBinding.setUser(user); + } + checkBasicInfos(user, dataBinding); + this.checkPrecondictionUpdate(user, dataBinding); + final AdminUserDataBinding salved = repository.save(dataBinding); + //this.localDatabindingService.expireUserDataBindings(user); + return salved; + } + + public void checkPrecondictionUpdate(final User user, final AdminUserDataBinding dataBinding) { + if (!user.equals(dataBinding.getUser())) { + throw new MuttleyBadRequestException(AdminUserDataBinding.class, "user", "O usuário informado é diferente do da requisição!"); + } + //verificando se já não existe um registro com as informações + this.checkIndex(user, dataBinding); + this.validator.validate(dataBinding); + } + + @Override + public List listByUserName(final User user, final String userName) { + if (user.getUserName().equals(userName)) { + return this.listBy(user); + } + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6")}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$objectToArray:"$user"}}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$arrayElemAt:["$user.v", 1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as: "user" + * }}, + * {$unwind:"$user"}, + * {$match:{"user.userName":"OwnerSiapi5e28b392637e580001e465d3"}} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId())), + project("key", "value", "metadata", "owner").and(context -> new BasicDBObject("$objectToArray", "$user")).as("user"), + project("key", "value", "metadata", "owner").and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup(documentNameConfig.getNameCollectionUser(), "user", "_id", "user"), + unwind("$user"), + match(where("user.userName").is(userName)) + ), + documentNameConfig.getNameCollectionAdminUserDataBinding(), + AdminUserDataBinding.class + ); + if (results == null || Collections.isEmpty(results.getMappedResults())) { + return new ArrayList<>(); + } + return results.getMappedResults(); + } + + @Override + public List listBy(final User user) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"),"user.$id":ObjectId("5e28b3e3637e580001e465d6")}}, + * ]) + */ + final Owner currentOwner = user.getCurrentOwner(); + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(currentOwner != null ? currentOwner.getObjectId() : null).and("user.$id").is(new ObjectId(user.getId()))) + ), + AdminUserDataBinding.class, + AdminUserDataBinding.class + ); + if (results == null || Collections.isEmpty(results.getMappedResults())) { + new ArrayList<>(); + } + return results.getMappedResults(); + } + + @Override + public AdminUserDataBinding saveByUserName(final User user, final String userName, final AdminUserDataBinding dataBinding) { + if (dataBinding.getUser() == null) { + dataBinding.setUser(this.findByUserName(userName)); + } + checkBasicInfos(user, dataBinding); + checkPrecondictionSaveByUserName(user, userName, dataBinding); + + final AdminUserDataBinding salved = repository.save(dataBinding); + //this.localDatabindingService.expireUserDataBindings(user); + return salved; + } + + public void checkPrecondictionSaveByUserName(final User user, final String userName, final AdminUserDataBinding dataBinding) { + if (!userName.equals(dataBinding.getUser().getUserName())) { + throw new MuttleyBadRequestException(AdminUserDataBinding.class, "user", "O usuário informado é diferente do da requisição!") + .addDetails("useName", userName) + .addDetails("dataBinding", dataBinding); + } + this.checkIndex(user, userName, dataBinding); + this.validator.validate(dataBinding); + } + + @Override + public AdminUserDataBinding updateByUserName(final User user, final String userName, final AdminUserDataBinding dataBinding) { + if (dataBinding.getUser() == null) { + dataBinding.setUser(this.findByUserName(userName)); + } + if (StringUtils.isEmpty(dataBinding.getId())) { + dataBinding.setId(this.loadIdFrom(user, dataBinding)); + } + checkBasicInfos(user, dataBinding); + checkPrecondictionUpdateByUserName(user, userName, dataBinding); + + final AdminUserDataBinding salved = repository.save(dataBinding); + //this.localDatabindingService.expireUserDataBindings(user); + return salved; + } + + public void checkPrecondictionUpdateByUserName(final User user, final String userName, final AdminUserDataBinding dataBinding) { + if (!userName.equals(dataBinding.getUser().getUserName())) { + throw new MuttleyBadRequestException(AdminUserDataBinding.class, "user", "O usuário informado é diferente do da requisição!"); + } + this.validator.validate(dataBinding); + //verificando se já não existe um registro com as informações + this.checkIndex(user, userName, dataBinding); + } + + @Override + public void merge(final User user, final String userName, final AdminUserDataBinding dataBinding) { + if (dataBinding.getUser() == null) { + dataBinding.setUser(this.findByUserName(userName)); + } + if (exists(user, dataBinding)) { + + this.updateByUserName(user, userName, dataBinding); + } else { + + this.saveByUserName(user, userName, dataBinding); + } + } + + @Override + public void merge(final User user, final String userName, final Set dataBindings) { + User userCurrent = null; + for (AdminUserDataBinding dataBinding : dataBindings) { + if (dataBinding.getUser() == null) { + if (userCurrent == null) { + if (user.getUserName().equals(userName)) { + userCurrent = user; + } else { + userCurrent = this.findByUserName(userName); + } + } + dataBinding.setUser(userCurrent); + } + if (exists(user, dataBinding)) { + this.updateByUserName(user, userName, dataBinding); + } else { + this.saveByUserName(user, userName, dataBinding); + } + } + } + + @Override + public AdminUserDataBinding getKey(User user, KeyUserDataBinding key) { + return this.getKey(user, key.getKey()); + } + + @Override + public AdminUserDataBinding getKey(final User user, final String key) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"),"user.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"asdfasd"}}, + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("user.$id").is(new ObjectId(user.getId())) + .and("key").is(key) + ) + ), + AdminUserDataBinding.class, + AdminUserDataBinding.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + return null; + } + return results.getUniqueMappedResult(); + } + + @Override + public AdminUserDataBinding getKeyByUserName(User user, String userName, KeyUserDataBinding key) { + return this.getKeyByUserName(user, userName, key.getKey()); + } + + @Override + public AdminUserDataBinding getKeyByUserName(final User user, final String userName, final String key) { + if (user.getUserName().equals(userName)) { + return this.getKey(user, key); + } + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"asdfasd"}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$objectToArray:"$user"}}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$arrayElemAt:["$user.v", 1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as: "user" + * }}, + * {$unwind:"$user"}, + * {$match:{"user.userName":"OwnerSiapi5e28b392637e580001e465d3", "key":"asdfasd"}} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("key").is(key)), + project("key", "value", "metadata", "owner").and(context -> new BasicDBObject("$objectToArray", "$user")).as("user"), + project("key", "value", "metadata", "owner").and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup(documentNameConfig.getNameCollectionUser(), "user", "_id", "user"), + unwind("$user"), + match(where("user.userName").is(userName).and("key").is(key)) + ), + documentNameConfig.getNameCollectionAdminUserDataBinding(), + AdminUserDataBinding.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + return null; + } + return results.getUniqueMappedResult(); + } + + @Override + public boolean contains(User user, KeyUserDataBinding key) { + return this.contains(user, key.getKey()); + } + + @Override + public boolean contains(final User user, final String key) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"),"user.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"asdfasd"}}, + * ]) + */ + return this.mongoTemplate.exists( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("user.$id").is(new ObjectId(user.getId())) + .and("key").is(key) + ), AdminUserDataBinding.class); + } + + @Override + public boolean containsByUserNameAndKey(User user, String userName, KeyUserDataBinding key) { + return this.containsByUserNameAndKey(user, userName, key); + } + + @Override + public boolean containsByUserNameAndKey(final User user, final String userName, final String key) { + if (user.getUserName().equals(userName)) { + return this.contains(user, key); + } + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"asdfasd"}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$objectToArray:"$user"}}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$arrayElemAt:["$user.v", 1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as: "user" + * }}, + * {$unwind:"$user"}, + * {$match:{"user.userName":"OwnerSiapi5e28b392637e580001e465d3", "key":"asdfasd"}} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("key").is(key)), + project("key", "value", "metadata", "owner").and(context -> new BasicDBObject("$objectToArray", "$user")).as("user"), + project("key", "value", "metadata", "owner").and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup(documentNameConfig.getNameCollectionUser(), "user", "_id", "user"), + unwind("$user"), + match(where("user.userName").is(userName).and("key").is(key)), + count().as("result") + ), + documentNameConfig.getNameCollectionAdminUserDataBinding(), + BasicAggregateResultCount.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + return false; + //throw new MuttleyNoContentException(UserDataBinding.class, "userName", "Nenhum registro encontrado para o usuário desejado"); + } + return results.getUniqueMappedResult().getResult() > 0; + } + + @Override + public boolean containsByKeyAndValue(User user, KeyUserDataBinding key, String value) { + return this.containsByKeyAndValue(user, key.getKey(), value); + } + + @Override + public boolean containsByKeyAndValue(final User user, final String key, final String value) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"UserColaborador", value:"5ff65e339208bc0007c0d6ba"}}, + * {} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("key").is(key).and("value").is(value)), + count().as("result") + ), + documentNameConfig.getNameCollectionAdminUserDataBinding(), + BasicAggregateResultCount.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + return false; + //throw new MuttleyNoContentException(UserDataBinding.class, "userName", "Erro na consulta"); + } + return results.getUniqueMappedResult().getResult() > 0; + } + + @Override + public boolean containsByKeyAndValueAndUserNameNotEq(User user, String userName, KeyUserDataBinding key, String value) { + return this.containsByKeyAndValueAndUserNameNotEq(user, userName, key.getKey(), value); + } + + @Override + public boolean containsByKeyAndValueAndUserNameNotEq(final User user, final String userName, final String key, final String value) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"UserColaborador", value:"5ff65e339208bc0007c0d6ba"}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$objectToArray:"$user"}}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$arrayElemAt:["$user.v", 1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as: "user" + * }}, + * {$unwind:"$user"}, + * {$match:{"user.userName":{$ne:"0756143600010711000523BRUNA.Ab"}}} + * ]) + */ + final List operations = new LinkedList<>( + asList( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("key").is(key).and("value").is(value)) + ) + ); + /*if (!StringUtils.isEmpty(userName)) { + operations.addAll( + asList( + project("key", "value", "metadata", "historic", "owner").and(context -> new BasicDBObject("$objectToArray", "$user")).as("user"), + project("key", "value", "metadata", "historic", "owner").and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup(documentNameConfig.getNameCollectionUser(), "user", "_id", "user"), + unwind("$user") + //match(where("user.userName").ne(userName).and("key").is(key)) + ) + ); + }*/ + //operations.add(count().as("result")); + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + operations + ), + documentNameConfig.getNameCollectionAdminUserDataBinding(), + AdminUserDataBinding.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + //se não encontrou nada é sinal que o databinding está disponivel + return false; + } else { + return results.getMappedResults() + .parallelStream() + .filter(it -> !it.getUser().getUserName().equals(userName)) + .count() > 0; + } + } + + @Override + public UserData getUserBy(final User user, final KeyUserDataBinding key, final String value) { + return this.getUserBy(user, key.getKey(), value); + } + + @Override + public UserData getUserBy(final User user, final String key, final String value) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id": ObjectId("5e28b3e3637e580001e465d6"), "key":"UserColaborador", "value":"5e28bcf86f985c00017e7a28"}}, + * {$project:{user:1}} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("key").is(key).and("value").is(value)) + ), + AdminUserDataBinding.class, + AdminUserDataBinding.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + return null; + } + return results.getUniqueMappedResult().getUser(); + } + + private final void checkIndex(final User user, final AdminUserDataBinding dataBinding) { + if (StringUtils.isEmpty(dataBinding.getId())) { + if (this.exists(user, dataBinding)) { + throw new MuttleyConflictException(AdminUserDataBinding.class, "key", "Jás existe um registro com essas informações"); + } + } else { + if (this.mongoTemplate.exists( + new Query( + where("id").ne(dataBinding.getObjectId()) + .and("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("user.$id").is(new ObjectId(dataBinding.getUser().getId())) + .and("key").is(dataBinding.getKey()) + ), AdminUserDataBinding.class)) { + throw new MuttleyConflictException(AdminUserDataBinding.class, "key", "Jás existe um registro com essas informações"); + } + } + + if (dataBinding.getKey().isUnique()) { + //verificando se outro usário já tem esse databinding + if (this.containsByKeyAndValueAndUserNameNotEq(user, user.getUserName(), dataBinding.getKey().getKey(), dataBinding.getValue())) { + throw new MuttleyBadRequestException(AdminUserDataBinding.class, "key", "Já existe um usuário que possui ligação com " + dataBinding.getKey().getDisplayKey() + " informado(a)"); + } + } + } + + private final void checkIndex(final User user, final String userName, final AdminUserDataBinding dataBinding) { + if (user.getUserName().equals(userName)) { + this.checkIndex(user, dataBinding); + } else { + + //caso o registro não tenha id é sinal que estamos inserindo um novo + //com isso se faz necessário verificar se já não existe o mesmo + if (!dataBinding.contaisObjectId()) { + if (this.exists(user, userName, dataBinding.getKey().getKey())) { + throw new MuttleyConflictException(AdminUserDataBinding.class, "key", "Jás existe um registro com essas informações"); + } + } + + if (dataBinding.getKey().isUnique()) { + //verificando se outro usário já tem esse databinding + if (this.containsByKeyAndValueAndUserNameNotEq(user, userName, dataBinding.getKey().getKey(), dataBinding.getValue())) { + throw new MuttleyBadRequestException(AdminUserDataBinding.class, "key", "Já existe um usuário que possui ligação com " + dataBinding.getKey().getDisplayKey() + " informado(a)"); + } + } + } + } + + private boolean exists(final User user, AdminUserDataBinding dataBinding) { + return this.repository.exists("owner.$id", user.getCurrentOwner().getObjectId(), "user.$id", new ObjectId(dataBinding.getUser().getId()), "key", dataBinding.getKey().getKey()); + } + + private boolean exists(final User user, final String userName, final String key) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6")}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$objectToArray:"$user"}}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$arrayElemAt:["$user.v", 1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as: "user" + * }}, + * {$unwind:"$user"}, + * {$match:{"user.userName":"OwnerSiapi5e28b392637e580001e465d3"}} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId())), + project("key", "value").and(context -> new BasicDBObject("$objectToArray", "$user")).as("user"), + project("key", "value").and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup(documentNameConfig.getNameCollectionUser(), "user", "_id", "user"), + unwind("$user"), + match(where("user.userName").is(userName)), + count().as("result") + ), + documentNameConfig.getNameCollectionAdminUserDataBinding(), + BasicAggregateResultCount.class + ); + + return results != null && results.getUniqueMappedResult() != null && results.getUniqueMappedResult().getResult() > 0; + } + + private String loadIdFrom(final User user, final AdminUserDataBinding dataBinding) { + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("user.$id").is(new ObjectId(dataBinding.getUser().getId())) + .and("key").is(dataBinding.getKey()) + ), + project().and("_id").as("result") + ), + documentNameConfig.getNameCollectionAdminUserDataBinding(), + BasicAggregateResult.class + ); + return results.getUniqueMappedResult().getResult().toString(); + } + + private void checkBasicInfos(final User user, final AdminUserDataBinding userDataBinding) { + userDataBinding.setOwner(user.getCurrentOwner()); + if (StringUtils.isEmpty(userDataBinding.getId())) { + this.metadataService.generateNewMetadataFor(user, userDataBinding); + } else { + this.metadataService.generateMetaDataUpdateFor(user, this.repository.loadMetadata(userDataBinding), userDataBinding); + } + } + + + protected User findByUserName(final String userName) { + final UserResolverEvent event = new UserResolverEvent(userName); + this.eventPublisher.publishEvent(event); + return event.getUserResolver(); + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/MuttleyHeadersMetadataServiceImpl.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/MuttleyHeadersMetadataServiceImpl.java new file mode 100644 index 00000000..4f78c9d3 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/MuttleyHeadersMetadataServiceImpl.java @@ -0,0 +1,45 @@ +package br.com.muttley.admin.server.service.impl; + +import br.com.muttley.feign.service.service.MuttleyHeadersMetadataService; +import br.com.muttley.headers.components.MuttleyCurrentVersion; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +import static br.com.muttley.headers.model.MuttleyHeader.KEY_ADMIN_SERVER; + +/** + * @author Joel Rodrigues Moreira 22/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class MuttleyHeadersMetadataServiceImpl implements MuttleyHeadersMetadataService { + private boolean resolved = false; + private MuttleyCurrentVersion currentVersion; + @Autowired + protected ObjectProvider currentVersionProvider; + + @Override + public Map getHeadersMetadata() { + final Map headers = new HashMap<>(1); + try { + if (!resolved) { + resolved = true; + this.currentVersion = currentVersionProvider.getIfAvailable(); + } + if (currentVersion != null) { + headers.put(KEY_ADMIN_SERVER, currentVersion.getCurrenteFromServer()); + } else { + headers.put(KEY_ADMIN_SERVER, null); + } + } catch (final BeanCreationException ex) { + headers.put(KEY_ADMIN_SERVER, null); + } + return headers; + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/NoSecurityAdminOwnerServiceImpl.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/NoSecurityAdminOwnerServiceImpl.java new file mode 100644 index 00000000..d9ee7809 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/NoSecurityAdminOwnerServiceImpl.java @@ -0,0 +1,58 @@ +package br.com.muttley.admin.server.service.impl; + +import br.com.muttley.admin.server.repository.AdminOwnerRepository; +import br.com.muttley.admin.server.service.NoSecurityAdminOwnerService; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.Passaport; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.stereotype.Service; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class NoSecurityAdminOwnerServiceImpl extends AbstractNoSecurityService implements NoSecurityAdminOwnerService { + protected final AdminOwnerRepository repository; + private final MongoTemplate template; + + @Autowired + public NoSecurityAdminOwnerServiceImpl(final MongoTemplate template, final AdminOwnerRepository repository) { + this.template = template; + this.repository = repository; + } + + @Override + public AdminOwner findByName(final String name) { + //validando contexto de execução + this.validateContext(); + final AdminOwner owner = this.repository.findByName(name); + if (owner == null) { + throw new MuttleyNotFoundException(Owner.class, "name", "Registro não encontrado"); + } + return owner; + } + + @Override + public AdminOwner save(final AdminOwner owner) { + //validando contexto de execução + this.validateContext(); + //verificando se realmente está criando um novo registro + if (owner.getId() != null) { + throw new MuttleyBadRequestException(Passaport.class, "id", "Não é possível criar um registro com um id existente"); + } + //validando dados + this.validator.validate(owner); + /*//verificando precondições + this.checkPrecondictionSave(user, value); + this.beforeSave(user, value);*/ + return this.repository.save(owner); + } + + +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/NoSecurityAdminPassaportServiceImpl.java b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/NoSecurityAdminPassaportServiceImpl.java new file mode 100644 index 00000000..cf193727 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/java/br/com/muttley/admin/server/service/impl/NoSecurityAdminPassaportServiceImpl.java @@ -0,0 +1,40 @@ +package br.com.muttley.admin.server.service.impl; + +import br.com.muttley.admin.server.repository.AdminPassaportRepository; +import br.com.muttley.admin.server.service.NoSecurityAdminPassaportService; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.model.admin.AdminPassaport; +import br.com.muttley.model.security.Passaport; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class NoSecurityAdminPassaportServiceImpl extends AbstractNoSecurityService implements NoSecurityAdminPassaportService { + private final AdminPassaportRepository repository; + + @Autowired + public NoSecurityAdminPassaportServiceImpl(final AdminPassaportRepository repository) { + this.repository = repository; + } + + @Override + public AdminPassaport save(final AdminPassaport passaport) { + //validando contexto de execução + this.validateContext(); + //verificando se realmente está criando um novo registro + if (passaport.getId() != null) { + throw new MuttleyBadRequestException(Passaport.class, "id", "Não é possível criar um registro com um id existente"); + } + //validando dados + this.validator.validate(passaport); + /*//verificando precondições + this.checkPrecondictionSave(user, value); + this.beforeSave(user, value);*/ + return this.repository.save(passaport); + } +} diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/resources/META-INF/spring.factories b/muttley-admin-server.pom/muttley-admin-server/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..568fe29a --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + br.com.muttley.admin.server.MuttleyAdminServerConfig diff --git a/muttley-admin-server.pom/muttley-admin-server/src/main/resources/application.properties b/muttley-admin-server.pom/muttley-admin-server/src/main/resources/application.properties new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/muttley-admin-server.pom/muttley-admin-server/src/test/java/br/com/muttley/admin/server/MuttleyHeadersMetadataServiceImplAdminServerApplicationTests.java b/muttley-admin-server.pom/muttley-admin-server/src/test/java/br/com/muttley/admin/server/MuttleyHeadersMetadataServiceImplAdminServerApplicationTests.java new file mode 100644 index 00000000..cc22ae5b --- /dev/null +++ b/muttley-admin-server.pom/muttley-admin-server/src/test/java/br/com/muttley/admin/server/MuttleyHeadersMetadataServiceImplAdminServerApplicationTests.java @@ -0,0 +1,14 @@ +package br.com.muttley.admin.server; + +import org.junit.Test; + + +/*@RunWith(SpringRunner.class) +@SpringBootTest*/ +public class MuttleyHeadersMetadataServiceImplAdminServerApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/muttley-admin-server.pom/mvnw b/muttley-admin-server.pom/mvnw new file mode 100755 index 00000000..a16b5431 --- /dev/null +++ b/muttley-admin-server.pom/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-admin-server.pom/mvnw.cmd b/muttley-admin-server.pom/mvnw.cmd new file mode 100644 index 00000000..c8d43372 --- /dev/null +++ b/muttley-admin-server.pom/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/muttley-admin-server.pom/pom.xml b/muttley-admin-server.pom/pom.xml new file mode 100644 index 00000000..fb192777 --- /dev/null +++ b/muttley-admin-server.pom/pom.xml @@ -0,0 +1,20 @@ + + + + br.com.muttley + muttley-cloud + ${revision} + + 4.0.0 + pom + + muttley-admin-server.pom + + muttley-admin-server.pom + Demo project for Spring Boot + + + muttley-admin-server + + diff --git a/muttley-config-server/README.adoc b/muttley-config-server/README.adoc index a54373d8..a8df427d 100644 --- a/muttley-config-server/README.adoc +++ b/muttley-config-server/README.adoc @@ -6,7 +6,7 @@ Adicione a dependência em seu serviço de configuração; br.com.br muttley-config-server - 0.0.2-SNAPSHOT + ${revision} ---- diff --git a/muttley-config-server/pom.xml b/muttley-config-server/pom.xml index 8ce898b8..8a62ac1f 100644 --- a/muttley-config-server/pom.xml +++ b/muttley-config-server/pom.xml @@ -1,11 +1,11 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} jar diff --git a/muttley-discovery-server/README.adoc b/muttley-discovery-server/README.adoc index 32c99c94..7a7292dd 100644 --- a/muttley-discovery-server/README.adoc +++ b/muttley-discovery-server/README.adoc @@ -6,7 +6,7 @@ Adicione a dependência em seu serviço de configuração; br.com.muttley muttley-discovery-server - 0.0.2-SNAPSHOT + ${revision} ---- diff --git a/muttley-discovery-server/pom.xml b/muttley-discovery-server/pom.xml index 5b2e8e54..f0430815 100644 --- a/muttley-discovery-server/pom.xml +++ b/muttley-discovery-server/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar diff --git a/muttley-discovery-server/src/test/java/br/com/muttley/muttleydiscoveryserver/MuttleyDiscoveryServerApplicationTests.java b/muttley-discovery-server/src/test/java/br/com/muttley/muttleydiscoveryserver/MuttleyDiscoveryServerApplicationTests.java index a2c12fc0..24565aa6 100644 --- a/muttley-discovery-server/src/test/java/br/com/muttley/muttleydiscoveryserver/MuttleyDiscoveryServerApplicationTests.java +++ b/muttley-discovery-server/src/test/java/br/com/muttley/muttleydiscoveryserver/MuttleyDiscoveryServerApplicationTests.java @@ -1,16 +1,71 @@ package br.com.muttley.muttleydiscoveryserver; +import org.apache.commons.lang.StringUtils; import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; +import sun.security.rsa.RSAUtil; -@RunWith(SpringRunner.class) -@SpringBootTest +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/*@RunWith(SpringRunner.class) +@SpringBootTest*/ public class MuttleyDiscoveryServerApplicationTests { + private static final String TIME24HOURS_PATTERN = "(([+-]|)([01]?[0-9]|2[0-3]):[0-5][0-9])|(([+-]|)([01]?[0-9]|2[0-3])([0-5][0-9]))"; @Test - public void contextLoads() { + public void contextLoads() throws ParseException { + final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + final Date date = df.parse("2019-10-14T11:10:32.399-0300"); + final OffsetDateTime offsetDateTime = OffsetDateTime.parse("2019-10-14T11:10:32.399-0500", dateFormatter); + final ZonedDateTime zonedDateTime = ZonedDateTime.parse("2019-10-14T11:10:32.399-0500", dateFormatter); + + System.out.println("date " + date); + System.out.println("offsetDateTime " + offsetDateTime); + System.out.println("zonedDateTime " + zonedDateTime); + System.out.println("date from offse " + Date.from(offsetDateTime.toInstant())); + + System.out.println(zonedDateTime.getZone()); + System.out.println(ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())); + System.out.println(ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).withZoneSameLocal(ZoneId.of("-0200"))); + System.out.println(); + System.out.println(); + System.out.println(); + + System.out.println(Date.from(ZonedDateTime.now(ZoneId.of("-0300")).toInstant())); + System.out.println(ZonedDateTime.ofInstant(Date.from(ZonedDateTime.now().toInstant()).toInstant(), ZoneId.systemDefault())); + System.out.println(Date.from(ZonedDateTime.ofInstant(Date.from(ZonedDateTime.now().toInstant()).toInstant(), ZoneId.systemDefault()).toInstant())); + System.out.println(ZonedDateTime.ofInstant(Date.from(ZonedDateTime.now().toInstant()).toInstant(), ZoneId.systemDefault()).getZone()); + System.out.println(ZonedDateTime.ofInstant(Date.from(ZonedDateTime.now().toInstant()).toInstant(), ZoneId.systemDefault()).getOffset()); + //System.out.println("date from local " + localDateTime.toInstant(localDateTime.get())); + //System.out.println("other " + OffsetDateTime.from(Date.from(offsetDateTime.toInstant()).toInstant())); + //System.out.println("other " + OffsetDateTime.from(Date.from(offsetDateTime.toInstant()).toInstant()).atZoneSimilarLocal(ZoneOffset.of("America/Sao_Paulo"))); + + final String[] te = "teste".split("\\."); + Stream.of(te).forEach(System.out::println); + System.out.println("teste".split("\\.").length); +//verificando se é já tem lookup a ser gerado + Pattern pattern = Pattern.compile("\\."); + Matcher matcher = pattern.matcher("elep.han.t.$id"); + int count = 0; + //matcher. + while (matcher.find()) { + count++; + } + + System.out.println("count" + count); + System.out.println(StringUtils.countMatches("elephant", "e")); } } diff --git a/muttley-domain-service/pom.xml b/muttley-domain-service/pom.xml index 46b9351f..55b9336d 100644 --- a/muttley-domain-service/pom.xml +++ b/muttley-domain-service/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar @@ -15,6 +15,23 @@ Demo project for Spring Boot + + org.projectlombok + lombok + true + + + + br.com.muttley + muttley-headers + provided + + + + br.com.muttley + muttley-security + provided + br.com.muttley diff --git a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/ModelSyncService.java b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/ModelSyncService.java new file mode 100644 index 00000000..78ab20af --- /dev/null +++ b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/ModelSyncService.java @@ -0,0 +1,344 @@ +package br.com.muttley.domain.service; + +import br.com.muttley.model.ModelSync; +import br.com.muttley.model.SyncObjectId; +import br.com.muttley.model.security.User; +import org.springframework.security.access.prepost.PreAuthorize; + +import java.util.Collection; +import java.util.Date; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface ModelSyncService extends ModelService { + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('create', this.getBasicRoles())," + + " T(br.com.muttley.model.security.Role).toPatternRole('update', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('create', 'MOBILE_' + this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('update', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + void synchronize(final User user, final Collection records); + + /** + * Algumas collections pode ter indice pelo de maneira diferente, + * por conta disso devemos validar o index do sync de acordo com o indice implementado + */ + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + void checkSyncIndex(final User user, final T value); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('update', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('update', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + T updateBySync(final User user, final T value); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + T findBySync(final User user, final String sync); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + T findReferenceBySync(final User user, final String sync); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + T findByIdOrSync(final User user, final String idSync); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + Date getLastModify(final User user); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('delete', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('delete', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + void deleteBySync(final User user, final String sync); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + boolean existSync(final User user, final T value); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + String getIdOfSync(final User user, final String sync); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + String getSyncOfId(final User user, final String id); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + Set getIdsOfSyncs(final User user, final Set syncs); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + Set getSyncsOfIds(final User user, final Set ids); +} diff --git a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/Service.java b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/Service.java index 0b51d228..f0f022c6 100644 --- a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/Service.java +++ b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/Service.java @@ -1,11 +1,13 @@ package br.com.muttley.domain.service; import br.com.muttley.model.Document; -import br.com.muttley.model.Historic; import br.com.muttley.model.security.User; +import org.springframework.security.access.prepost.PreAuthorize; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; /** * @author Joel Rodrigues Moreira on 23/02/18. @@ -13,6 +15,11 @@ * @project muttley-cloud */ public interface Service { + + boolean isCheckRole(); + + String[] getBasicRoles(); + /** * Este método é sempre chamado antes de persistir algum registro no banco de dados, * e também antes de se chamar o metodo {@link #beforeSave(User, Document)}. @@ -45,6 +52,28 @@ public interface Service { * @param user -> usuário da requisição corrente * @param value -> registro a ser salvo */ + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('create', this.getBasicRoles()) " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('create', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) T save(final User user, final T value); /** @@ -53,9 +82,112 @@ public interface Service { * @param user -> usuário da requisição corrente * @param value -> registro a ser salvo */ - void afterSave(final User user, final T value); + /** + * Este método é sempre chamado antes de persistir algum registro no banco de dados, + * e também antes de se chamar o metodo {@link #beforeSave(User, Collection)}. + * Caso queira realizar algum tipo de validação antes de salvar algo, sobrescreva esse método + * com sua regra de negócio jutamente com suas exceptions. + *

+ * Por padrao esse metodo faz uma interação item por item chamando o methodo {@link #checkPrecondictionSave(User, Document)}. + * + * @param user -> usuário da requisição corrente + * @param values -> registro a ser salvo + */ + void checkPrecondictionSave(final User user, final Collection values); + + /** + * Este método é chamado toda vez antes de se salvar algum registro e depois de se chamar o metodo + * {@link #checkPrecondictionSave(User, Collection)}. + * Este método não deve ser utilizado para executar válidações mas sim para log's, pequenos ajuste + * e ou regras de négocio antes de se salvar um registro + *

+ * Por padrao esse metodo faz uma interação item por item chamando o methodo {@link #beforeSave(User, Document)}. + * + * @param user -> usuário da requisição corrente + * @param values -> registro a ser salvo + */ + void beforeSave(final User user, final Collection values); + + /** + * Salva um novo registro no banco de dados, + * garantindo sempre que ele esteja relacionado a um usuário/owner. + *

+ * Antes de ser salvo qualquer registro, primeiramente é executado a regra + * de negócio presente no metodo {@link #checkPrecondictionSave(User, Document)} + * + * @param user -> usuário da requisição corrente + * @param values -> registro a ser salvo + */ + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('create', this.getBasicRoles()) " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('create', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + Collection save(final User user, final Collection values); + + /** + * Salva um novo registro no banco de dados, + * garantindo sempre que ele esteja relacionado a um usuário/owner. + *

+ * Antes de ser salvo qualquer registro, primeiramente é executado a regra + * de negócio presente no metodo {@link #checkPrecondictionSave(User, Collection)} + * + * @param user -> usuário da requisição corrente + * @param values -> registro a ser salvo + */ + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('create', this.getBasicRoles()) " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('create', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + void saveOnly(final User user, final Collection values); + + /** + * Este metodo é chamado toda vez em que é salvo um registro no banco de dados + *

+ * Por padrao esse metodo faz uma interação item por item chamando o methodo {@link #afterSave(User, Document)}. + * + * @param user -> usuário da requisição corrente + * @param values -> registro a ser salvo + */ + void afterSave(final User user, final Collection values); + /** * Este método é sempre chamado antes de persistir a atualização de algum registro no banco de dados. * Caso queira realizar algum tipo de validação antes de atualizar algo, sobrescreva esse método @@ -87,6 +219,28 @@ public interface Service { * @param user -> usuário da requisição corrente * @param value -> registro a ser atualizado */ + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('update', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('update', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) T update(final User user, final T value); /** @@ -98,38 +252,192 @@ public interface Service { void afterUpdate(final User user, final T value); + /** + * Este método é sempre chamado antes de persistir a atualização de algum registro no banco de dados. + * Caso queira realizar algum tipo de validação antes de atualizar algo, sobrescreva esse método + * com sua regra de negócio jutamente com suas exceptions. + * + * @param user -> usuário da requisição corrente + * @param values -> registros a ser atualizado + */ + void checkPrecondictionUpdate(final User user, final Collection values); + + /** + * Este método é chamado toda vez antes de se atualizar algum registro e depois de se chamar o metodo + * {@link #checkPrecondictionUpdate(User, Document)}. + * Este método não deve ser utilizado para executar válidações mas sim para log's, pequenos ajuste + * e ou regras de négocio antes de se salvar a alteração em algum registro + * + * @param user -> usuário da requisição corrente + * @param values -> registro a ser salvo + */ + void beforeUpdate(final User user, final Collection values); + + /** + * Atualiza um novo registro no banco de dados, + * garantindo sempre que ele esteja relacionado a um usuário/owner. + *

+ * Antes de ser atualizado qualquer registro, primeiramente é executado a regra + * de negócio presente no metodo checkPrecondictionUpdate + * + * @param user -> usuário da requisição corrente + * @param values -> registro a ser atualizado + */ + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('update', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('update', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + void update(final User user, final Collection values); + + /** + * Este metodo é chamado toda vez em que é atualizado um registro no banco de dados + * + * @param user -> usuário da requisição corrente + * @param value -> registro a ser salvo + */ + + void afterUpdate(final User user, final Collection value); + /** * Busca um registro pelo id * * @param user -> usuário da requisição corrente * @param id -> id procurado */ + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) T findById(final User user, final String id); /** - * Pega o primeiro registro que encontrar + * Busca um registro pelo id e não carrega os demais campos * * @param user -> usuário da requisição corrente + * @param id -> id procurado */ - T findFirst(final User user); + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + T findReferenceById(final User user, final String id); /** - * Busca o histórico de um determinado registro + * Busca varios registros pelo id * * @param user -> usuário da requisição corrente - * @param id -> id do registro a ser buscado - * @return Historic + * @param ids -> array de id a ser procurado */ - Historic loadHistoric(final User user, final String id); + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + Set findByIds(User user, String[] ids); /** - * Busca o histórico de um determinado registro + * Pega o primeiro registro que encontrar * - * @param user -> usuário da requisição corrente - * @param value -> instancia do registro a ser buscado - * @return Historic + * @param user -> usuário da requisição corrente */ - Historic loadHistoric(final User user, final T value); + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + T findFirst(final User user); /** * Qualquer regra de négocio que valide o processo de delete deve ser implementada @@ -140,6 +448,15 @@ public interface Service { */ void checkPrecondictionDelete(final User user, final String id); + /** + * Qualquer regra de négocio que valide o processo de delete deve ser implementada + * nesse método através de sobrescrita + * + * @param user -> usuário da requisição corrente + * @param value -> registro que está sendo deletado + */ + void checkPrecondictionDelete(final User user, final T value); + /** * Este método é chamado toda vez antes de se deltetar algum registro e depois de se chamar o metodo * {@link #checkPrecondictionDelete(User, String)}. @@ -157,6 +474,28 @@ public interface Service { * @param user -> usuário da requisição corrente * @param id -> id procurado */ + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('delete', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('delete', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) void deleteById(final User user, final String id); @@ -171,7 +510,7 @@ public interface Service { /** * Este método é chamado toda vez antes de se deltetar algum registro e depois de se chamar o metodo - * {@link #checkPrecondictionDelete(User, String)}. + * {@link #checkPrecondictionDelete(User, T)}. * Este método não deve ser utilizado para executar válidações mas sim para log's, pequenos ajuste * e ou regras de négocio antes de se deletar algum registro * @@ -188,6 +527,28 @@ public interface Service { * @param user -> usuário da requisição corrente * @param value -> registro a ser deletado */ + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('delete', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('delete', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) void delete(final User user, final T value); /** @@ -206,7 +567,30 @@ public interface Service { * @param user -> usuário da requisição corrente * @param allRequestParams -> Todos os parametros passado na query da requisição */ - Long count(final User user, final Map allRequestParams); + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + Long count(final User user, final Map allRequestParams); /** * Realiza o processo de listagem com base nos critérios @@ -215,10 +599,140 @@ public interface Service { * @param user -> usuário da requisição corrente * @param allRequestParams -> Todos os parametros passado na query da requisição */ - List findAll(final User user, final Map allRequestParams); + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + List findAll(final User user, final Map allRequestParams); /** * Verifica se existe algum registro no DB */ + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) boolean isEmpty(final User user); + + /** + * Recupera valor de uma propriedade especifica de um registro no banco de dados, levando em consideração o id + */ + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + Object getPropertyValueFromId(final User user, final String id, final String property); + + /** + * Recupera valor de propriedades especificas de registro no banco de dados, levando em consideração a condição de filtro + */ + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + Object getPropertyValueFrom(final User user, final Map condictions, final String property); + + /** + * Recupera valor de propriedades especificas de um registro no banco de dados, levando em consideração a condição de filtro + */ + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles()), " + + " T(br.com.muttley.model.security.Role).toPatternRole('simple_use', this.getBasicRoles()) " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) + Object[] getPropertiesValueFrom(final User user, final Map condictions, final String... properties); } diff --git a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/Validator.java b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/Validator.java index bb5b6f4b..fa865a9b 100644 --- a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/Validator.java +++ b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/Validator.java @@ -6,6 +6,7 @@ import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; +import java.util.Collection; import java.util.Set; import static org.springframework.util.CollectionUtils.isEmpty; @@ -29,4 +30,8 @@ public final void validate(final Object o) { throw new ConstraintViolationException("test", violations); } } + + public final void validateCollection(final Collection value) { + value.stream().forEach(this::validate); + } } diff --git a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/factory/query/AbstractAggregationFactory.java b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/factory/query/AbstractAggregationFactory.java new file mode 100644 index 00000000..5af90095 --- /dev/null +++ b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/factory/query/AbstractAggregationFactory.java @@ -0,0 +1,16 @@ +package br.com.muttley.domain.service.factory.query; + +import br.com.muttley.model.security.User; +import br.com.muttley.security.infra.service.AuthService; +import org.springframework.data.mongodb.core.aggregation.Aggregation; + +/** + * @author Joel Rodrigues Moreira 17/08/2020 + * joel.databox@gmail.com + * @project muttley-cloud + *

+ * Interface básica para armazenar a implementação básica uma fábrica de query + */ +public abstract class AbstractAggregationFactory { + public abstract Aggregation factory(final AuthService authService, final User user); +} diff --git a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/factory/query/AbstractQueryFactory.java b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/factory/query/AbstractQueryFactory.java new file mode 100644 index 00000000..3362ed44 --- /dev/null +++ b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/factory/query/AbstractQueryFactory.java @@ -0,0 +1,16 @@ +package br.com.muttley.domain.service.factory.query; + +import br.com.muttley.model.security.User; +import br.com.muttley.security.infra.service.AuthService; +import org.springframework.data.mongodb.core.query.Query; + +/** + * @author Joel Rodrigues Moreira 17/08/2020 + * joel.databox@gmail.com + * @project muttley-cloud + *

+ * Interface básica para armazenar a implementação básica uma fábrica de query + */ +public abstract class AbstractQueryFactory { + public abstract Query factory(final AuthService authService, final User user); +} diff --git a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/LocalModelServiceImpl.java b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/LocalModelServiceImpl.java new file mode 100644 index 00000000..c2905997 --- /dev/null +++ b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/LocalModelServiceImpl.java @@ -0,0 +1,21 @@ +package br.com.muttley.domain.service.impl; + +import br.com.muttley.localcache.services.impl.AbstractLocalModelServiceImpl; +import br.com.muttley.model.Model; +import br.com.muttley.redis.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author Joel Rodrigues Moreira on 02/09/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class LocalModelServiceImpl extends AbstractLocalModelServiceImpl { + + @Autowired + public LocalModelServiceImpl(final RedisService redisService) { + super(redisService); + } +} diff --git a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/ModelServiceImpl.java b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/ModelServiceImpl.java index 2b1b0e90..d20abd1c 100644 --- a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/ModelServiceImpl.java +++ b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/ModelServiceImpl.java @@ -1,46 +1,82 @@ package br.com.muttley.domain.service.impl; import br.com.muttley.domain.service.ModelService; +import br.com.muttley.domain.service.impl.utils.MetadataAndIdModel; import br.com.muttley.exception.throwables.MuttleyBadRequestException; import br.com.muttley.exception.throwables.MuttleyNoContentException; -import br.com.muttley.exception.throwables.MuttleyNotFoundException; -import br.com.muttley.model.Historic; +import br.com.muttley.exception.throwables.repository.MuttleyRepositoryOwnerNotInformedException; +import br.com.muttley.model.BasicAggregateResultCount; +import br.com.muttley.model.Document; import br.com.muttley.model.Model; +import br.com.muttley.model.security.Owner; import br.com.muttley.model.security.User; +import br.com.muttley.mongo.service.infra.AggregationUtils; import br.com.muttley.mongo.service.repository.CustomMongoRepository; +import com.mongodb.BasicDBObject; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.core.BulkOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import static br.com.muttley.model.security.domain.Domain.PUBLIC; +import static br.com.muttley.model.security.domain.Domain.RESTRICTED; +import static java.util.Arrays.asList; import static java.util.Objects.isNull; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static org.springframework.data.mongodb.core.BulkOperations.BulkMode.UNORDERED; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.query.Criteria.where; /** * @author Joel Rodrigues Moreira on 30/01/18. * @project muttley-cloud */ public abstract class ModelServiceImpl extends ServiceImpl implements ModelService { - final CustomMongoRepository repository; + protected final CustomMongoRepository repository; - public ModelServiceImpl(final CustomMongoRepository repository, final Class clazz) { - super(repository, clazz); + public ModelServiceImpl(final CustomMongoRepository repository, final MongoTemplate mongoTemplate, final Class clazz) { + super(repository, mongoTemplate, clazz); this.repository = repository; } @Override public T save(final User user, final T value) { //verificando se realmente está criando um novo registro - if (value.getId() != null) { - throw new MuttleyBadRequestException(clazz, "id", "Não é possível criar um registro com um id existente"); - } + checkIdForSave(value); + //setando o dono do registro value.setOwner(user); - //garantindo que o históriconão ficará nulo - value.setHistoric(this.createHistoric(user)); - //validando dados - this.validator.validate(value); + //garantindo que o metadata ta preenchido + this.generateNewMetadataFor(user, value, null); + //processa regra de negocio antes de qualquer validação + this.beforeSave(user, value); //verificando precondições this.checkPrecondictionSave(user, value); - return repository.save(user.getCurrentOwner(), value); + //validando dados do objeto + this.validator.validate(value); + final T salvedValue = repository.save(user.getCurrentOwner(), value); + //realizando regras de enegocio depois do objeto ter sido salvo + this.afterSave(user, salvedValue); + //valor salvo + return salvedValue; } @Override @@ -48,6 +84,25 @@ public void checkPrecondictionSave(final User user, final T value) { } + @Override + public Collection save(final User user, final Collection values) { + //verificando se realmente está criando um novo registro + checkIdForSave(values); + //garantindo que o metadata ta preenchido + this.generateNewMetadataFor(user, values, null); + //processa regra de negocio antes de qualquer validação + this.beforeSave(user, values); + //verificando precondições + this.checkPrecondictionSave(user, values); + //validando dados do objeto + this.validator.validateCollection(values); + final Collection otherValues = repository.save(user.getCurrentOwner(), values); + //realizando regras de enegocio depois do objeto ter sido salvo + this.afterSave(user, otherValues); + //valor salvo + return otherValues; + } + @Override public T update(final User user, final T value) { //verificando se realmente está alterando um registro @@ -55,17 +110,22 @@ public T update(final User user, final T value) { throw new MuttleyBadRequestException(clazz, "id", "Não é possível alterar um registro sem informar um id válido"); } //verificando se o registro realmente existe - if (!this.repository.exists(value.getId())) { - throw new MuttleyNotFoundException(clazz, "id", "Registro não encontrado"); + if (!this.repository.exists(value)) { + throw this.createNotFoundExceptionById(user, value.getId()); + //throw new MuttleyNotFoundException(clazz, "id", "Registro não encontrado"); } value.setOwner(user); - //gerando histórico de alteração - value.setHistoric(generateHistoricUpdate(user, repository.loadHistoric(user.getCurrentOwner(), value))); - //validando dados - this.validator.validate(value); + //gerando metadata de alteração + this.metadataService.generateMetaDataUpdateFor(user, this.repository.loadMetaData(user.getCurrentOwner(), value), value); + //processa regra de negocio antes de qualquer validação + this.beforeUpdate(user, value); //verificando precondições checkPrecondictionUpdate(user, value); - return repository.save(user.getCurrentOwner(), value); + //validando dados + this.validator.validate(value); + final T salvedValue = repository.save(user.getCurrentOwner(), value); + afterUpdate(user, salvedValue); + return salvedValue; } @Override @@ -73,64 +133,169 @@ public void checkPrecondictionUpdate(final User user, final T value) { } + @Override + public void update(final User user, final Collection values) { + //verificando se realmente está alterando um registro + this.checkIdForUpdate(values); + //verificando se o registro realmente existe + + final List metadatasAndIds = this.loadIdsAndMetadatasAndHisotricsFor(user, values); + final Map> agroupedValues = values.stream() + .collect(groupingBy(it -> { + final Optional itemOpt = metadatasAndIds + .parallelStream() + .filter(itMeta -> Objects.equals(it.getId(), itMeta.getId())) + .findFirst(); + if (itemOpt.isPresent()) { + final MetadataAndIdModel metadataAndIdModel = itemOpt.get(); + this.metadataService.generateMetaDataUpdateFor(user, metadataAndIdModel.getMetadata(), it); + return true; + } + return false; + })); + + final List valuesForSave = agroupedValues.get(Boolean.TRUE); + + if (!CollectionUtils.isEmpty(valuesForSave)) { + //gerando metadata de alteração + //valuesForSave.forEach(it -> generateMetaDataUpdate(user, it)); + //gerando histórico de alteração + //valuesForSave.forEach(it -> it.setHistoric(generateHistoricUpdate(user, repository.loadHistoric(it)))); + //processa regra de negocio antes de qualquer validação + beforeUpdate(user, valuesForSave); + //verificando precondições + checkPrecondictionUpdate(user, valuesForSave); + //validando dados + this.validator.validateCollection(valuesForSave); + //criando o bulk para atualização em massa + final BulkOperations operations = this.mongoTemplate.bulkOps(UNORDERED, clazz); + valuesForSave.forEach(it -> { + operations.updateOne( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("_id").is(new ObjectId(it.getId())) + ), + + Update.fromDBObject(new BasicDBObject("$set", it)) + ); + }); + operations.execute(); + //final Collection otherValue = repository.save(user.getCurrentOwner(), valuesForSave); + //realizando regras de enegocio depois do objeto ter sido alterado + afterUpdate(user, valuesForSave); + } + final List valuesNotSaved = agroupedValues.get(Boolean.FALSE); + if (!CollectionUtils.isEmpty(valuesNotSaved)) { + throw this.createNotFoundExceptionById(user, valuesNotSaved.parallelStream().map(Document::getId).collect(toList())); + /*throw new MuttleyNotFoundException(clazz, "id", "Registros não encontrados") + .addDetails("ids", valuesNotSaved.parallelStream().map(Document::getId).collect(toList()));*/ + } + } + + protected List loadIdsAndMetadatasAndHisotricsFor(final User user, final Collection values) { + /** + * db.getCollection("contas-pagar").aggregate([ + * {$match:{"owner.$id":ObjectId("60cc8953279e841c0974da56"), _id:{$in:[ObjectId("60cca012279e8437442bc81c"), ObjectId("60cca012279e8437442bc81d")]}}}, + * {$project:{_id:1, metadata:1}} + * ]) + */ + final AggregationResults ids = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("id").in( + values.parallelStream().map(it -> it.getObjectId()).collect(toSet()) + )), + project("id", "metadata") + ), + clazz, MetadataAndIdModel.class); + if (ids == null || CollectionUtils.isEmpty(ids.getMappedResults())) { + return Collections.emptyList(); + } + return ids.getMappedResults(); + } + @Override public T findById(final User user, final String id) { - if (isNull(id)) { + if (!ObjectId.isValid(id)) { throw new MuttleyBadRequestException(clazz, "id", "informe um id válido"); } final T result = this.repository.findOne(user.getCurrentOwner(), id); if (isNull(result)) { - throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); + throw this.createNotFoundExceptionById(user, id); + //throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); } return result; } @Override - public T findFirst(final User user) { - final T result = this.repository.findFirst(user.getCurrentOwner()); - if (isNull(result)) { - throw new MuttleyNotFoundException(clazz, "user", "Nenhum registro encontrado"); + public T findReferenceById(User user, String id) { + if (!ObjectId.isValid(id)) { + throw new MuttleyBadRequestException(clazz, "id", "informe um id válido"); } - return result; + + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("id").is(new ObjectId(id))), + project("id", "owner") + ) + , clazz, clazz); + if (results == null || results.getUniqueMappedResult() == null) { + throw this.createNotFoundExceptionById(user, id); + //throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); + } + return results.getUniqueMappedResult(); } @Override - public Historic loadHistoric(final User user, final String id) { - final Historic historic = repository.loadHistoric(user.getCurrentOwner(), id); - if (isNull(historic)) { - throw new MuttleyNotFoundException(clazz, "historic", "Nenhum registro encontrado"); + public Set findByIds(final User user, final String[] ids) { + if (ObjectUtils.isEmpty(ids)) { + throw new MuttleyBadRequestException(clazz, "id", "informe pelo menos um id válido"); + } + if (ids.length > 50) { + throw new MuttleyBadRequestException(clazz, "ids", "Quantidade máxima excedida") + .addDetails("min", 1) + .addDetails("max", 50); + } + final Set records = this.repository.findMulti(user.getCurrentOwner(), ids); + if (records == null) { + return Collections.emptySet(); } - return historic; + return records; } @Override - public Historic loadHistoric(final User user, final T value) { - final Historic historic = repository.loadHistoric(user.getCurrentOwner(), value); - if (isNull(historic)) { - throw new MuttleyNotFoundException(clazz, "historic", "Nenhum registro encontrado"); + public T findFirst(final User user) { + final T result = this.repository.findFirst(user.getCurrentOwner()); + if (isNull(result)) { + throw this.createNotFoundExceptionById(user).setField("user"); + //throw new MuttleyNotFoundException(clazz, "user", "Nenhum registro encontrado"); } - return historic; + return result; } @Override public void deleteById(final User user, final String id) { + this.beforeDelete(user, id); checkPrecondictionDelete(user, id); if (!repository.exists(user.getCurrentOwner(), id)) { - throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); + throw this.createNotFoundExceptionById(user, id); + //throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); } this.repository.delete(user.getCurrentOwner(), id); - beforeDelete(user, id); + this.afterDelete(user, id); } @Override public void delete(final User user, final T value) { - checkPrecondictionDelete(user, value.getId()); + this.beforeDelete(user, value); + checkPrecondictionDelete(user, value); if (!repository.exists(user.getCurrentOwner(), value)) { - throw new MuttleyNotFoundException(clazz, "id", value.getId() + " este registro não foi encontrado"); + throw this.createNotFoundExceptionById(user, value.getId()); + //throw new MuttleyNotFoundException(clazz, "id", value.getId() + " este registro não foi encontrado"); } this.repository.delete(user.getCurrentOwner(), value); - beforeDelete(user, value); + this.afterDelete(user, value); } @Override @@ -138,6 +303,11 @@ public void checkPrecondictionDelete(final User user, final String id) { } + @Override + public void checkPrecondictionDelete(final User user, final T value) { + + } + @Override public void beforeDelete(final User user, final T value) { @@ -149,17 +319,47 @@ public void beforeDelete(final User user, final String id) { } @Override - public Long count(final User user, final Map allRequestParams) { - return this.repository.count(user.getCurrentOwner(), allRequestParams); + public Long count(final User user, final Map allRequestParams) { + validateOwner(user.getCurrentOwner()); + + + final AggregationResults result = mongoTemplate.aggregate( + newAggregation( + AggregationUtils.createAggregationsCount( + this.entityMetaData, + this.getFilterAggregationOperationByWorkteam(user), + addOwnerQueryParam(user.getCurrentOwner(), allRequestParams), + "result" + ) + ), + clazz, BasicAggregateResultCount.class); + return result.getUniqueMappedResult() != null ? result.getUniqueMappedResult().getResult() : 0l; } @Override - public List findAll(final User user, final Map allRequestParams) { - final List results = this.repository.findAll(user.getCurrentOwner(), allRequestParams); - if (CollectionUtils.isEmpty(results)) { + public List findAll(final User user, final Map allRequestParams) { + //validando se o usuário tem owner informado + this.validateOwner(user.getCurrentOwner()); + //perando o filtro + + final List aggregateions = AggregationUtils.createAggregations(this.entityMetaData, null, addOwnerQueryParam(user.getCurrentOwner(), allRequestParams)); + //adicionando filtro para agregação + aggregateions.addAll(this.getFilterAggregationOperationByWorkteam(user)); + + final AggregationResults results = mongoTemplate.aggregate( + newAggregation(aggregateions), clazz, clazz + ); + + if (results == null || CollectionUtils.isEmpty(results.getMappedResults())) { throw new MuttleyNoContentException(clazz, "user", "não foi encontrado nenhum registro"); } - return results; + return results.getMappedResults(); + } + + @Override + protected AggregationResults createAggregateForLoadProperties(final User user, final Map condictions, final String... properties) { + condictions.put("owner.$id", user.getCurrentOwner().getObjectId()); + return super.createAggregateForLoadProperties(user, condictions, properties); } /** @@ -172,4 +372,88 @@ private final void checkOwner(final User user, final T value) { throw new MuttleyBadRequestException(clazz, "user", "não é possível fazer a alteração do usuário dono do registro"); } } + + + private final void validateOwner(final Owner owner) { + if (owner == null) { + throw new MuttleyRepositoryOwnerNotInformedException(this.clazz); + } + } + + private final Map addOwnerQueryParam(final Owner owner, final Map queryParams) { + final Map query = new LinkedHashMap<>(1); + query.put("owner.$id.$is", owner.getObjectId().toString()); + if (queryParams != null) { + query.putAll(queryParams); + } + return query; + } + + protected List getFilterAggregationOperationByWorkteam(final User user) { + //se for o owner do sistema não faz sentido colocar restrição pois ele tem acesso a tudo + if (user.isOwner()) { + return Collections.emptyList(); + } + return asList( + match( + new Criteria().orOperator( + //pegando todos os registros que forem publicos + where("metadata.domain").is(PUBLIC), + //pegando todos os registros que o proprio usuário criou + where("metadata.historic.createdBy.$id").is(user.getObjectId()), + //pegando todos os registros de subordinados + where("metadata.historic.createdBy.$id") + .in( + user.getWorkTeamDomain() + .getSubordinates() + .parallelStream() + .map(it -> it.getUser().getObjectId()) + .collect(toSet()) + ), + //pegando todos os registros dos colegas presentes no workteam + where("metadata.historic.createdBy.$id") + .in( + user.getWorkTeamDomain() + .getColleagues() + .parallelStream() + .map(it -> it.getUser().getObjectId()) + .collect(toSet()) + ).and("metadata.domain").is(RESTRICTED) + ) + ) + ); + } + + protected List getFilterCriteriaByWorkteam(final User user) { + if (user.isOwner()) { + return null; + } + return asList( + + new Criteria().orOperator( + //pegando todos os registros que forem publicos + where("metadata.domain").is(PUBLIC), + //pegando todos os registros que o proprio usuário criou + where("metadata.historic.createdBy.$id").is(user.getObjectId()), + //pegando todos os registros de subordinados + where("metadata.historic.createdBy.$id") + .in( + user.getWorkTeamDomain() + .getSubordinates() + .parallelStream() + .map(it -> it.getUser().getObjectId()) + .collect(toSet()) + ), + //pegando todos os registros dos colegas presentes no workteam + where("metadata.historic.createdBy.$id") + .in( + user.getWorkTeamDomain() + .getColleagues() + .parallelStream() + .map(it -> it.getUser().getObjectId()) + .collect(toSet()) + ).and("metadata.domain").is(RESTRICTED) + ) + ); + } } diff --git a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/ModelSyncServiceImpl.java b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/ModelSyncServiceImpl.java new file mode 100644 index 00000000..0bd1514f --- /dev/null +++ b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/ModelSyncServiceImpl.java @@ -0,0 +1,464 @@ +package br.com.muttley.domain.service.impl; + +import br.com.muttley.domain.service.ModelSyncService; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyConflictException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.localcache.services.LocalModelService; +import br.com.muttley.model.Historic; +import br.com.muttley.model.ModelSync; +import br.com.muttley.model.SyncObjectId; +import br.com.muttley.model.security.User; +import br.com.muttley.mongo.service.repository.CustomMongoRepository; +import com.google.common.collect.Lists; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.joining; +import static org.springframework.data.domain.Sort.Direction.DESC; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.sort; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class ModelSyncServiceImpl extends ModelServiceImpl implements ModelSyncService { + + protected final MongoTemplate mongoTemplate; + protected final CustomMongoRepository repository; + protected final Integer MAX_RECORD_SYNC; + @Autowired + protected LocalModelService localModelService; + + + public ModelSyncServiceImpl(final CustomMongoRepository repository, final Class clazz, final MongoTemplate mongoTemplate) { + this(repository, clazz, mongoTemplate, 100); + } + + public ModelSyncServiceImpl(final CustomMongoRepository repository, final Class clazz, final MongoTemplate mongoTemplate, final Integer maxRecordSync) { + super(repository, mongoTemplate, clazz); + this.mongoTemplate = mongoTemplate; + this.repository = repository; + this.MAX_RECORD_SYNC = maxRecordSync; + } + + + @Override + public T save(final User user, final T value) { + //validando o sync + this.checkSyncIndex(user, value); + this.checkDtSync(user, value); + return super.save(user, value); + } + + @Override + public T update(final User user, final T value) { + //devemos garantir que ninguem alterou o sync do registro + final T other = findById(user, value.getId()); + //garantindo o sync do registro + value.setSync(other.getSync()); + this.checkDtSync(user, value); + final T updatedValue = super.update(user, value); + //removendo do cache temporario caso exista + this.localModelService.expire(user, clazz, updatedValue.getId()); + this.localModelService.expire(user, clazz, updatedValue.getSync()); + return updatedValue; + } + + @Override + public void update(User user, Collection values) { + //devemos garantir que ninguem alterou o sync do registro + final Set syncObjectIds = this.getSyncsOfIds(user, values.parallelStream() + .map(T::getId) + .collect(Collectors.toSet())); + values.parallelStream() + .forEach(it -> { + it.setSync( + syncObjectIds + .parallelStream() + .filter(itt -> itt.getId().equals(it.getId())) + .findFirst() + .orElseThrow(MuttleyBadRequestException::new) + .getSync() + ).setDtSync(new Date()); + }); + super.update(user, values); + values.forEach(it -> { + this.localModelService.expire(user, clazz, it.getId()); + this.localModelService.expire(user, clazz, it.getSync()); + }); + } + + @Override + public T findById(User user, String id) { + final T result; + /*if (this.localModelService.containsInCahce(user, clazz, id)) { + result = (T) this.localModelService.loadModel(user, clazz, id); + } else {*/ + result = super.findById(user, id); +/* + this.localModelService.addCache(user, result, id); + } +*/ + return result; + } + + @Override + public T findReferenceById(User user, String id) { + final T result; + if (this.localModelService.containsReferenceInCahce(user, clazz, id)) { + result = (T) this.localModelService.loadReference(user, clazz, id); + } else { + if (!ObjectId.isValid(id)) { + throw new MuttleyBadRequestException(clazz, "id", "informe um id válido"); + } + + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("id").is(new ObjectId(id))), + project("id", "owner", "sync") + ) + , clazz, clazz); + if (results == null || results.getUniqueMappedResult() == null) { + throw this.createNotFoundExceptionById(user, id); + //throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); + } + result = results.getUniqueMappedResult(); + this.localModelService.addReferenceCache(user, result, id); + } + + + return result; + } + + + @Override + public void synchronize(final User user, final Collection records) { + //this.mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, clazz).execute(); + if (MAX_RECORD_SYNC != null && records.size() > MAX_RECORD_SYNC) { + throw new MuttleyBadRequestException(this.clazz, null, "Cada requisiçao pode ter no maximo 100 registros"); + } + //quebrando em pacotes de 50 registros + Lists.partition(records instanceof List ? (List) records : new ArrayList<>(records), 50) + .stream() + //processando para carregar os respectivos ids caso os registros não tenha id + .map(subList -> { + this.getIdsOfSyncs(user, subList.parallelStream() + //removendo os itens que já tem id + .filter(it -> StringUtils.isEmpty(it.getId())) + //pegando apenas os ids para busca + .map(ModelSync::getSync) + .collect(Collectors.toSet()) + ).parallelStream() + //com base no syncs carregado, vamos interar a lista e preencher nos objtos que não tem sync + .forEach((final SyncObjectId syncId) -> { + subList.parallelStream() + .filter(it -> syncId.getSync().equals(it.getSync())) + .forEach(it -> it.setId(syncId.getId())); + }); + return subList; + }).forEach(subList -> { + subList.parallelStream() + .peek(it -> this.checkDtSync(user, it)) + //agrupando os dados em registros que possui id ou não + .collect(Collectors.groupingBy(ModelSync::contaisObjectId)) + //interando o agrupamento + .entrySet() + .forEach((key) -> { + if (key.getKey()) { + //update em cascata + super.update(user, key.getValue()); + //removendo do cache temporario caso exista + key.getValue().forEach(it -> { + this.localModelService.expire(user, clazz, it.getId()); + this.localModelService.expire(user, clazz, it.getSync()); + }); + } else { + //insere em cascata + saveOnly(user, key.getValue()); + } + }); + ; + });/* + + + Lists.partition(records instanceof List ? (List) records : new ArrayList<>(records), 50); + + patials.stream() + .forEach(it -> { + this.getIdsOfSyncs(user, it.stream().filter(iit -> StringUtils.isEmpty(iit.getId())).map(iit -> iit.getSync()).collect(Collectors.toSet())) + .forEach(iit -> { + it.stream().filter(iiit -> iit.getSync().equals(iiit.getSync())).forEach(iiit ->); + }); + });*//* + records.stream() + .map((final T value) -> { + if (StringUtils.isEmpty(value.getId())) { + value.setId(this.getIdOfSync(user, value.getSync())); + } + return value; + }) + .forEach((final T value) -> { + if (value.getId() == null) { + this.save(user, value); + } else { + this.update(user, value); + } + });*/ + } + + @Override + public void checkSyncIndex(final User user, final T value) { + if (!StringUtils.isEmpty(value.getSync())) { + //devemos garantir se já não existe o determinado sync + if (this.existSync(user, value)) { + throw new MuttleyConflictException(clazz, "sync", "Já existe um registro com esse sync"); + } + } else { + //Se não foi passado nenhum syn vamos gerar algum + value.setSync("agrifocus-" + new ObjectId(new Date()).toString()); + } + } + + @Override + public T updateBySync(final User user, final T value) { + this.validadeSyncParam(value.getSync()); + //devemos garantir que ninguem irá altera o ObjectId do registro + final T other = findBySync(user, value.getSync()); + //garantindo o id do registro + value.setId(other.getId()); + final T updatedValue = update(user, value); + this.localModelService.expire(user, clazz, updatedValue.getId()); + this.localModelService.expire(user, clazz, updatedValue.getSync()); + return updatedValue; + } + + @Override + public T findBySync(final User user, final String sync) { + this.validadeSyncParam(sync); + final T value; + /*if (this.localModelService.containsInCahce(user, clazz, sync)) { + value = (T) this.localModelService.loadModel(user, clazz, sync); + } else {*/ + + value = this.mongoTemplate + .findOne( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("sync").is(sync) + ), clazz); + if (value == null) { + throw this.createNotFoundExceptionBySync(user, sync); + /*throw new MuttleyNotFoundException(clazz, "sync", "Registro não encontrado!") + .addDetails("syncInformado", sync);*/ + } + /*this.localModelService.addCache(user, (Model) value, sync); + }*/ + return value; + } + + @Override + public T findReferenceBySync(User user, String sync) { + this.validadeSyncParam(sync); + final T result; + if (this.localModelService.containsReferenceInCahce(user, clazz, sync)) { + result = (T) this.localModelService.loadReference(user, clazz, sync); + } else { + + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("sync").is(sync)), + project("id", "owner", "sync") + ) + , clazz, clazz); + if (results == null || results.getUniqueMappedResult() == null) { + throw this.createNotFoundExceptionBySync(user, sync); + /*throw new MuttleyNotFoundException(clazz, "sync", "Registro não encontrado!") + .addDetails("syncInformado", sync);*/ + } + result = results.getUniqueMappedResult(); + this.localModelService.addReferenceCache(user, result, sync); + } + + + return result; + } + + @Override + public T findByIdOrSync(final User user, final String idSync) { + this.validadeIdOrSync(idSync); + if (ObjectId.isValid(idSync)) { + return findById(user, idSync); + } else { + return findBySync(user, idSync); + } + } + + @Override + public Date getLastModify(final User user) { + final AggregationResults results = this.mongoTemplate + .aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId())), + project().and("$historic.dtChange").as("dtChange"), + sort(DESC, "dtChange"), + limit(1) + ), clazz, Historic.class + ); + return results.getUniqueMappedResult() != null ? ((Historic) results.getUniqueMappedResult()).getDtChange() : null; + } + + @Override + public void deleteById(final User user, final String id) { + super.deleteById(user, id); + this.localModelService.expire(user, clazz, id); + } + + @Override + public void delete(final User user, final T value) { + super.delete(user, value); + this.localModelService.expire(user, clazz, value.getId()); + this.localModelService.expire(user, clazz, value.getSync()); + } + + @Override + public void deleteBySync(final User user, final String sync) { + this.validadeSyncParam(sync); + final T value = findBySync(user, sync); + this.delete(user, value); + this.localModelService.expire(user, clazz, value.getId()); + this.localModelService.expire(user, clazz, value.getSync()); + } + + @Override + public boolean existSync(final User user, final T value) { + return this.mongoTemplate.exists( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("sync").is(value.getSync()) + ), clazz + ); + } + + @Override + public String getIdOfSync(final User user, final String sync) { + this.validadeSyncParam(sync); + final Map map = new HashMap<>(); + map.put("sync", sync); + return (String) this.getPropertyValueFrom(user, map, "id"); + /*final AggregationResults results = this.mongoTemplate + .aggregate( + newAggregation( + match( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("sync").is(sync) + ), + project("id"), + limit(1) + ), clazz, clazz + ); + return results.getUniqueMappedResult() != null ? ((T) results.getUniqueMappedResult()).getId() : null;*/ + } + + @Override + public String getSyncOfId(User user, String id) { + final Map map = new HashMap<>(); + map.put("_id", new ObjectId(id)); + return (String) this.getPropertyValueFrom(user, map, "sync"); + } + + @Override + public Set getIdsOfSyncs(final User user, final Set syncs) { + if (CollectionUtils.isEmpty(syncs)) { + return Collections.emptySet(); + } + + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("sync").in(syncs)), + project("id", "sync") + ) + , clazz, SyncObjectId.class); + + if (results == null || CollectionUtils.isEmpty(results.getMappedResults())) { + return Collections.emptySet(); + } + return new HashSet<>(results.getMappedResults()); + } + + @Override + public Set getSyncsOfIds(User user, Set ids) { + if (CollectionUtils.isEmpty(ids)) { + return Collections.emptySet(); + } + + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("_id").in(ids.parallelStream().map(ObjectId::new).collect(Collectors.toList()))), + project("id", "sync") + ) + , clazz, SyncObjectId.class); + + if (results == null || CollectionUtils.isEmpty(results.getMappedResults())) { + return Collections.emptySet(); + } + return new HashSet<>(results.getMappedResults()); + } + + protected void checkDtSync(final User user, final T value) { + if (value.getDtSync() == null) { + value.setDtSync(new Date()); + } + } + + protected void validadeSyncParam(final String sync) { + if (StringUtils.isEmpty(sync)) { + throw new MuttleyBadRequestException(this.clazz, "sync", "Informe um sync válido"); + } + } + + protected void validadeIdOrSync(final String idSync) { + this.validadeSyncParam(idSync); + } + + protected MuttleyNotFoundException createNotFoundExceptionBySync(final User user) { + return new MuttleyNotFoundException(clazz, "sync", this.getSingularNameAlias(this.clazz) + " não encontrado(a)"); + } + + protected MuttleyNotFoundException createNotFoundExceptionBySync(final User user, final String sync) { + final MuttleyNotFoundException exception = new MuttleyNotFoundException(clazz, "id", this.getSingularNameAlias(this.clazz) + "(" + sync + ") não encontrado(a)"); + exception.addDetails("sync", sync); + return exception; + } + + protected MuttleyNotFoundException createNotFoundExceptionBySync(final User user, final Collection syncs) { + final String syncsConcat = syncs.parallelStream() + .limit(3) + .collect(joining(", ")); + + final MuttleyNotFoundException exception = new MuttleyNotFoundException(clazz, "id", this.getSingularNameAlias(this.clazz) + "(" + syncsConcat + ") não encontrados(as)"); + exception.addDetails("syncs", syncs); + return exception; + } +} diff --git a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/ServiceImpl.java b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/ServiceImpl.java index 99bd2087..b01dd456 100644 --- a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/ServiceImpl.java +++ b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/ServiceImpl.java @@ -5,18 +5,40 @@ import br.com.muttley.exception.throwables.MuttleyBadRequestException; import br.com.muttley.exception.throwables.MuttleyNoContentException; import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.headers.services.MetadataService; import br.com.muttley.model.Document; -import br.com.muttley.model.Historic; +import br.com.muttley.model.NameAlias; import br.com.muttley.model.security.User; +import br.com.muttley.model.security.domain.Domain; +import br.com.muttley.mongo.service.infra.metadata.EntityMetaData; import br.com.muttley.mongo.service.repository.DocumentMongoRepository; +import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; -import java.util.Date; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; +import static br.com.muttley.model.Document.getPropertyFrom; import static java.util.Objects.isNull; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.query.Criteria.where; /** * @author Joel Rodrigues Moreira on 30/01/18. @@ -25,14 +47,32 @@ public abstract class ServiceImpl implements Service { protected final DocumentMongoRepository repository; + protected final MongoTemplate mongoTemplate; protected final Class clazz; + protected final EntityMetaData entityMetaData; + @Value("${muttley.security.check-roles:false}") + private boolean checkRoles; + + @Autowired + protected MetadataService metadataService; @Autowired protected Validator validator; - public ServiceImpl(final DocumentMongoRepository repository, final Class clazz) { + public ServiceImpl(final DocumentMongoRepository repository, final MongoTemplate mongoTemplate, final Class clazz) { this.repository = repository; + this.mongoTemplate = mongoTemplate; this.clazz = clazz; + this.entityMetaData = EntityMetaData.of(this.clazz); + } + + @Override + public boolean isCheckRole() { + return this.checkRoles; + } + + public String[] getBasicRoles() { + return new String[]{""}; } @Override @@ -46,18 +86,19 @@ public void beforeSave(final User user, final T value) { @Override public T save(final User user, final T value) { //verificando se realmente está criando um novo registro - if (value.getId() != null) { - throw new MuttleyBadRequestException(clazz, "id", "Não é possível criar um registro com um id existente"); - } - //garantindo que o históriconão ficará nulo - value.setHistoric(this.createHistoric(user)); - //validando dados - this.validator.validate(value); + checkIdForSave(value); + //garantindo que o metadata ta preenchido + this.generateNewMetadataFor(user, value, null); + //processa regra de negocio antes de qualquer validação + this.beforeSave(user, value); //verificando precondições this.checkPrecondictionSave(user, value); - this.beforeSave(user, value); + //validando dados do objeto + this.validator.validate(value); final T otherValue = repository.save(value); + //realizando regras de enegocio depois do objeto ter sido salvo this.afterSave(user, otherValue); + //valor salvo return otherValue; } @@ -65,6 +106,45 @@ public T save(final User user, final T value) { public void afterSave(final User user, final T value) { } + @Override + public void checkPrecondictionSave(final User user, final Collection values) { + values.forEach(it -> this.checkPrecondictionSave(user, it)); + } + + @Override + public void beforeSave(final User user, final Collection values) { + values.forEach(it -> this.beforeSave(user, it)); + } + + @Override + public Collection save(final User user, final Collection values) { + //verificando se realmente está criando um novo registro + checkIdForSave(values); + //garantindo que o metadata ta preenchido + this.generateNewMetadataFor(user, values, null); + //processa regra de negocio antes de qualquer validação + this.beforeSave(user, values); + //verificando precondições + this.checkPrecondictionSave(user, values); + //validando dados do objeto + this.validator.validateCollection(values); + final Collection otherValues = repository.save(values); + //realizando regras de enegocio depois do objeto ter sido salvo + this.afterSave(user, otherValues); + //valor salvo + return otherValues; + } + + @Override + public void saveOnly(final User user, final Collection values) { + this.save(user, values); + } + + @Override + public void afterSave(final User user, final Collection values) { + values.forEach(it -> this.afterSave(user, it)); + } + @Override public void checkPrecondictionUpdate(final User user, final T value) { @@ -78,21 +158,22 @@ public void beforeUpdate(final User user, final T value) { @Override public T update(final User user, final T value) { //verificando se realmente está alterando um registro - if (value.getId() == null) { - throw new MuttleyBadRequestException(clazz, "id", "Não é possível alterar um registro sem informar um id válido"); - } + this.checkIdForUpdate(value); //verificando se o registro realmente existe if (!this.repository.exists(value.getId())) { - throw new MuttleyNotFoundException(clazz, "id", "Registro não encontrado"); + throw this.createNotFoundExceptionById(user, value.getId()); + //throw new MuttleyNotFoundException(clazz, "id", "Registro não encontrado"); } - //gerando histórico de alteração - value.setHistoric(generateHistoricUpdate(user, repository.loadHistoric(value))); - //validando dados - this.validator.validate(value); + //gerando metadata de alteração + this.metadataService.generateMetaDataUpdateFor(user, repository.loadMetadata(value), value); + //processa regra de negocio antes de qualquer validação + beforeUpdate(user, value); //verificando precondições checkPrecondictionUpdate(user, value); - beforeUpdate(user, value); + //validando dados + this.validator.validate(value); final T otherValue = repository.save(value); + //realizando regras de enegocio depois do objeto ter sido alterado afterUpdate(user, value); return otherValue; } @@ -102,40 +183,112 @@ public void afterUpdate(final User user, final T value) { } + @Override + public void checkPrecondictionUpdate(final User user, final Collection values) { + values.forEach(it -> this.checkPrecondictionUpdate(user, it)); + } + + @Override + public void beforeUpdate(final User user, final Collection values) { + values.forEach(it -> this.beforeUpdate(user, it)); + } + + @Override + public void update(final User user, final Collection values) { + //verificando se realmente está alterando um registro + this.checkIdForUpdate(values); + //verificando se o registro realmente existe + + final Map> agroupedValues = values.stream() + .collect(groupingBy(it -> this.repository.exists(it.getId()))); + + final List valuesForSave = agroupedValues.get(Boolean.TRUE); + + if (!CollectionUtils.isEmpty(valuesForSave)) { + //gerando metadata de alteração + valuesForSave.forEach(it -> this.metadataService.generateMetaDataUpdateFor(user, this.repository.loadMetadata(it), it)); + //processa regra de negocio antes de qualquer validação + beforeUpdate(user, valuesForSave); + //verificando precondições + checkPrecondictionUpdate(user, valuesForSave); + //validando dados + this.validator.validateCollection(valuesForSave); + final Collection otherValue = repository.save(valuesForSave); + //realizando regras de enegocio depois do objeto ter sido alterado + afterUpdate(user, otherValue); + } + final List valuesNotSaved = agroupedValues.get(Boolean.FALSE); + if (!CollectionUtils.isEmpty(valuesNotSaved)) { + throw this.createNotFoundExceptionById(user, valuesNotSaved.parallelStream().map(Document::getId).collect(toList())); + /*throw new MuttleyNotFoundException(clazz, "id", "Registros não encontrados") + .addDetails("ids", valuesNotSaved.parallelStream().map(Document::getId).collect(toList()));*/ + } + } + + @Override + public void afterUpdate(final User user, final Collection values) { + values.forEach(it -> this.afterUpdate(user, it)); + } + @Override public T findById(final User user, final String id) { - if (isNull(id)) { + if (!ObjectId.isValid(id)) { throw new MuttleyBadRequestException(clazz, "id", "informe um id válido"); } final T result = this.repository.findOne(id); - if (isNull(id)) { - throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); + if (isNull(result)) { + throw this.createNotFoundExceptionById(user, id); + //throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); } return result; } @Override - public T findFirst(final User user) { - final T result = this.repository.findFirst(); - if (isNull(result)) { - throw new MuttleyNotFoundException(clazz, "user", "Nenhum registro encontrado"); + public T findReferenceById(User user, String id) { + if (!ObjectId.isValid(id)) { + throw new MuttleyBadRequestException(clazz, "id", "informe um id válido"); } - return result; + + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("id").is(new ObjectId(id))), + project("id") + ) + , clazz, clazz); + + if (results == null || results.getUniqueMappedResult() == null) { + throw this.createNotFoundExceptionById(user, id); + //throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); + } + return results.getUniqueMappedResult(); } @Override - public Historic loadHistoric(final User user, final String id) { - final Historic historic = this.repository.loadHistoric(id); - if (isNull(historic)) { - throw new MuttleyNotFoundException(clazz, "historic", "Nenhum registro encontrado"); + public Set findByIds(final User user, final String[] ids) { + if (ObjectUtils.isEmpty(ids)) { + throw new MuttleyBadRequestException(clazz, "id", "informe pelo menos um id válido"); + } + if (ids.length > 50) { + throw new MuttleyBadRequestException(clazz, "ids", "Quantidade máxima excedida") + .addDetails("min", 1) + .addDetails("max", 50); + } + final Set records = this.repository.findMulti(ids); + if (records == null) { + return Collections.emptySet(); } - return historic; + return records; } @Override - public Historic loadHistoric(final User user, final T value) { - return this.loadHistoric(user, value.getId()); + public T findFirst(final User user) { + final T result = this.repository.findFirst(); + if (isNull(result)) { + throw this.createNotFoundExceptionById(user).setField("user"); + //*throw new MuttleyNotFoundException(clazz, "user", "Nenhum registro encontrado"); + } + return result; } @Override @@ -143,6 +296,11 @@ public void checkPrecondictionDelete(final User user, final String id) { } + @Override + public void checkPrecondictionDelete(User user, T value) { + + } + @Override public void beforeDelete(final User user, final String id) { @@ -150,11 +308,12 @@ public void beforeDelete(final User user, final String id) { @Override public void deleteById(final User user, final String id) { + beforeDelete(user, id); checkPrecondictionDelete(user, id); if (!repository.exists(id)) { - throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); + throw this.createNotFoundExceptionById(user, id); + //throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); } - beforeDelete(user, id); this.repository.delete(id); afterDelete(user, id); } @@ -171,11 +330,12 @@ public void beforeDelete(final User user, final T value) { @Override public void delete(final User user, final T value) { - checkPrecondictionDelete(user, value.getId()); + beforeDelete(user, value); + checkPrecondictionDelete(user, value); if (!repository.exists(value)) { - throw new MuttleyNotFoundException(clazz, "id", value.getId() + " este registro não foi encontrado"); + throw this.createNotFoundExceptionById(user, value.getId()); + //throw new MuttleyNotFoundException(clazz, "id", value.getId() + " este registro não foi encontrado"); } - beforeDelete(user, value); this.repository.delete(value); afterDelete(user, value); } @@ -186,12 +346,12 @@ public void afterDelete(final User user, final T value) { } @Override - public Long count(final User user, final Map allRequestParams) { + public Long count(final User user, final Map allRequestParams) { return this.repository.count(allRequestParams); } @Override - public List findAll(final User user, final Map allRequestParams) { + public List findAll(final User user, final Map allRequestParams) { final List results = this.repository.findAll(allRequestParams); if (CollectionUtils.isEmpty(results)) { throw new MuttleyNoContentException(clazz, "user", "não foi encontrado nenhum registro"); @@ -199,23 +359,120 @@ public List findAll(final User user, final Map allRequestPara return results; } - protected Historic createHistoric(final User user) { - final Date now = new Date(); - return new Historic() - .setCreatedBy(user) - .setDtCreate(now) - .setLastChangeBy(user) - .setDtChange(now); + @Override + public boolean isEmpty(final User user) { + return this.count(user, null) == 0l; } - protected Historic generateHistoricUpdate(final User user, final Historic historic) { - return historic - .setLastChangeBy(user) - .setDtChange(new Date()); + + @Override + public Object getPropertyValueFromId(final User user, final String id, final String property) { + final Map map = new HashMap<>(1); + map.put("id", id); + return this.getPropertyValueFrom(user, map, property); } @Override - public boolean isEmpty(final User user) { - return this.count(user, null) == 0l; + public Object getPropertyValueFrom(final User user, final Map condictions, final String property) { + //Criteria condiction = Criteria.where("owner.$id").is(user.getCurrentOwner().getObjectId()); + final AggregationResults results = createAggregateForLoadProperties(user, condictions, property); + return getPropertyFrom(results.getUniqueMappedResult(), property); + } + + @Override + public Object[] getPropertiesValueFrom(final User user, final Map condictions, final String... properties) { + //Criteria condiction = Criteria.where("owner.$id").is(user.getCurrentOwner().getObjectId()); + final AggregationResults results = createAggregateForLoadProperties(user, condictions, properties); + + final T result = results.getUniqueMappedResult(); + + return Stream.of(properties) + .map(it -> getPropertyFrom(result, it)) + .toArray(Object[]::new); + } + + protected AggregationResults createAggregateForLoadProperties(final User user, final Map condictions, final String... properties) { + Criteria condiction = new Criteria(); + final Set keysOfCondictions = condictions.keySet(); + + for (final String key : keysOfCondictions) { + condiction = condiction.and(key).is(condictions.get(key)); + } + + return this.mongoTemplate + .aggregate( + newAggregation( + match(condiction), + project(properties), + limit(1) + ), clazz, clazz + ); + } + + protected void checkIdForSave(final Collection values) { + values.parallelStream().forEach(it -> this.checkIdForSave(it)); + } + + protected void checkIdForSave(final T value) { + if ("".equals(value.getId())) { + value.setId(null); + } + if (value.getId() != null) { + throw new MuttleyBadRequestException(clazz, "id", "Não é possível criar um registro com um id existente"); + } + } + + protected void checkIdForUpdate(final Collection values) { + values.parallelStream().forEach(it -> this.checkIdForUpdate(it)); + } + + protected void checkIdForUpdate(final T value) { + if (value.getId() == null) { + throw new MuttleyBadRequestException(clazz, "id", "Não é possível alterar um registro sem informar um id válido"); + } + } + + protected MuttleyNotFoundException createNotFoundExceptionById(final User user) { + return new MuttleyNotFoundException(clazz, "id", this.getSingularNameAlias(this.clazz) + " não encontrado(a)"); + } + + protected MuttleyNotFoundException createNotFoundExceptionById(final User user, final String id) { + final MuttleyNotFoundException exception = new MuttleyNotFoundException(clazz, "id", this.getSingularNameAlias(this.clazz) + "(" + id + ") não encontrado(a)"); + exception.addDetails("id", id); + return exception; + } + + protected MuttleyNotFoundException createNotFoundExceptionById(final User user, final Collection ids) { + final String idsConcat = ids.parallelStream() + .limit(3) + .collect(joining(", ")); + + final MuttleyNotFoundException exception = new MuttleyNotFoundException(clazz, "id", this.getSingularNameAlias(this.clazz) + "(" + idsConcat + ") não encontrados(as)"); + exception.addDetails("ids", ids); + return exception; + } + + protected String getSingularNameAlias(final Class clazz) { + final NameAlias nameAlias = clazz.getAnnotation(NameAlias.class); + if (nameAlias != null) { + return nameAlias.singularName(); + } + return clazz.getSimpleName(); + } + + protected String getPluralNameAlias(final Class clazz) { + final NameAlias nameAlias = clazz.getAnnotation(NameAlias.class); + if (nameAlias != null) { + return nameAlias.pluralName(); + } + return clazz.getSimpleName(); + } + + protected void generateNewMetadataFor(final User user, final T value, final Domain domain) { + this.metadataService.generateNewMetadataFor(user, value, domain); + } + + protected void generateNewMetadataFor(final User user, final Collection value, final Domain domain) { + this.metadataService.generateNewMetadataFor(user, value, domain); } } diff --git a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/utils/MetadataAndIdModel.java b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/utils/MetadataAndIdModel.java new file mode 100644 index 00000000..79ec56ec --- /dev/null +++ b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/impl/utils/MetadataAndIdModel.java @@ -0,0 +1,24 @@ +package br.com.muttley.domain.service.impl.utils; + +import br.com.muttley.model.MetadataDocument; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.springframework.data.annotation.PersistenceConstructor; + +/** + * @author Joel Rodrigues Moreira on 18/06/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@EqualsAndHashCode(of = "id") +public class MetadataAndIdModel { + private final String id; + private final MetadataDocument metadata; + + @PersistenceConstructor + public MetadataAndIdModel(final String id, final MetadataDocument metadata) { + this.id = id; + this.metadata = metadata; + } +} diff --git a/muttley-domain-service/src/main/java/br/com/muttley/domain/service/listener/AbstractModelSyncResolverEventListener.java b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/listener/AbstractModelSyncResolverEventListener.java new file mode 100644 index 00000000..15e47264 --- /dev/null +++ b/muttley-domain-service/src/main/java/br/com/muttley/domain/service/listener/AbstractModelSyncResolverEventListener.java @@ -0,0 +1,45 @@ +package br.com.muttley.domain.service.listener; + +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.headers.components.MuttleySerializeType; +import br.com.muttley.model.ModelSync; +import br.com.muttley.model.jackson.converter.event.ModelSyncResolverEvent; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author Joel Rodrigues Moreira on 10/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AbstractModelSyncResolverEventListener> extends AbstractModelResolverEventListener { + @Autowired + private MuttleySerializeType serializerType; + + public AbstractModelSyncResolverEventListener() { + super(); + } + + @Override + public void onApplicationEvent(final M event) { + if (serializerType.isInternal()) { + if (ObjectId.isValid(event.getSource())) { + super.onApplicationEvent(event); + } else { + event.setValueResolved(this.loadValueBySync(event.getSource())); + } + } else if (serializerType.isObjectId()) { + if (ObjectId.isValid(event.getSource())) { + super.onApplicationEvent(event); + } else { + event.setValueResolved(this.loadValueBySync(event.getSource())); + } + } else if (serializerType.isSync()) { + event.setValueResolved(this.loadValueBySync(event.getSource())); + } else { + throw new MuttleyException("deu asdfasdfasdf"); + } + } + + protected abstract T loadValueBySync(final String sync); +} diff --git a/muttley-exception/pom.xml b/muttley-exception/pom.xml index 87fb7cef..91b3e752 100644 --- a/muttley-exception/pom.xml +++ b/muttley-exception/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar @@ -29,9 +29,8 @@ - br.com.muttley - muttley-feign - provided + org.springframework.cloud + spring-cloud-starter-feign diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/service/CustomResponseEntityExceptionHandler.java b/muttley-exception/src/main/java/br/com/muttley/exception/service/CustomResponseEntityExceptionHandler.java index 1ee3894f..f78dc15c 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/service/CustomResponseEntityExceptionHandler.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/service/CustomResponseEntityExceptionHandler.java @@ -4,6 +4,7 @@ import br.com.muttley.exception.throwables.MuttleyException; import br.com.muttley.exception.throwables.MuttleyNotFoundException; import br.com.muttley.exception.throwables.repository.MuttleyRepositoryException; +import br.com.muttley.exception.throwables.security.MuttleySecurityCredentialException; import br.com.muttley.exception.throwables.security.MuttleySecurityUnauthorizedException; import br.com.muttley.exception.throwables.security.MuttleySecurityUserNameOrPasswordInvalidException; import org.springframework.beans.TypeMismatchException; @@ -30,6 +31,7 @@ import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import javax.servlet.http.HttpServletRequest; import javax.validation.ConstraintViolationException; import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; @@ -122,36 +124,36 @@ public ResponseEntity handleSerializationException(final SerializationException } @ExceptionHandler(value = Exception.class) - public ResponseEntity handleException(final Exception ex) { + public ResponseEntity handleException(final HttpServletRequest request, final Exception ex) { if (ex instanceof MuttleyException) { - return handleMuttleyException((MuttleyException) ex); + return handleMuttleyException(request, (MuttleyException) ex); } return messageBuilder.buildMessage(new MuttleyException("ERROR *-*", ex)).toResponseEntity(); } @ExceptionHandler(value = RuntimeException.class) - public ResponseEntity exceptionRuntime(final RuntimeException ex) { + public ResponseEntity exceptionRuntime(final HttpServletRequest request, final RuntimeException ex) { if (ex instanceof MuttleyException) { - return handleMuttleyException((MuttleyException) ex); + return handleMuttleyException(request, (MuttleyException) ex); } if (ex.getCause() instanceof MuttleyConflictException) { - return handleMuttleyException((MuttleyException) ex.getCause()); + return handleMuttleyException(request, (MuttleyException) ex.getCause()); } return messageBuilder.buildMessage(new MuttleyException("ERROR *-*", ex)).toResponseEntity(); } @ExceptionHandler(value = Throwable.class) - public ResponseEntity exceptionThrowable(final Throwable ex) { + public ResponseEntity exceptionThrowable(final HttpServletRequest request, final Throwable ex) { if (ex instanceof MuttleyException) { - return handleMuttleyException((MuttleyException) ex); + return handleMuttleyException(request, (MuttleyException) ex); } return messageBuilder.buildMessage(new MuttleyException("ERROR *-*", ex)).toResponseEntity(); } @ExceptionHandler(MuttleyException.class) - public ResponseEntity handleMuttleyException(final MuttleyException ex) { - return messageBuilder.buildMessage(ex).toResponseEntity(); + public ResponseEntity handleMuttleyException(HttpServletRequest request, final MuttleyException ex) { + return messageBuilder.buildMessage(ex).toResponseEntity(request); } @ExceptionHandler(MuttleyRepositoryException.class) @@ -170,6 +172,7 @@ public ResponseEntity handleUsernameNotFoundException(final UsernameNotFoundExce public ResponseEntity handleAccessDeniedException(final AccessDeniedException ex) { final MuttleySecurityUnauthorizedException exx = new MuttleySecurityUnauthorizedException().setStatus(FORBIDDEN); exx.addSuppressed(ex); + exx.setMessage("Você não tem permissão para acessar esse recurso"); return handleMuttleySecurityUnauthorizedException(exx); } @@ -178,6 +181,11 @@ public ResponseEntity handleMuttleySecurityUserNameOrPasswordInvalidException(fi return messageBuilder.buildMessage(ex).toResponseEntity(); } + @ExceptionHandler(value = MuttleySecurityCredentialException.class) + public ResponseEntity handleMuttleySecurityCredentialException(final MuttleySecurityCredentialException ex) { + return this.handleMuttleySecurityUnauthorizedException(ex); + } + @ExceptionHandler(value = MuttleySecurityUnauthorizedException.class) public ResponseEntity handleMuttleySecurityUnauthorizedException(final MuttleySecurityUnauthorizedException ex) { return messageBuilder.buildMessage(ex).toResponseEntity(); diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/service/ErrorMessage.java b/muttley-exception/src/main/java/br/com/muttley/exception/service/ErrorMessage.java index 8f14bd6f..fddda68c 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/service/ErrorMessage.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/service/ErrorMessage.java @@ -11,16 +11,25 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import static java.util.Arrays.asList; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.MediaType.ALL_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8; import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; import static org.springframework.util.StringUtils.isEmpty; +import static org.springframework.web.servlet.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE; /** * @author Joel Rodrigues Moreira on 14/01/18. @@ -40,11 +49,13 @@ public final class ErrorMessage { protected String message; protected String objectName; protected final Map details; + protected final Map> headers; @JsonIgnore public static final String RESPONSE_HEADER = "error-message"; public ErrorMessage() { this.details = new HashMap<>(); + this.headers = new HashMap<>(); } @JsonCreator @@ -53,12 +64,14 @@ public ErrorMessage( @JsonProperty("status") final HttpStatus status, @JsonProperty("message") final String message, @JsonProperty("objectName") final String objectName, - @JsonProperty("details") final Map details) { + @JsonProperty("details") final Map details, + @JsonProperty("headers") final Map> headers) { this.field = field; this.status = status; this.message = message; this.objectName = objectName; this.details = details; + this.headers = headers; } public String getField() { @@ -144,8 +157,47 @@ public boolean containsDetails() { return details != null && !details.isEmpty(); } + public Map> getHeaders() { + return headers; + } + + @JsonIgnore + public ErrorMessage addHeaders(final String key, final String value) { + if (this.headers.containsKey(key)) { + this.headers.get(key).add(value); + } else { + final List values = new ArrayList<>(1); + values.add(value); + this.headers.put(key, values); + } + return this; + } + + @JsonIgnore + public ErrorMessage addHeaders(final String key, final String... value) { + return this.addHeaders(key, asList(value)); + } + + @JsonIgnore + public ErrorMessage addHeaders(final String key, final List value) { + if (this.headers.containsKey(key)) { + this.headers.get(key).addAll(value); + } else { + final List values = new ArrayList<>(1); + values.addAll(value); + this.headers.put(key, values); + } + return this; + } + + @JsonIgnore + public ErrorMessage addHeaders(final Map> headers) { + this.headers.putAll(headers); + return this; + } + @JsonIgnore - protected final String toJson() { + public final String toJsonPretty() { try { return new ObjectMapper() .writerWithDefaultPrettyPrinter() @@ -156,15 +208,56 @@ protected final String toJson() { } } + @JsonIgnore + public final String toJson() { + try { + return new ObjectMapper() + .writeValueAsString(this); + } catch (IOException ex) { + ex.printStackTrace(); + return null; + } + } + @JsonIgnore public ResponseEntity toResponseEntity() { - return toResponseEntity(new HttpHeaders()); + return toResponseEntity((HttpHeaders) null); } @JsonIgnore public ResponseEntity toResponseEntity(final HttpHeaders headers) { - headers.add(CONTENT_TYPE, APPLICATION_JSON_UTF8_VALUE); - headers.add(RESPONSE_HEADER, RESPONSE_HEADER_VALUE); - return new ResponseEntity(this, headers, this.status); + return this.toResponseEntity(true, headers); } + + @JsonIgnore + public ResponseEntity toResponseEntity(boolean serializeResponse, final HttpHeaders headers) { + final HttpHeaders currentHeaders = new HttpHeaders(); + currentHeaders.putAll(this.getHeaders()); + if (headers != null) { + currentHeaders.putAll(headers); + } + + currentHeaders.add(RESPONSE_HEADER, RESPONSE_HEADER_VALUE); + if (serializeResponse) { + currentHeaders.add(CONTENT_TYPE, APPLICATION_JSON_UTF8_VALUE); + return new ResponseEntity(this, currentHeaders, this.status); + } + return ResponseEntity.status(this.status).headers(currentHeaders).build(); + } + + @JsonIgnore + public ResponseEntity toResponseEntity(final HttpServletRequest request) { + //verificando se tem algum Media type que possa retornar json + final LinkedHashSet headers = (LinkedHashSet) request.getAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); + if (headers != null && headers.parallelStream() + .filter(it -> APPLICATION_JSON.equals(it) || APPLICATION_JSON_UTF8.equals(it) || ALL_VALUE.equals(it)) + .count() > 0) { + //se chegou aqui quer dizer que podemo retornar um json, + //vamos remover qualquer coisa que possa dar outra exception relacionada a media type + request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); + return this.toResponseEntity(true, null); + } + return this.toResponseEntity(false, null); + } + } diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/service/ErrorMessageBuilder.java b/muttley-exception/src/main/java/br/com/muttley/exception/service/ErrorMessageBuilder.java index 5435d751..e83a662e 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/service/ErrorMessageBuilder.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/service/ErrorMessageBuilder.java @@ -1,5 +1,6 @@ package br.com.muttley.exception.service; +import br.com.muttley.exception.service.event.MuttleyExceptionEvent; import br.com.muttley.exception.throwables.MuttleyBadRequestException; import br.com.muttley.exception.throwables.MuttleyException; import br.com.muttley.exception.throwables.repository.MuttleyRepositoryException; @@ -9,9 +10,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import org.springframework.validation.BindException; import org.springframework.validation.ObjectError; import org.springframework.web.HttpMediaTypeNotSupportedException; @@ -23,6 +27,7 @@ import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; +import java.util.Collection; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.METHOD_NOT_ALLOWED; @@ -42,6 +47,8 @@ public class ErrorMessageBuilder { private Boolean STACK_TRACE; @Value("${muttley.print.responseException:false}") private Boolean RESPONSE_EXCEPTION; + @Autowired + private ApplicationEventPublisher publisher; public ErrorMessage buildMessage(final MethodArgumentNotValidException ex) { final ErrorMessage message = new ErrorMessage() @@ -53,6 +60,7 @@ public ErrorMessage buildMessage(final MethodArgumentNotValidException ex) { String key = fieldError.getCodes()[0].replace(fieldError.getCodes()[fieldError.getCodes().length - 1] + ".", ""); message.addDetails(key, fieldError.getDefaultMessage()); } + this.publishEvents(message); printException(ex, message); return message; } @@ -79,6 +87,7 @@ public ErrorMessage buildMessage(final ConstraintViolationException ex) { keyDetail.startsWith(message.objectName) ? keyDetail : message.objectName + "." + keyDetail, violation.getMessage() ); } + this.publishEvents(message); printException(ex, message); return message; } @@ -88,6 +97,7 @@ public ErrorMessage buildMessage(final BindException ex) { ex.getBindingResult().getFieldErrors().forEach(e -> { message.addDetails(e.getField(), e.getDefaultMessage()); }); + this.publishEvents(message); printException(ex, message); return message; } @@ -95,23 +105,29 @@ public ErrorMessage buildMessage(final BindException ex) { public ErrorMessage buildMessage(final TypeMismatchException ex) { final ErrorMessage message = buildMessage(new MuttleyBadRequestException(ex)); printException(ex, message); + this.publishEvents(message); return message; } public ErrorMessage buildMessage(final MissingServletRequestPartException ex) { final ErrorMessage message = buildMessage(new MuttleyBadRequestException(ex)); printException(ex, message); + this.publishEvents(message); return message; } public ErrorMessage buildMessage(final MissingServletRequestParameterException ex) { - final ErrorMessage message = buildMessage(new MuttleyBadRequestException(ex)); + final ErrorMessage message = buildMessage(new MuttleyBadRequestException(ex)) + .setMessage("Informe os parametros necessários") + .addDetails("nameParam", ex.getParameterName()); + this.publishEvents(message); printException(ex, message); return message; } public ErrorMessage buildMessage(final MethodArgumentTypeMismatchException ex) { final ErrorMessage message = buildMessage(new MuttleyBadRequestException(ex)); + this.publishEvents(message); printException(ex, message); return message; } @@ -120,6 +136,7 @@ public ErrorMessage buildMessage(final HttpRequestMethodNotSupportedException ex final ErrorMessage message = buildMessage(new MuttleyBadRequestException(ex)) .setStatus(METHOD_NOT_ALLOWED) .setMessage(METHOD_NOT_ALLOWED.getReasonPhrase()); + this.publishEvents(message); printException(ex, message); return message; } @@ -130,6 +147,7 @@ public ErrorMessage buildMessage(final HttpMediaTypeNotSupportedException ex) { .setMessage(ex.getMessage().replace("'null' ", "")) .addDetails("ContentType", ex.getContentType() == null ? "uninformed" : ex.getContentType().toString()) .addDetails("SupportedMediaTypes", APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE); + this.publishEvents(message); printException(ex, message); return message; } @@ -143,17 +161,33 @@ public ErrorMessage buildMessage(final HttpMessageNotReadableException ex) { if (throwable instanceof MuttleyException) { return buildMessage((MuttleyException) throwable); } else if (ex.getCause() instanceof com.fasterxml.jackson.core.JsonParseException) { - JsonLocation location = ((com.fasterxml.jackson.core.JsonParseException) ex.getCause()).getLocation(); + final JsonLocation location = ((com.fasterxml.jackson.core.JsonParseException) ex.getCause()).getLocation(); message.setMessage("Illegal character in line:" + location.getLineNr() + " column:" + location.getColumnNr()); } else if (ex.getCause() instanceof JsonMappingException) { - JsonLocation location = ((JsonMappingException) ex.getCause()).getLocation(); - if (location != null) { - message.setMessage("Illegal character in line:" + location.getLineNr() + " column:" + location.getColumnNr()); + final JsonLocation location = ((JsonMappingException) ex.getCause()).getLocation(); + final String messageStr = ex.getMessage(); + + if (messageStr.startsWith("JSON parse error: Can not deserialize instance of ")) { + final String clazz = messageStr.replace("JSON parse error: Can not deserialize instance of ", "").replace(messageStr.substring(messageStr.indexOf(" out of START")), ""); + final String[] packageClazz = clazz.split("\\."); + final String type = packageClazz[packageClazz.length - 1]; + final String typeStr = type.equals("ArrayList") || type.equals("HashSet") ? "Array" : type; + final String typeSeted = messageStr.contains("START_OBJECT") ? "Object" : messageStr.contains("START_ARRAY") ? "Array" : "Unknown"; + message.setMessage("Era esperado um " + typeStr + "(a) porem foi passado um " + typeSeted); + if (location != null) { + message.addDetails("info", "Illegal character in line:" + location.getLineNr() + " column:" + location.getColumnNr()); + } + } else { + if (location != null) { + message.setMessage("Illegal character in line:" + location.getLineNr() + " column:" + location.getColumnNr()); + } } + } else if (ex.getMessage().contains("Required request body is missing:")) { message.setMessage("Insira o corpo na requisição!") .addDetails("body", "body is empty"); } + this.publishEvents(message); printException(ex, message); return message; } @@ -163,14 +197,18 @@ public ErrorMessage buildMessage(final MuttleyException ex) { .setStatus(ex.getStatus()) .setMessage(ex.getMessage()) .setObjectName(ex.getObjectName()) - .addDetails(ex.getDetails()); + .addDetails(ex.getDetails()) + .addHeaders(ex.getHeaders()); + this.publishEvents(message, ex.getEvents()); printException(ex, message); return message; } public ErrorMessage buildMessage(final MuttleyRepositoryException ex) { final ErrorMessage message = new ErrorMessage() - .setStatus(ex.getStatus()); + .setStatus(ex.getStatus()) + .addHeaders(ex.getHeaders()); + this.publishEvents(message, ex.getEvents()); printException(ex, message); return message; } @@ -180,11 +218,26 @@ public ErrorMessage buildMessage(final MuttleySecurityUnauthorizedException ex) .setStatus(ex.getStatus()) .setMessage(ex.getMessage()) .setObjectName(ex.getObjectName()) - .addDetails(ex.getDetails()); + .addDetails(ex.getDetails()) + .addHeaders(ex.getHeaders()); + this.publishEvents(message, ex.getEvents()); printException(ex, message); return message; } + private void publishEvents(final ErrorMessage message) { + this.publishEvents(message, null); + } + + private void publishEvents(final ErrorMessage message, final Collection events) { + if (this.publisher != null) { + this.publisher.publishEvent(new MuttleyExceptionEvent(message)); + if (!CollectionUtils.isEmpty(events)) { + events.forEach(it -> this.publisher.publishEvent(it.setSource(message))); + } + } + } + /** * Imprime a pilha de Exceptions de acordo com application.properties * @@ -195,7 +248,7 @@ private void printException(final Exception ex, final ErrorMessage message) { ex.printStackTrace(); } if (RESPONSE_EXCEPTION == null || RESPONSE_EXCEPTION) { - logger.info(message.toJson()); + logger.info(message.toJsonPretty()); } } diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/service/ExceptionBuilder.java b/muttley-exception/src/main/java/br/com/muttley/exception/service/ExceptionBuilder.java index d73ea8c9..b2c145a6 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/service/ExceptionBuilder.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/service/ExceptionBuilder.java @@ -57,6 +57,9 @@ public static Exception buildException(final Response response, final ObjectMapp } private static ErrorMessage getErrorMessage(final Response response, final ObjectMapper mapper) throws IOException { + if (response.body() == null) { + return new ErrorMessage().setStatus(HttpStatus.valueOf(response.status())); + } return mapper.readValue(response.body().asInputStream(), ErrorMessage.class); } } diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/service/config/ConfigEndPointsErros.java b/muttley-exception/src/main/java/br/com/muttley/exception/service/config/ConfigEndPointsErros.java index eb00c8e8..3c2d2d1a 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/service/config/ConfigEndPointsErros.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/service/config/ConfigEndPointsErros.java @@ -17,6 +17,8 @@ public class ConfigEndPointsErros implements EmbeddedServletContainerCustomizer @Override public void customize(ConfigurableEmbeddedServletContainer container) { + container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/401")); + container.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN, "/403")); container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404")); container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500")); } diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/service/config/ErrorsController.java b/muttley-exception/src/main/java/br/com/muttley/exception/service/config/ErrorsController.java index 817de291..25806f5a 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/service/config/ErrorsController.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/service/config/ErrorsController.java @@ -3,6 +3,8 @@ import br.com.muttley.exception.service.ErrorMessageBuilder; import br.com.muttley.exception.throwables.MuttleyException; import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.exception.throwables.security.MuttleySecurityCredentialException; +import br.com.muttley.exception.throwables.security.MuttleySecurityUnauthorizedException; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,6 +19,19 @@ @Controller @RequestMapping public class ErrorsController { + + @RequestMapping(value = "/401", method = GET) + public ResponseEntity noHandlerUnauthorizedException(final ErrorMessageBuilder messageBuilder) { + return messageBuilder.buildMessage(new MuttleySecurityUnauthorizedException()).toResponseEntity(); + } + + @RequestMapping(value = "/403", method = GET) + public ResponseEntity noHandlerForbiden(final ErrorMessageBuilder messageBuilder) { + return messageBuilder.buildMessage( + new MuttleySecurityCredentialException("Você não tem permissão para acessar esse recurso") + ).toResponseEntity(); + } + @RequestMapping(value = "/404", method = GET) public ResponseEntity noHandlerFoundException(final ErrorMessageBuilder messageBuilder) { return messageBuilder.buildMessage( diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/service/event/MuttleyExceptionEvent.java b/muttley-exception/src/main/java/br/com/muttley/exception/service/event/MuttleyExceptionEvent.java new file mode 100644 index 00000000..c72d8e0e --- /dev/null +++ b/muttley-exception/src/main/java/br/com/muttley/exception/service/event/MuttleyExceptionEvent.java @@ -0,0 +1,37 @@ +package br.com.muttley.exception.service.event; + +import br.com.muttley.exception.service.ErrorMessage; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira 15/06/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class MuttleyExceptionEvent extends ApplicationEvent { + private T source; + + public MuttleyExceptionEvent() { + super("null"); + } + + /** + * Create a new ApplicationEvent. + * + * @param source the object on which the event initially occurred (never {@code null}) + */ + public MuttleyExceptionEvent(final T source) { + super(source); + this.source = source; + } + + @Override + public T getSource() { + return source; + } + + public MuttleyExceptionEvent setSource(final T source) { + this.source = source; + return this; + } +} diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyBadRequestException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyBadRequestException.java index 11acd975..e1df8735 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyBadRequestException.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyBadRequestException.java @@ -22,7 +22,7 @@ public MuttleyBadRequestException(final Throwable cause) { } public MuttleyBadRequestException(final Class clazz, final String field, final String message) { - super("Bad Request", BAD_REQUEST, clazz, field, message); + super(message, BAD_REQUEST, clazz, field, message); } public MuttleyBadRequestException(final ErrorMessage errorMessage) { diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyConflictException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyConflictException.java index 29d73ef5..b9a58669 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyConflictException.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyConflictException.java @@ -12,7 +12,7 @@ public class MuttleyConflictException extends MuttleyException { public MuttleyConflictException(final Class clazz, final String field, final String message) { - super("Conflict", HttpStatus.CONFLICT, clazz, field, message); + super(message, HttpStatus.CONFLICT, clazz, field, message); /*this.message = "Conflict"; this.status = HttpStatus.CONFLICT; this.objectName = clazz.getSimpleName().toLowerCase(); diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyDeleteException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyDeleteException.java new file mode 100644 index 00000000..304d97f7 --- /dev/null +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyDeleteException.java @@ -0,0 +1,19 @@ +package br.com.muttley.exception.throwables; + +import br.com.muttley.exception.service.ErrorMessage; + +/** + * @author Joel Rodrigues Moreira on 13/01/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class MuttleyDeleteException extends MuttleyBadRequestException { + + public MuttleyDeleteException(Class clazz, String field, String message) { + super(clazz, field, message); + } + + public MuttleyDeleteException(ErrorMessage errorMessage) { + super(errorMessage); + } +} diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyEmailEmUsoException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyEmailEmUsoException.java new file mode 100644 index 00000000..334c4a44 --- /dev/null +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyEmailEmUsoException.java @@ -0,0 +1,19 @@ +package br.com.muttley.exception.throwables; + +import br.com.muttley.exception.service.ErrorMessage; + +/** + * @author Carolina Cedro on 10/10/2024. + * e-mail: ana.carolina@maxxsoft.com.br + * @project muttley-cloud + */ +public class MuttleyEmailEmUsoException extends MuttleyBadRequestException { + + public MuttleyEmailEmUsoException(Class clazz, String field, String message) { + super(clazz, field, message); + } + + public MuttleyEmailEmUsoException(ErrorMessage errorMessage) { + super(errorMessage); + } +} diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyException.java index 9f9df68b..cdb1743e 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyException.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyException.java @@ -1,11 +1,16 @@ package br.com.muttley.exception.throwables; import br.com.muttley.exception.service.ErrorMessage; +import br.com.muttley.exception.service.event.MuttleyExceptionEvent; import org.springframework.http.HttpStatus; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; /** * @author Joel Rodrigues Moreira on 14/01/18. @@ -18,15 +23,18 @@ public class MuttleyException extends RuntimeException { protected String message; protected String objectName; protected Map details = new HashMap<>(); + protected Map> headers = new HashMap<>(); protected String field; + protected List events = new ArrayList<>(); public MuttleyException() { this.status = HttpStatus.INTERNAL_SERVER_ERROR; - this.message = "ERROR *-*"; - this.objectName = "unknow :("; - this.field = null; + this.message = "Ocorreu um erro interno não esperado !"; + this.objectName = "Erro desconhecido"; + this.field = "Campo não identificado"; } + public MuttleyException(final String message, final HttpStatus status, final Class clazz, final String field, final String info) { this.message = message; this.status = status; @@ -153,6 +161,64 @@ public MuttleyException addDetails(final Map details) { return this; } + public Map> getHeaders() { + return headers; + } + + public MuttleyException setHeaders(final Map> headers) { + this.headers = headers; + return this; + } + + public MuttleyException addHeaders(final String key, final String value) { + if (this.headers.containsKey(key)) { + this.headers.get(key).add(value); + } else { + final List values = new ArrayList<>(1); + values.add(value); + this.headers.put(key, values); + } + return this; + } + + public MuttleyException addHeaders(final String key, final String... value) { + return this.addHeaders(key, asList(value)); + } + + public MuttleyException addHeaders(final String key, final List value) { + if (this.headers.containsKey(key)) { + this.headers.get(key).addAll(value); + } else { + final List values = new ArrayList<>(1); + values.addAll(value); + this.headers.put(key, values); + } + return this; + } + + public MuttleyException addHeaders(final Map> headers) { + this.headers.putAll(headers); + return this; + } + + public MuttleyException addEvent(final MuttleyExceptionEvent... events) { + if (events != null) { + this.getEvents().addAll( + asList(events) + .parallelStream() + .filter(e -> e != null) + .collect( + Collectors.toSet() + ) + ); + } + return this; + } + + public List getEvents() { + return this.events; + } + public boolean containsDetais() { return !this.details.isEmpty(); } diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyIamATeapotException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyIamATeapotException.java new file mode 100644 index 00000000..ff74604f --- /dev/null +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyIamATeapotException.java @@ -0,0 +1,31 @@ +package br.com.muttley.exception.throwables; + +import br.com.muttley.exception.service.ErrorMessage; +import org.springframework.http.HttpStatus; + +/** + * @author Joel Rodrigues Moreira on 14/01/18. + * e-mail: joel.databox@gmail.com + * @project spring-cloud + */ + +public class MuttleyIamATeapotException extends MuttleyException { + + public MuttleyIamATeapotException(final Throwable cause) { + super(cause); + setStatus(HttpStatus.I_AM_A_TEAPOT); + } + + public MuttleyIamATeapotException(final Class clazz, final String field, final String message) { + super(message, HttpStatus.I_AM_A_TEAPOT, clazz, field, message); + } + + public MuttleyIamATeapotException(final String message) { + super(message, HttpStatus.I_AM_A_TEAPOT); + } + + public MuttleyIamATeapotException(final ErrorMessage errorMessage) { + super(errorMessage); + setStatus(HttpStatus.I_AM_A_TEAPOT); + } +} diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyInvalidObjectIdException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyInvalidObjectIdException.java new file mode 100644 index 00000000..1cfa35c2 --- /dev/null +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyInvalidObjectIdException.java @@ -0,0 +1,18 @@ +package br.com.muttley.exception.throwables; + +/** + * @author Joel Rodrigues Moreira on 14/01/18. + * e-mail: joel.databox@gmail.com + * @project spring-cloud + */ + +public class MuttleyInvalidObjectIdException extends MuttleyBadRequestException { + + public MuttleyInvalidObjectIdException() { + this(null, null, "invalid id"); + } + + public MuttleyInvalidObjectIdException(final Class clazz, final String field, final String message) { + super(clazz, field, message); + } +} diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyMethodNotAllowedException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyMethodNotAllowedException.java index 63b8cc2d..7dc1a9e6 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyMethodNotAllowedException.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyMethodNotAllowedException.java @@ -17,7 +17,7 @@ public MuttleyMethodNotAllowedException(final String message) { } public MuttleyMethodNotAllowedException(final Class clazz, final String field, final String message) { - super("Method not allowed", HttpStatus.METHOD_NOT_ALLOWED, clazz, field, message); + super(message, HttpStatus.METHOD_NOT_ALLOWED, clazz, field, message); } public MuttleyMethodNotAllowedException(final ErrorMessage errorMessage) { diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNoContentException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNoContentException.java index c16fd051..4f58a8de 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNoContentException.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNoContentException.java @@ -12,7 +12,7 @@ public class MuttleyNoContentException extends MuttleyException { public MuttleyNoContentException(final Class clazz, final String field, final String message) { - super("No Content", HttpStatus.NO_CONTENT, clazz, field, message); + super(message, HttpStatus.NO_CONTENT, clazz, field, message); } public MuttleyNoContentException(final ErrorMessage errorMessage) { diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNotAcceptableException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNotAcceptableException.java index f5a8d77e..7cc0beab 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNotAcceptableException.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNotAcceptableException.java @@ -12,7 +12,7 @@ public class MuttleyNotAcceptableException extends MuttleyException { public MuttleyNotAcceptableException(final Class clazz, final String field, final String message) { - super("Not Acceptable", HttpStatus.NOT_ACCEPTABLE, clazz, field, message); + super(message, HttpStatus.NOT_ACCEPTABLE, clazz, field, message); } public MuttleyNotAcceptableException(final ErrorMessage errorMessage) { diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNotFoundException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNotFoundException.java index 063aa675..06046eaf 100644 --- a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNotFoundException.java +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/MuttleyNotFoundException.java @@ -16,7 +16,7 @@ public MuttleyNotFoundException(final Throwable cause) { } public MuttleyNotFoundException(final Class clazz, final String field, final String message) { - super("AbstractResource Not Found", HttpStatus.NOT_FOUND, clazz, field, message); + super(message, HttpStatus.NOT_FOUND, clazz, field, message); } public MuttleyNotFoundException(final ErrorMessage errorMessage) { diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecondaryEmailException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecondaryEmailException.java new file mode 100644 index 00000000..5e006c0e --- /dev/null +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecondaryEmailException.java @@ -0,0 +1,16 @@ +package br.com.muttley.exception.throwables.security; + +import org.springframework.http.HttpStatus; + +/** + * @author Carolina Cedro on 09/10/2024. + * e-mail: ana.carolina@maxxsoft.com.br + * @project spring-cloud + */ +public class MuttleySecondaryEmailException extends MuttleySecurityUnauthorizedException { + + public MuttleySecondaryEmailException(final Class clazz, final String field, final String message) { + super(message, HttpStatus.BAD_REQUEST, clazz, field, + "O campo de email secundário não pode estar vazio ou nulo."); + } +} diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecurityEmailNotFoundtException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecurityEmailNotFoundtException.java new file mode 100644 index 00000000..2230fee9 --- /dev/null +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecurityEmailNotFoundtException.java @@ -0,0 +1,16 @@ +package br.com.muttley.exception.throwables.security; + +import org.springframework.http.HttpStatus; + +/** + * @author Carolina Cedro on 08/10/2024. + * e-mail: ana.carolina@maxxsoft.com.br + * @project spring-cloud + */ +public class MuttleySecurityEmailNotFoundtException extends MuttleySecurityUnauthorizedException { + + public MuttleySecurityEmailNotFoundtException(final Class clazz, final String field, final String message) { + super(message, HttpStatus.BAD_REQUEST, clazz, field, "O email informado não foi encontrado. Verifique e tente novamente."); + } + +} diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecurityExpiredTokenException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecurityExpiredTokenException.java new file mode 100644 index 00000000..be9b20e5 --- /dev/null +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecurityExpiredTokenException.java @@ -0,0 +1,16 @@ +package br.com.muttley.exception.throwables.security; + +import org.springframework.http.HttpStatus; + +/** + * @author Carolina Cedro on 08/10/2024. + * e-mail: ana.carolina@maxxsoft.com.br + * @project spring-cloud + */ +public class MuttleySecurityExpiredTokenException extends MuttleySecurityUnauthorizedException { + + public MuttleySecurityExpiredTokenException(final Class clazz, final String field, final String message) { + super(message, HttpStatus.BAD_REQUEST, clazz, field, + "O token de redefinição de senha expirou ou é inválido. "); + } +} diff --git a/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecurityUserNotFoundException.java b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecurityUserNotFoundException.java new file mode 100644 index 00000000..c5e2c9bb --- /dev/null +++ b/muttley-exception/src/main/java/br/com/muttley/exception/throwables/security/MuttleySecurityUserNotFoundException.java @@ -0,0 +1,16 @@ +package br.com.muttley.exception.throwables.security; + +import org.springframework.http.HttpStatus; + +/** + * @author Carolina Cedro on 08/10/2024. + * e-mail: ana.carolina@maxxsoft.com.br + * @project spring-cloud + */ +public class MuttleySecurityUserNotFoundException extends MuttleySecurityUnauthorizedException { + + public MuttleySecurityUserNotFoundException(final Class clazz, final String field, final String message) { + super(message, HttpStatus.BAD_REQUEST, clazz, field, "user não encontrado !"); + } + +} diff --git a/muttley-feign/pom.xml b/muttley-feign/pom.xml index 93746ad1..90895420 100644 --- a/muttley-feign/pom.xml +++ b/muttley-feign/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar @@ -17,6 +17,12 @@ + + br.com.muttley + muttley-model + provided + + org.springframework.cloud spring-cloud-starter-feign diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/FeignConfig.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/FeignConfig.java index d4f1d57d..cf0cdfcf 100644 --- a/muttley-feign/src/main/java/br/com/muttley/feign/service/FeignConfig.java +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/FeignConfig.java @@ -3,6 +3,11 @@ import br.com.muttley.feign.service.converters.BooleanHttpMessageConverter; import br.com.muttley.feign.service.converters.DateHttpMessageConverter; import br.com.muttley.feign.service.converters.LongHttpMessageConverter; +import br.com.muttley.feign.service.converters.OwnerDataHttpMessageConverter; +import br.com.muttley.feign.service.converters.UserHttpMessageConverter; +import br.com.muttley.feign.service.interceptors.PropagateHeadersInterceptor; +import br.com.muttley.feign.service.service.MuttleyDecodersService; +import br.com.muttley.feign.service.service.MuttleyPropagateHeadersService; import feign.Feign; import feign.Logger; import feign.Retryer; @@ -10,6 +15,7 @@ import feign.okhttp.OkHttpClient; import feign.slf4j.Slf4jLogger; import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.web.HttpMessageConverters; @@ -18,6 +24,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertySource; import org.springframework.http.converter.HttpMessageConverter; import java.util.ArrayList; @@ -33,9 +40,15 @@ */ @Configuration public class FeignConfig extends FeignClientsConfiguration { - private final String PROPERTY_SOURCE = "applicationConfig: [classpath:/bootstrap.properties]"; + private final String PROPERTY_SOURCE_PROP = "applicationConfig: [classpath:/bootstrap.properties]"; + private final String PROPERTY_SOURCE_YAML = "applicationConfig: [classpath:/bootstrap.yaml]"; @Autowired private ObjectFactory messageConverters; + @Autowired + private ObjectProvider muttleyPropagateHeadersService; + + @Autowired + private ObjectProvider muttleyDecodersService; @Bean @@ -43,9 +56,25 @@ public Feign.Builder feignBuilder( final Retryer retryer, final @Autowired ConfigurableEnvironment env, final @Value("${muttley.feign.loggin.level:#{null}}") String logLevel) { - final Map map = (Map) env.getPropertySources().get(PROPERTY_SOURCE).getSource(); + PropertySource propertySource = env.getPropertySources().get(PROPERTY_SOURCE_PROP); + if (propertySource == null) { + propertySource = env.getPropertySources().get(PROPERTY_SOURCE_YAML); + } + final Map map = (Map) propertySource.getSource(); map.put("feign.okhttp.enabled", "true"); + final Feign.Builder builder = super.feignBuilder(retryer).client(new OkHttpClient()); + + //injetando o serviço de headers a ser propagados + final MuttleyPropagateHeadersService service = this.muttleyPropagateHeadersService.getIfAvailable(); + //foi injetado? + if (service != null) { + //adicionando o interceptor + builder.requestInterceptor(new PropagateHeadersInterceptor(service)); + } + builder.requestInterceptor(template -> template.decodeSlash(false)); + + return includeLogger(logLevel, super.feignBuilder(retryer).client(new OkHttpClient())); } @@ -55,6 +84,15 @@ public Decoder feignDecoder() { decoderConverters.add(new LongHttpMessageConverter()); decoderConverters.add(new BooleanHttpMessageConverter()); decoderConverters.add(new DateHttpMessageConverter()); + decoderConverters.add(new OwnerDataHttpMessageConverter()); + decoderConverters.add(new UserHttpMessageConverter()); + //decoderConverters.add(new ListOwnerDataHttpMessageConverter()); + + //verificando se alguem implementou algum decoder + final MuttleyDecodersService decodersService = this.muttleyDecodersService.getIfAvailable(); + if (decodersService != null) { + decodersService.getDecoders().forEach(it -> decoderConverters.add(it)); + } //HttpMessageConverters httpMessageConverters = new HttpMessageConverters(decoderConverters); @@ -86,4 +124,4 @@ private Feign.Builder includeLogger(final String logLevel, final Feign.Builder b } return builder; } -} \ No newline at end of file +} diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/BooleanHttpMessageConverter.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/BooleanHttpMessageConverter.java index ae8786af..2358e90f 100644 --- a/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/BooleanHttpMessageConverter.java +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/BooleanHttpMessageConverter.java @@ -21,7 +21,7 @@ public BooleanHttpMessageConverter() { @Override protected boolean supports(Class clazz) { - return Boolean.class.isAssignableFrom(clazz); + return boolean.class.isAssignableFrom(clazz) || Boolean.class.isAssignableFrom(clazz); } @Override @@ -34,4 +34,4 @@ protected Boolean readInternal(Class clazz, HttpInputMessage protected void writeInternal(Boolean value, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { this.writeString(value == null ? "null" : value.toString(), outputMessage); } -} \ No newline at end of file +} diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/ListOwnerDataHttpMessageConverter.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/ListOwnerDataHttpMessageConverter.java new file mode 100644 index 00000000..766eace3 --- /dev/null +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/ListOwnerDataHttpMessageConverter.java @@ -0,0 +1,46 @@ +package br.com.muttley.feign.service.converters; + +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.OwnerDataImpl; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira 12/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class ListOwnerDataHttpMessageConverter extends MuttleyHttpMessageConverter> { + private final Class> _type = (Class>) Collections.emptyList().getClass(); + + public ListOwnerDataHttpMessageConverter() { + super(MediaType.APPLICATION_JSON); + } + + @Override + protected boolean supports(final Class clazz) { + return this._type.isAssignableFrom(clazz); + } + + @Override + protected List readInternal(final Class> clazz, final HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { + final ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(inputMessage.getBody(), new TypeReference>() { + }); + } + + @Override + protected void writeInternal(final List owners, final HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { + final ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(outputMessage.getBody(), owners); + } +} diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/MuttleyHttpMessageConverter.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/MuttleyHttpMessageConverter.java index ba935346..845daa9d 100644 --- a/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/MuttleyHttpMessageConverter.java +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/MuttleyHttpMessageConverter.java @@ -3,6 +3,7 @@ import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; import java.io.IOException; import java.io.InputStream; @@ -15,7 +16,7 @@ * e-mail: joel.databox@gmail.com * @project muttley-cloud */ -public abstract class MuttleyHttpMessageConverter extends AbstractHttpMessageConverter { +public abstract class MuttleyHttpMessageConverter extends AbstractHttpMessageConverter implements HttpMessageConverter { public MuttleyHttpMessageConverter() { } diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/OwnerDataHttpMessageConverter.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/OwnerDataHttpMessageConverter.java new file mode 100644 index 00000000..a3afabfa --- /dev/null +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/OwnerDataHttpMessageConverter.java @@ -0,0 +1,43 @@ +package br.com.muttley.feign.service.converters; + +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.OwnerDataImpl; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira 12/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class OwnerDataHttpMessageConverter extends MuttleyHttpMessageConverter { + + public OwnerDataHttpMessageConverter() { + super(MediaType.APPLICATION_JSON); + } + + @Override + protected boolean supports(final Class clazz) { + return OwnerData.class.isAssignableFrom(clazz); + } + + @Override + protected OwnerData readInternal(final Class clazz, final HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { + final ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(inputMessage.getBody(), new TypeReference() { + }); + } + + @Override + protected void writeInternal(final OwnerData ownerData, final HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { + final ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(outputMessage.getBody(), ownerData); + } +} diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/UserHttpMessageConverter.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/UserHttpMessageConverter.java new file mode 100644 index 00000000..2ab64e28 --- /dev/null +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/converters/UserHttpMessageConverter.java @@ -0,0 +1,81 @@ +package br.com.muttley.feign.service.converters; + +import br.com.muttley.model.security.Authority; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.preference.UserPreferences; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; + +import java.io.IOException; +import java.util.List; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 26/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class UserHttpMessageConverter extends MuttleyHttpMessageConverter { + + public UserHttpMessageConverter() { + super(MediaType.APPLICATION_JSON); + } + + @Override + protected boolean supports(final Class clazz) { + return User.class.isAssignableFrom(clazz); + } + + @Override + protected User readInternal(final Class clazz, final HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { + final ObjectMapper mapper = new ObjectMapper() + .registerModule( + new SimpleModule("ObjectId", + new Version(1, 0, 0, null, null, null) + ).addDeserializer(User.class, new UserSerializer()) + ); + return mapper.readValue(inputMessage.getBody(), User.class); + } + + @Override + protected void writeInternal(final User user, final HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { + final ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(outputMessage.getBody(), user); + } + + private static class UserSerializer extends JsonDeserializer { + @Override + public User deserialize(final JsonParser parser, final DeserializationContext ctxt) throws IOException, JsonProcessingException { + final ObjectCodec codec = parser.getCodec(); + final JsonNode node = codec.readTree(parser); + if (node.isNull()) { + return null; + } + return new User().setId(node.get("id").asText()) + .setName(node.get("name").asText()) + .setDescription(node.get("description").asText()) + .setEmail(node.get("email").asText()) + .setUserName(node.get("userName").asText()) + .setNickUsers(node.get("nickUsers").traverse(codec).readValueAs(new TypeReference>() { + })).setEnable(node.get("enable").asBoolean()) + .setAuthorities((Set) node.get("authorities").traverse(codec).readValueAs(new TypeReference>() { + })).setPreferences(node.get("preferences").traverse(codec).readValueAs(UserPreferences.class)) + .setDataBindings(node.get("preferences").traverse(codec).readValueAs(new TypeReference>() { + })).setOdinUser(node.get("odinUser").asBoolean()); + } + } +} diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/interceptors/HeadersMetadataInterceptor.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/interceptors/HeadersMetadataInterceptor.java new file mode 100644 index 00000000..8ef7012b --- /dev/null +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/interceptors/HeadersMetadataInterceptor.java @@ -0,0 +1,36 @@ +package br.com.muttley.feign.service.interceptors; + +import br.com.muttley.feign.service.service.MuttleyHeadersMetadataService; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Map; + +import static org.springframework.util.ObjectUtils.isEmpty; + +/** + * @author Joel Rodrigues Moreira 22/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class HeadersMetadataInterceptor implements RequestInterceptor { + private final ObjectProvider headersMetadataService; + + @Autowired + public HeadersMetadataInterceptor(final ObjectProvider headersMetadataService) { + this.headersMetadataService = headersMetadataService; + } + + @Override + public void apply(final RequestTemplate template) { + final MuttleyHeadersMetadataService service = this.headersMetadataService.getIfAvailable(); + if (service != null) { + final Map headers = service.getHeadersMetadata(); + if (!isEmpty(headers)) { + headers.forEach((final String key, final String value) -> template.header(key, value)); + } + } + } +} diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/interceptors/PropagateHeadersInterceptor.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/interceptors/PropagateHeadersInterceptor.java new file mode 100644 index 00000000..c53507ad --- /dev/null +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/interceptors/PropagateHeadersInterceptor.java @@ -0,0 +1,71 @@ +package br.com.muttley.feign.service.interceptors; + +import br.com.muttley.feign.service.service.MuttleyPropagateHeadersService; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import static java.util.stream.Stream.of; + +/** + * @author Joel Rodrigues Moreira on 15/08/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class PropagateHeadersInterceptor implements RequestInterceptor { + + private final MuttleyPropagateHeadersService muttleyPropagateHeadersService; + + public PropagateHeadersInterceptor(final MuttleyPropagateHeadersService muttleyPropagateHeadersService) { + this.muttleyPropagateHeadersService = muttleyPropagateHeadersService; + } + + @Override + public void apply(final RequestTemplate template) { + if (this.muttleyPropagateHeadersService != null) { + final String[] propagateHeaders = this.muttleyPropagateHeadersService.getPropagateHeaders(); + if (propagateHeaders != null && propagateHeaders.length > 0) { + final Map headers = getHeadersFromCurrentRequest(propagateHeaders); + if (headers != null) { + headers.forEach((final String key, final String value) -> template.header(key, value)); + } + } + } + } + + + private Map getHeadersFromCurrentRequest(final String[] propagateHeaders) { + final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null) { + //recuperando a requisicao corrent + final HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); + //map para armazenar o retorno + final Map headers = new HashMap<>(propagateHeaders.length); + //pegando um enum para interar o mesmo + final Enumeration headerNames = request.getHeaderNames(); + //interando + while (headerNames.hasMoreElements()) { + //header atual + final String currentHeader = headerNames.nextElement().toString(); + of(propagateHeaders) + .parallel() + .forEach(prop -> { + if (prop.equalsIgnoreCase(currentHeader)) { + headers.put(prop, request.getHeader(currentHeader)); + } + }); + } + return headers; + } + + return null; + } + +} diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/registrar/MuttleyFeignFormatterRegister.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/registrar/MuttleyFeignFormatterRegister.java new file mode 100644 index 00000000..d0c9810b --- /dev/null +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/registrar/MuttleyFeignFormatterRegister.java @@ -0,0 +1,40 @@ +package br.com.muttley.feign.service.registrar; + +import org.springframework.cloud.netflix.feign.FeignFormatterRegistrar; +import org.springframework.format.Formatter; +import org.springframework.format.FormatterRegistry; +import org.springframework.stereotype.Component; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * @author Joel Rodrigues Moreira 23/11/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class MuttleyFeignFormatterRegister implements FeignFormatterRegistrar { + @Override + public void registerFormatters(final FormatterRegistry registry) { + registry.addFormatter(new DateFormatter()); + } + + private static class DateFormatter implements Formatter { + static final ThreadLocal FORMAT = ThreadLocal.withInitial( + () -> new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + ); + + @Override + public Date parse(String text, Locale locale) throws ParseException { + return FORMAT.get().parse(text); + } + + @Override + public String print(Date date, Locale locale) { + return FORMAT.get().format(date); + } + } +} diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/service/MuttleyDecodersService.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/service/MuttleyDecodersService.java new file mode 100644 index 00000000..88806734 --- /dev/null +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/service/MuttleyDecodersService.java @@ -0,0 +1,14 @@ +package br.com.muttley.feign.service.service; + +import br.com.muttley.feign.service.converters.MuttleyHttpMessageConverter; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 12/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyDecodersService { + Set getDecoders(); +} diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/service/MuttleyHeadersMetadataService.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/service/MuttleyHeadersMetadataService.java new file mode 100644 index 00000000..7fd927d2 --- /dev/null +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/service/MuttleyHeadersMetadataService.java @@ -0,0 +1,12 @@ +package br.com.muttley.feign.service.service; + +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira 22/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyHeadersMetadataService { + Map getHeadersMetadata(); +} diff --git a/muttley-feign/src/main/java/br/com/muttley/feign/service/service/MuttleyPropagateHeadersService.java b/muttley-feign/src/main/java/br/com/muttley/feign/service/service/MuttleyPropagateHeadersService.java new file mode 100644 index 00000000..f46a4d4c --- /dev/null +++ b/muttley-feign/src/main/java/br/com/muttley/feign/service/service/MuttleyPropagateHeadersService.java @@ -0,0 +1,10 @@ +package br.com.muttley.feign.service.service; + +/** + * @author Joel Rodrigues Moreira on 15/08/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyPropagateHeadersService { + public String[] getPropagateHeaders(); +} diff --git a/muttley-files/.gitignore b/muttley-files/.gitignore new file mode 100644 index 00000000..549e00a2 --- /dev/null +++ b/muttley-files/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/muttley-files/.mvn/wrapper/maven-wrapper.jar b/muttley-files/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..cb28b0e3 Binary files /dev/null and b/muttley-files/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-files/.mvn/wrapper/maven-wrapper.properties b/muttley-files/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..462686e2 --- /dev/null +++ b/muttley-files/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/muttley-files/mvnw b/muttley-files/mvnw new file mode 100755 index 00000000..66df2854 --- /dev/null +++ b/muttley-files/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-files/mvnw.cmd b/muttley-files/mvnw.cmd new file mode 100644 index 00000000..95ba6f54 --- /dev/null +++ b/muttley-files/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/muttley-files/pom.xml b/muttley-files/pom.xml new file mode 100644 index 00000000..ffb85a1f --- /dev/null +++ b/muttley-files/pom.xml @@ -0,0 +1,30 @@ + + + + br.com.muttley + muttley-cloud + ${revision} + + 4.0.0 + jar + + muttley-files + + muttley-files + Demo project for Spring Boot + + + + org.projectlombok + lombok + true + + + + br.com.muttley + muttley-utils + + + + diff --git a/muttley-files/src/main/java/br/com/muttley/files/config/MuttleyFilesConfig.java b/muttley-files/src/main/java/br/com/muttley/files/config/MuttleyFilesConfig.java new file mode 100644 index 00000000..f05e9cc7 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/config/MuttleyFilesConfig.java @@ -0,0 +1,32 @@ +package br.com.muttley.files.config; + +import br.com.muttley.files.properties.Properties; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; + +import java.nio.file.Paths; + +/** + * @author Joel Rodrigues Moreira on 28/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Configuration +@EnableAsync +@ComponentScan(basePackages = "br.com.muttley.files.listeners") +@EnableConfigurationProperties(Properties.class) +public class MuttleyFilesConfig implements InitializingBean { + @Autowired + private Properties properties; + + @Override + public void afterPropertiesSet() throws Exception { + LoggerFactory.getLogger(MuttleyFilesConfig.class).info("File caching service has been configured: \n\t" + Paths.get(properties.getFiles()).toAbsolutePath().toString()); + } +} + diff --git a/muttley-files/src/main/java/br/com/muttley/files/events/DeleteAsyncFilesEvent.java b/muttley-files/src/main/java/br/com/muttley/files/events/DeleteAsyncFilesEvent.java new file mode 100644 index 00000000..2fdbb52a --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/events/DeleteAsyncFilesEvent.java @@ -0,0 +1,16 @@ +package br.com.muttley.files.events; + +import br.com.muttley.files.model.FileForDelete; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 03/08/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class DeleteAsyncFilesEvent extends DeleteFilesEvent { + public DeleteAsyncFilesEvent(Set files) { + super(files); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/events/DeleteFilesEvent.java b/muttley-files/src/main/java/br/com/muttley/files/events/DeleteFilesEvent.java new file mode 100644 index 00000000..6d5dbcd3 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/events/DeleteFilesEvent.java @@ -0,0 +1,27 @@ +package br.com.muttley.files.events; + +import br.com.muttley.files.model.FileForDelete; +import org.springframework.context.ApplicationEvent; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 03/08/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ + +abstract class DeleteFilesEvent extends ApplicationEvent { + + final Set files; + + protected DeleteFilesEvent(final Set files) { + super(files); + this.files = files; + } + + @Override + public Set getSource() { + return this.files; + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/events/DeleteSyncFilesEvent.java b/muttley-files/src/main/java/br/com/muttley/files/events/DeleteSyncFilesEvent.java new file mode 100644 index 00000000..addfb4e0 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/events/DeleteSyncFilesEvent.java @@ -0,0 +1,16 @@ +package br.com.muttley.files.events; + +import br.com.muttley.files.model.FileForDelete; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 03/08/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class DeleteSyncFilesEvent extends DeleteFilesEvent { + public DeleteSyncFilesEvent(Set files) { + super(files); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/events/DownloadAsyncFilesEvent.java b/muttley-files/src/main/java/br/com/muttley/files/events/DownloadAsyncFilesEvent.java new file mode 100644 index 00000000..e4d8f3f3 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/events/DownloadAsyncFilesEvent.java @@ -0,0 +1,17 @@ +package br.com.muttley.files.events; + +import br.com.muttley.files.model.FileForDownload; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 28/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class DownloadAsyncFilesEvent extends DownloadFilesEvent { + + public DownloadAsyncFilesEvent(Set files) { + super(files); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/events/DownloadFilesEvent.java b/muttley-files/src/main/java/br/com/muttley/files/events/DownloadFilesEvent.java new file mode 100644 index 00000000..7878f09f --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/events/DownloadFilesEvent.java @@ -0,0 +1,26 @@ +package br.com.muttley.files.events; + +import br.com.muttley.files.model.FileForDownload; +import org.springframework.context.ApplicationEvent; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 28/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +abstract class DownloadFilesEvent extends ApplicationEvent { + + final Set files; + + protected DownloadFilesEvent(final Set files) { + super(files); + this.files = files; + } + + @Override + public Set getSource() { + return this.files; + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/events/DownloadSyncFilesEvent.java b/muttley-files/src/main/java/br/com/muttley/files/events/DownloadSyncFilesEvent.java new file mode 100644 index 00000000..bdbd305a --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/events/DownloadSyncFilesEvent.java @@ -0,0 +1,16 @@ +package br.com.muttley.files.events; + +import br.com.muttley.files.model.FileForDownload; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 28/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class DownloadSyncFilesEvent extends DownloadFilesEvent { + public DownloadSyncFilesEvent(Set files) { + super(files); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/events/MergeAsyncFilesEvent.java b/muttley-files/src/main/java/br/com/muttley/files/events/MergeAsyncFilesEvent.java new file mode 100644 index 00000000..ef965d28 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/events/MergeAsyncFilesEvent.java @@ -0,0 +1,14 @@ +package br.com.muttley.files.events; + +import br.com.muttley.files.model.FileMerge; + +/** + * @author Joel Rodrigues Moreira on 03/08/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class MergeAsyncFilesEvent extends MergeFilesEvent{ + protected MergeAsyncFilesEvent(FileMerge merge) { + super(merge); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/events/MergeFilesEvent.java b/muttley-files/src/main/java/br/com/muttley/files/events/MergeFilesEvent.java new file mode 100644 index 00000000..a4ea15b2 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/events/MergeFilesEvent.java @@ -0,0 +1,25 @@ +package br.com.muttley.files.events; + +import br.com.muttley.files.model.FileMerge; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira on 03/08/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ + +abstract class MergeFilesEvent extends ApplicationEvent { + + final FileMerge merge; + + protected MergeFilesEvent(FileMerge merge) { + super(merge); + this.merge = merge; + } + + @Override + public FileMerge getSource() { + return this.merge; + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/events/MergeSyncFilesEvent.java b/muttley-files/src/main/java/br/com/muttley/files/events/MergeSyncFilesEvent.java new file mode 100644 index 00000000..9839dbe4 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/events/MergeSyncFilesEvent.java @@ -0,0 +1,14 @@ +package br.com.muttley.files.events; + +import br.com.muttley.files.model.FileMerge; + +/** + * @author Joel Rodrigues Moreira on 03/08/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class MergeSyncFilesEvent extends MergeFilesEvent { + protected MergeSyncFilesEvent(FileMerge merge) { + super(merge); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/listeners/DeleteAsyncFilesEventListener.java b/muttley-files/src/main/java/br/com/muttley/files/listeners/DeleteAsyncFilesEventListener.java new file mode 100644 index 00000000..88b45d8b --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/listeners/DeleteAsyncFilesEventListener.java @@ -0,0 +1,42 @@ +package br.com.muttley.files.listeners; + +import br.com.muttley.files.events.DeleteAsyncFilesEvent; +import br.com.muttley.files.properties.Properties; +import br.com.muttley.utils.FilesUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * @author Joel Rodrigues Moreira on 28/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class DeleteAsyncFilesEventListener { + private final Properties properties; + private final Logger logger = LoggerFactory.getLogger(DeleteAsyncFilesEventListener.class); + + @Autowired + public DeleteAsyncFilesEventListener(final Properties properties) { + this.properties = properties; + } + + @EventListener(DeleteAsyncFilesEvent.class) + @Async + public void onApplicationEvent(DeleteAsyncFilesEvent event) { + event.getSource() + .parallelStream() + .forEach(it -> { + final Path path = Paths.get(this.properties.getFiles(), it.getPath().toString()); + FilesUtils.removeFile(path, it.isDropParentIfEmpty()); + logger.info("The successfully deleted file: \n\t Local file -> " + path.toAbsolutePath()); + }); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/listeners/DeleteSyncFilesEventListener.java b/muttley-files/src/main/java/br/com/muttley/files/listeners/DeleteSyncFilesEventListener.java new file mode 100644 index 00000000..aa08c877 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/listeners/DeleteSyncFilesEventListener.java @@ -0,0 +1,40 @@ +package br.com.muttley.files.listeners; + +import br.com.muttley.files.events.DeleteSyncFilesEvent; +import br.com.muttley.files.properties.Properties; +import br.com.muttley.utils.FilesUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * @author Joel Rodrigues Moreira on 28/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class DeleteSyncFilesEventListener { + private final Properties properties; + private final Logger logger = LoggerFactory.getLogger(DeleteSyncFilesEventListener.class); + + @Autowired + public DeleteSyncFilesEventListener(final Properties properties) { + this.properties = properties; + } + + @EventListener(classes = DeleteSyncFilesEvent.class) + public void onApplicationEvent(DeleteSyncFilesEvent event) { + event.getSource() + .parallelStream() + .forEach(it -> { + final Path path = Paths.get(this.properties.getFiles(), it.getPath().toString()); + FilesUtils.removeFile(path, it.isDropParentIfEmpty()); + logger.info("The successfully deleted file: \n\t Local file -> " + path.toAbsolutePath()); + }); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/listeners/DownloadAsyncFilesEventListener.java b/muttley-files/src/main/java/br/com/muttley/files/listeners/DownloadAsyncFilesEventListener.java new file mode 100644 index 00000000..5f7d5dd6 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/listeners/DownloadAsyncFilesEventListener.java @@ -0,0 +1,42 @@ +package br.com.muttley.files.listeners; + +import br.com.muttley.files.events.DownloadAsyncFilesEvent; +import br.com.muttley.files.properties.Properties; +import br.com.muttley.utils.FilesUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * @author Joel Rodrigues Moreira on 28/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class DownloadAsyncFilesEventListener { + private final Properties properties; + private final Logger logger = LoggerFactory.getLogger(DownloadAsyncFilesEventListener.class); + + @Autowired + public DownloadAsyncFilesEventListener(final Properties properties) { + this.properties = properties; + } + + @EventListener(classes = DownloadAsyncFilesEvent.class) + @Async + public void onApplicationEvent(DownloadAsyncFilesEvent event) { + event.getSource() + .parallelStream() + .forEach(it -> { + final Path path = Paths.get(this.properties.getFiles(), it.getPath().toString()); + FilesUtils.downloadFile(it.getUrl(), path, it.isReplaceIfExists()); + logger.info("The file has been successfully downloaded: \n\t Local file -> " + path.toAbsolutePath() + "\n\t Remote file -> " + it.getUrl()); + }); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/listeners/DownloadSyncFilesEventListener.java b/muttley-files/src/main/java/br/com/muttley/files/listeners/DownloadSyncFilesEventListener.java new file mode 100644 index 00000000..43409ad9 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/listeners/DownloadSyncFilesEventListener.java @@ -0,0 +1,40 @@ +package br.com.muttley.files.listeners; + +import br.com.muttley.files.events.DownloadSyncFilesEvent; +import br.com.muttley.files.properties.Properties; +import br.com.muttley.utils.FilesUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * @author Joel Rodrigues Moreira on 28/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class DownloadSyncFilesEventListener { + private final Properties properties; + private final Logger logger = LoggerFactory.getLogger(DownloadSyncFilesEventListener.class); + + @Autowired + public DownloadSyncFilesEventListener(final Properties properties) { + this.properties = properties; + } + + @EventListener(DownloadSyncFilesEvent.class) + public void onApplicationEvent(DownloadSyncFilesEvent event) { + event.getSource() + .parallelStream() + .forEach(it -> { + final Path path = Paths.get(this.properties.getFiles(), it.getPath().toString()); + FilesUtils.downloadFile(it.getUrl(), path, it.isReplaceIfExists()); + logger.info("The file has been successfully downloaded: \n\t Local file -> " + path.toAbsolutePath() + "\n\t Remote file -> " + it.getUrl()); + }); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/listeners/MergeAsyncFilesEventListener.java b/muttley-files/src/main/java/br/com/muttley/files/listeners/MergeAsyncFilesEventListener.java new file mode 100644 index 00000000..e5c40571 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/listeners/MergeAsyncFilesEventListener.java @@ -0,0 +1,64 @@ +package br.com.muttley.files.listeners; + +import br.com.muttley.files.events.MergeAsyncFilesEvent; +import br.com.muttley.files.properties.Properties; +import br.com.muttley.utils.FilesUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * @author Joel Rodrigues Moreira on 03/08/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class MergeAsyncFilesEventListener { + private final Properties properties; + private final Logger logger = LoggerFactory.getLogger(MergeAsyncFilesEventListener.class); + + @Autowired + public MergeAsyncFilesEventListener(final Properties properties) { + this.properties = properties; + } + + @EventListener(MergeAsyncFilesEvent.class) + @Async + public void onApplicationEvent(MergeAsyncFilesEvent event) { + //removendo todos os arquivos primeiramente + event.getSource() + .getFilesForDelete() + .parallelStream() + .forEach(it -> { + try { + final Path path = Paths.get(this.properties.getFiles(), it.getPath().toString()); + FilesUtils.removeFile(path, it.isDropParentIfEmpty()); + logger.info("The successfully deleted file: \n\t Local file -> " + path.toAbsolutePath()); + } catch (Exception ex) { + ex.printStackTrace(); + } + }); + + //baixando os arquivo necessários + event.getSource() + .getFilesForDownload() + .parallelStream() + .forEach(it -> { + try { + final Path path = Paths.get(this.properties.getFiles(), it.getPath().toString()); + FilesUtils.downloadFile(it.getUrl(), path, it.isReplaceIfExists()); + logger.info("The file has been successfully downloaded: \n\t Local file -> " + path.toAbsolutePath() + "\n\t Remote file -> " + it.getUrl()); + } catch (Exception ex) { + ex.printStackTrace(); + } + }); + + + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/listeners/MergeSyncFilesEventListener.java b/muttley-files/src/main/java/br/com/muttley/files/listeners/MergeSyncFilesEventListener.java new file mode 100644 index 00000000..c756ac18 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/listeners/MergeSyncFilesEventListener.java @@ -0,0 +1,54 @@ +package br.com.muttley.files.listeners; + +import br.com.muttley.files.events.MergeSyncFilesEvent; +import br.com.muttley.files.properties.Properties; +import br.com.muttley.utils.FilesUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * @author Joel Rodrigues Moreira on 03/08/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class MergeSyncFilesEventListener { + private final Properties properties; + private final Logger logger = LoggerFactory.getLogger(MergeSyncFilesEventListener.class); + + @Autowired + public MergeSyncFilesEventListener(final Properties properties) { + this.properties = properties; + } + + @EventListener(MergeSyncFilesEvent.class) + public void onApplicationEvent(MergeSyncFilesEvent event) { + //removendo todos os arquivos primeiramente + event.getSource() + .getFilesForDelete() + .parallelStream() + .forEach(it -> { + final Path path = Paths.get(this.properties.getFiles(), it.getPath().toString()); + FilesUtils.removeFile(path, it.isDropParentIfEmpty()); + logger.info("The successfully deleted file: \n\t Local file -> " + path.toAbsolutePath()); + }); + + //baixando os arquivo necessários + event.getSource() + .getFilesForDownload() + .parallelStream() + .forEach(it -> { + final Path path = Paths.get(this.properties.getFiles(), it.getPath().toString()); + FilesUtils.downloadFile(it.getUrl(), path, it.isReplaceIfExists()); + logger.info("The file has been successfully downloaded: \n\t Local file -> " + path.toAbsolutePath() + "\n\t Remote file -> " + it.getUrl()); + }); + + + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/model/FileForDelete.java b/muttley-files/src/main/java/br/com/muttley/files/model/FileForDelete.java new file mode 100644 index 00000000..1cc7c0c9 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/model/FileForDelete.java @@ -0,0 +1,30 @@ +package br.com.muttley.files.model; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.nio.file.Path; + +/** + * @author Joel Rodrigues Moreira on 03/08/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class FileForDelete { + final Path path; + final boolean dropParentIfEmpty; + + public FileForDelete(Path path, boolean dropParentIfEmpty) { + this.path = path; + this.dropParentIfEmpty = dropParentIfEmpty; + } + + public FileForDelete(Path path) { + this.path = path; + this.dropParentIfEmpty = false; + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/model/FileForDownload.java b/muttley-files/src/main/java/br/com/muttley/files/model/FileForDownload.java new file mode 100644 index 00000000..c198a894 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/model/FileForDownload.java @@ -0,0 +1,52 @@ +package br.com.muttley.files.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * @author Joel Rodrigues Moreira on 28/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@EqualsAndHashCode(of = "url") +public class FileForDownload { + final URL url; + final Path path; + final boolean replaceIfExists; + + public FileForDownload(final URL url, final Path path, final boolean replaceIfExists) { + this.url = url; + this.path = path; + this.replaceIfExists = replaceIfExists; + } + + public FileForDownload(final URL url, final Path path) { + this(url, path, false); + } + + public FileForDownload(final String url, final String path) throws MalformedURLException { + this(toURL(url), Paths.get(path), false); + } + + private static URL toURL(final String url) { + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + public FileForDelete toFileForDelete() { + return new FileForDelete(this.path); + } + + public FileForDelete toFileForDelete(final boolean removeDirectory) { + return new FileForDelete(this.path, removeDirectory); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/model/FileMerge.java b/muttley-files/src/main/java/br/com/muttley/files/model/FileMerge.java new file mode 100644 index 00000000..65da0763 --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/model/FileMerge.java @@ -0,0 +1,29 @@ +package br.com.muttley.files.model; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 03/08/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class FileMerge { + final Set filesForDownload; + final Set filesForDelete; + + public FileMerge(Set filesForDownload, Set filesForDelete) { + this.filesForDownload = filesForDownload; + this.filesForDelete = filesForDelete; + } + + public FileMerge(Set filesForDownload) { + this(filesForDownload, null); + } +} diff --git a/muttley-files/src/main/java/br/com/muttley/files/properties/Properties.java b/muttley-files/src/main/java/br/com/muttley/files/properties/Properties.java new file mode 100644 index 00000000..eb20a72e --- /dev/null +++ b/muttley-files/src/main/java/br/com/muttley/files/properties/Properties.java @@ -0,0 +1,24 @@ +package br.com.muttley.files.properties; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import static br.com.muttley.files.properties.Properties.PREFIX; + +/** + * @author Joel Rodrigues Moreira on 28/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ + +@ConfigurationProperties(prefix = PREFIX) +@Getter +@Setter +@Accessors(chain = true) +public class Properties { + public static final String PREFIX = "muttley-cloud"; + + private String files = "muttley-cloud-files"; +} diff --git a/muttley-files/src/main/resources/META-INF/spring.factories b/muttley-files/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..84ae76ca --- /dev/null +++ b/muttley-files/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + br.com.muttley.files.config.MuttleyFilesConfig diff --git a/muttley-files/src/main/resources/application.properties b/muttley-files/src/main/resources/application.properties new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/muttley-files/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/muttley-headers/.gitignore b/muttley-headers/.gitignore new file mode 100644 index 00000000..a2a3040a --- /dev/null +++ b/muttley-headers/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/muttley-headers/.mvn/wrapper/MavenWrapperDownloader.java b/muttley-headers/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..a45eb6ba --- /dev/null +++ b/muttley-headers/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/muttley-headers/.mvn/wrapper/maven-wrapper.jar b/muttley-headers/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..2cc7d4a5 Binary files /dev/null and b/muttley-headers/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-headers/.mvn/wrapper/maven-wrapper.properties b/muttley-headers/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..642d572c --- /dev/null +++ b/muttley-headers/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/muttley-headers/mvnw b/muttley-headers/mvnw new file mode 100755 index 00000000..a16b5431 --- /dev/null +++ b/muttley-headers/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-headers/mvnw.cmd b/muttley-headers/mvnw.cmd new file mode 100644 index 00000000..c8d43372 --- /dev/null +++ b/muttley-headers/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/muttley-headers/pom.xml b/muttley-headers/pom.xml new file mode 100644 index 00000000..35e0302a --- /dev/null +++ b/muttley-headers/pom.xml @@ -0,0 +1,39 @@ + + + + br.com.muttley + muttley-cloud + ${revision} + + 4.0.0 + jar + br.com.muttley + + muttley-headers + + muttley-headers + Demo project for Spring Boot + + + + + br.com.muttley + muttley-model + provided + + + + org.springframework.boot + spring-boot-starter + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/MuttleyHeaderConfig.java b/muttley-headers/src/main/java/br/com/muttley/headers/MuttleyHeaderConfig.java new file mode 100644 index 00000000..c5a906d2 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/MuttleyHeaderConfig.java @@ -0,0 +1,75 @@ +package br.com.muttley.headers; + +import br.com.muttley.headers.components.MuttleyCurrentTimezone; +import br.com.muttley.headers.components.MuttleyCurrentVersion; +import br.com.muttley.headers.components.MuttleyRequestHeader; +import br.com.muttley.headers.components.MuttleySerializeType; +import br.com.muttley.headers.components.MuttleyUserAgent; +import br.com.muttley.headers.components.MuttleyUserAgentName; +import br.com.muttley.headers.components.impl.MuttleyCurrentTimezoneImpl; +import br.com.muttley.headers.components.impl.MuttleyCurrentVersionImpl; +import br.com.muttley.headers.components.impl.MuttleyRequestHeaderImpl; +import br.com.muttley.headers.components.impl.MuttleySerializeTypeImpl; +import br.com.muttley.headers.components.impl.MuttleyUserAgentImpl; +import br.com.muttley.headers.components.impl.MuttleyUserAgentNameImpl; +import br.com.muttley.headers.services.MetadataService; +import br.com.muttley.headers.services.impl.MetadataServiceImpl; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.info.BuildProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Scope; + +import javax.servlet.http.HttpServletRequest; + +import static org.springframework.context.annotation.ScopedProxyMode.TARGET_CLASS; +import static org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST; + +@Configuration +public class MuttleyHeaderConfig { + @Bean(name = "userAgent") + @Scope(value = SCOPE_REQUEST, proxyMode = TARGET_CLASS) + public MuttleyUserAgent getUserAgent(@Autowired final ObjectProvider requestProvider) { + return new MuttleyUserAgentImpl(requestProvider); + } + + @Bean(name = "currentTimezone") + @Primary + @Scope(value = SCOPE_REQUEST, proxyMode = TARGET_CLASS) + public MuttleyCurrentTimezone getCurrentTimezone(@Autowired final ObjectProvider requestProvider) { + return new MuttleyCurrentTimezoneImpl(requestProvider); + } + + @Bean(name = "serializeType") + @Scope(value = SCOPE_REQUEST, proxyMode = TARGET_CLASS) + public MuttleySerializeType getSerializeType(@Autowired final HttpServletRequest requestProvider) { + return new MuttleySerializeTypeImpl(requestProvider); + } + + @Bean(name = "currentVersion") + @Scope(value = SCOPE_REQUEST, proxyMode = TARGET_CLASS) + @Autowired + public MuttleyCurrentVersion getCurrentVersion(final ObjectProvider requestProvider, final BuildProperties buildProperties) { + return new MuttleyCurrentVersionImpl(requestProvider, buildProperties); + } + + @Bean(name = "userAgentName") + @Scope(value = SCOPE_REQUEST, proxyMode = TARGET_CLASS) + public MuttleyUserAgentName getUserAgentName(@Autowired final ObjectProvider requestProvider) { + return new MuttleyUserAgentNameImpl(requestProvider); + } + + @Bean + @Primary + public MetadataService getMetadataService() { + return new MetadataServiceImpl(); + } + + @Bean("requestHeader") + @Scope(value = SCOPE_REQUEST, proxyMode = TARGET_CLASS) + public MuttleyRequestHeader getRequestHeader(@Autowired final HttpServletRequest request) { + return new MuttleyRequestHeaderImpl(request); + } +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyCurrentTimezone.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyCurrentTimezone.java new file mode 100644 index 00000000..5f0ccfc3 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyCurrentTimezone.java @@ -0,0 +1,21 @@ +package br.com.muttley.headers.components; + +import br.com.muttley.model.TimeZoneDocument; + +/** + * @author Joel Rodrigues Moreira on 02/06/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyCurrentTimezone { + + String getCurrentValue(); + + String getCurrentTimezoneFromRequestOrServer(); + + boolean containsValidValue(); + + TimeZoneDocument getCurrentTimezoneDocument(); + + String getCurrenteTimeZoneFromServer(); +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyCurrentVersion.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyCurrentVersion.java new file mode 100644 index 00000000..632608f0 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyCurrentVersion.java @@ -0,0 +1,14 @@ +package br.com.muttley.headers.components; + +/** + * @author Joel Rodrigues Moreira on 02/06/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyCurrentVersion { + String getCurrentValue(); + + boolean containsValidValue(); + + String getCurrenteFromServer(); +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyRequestHeader.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyRequestHeader.java new file mode 100644 index 00000000..a212c3e6 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyRequestHeader.java @@ -0,0 +1,14 @@ +package br.com.muttley.headers.components; + +/** + * @author Joel Rodrigues Moreira on 02/06/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyRequestHeader { + boolean isRequestFromAdminServer(); + + boolean hasKey(String key); + + String getByKey(String key); +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleySerializeType.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleySerializeType.java new file mode 100644 index 00000000..b4c14e05 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleySerializeType.java @@ -0,0 +1,18 @@ +package br.com.muttley.headers.components; + +/** + * @author Joel Rodrigues Moreira on 02/06/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleySerializeType { + boolean isSync(); + + boolean isObjectId(); + + boolean isObjectIdAndSync(); + + boolean isInternal(); + + boolean containsValidValue(); +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyUserAgent.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyUserAgent.java new file mode 100644 index 00000000..17efc31f --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyUserAgent.java @@ -0,0 +1,15 @@ +package br.com.muttley.headers.components; + +/** + * @author Joel Rodrigues Moreira on 02/06/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyUserAgent { + boolean isMobile(); + + boolean containsValidValue(); + + String getCurrentValue(); + +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyUserAgentName.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyUserAgentName.java new file mode 100644 index 00000000..420aa70e --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/MuttleyUserAgentName.java @@ -0,0 +1,11 @@ +package br.com.muttley.headers.components; + +/** + * @author Joel Rodrigues Moreira on 02/06/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyUserAgentName { + + String getCurrentValue(); +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyCurrentTimezoneImpl.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyCurrentTimezoneImpl.java new file mode 100644 index 00000000..e04fbe27 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyCurrentTimezoneImpl.java @@ -0,0 +1,90 @@ +package br.com.muttley.headers.components.impl; + +import br.com.muttley.headers.components.MuttleyCurrentTimezone; +import br.com.muttley.headers.model.MuttleyHeader; +import br.com.muttley.model.TimeZoneDocument; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.servlet.http.HttpServletRequest; +import java.util.TimeZone; + +import static br.com.muttley.utils.TimeZoneUtils.getTimezoneFromId; +import static br.com.muttley.utils.TimeZoneUtils.isValidTimeZone; +import static org.springframework.util.StringUtils.isEmpty; + +/** + * @author Joel Rodrigues Moreira on 29/07/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component("currentTimezone") +@RequestScope +public class MuttleyCurrentTimezoneImpl extends MuttleyHeader implements MuttleyCurrentTimezone { + public static final String CURRENT_TIMEZONE = "Current-Timezone"; + + public MuttleyCurrentTimezoneImpl(@Autowired final ObjectProvider requestProvider) { + super(CURRENT_TIMEZONE, requestProvider); + } + + @Override + public String getCurrentValue() { + if (this.containsValidValue()) { + if (this.isValid()) { + String currentTimezone = super.getCurrentValue(); + + if (!(currentTimezone.startsWith("+") || currentTimezone.startsWith("-"))) { + //se chegou aqui logo é timezone positivo + currentTimezone = "+" + currentTimezone; + } + if (!currentTimezone.contains(":")) { + currentTimezone = currentTimezone.substring(0, 3) + ":" + currentTimezone.substring(3, 5); + } + + return currentTimezone; + } + } + return null; + } + + /** + * O metodo irá tentar pegar o timezone atual da requisição + * caso não exista por padrão irá retornar o do servidor + */ + @Override + public String getCurrentTimezoneFromRequestOrServer() { + final String curretTimezone = this.getCurrentValue(); + if (curretTimezone != null) { + return curretTimezone; + } + return this.getCurrenteTimeZoneFromServer(); + } + + @Override + public boolean containsValidValue() { + return !isEmpty(super.getCurrentValue()); + } + + protected boolean isValid() { + return isValidTimeZone(super.getCurrentValue()); + } + + @Override + public TimeZoneDocument getCurrentTimezoneDocument() { + final String timeZoneServer = getCurrenteTimeZoneFromServer(); + return new TimeZoneDocument() + .setCurrentTimeZone(getCurrentValue()) + .setCreateTimeZone(getCurrentValue()) + .setServerCurrentTimeZone(timeZoneServer) + .setServerCreteTimeZone(timeZoneServer); + } + + + @Override + public String getCurrenteTimeZoneFromServer() { + final TimeZone tz = TimeZone.getDefault(); + return getTimezoneFromId(tz.getID()); + } +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyCurrentVersionImpl.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyCurrentVersionImpl.java new file mode 100644 index 00000000..e9abafe4 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyCurrentVersionImpl.java @@ -0,0 +1,50 @@ +package br.com.muttley.headers.components.impl; + +import br.com.muttley.headers.components.MuttleyCurrentVersion; +import br.com.muttley.headers.model.MuttleyHeader; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.info.BuildProperties; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.servlet.http.HttpServletRequest; + +import static org.springframework.util.StringUtils.isEmpty; + +/** + * @author Joel Rodrigues Moreira on 29/07/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component("currentVersion") +@RequestScope +public class MuttleyCurrentVersionImpl extends MuttleyHeader implements MuttleyCurrentVersion { + public static final String CURRENT_VERION = "Current-Version"; + private final BuildProperties buildProperties; + + @Autowired + public MuttleyCurrentVersionImpl(final ObjectProvider requestProvider, final BuildProperties buildProperties) { + super(CURRENT_VERION, requestProvider); + this.buildProperties = buildProperties; + } + + @Override + public String getCurrentValue() { + if (this.containsValidValue()) { + return super.getCurrentValue(); + } + return null; + } + + @Override + public boolean containsValidValue() { + return !isEmpty(super.getCurrentValue()); + } + + + @Override + public String getCurrenteFromServer() { + return this.buildProperties.getVersion(); + } +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyRequestHeaderImpl.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyRequestHeaderImpl.java new file mode 100644 index 00000000..820e7328 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyRequestHeaderImpl.java @@ -0,0 +1,41 @@ +package br.com.muttley.headers.components.impl; + +import br.com.muttley.headers.components.MuttleyRequestHeader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.servlet.http.HttpServletRequest; + +import static br.com.muttley.headers.model.MuttleyHeader.KEY_ADMIN_SERVER; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component("requestHeader") +@RequestScope +public class MuttleyRequestHeaderImpl implements MuttleyRequestHeader { + private final HttpServletRequest request; + + @Autowired + public MuttleyRequestHeaderImpl(final HttpServletRequest request) { + this.request = request; + } + + @Override + public boolean isRequestFromAdminServer() { + return this.hasKey(KEY_ADMIN_SERVER); + } + + @Override + public boolean hasKey(final String key) { + return this.request.getHeader(key) != null; + } + + @Override + public String getByKey(final String key) { + return this.request.getHeader(key); + } +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleySerializeTypeImpl.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleySerializeTypeImpl.java new file mode 100644 index 00000000..76bf6c94 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleySerializeTypeImpl.java @@ -0,0 +1,69 @@ +package br.com.muttley.headers.components.impl; + +import br.com.muttley.headers.components.MuttleySerializeType; +import br.com.muttley.headers.model.MuttleyHeader; +import br.com.muttley.model.SerializeType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.servlet.http.HttpServletRequest; + +import static br.com.muttley.model.SerializeType.KEY_FROM_HEADER; +import static br.com.muttley.model.SerializeType.OBJECT_ID_AND_SYNC_TYPE; +import static br.com.muttley.model.SerializeType.OBJECT_ID_TYPE; +import static br.com.muttley.model.SerializeType.SYNC_TYPE; + +/** + * @author Joel Rodrigues Moreira on 29/07/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + *

+ * Devemos verificar em cada requisição qual o tipo de serialização a ser utilizada + * SerializeType = 'sync' => devemos serializar o código vindo do serviço do cliente + * SerializeType = 'ObjectId' => devemos serializar o nosso próprio ObjectId + * SerializeType = 'ObjectIdAndSync' => devemos serializar o nosso próprio ObjectId juntamente com o sync + * SerializeType = null => devemos serializar o nosso próprio ObjectId + *

+ */ +@Component("serializeType") +@RequestScope +public class MuttleySerializeTypeImpl extends MuttleyHeader implements MuttleySerializeType { + private final SerializeType type; + + /*public MuttleySerializeType(@Autowired final ObjectProvider request) { + this(request.getIfAvailable()); + }*/ + + @Autowired + public MuttleySerializeTypeImpl(final HttpServletRequest request) { + super(KEY_FROM_HEADER, request); + this.type = SerializeType.Builder.build(request); + } + + @Override + public boolean isSync() { + return this.type.isSync(); + } + + @Override + public boolean isObjectId() { + return this.type.isObjectId(); + } + + @Override + public boolean isObjectIdAndSync() { + return this.type.isObjectIdAndSync(); + } + + @Override + public boolean isInternal() { + return this.type.isInternal(); + } + + @Override + public boolean containsValidValue() { + return getCurrentValue() != null && (getCurrentValue().equals(SYNC_TYPE) || getCurrentValue().equals(OBJECT_ID_TYPE) || getCurrentValue().equals(OBJECT_ID_AND_SYNC_TYPE)); + } + +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyUserAgentImpl.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyUserAgentImpl.java new file mode 100644 index 00000000..3863f6df --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyUserAgentImpl.java @@ -0,0 +1,32 @@ +package br.com.muttley.headers.components.impl; + +import br.com.muttley.headers.components.MuttleyUserAgent; +import br.com.muttley.headers.model.MuttleyHeader; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.servlet.http.HttpServletRequest; + +import static org.springframework.http.HttpHeaders.USER_AGENT; + +/** + * @author Joel Rodrigues Moreira on 29/07/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component("userAgente") +@RequestScope +public class MuttleyUserAgentImpl extends MuttleyHeader implements MuttleyUserAgent { + private static final String MOBILE = "MOBILE"; + + public MuttleyUserAgentImpl(@Autowired final ObjectProvider requestProvider) { + super(USER_AGENT, requestProvider); + } + + @Override + public boolean isMobile() { + return MOBILE.equalsIgnoreCase(getCurrentValue()); + } +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyUserAgentNameImpl.java b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyUserAgentNameImpl.java new file mode 100644 index 00000000..3c6ebd16 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/components/impl/MuttleyUserAgentNameImpl.java @@ -0,0 +1,25 @@ +package br.com.muttley.headers.components.impl; + +import br.com.muttley.headers.components.MuttleyUserAgentName; +import br.com.muttley.headers.model.MuttleyHeader; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author Joel Rodrigues Moreira on 29/07/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component("userAgentName") +@RequestScope +public class MuttleyUserAgentNameImpl extends MuttleyHeader implements MuttleyUserAgentName { + private static final String USER_AGENT_NAME = "User-Agent-Name"; + + public MuttleyUserAgentNameImpl(@Autowired final ObjectProvider requestProvider) { + super(USER_AGENT_NAME, requestProvider); + } +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/model/MuttleyHeader.java b/muttley-headers/src/main/java/br/com/muttley/headers/model/MuttleyHeader.java new file mode 100644 index 00000000..b8bc2ea2 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/model/MuttleyHeader.java @@ -0,0 +1,22 @@ +package br.com.muttley.headers.model; + +import org.springframework.beans.factory.ObjectProvider; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author Joel Rodrigues Moreira on 29/07/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class MuttleyHeader extends MuttleyRequestMetaData { + public static final String KEY_ADMIN_SERVER = "MuttleyAdminServe"; + + public MuttleyHeader(final String key, final ObjectProvider requestProvider) { + super(key, requestProvider); + } + + public MuttleyHeader(final String key, final HttpServletRequest request) { + super(key, request); + } +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/model/MuttleyRequestMetaData.java b/muttley-headers/src/main/java/br/com/muttley/headers/model/MuttleyRequestMetaData.java new file mode 100644 index 00000000..260cca0c --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/model/MuttleyRequestMetaData.java @@ -0,0 +1,46 @@ +package br.com.muttley.headers.model; + +import org.springframework.beans.factory.ObjectProvider; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author Joel Rodrigues Moreira on 29/07/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class MuttleyRequestMetaData { + protected final String key; + private final HttpServletRequest request; + protected String currentValue; + private boolean resolved = false; + + public MuttleyRequestMetaData(final String key, final HttpServletRequest request) { + this.key = key; + this.request = request; + } + + public MuttleyRequestMetaData(final String key, final ObjectProvider requestProvider) { + this(key, requestProvider.getIfAvailable()); + } + + public String getKey() { + return key; + } + + public String getCurrentValue() { + if (!this.resolved) { + this.resolved = true; + if (request != null) { + this.currentValue = request.getHeader(this.key); + } else { + this.currentValue = null; + } + } + return this.currentValue; + } + + public boolean containsValidValue() { + return this.getCurrentValue() != null; + } +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/services/MetadataService.java b/muttley-headers/src/main/java/br/com/muttley/headers/services/MetadataService.java new file mode 100644 index 00000000..13c1ace3 --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/services/MetadataService.java @@ -0,0 +1,25 @@ +package br.com.muttley.headers.services; + +import br.com.muttley.model.Document; +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.domain.Domain; + +import java.util.Collection; + +/** + * @author Joel Rodrigues Moreira 12/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MetadataService { + void generateNewMetadataFor(final User user, final Document value); + + void generateNewMetadataFor(final User user, final Document value, final Domain domain); + + void generateNewMetadataFor(final User user, final Collection values); + + void generateNewMetadataFor(final User user, final Collection values, final Domain domain); + + void generateMetaDataUpdateFor(final User user, final MetadataDocument currentMetadata, final Document value); +} diff --git a/muttley-headers/src/main/java/br/com/muttley/headers/services/impl/MetadataServiceImpl.java b/muttley-headers/src/main/java/br/com/muttley/headers/services/impl/MetadataServiceImpl.java new file mode 100644 index 00000000..9ec258ea --- /dev/null +++ b/muttley-headers/src/main/java/br/com/muttley/headers/services/impl/MetadataServiceImpl.java @@ -0,0 +1,175 @@ +package br.com.muttley.headers.services.impl; + +import br.com.muttley.headers.components.MuttleyCurrentTimezone; +import br.com.muttley.headers.components.MuttleyCurrentVersion; +import br.com.muttley.headers.components.MuttleyUserAgentName; +import br.com.muttley.headers.services.MetadataService; +import br.com.muttley.model.Document; +import br.com.muttley.model.Historic; +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.VersionDocument; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.domain.Domain; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Date; + +import static br.com.muttley.model.security.domain.Domain.PRIVATE; +import static br.com.muttley.model.security.domain.Domain.PUBLIC; + +/** + * @author Joel Rodrigues Moreira 12/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class MetadataServiceImpl implements MetadataService { + @Autowired + protected MuttleyCurrentTimezone currentTimezone; + @Autowired + protected MuttleyCurrentVersion currentVersion; + @Autowired + protected MuttleyUserAgentName userAgentName; + + @Override + public void generateNewMetadataFor(final User user, final Document value) { + this.generateNewMetadataFor(user, value, null); + } + + @Override + public void generateNewMetadataFor(final User user, final Document value, final Domain domain) { + //se não tiver nenhum metadata criado, vamos criar um + if (!value.containsMetadata()) { + value.setMetadata(new MetadataDocument(user) + .setTimeZones(this.currentTimezone.getCurrentTimezoneDocument()) + .setDomain(generateDoaminByUser(user, domain)) + .setVersionDocument( + new VersionDocument() + .setOriginVersionClientCreate(this.currentVersion.getCurrentValue()) + .setOriginVersionClientLastUpdate(this.currentVersion.getCurrentValue()) + .setOriginNameClientCreate(this.userAgentName.getCurrentValue()) + .setOriginNameClientLastUpdate(this.userAgentName.getCurrentValue()) + .setServerVersionCreate(this.currentVersion.getCurrenteFromServer()) + .setServerVersionLastUpdate(this.currentVersion.getCurrenteFromServer()) + )); + } else { + //se não tiver um domain definido devemos atribuir como private + if (!value.getMetadata().containsDomain()) { + value.getMetadata().setDomain(generateDoaminByUser(user, domain)); + } + //se não tem um timezone válido, vamos criar um + if (!value.getMetadata().containsTimeZones()) { + value.getMetadata().setTimeZones(this.currentTimezone.getCurrentTimezoneDocument()); + } else { + //se chegou aqui é sinal que já possui infos de timezones e devemos apenas checar e atualizar caso necessário + + //O timezone atual informado é valido? + if (value.getMetadata().getTimeZones().isValidCurrentTimeZone()) { + //adicionado a mesma info no createTimezone já que estamos criando um novo registro + value.getMetadata().getTimeZones().setCreateTimeZone(value.getMetadata().getTimeZones().getCurrentTimeZone()); + } + + //adicionando infos de timezone do servidor + final String currentServerTimezone = this.currentTimezone.getCurrenteTimeZoneFromServer(); + value.getMetadata().getTimeZones().setServerCreteTimeZone(currentServerTimezone); + value.getMetadata().getTimeZones().setServerCurrentTimeZone(currentServerTimezone); + } + + //criando version valido + value.getMetadata().setVersionDocument( + new VersionDocument() + .setOriginVersionClientCreate(this.currentVersion.getCurrentValue()) + .setOriginVersionClientLastUpdate(this.currentVersion.getCurrentValue()) + .setOriginNameClientCreate(this.userAgentName.getCurrentValue()) + .setOriginNameClientLastUpdate(this.userAgentName.getCurrentValue()) + .setServerVersionCreate(this.currentVersion.getCurrenteFromServer()) + .setServerVersionLastUpdate(this.currentVersion.getCurrenteFromServer()) + ); + + //criando o historic + if (!value.getMetadata().containsHistoric()) { + value.getMetadata().setHistoric(new Historic()); + } + + final Date now = new Date(); + value.getMetadata() + .getHistoric() + .setDtCreate(now) + .setDtChange(now) + .setCreatedBy(user) + .setLastChangeBy(user); + + } + } + + @Override + public void generateNewMetadataFor(final User user, final Collection values){ + this.generateNewMetadataFor(user, values, null); + } + + @Override + public void generateNewMetadataFor(final User user, final Collection values, final Domain domain) { + values.forEach(it -> { + this.generateNewMetadataFor(user, it, domain); + }); + } + + @Override + public void generateMetaDataUpdateFor(final User user, final MetadataDocument currentMetadata, final Document value) { + currentMetadata + .getTimeZones() + .setServerCurrentTimeZone( + this.currentTimezone.getCurrenteTimeZoneFromServer() + ); + + + //se veio informações no registro, devemos aproveitar + if (value.containsMetadata()) { + if (value.getMetadata().containsDomain()) { + currentMetadata.setDomain(value.getMetadata().getDomain()); + } + if (value.getMetadata().containsTimeZones()) { + if (value.getMetadata().getTimeZones().isValidCurrentTimeZone()) { + currentMetadata.getTimeZones().setCurrentTimeZone(value.getMetadata().getTimeZones().getCurrentTimeZone()); + } else { + currentMetadata.getTimeZones().setCurrentTimeZone(this.currentTimezone.getCurrentValue()); + } + } else { + currentMetadata.getTimeZones().setCurrentTimeZone(this.currentTimezone.getCurrentValue()); + } + } else { + currentMetadata.getTimeZones() + .setCurrentTimeZone(this.currentTimezone.getCurrentValue()) + .setServerCurrentTimeZone(this.currentTimezone.getCurrenteTimeZoneFromServer()); + } + //setando versionamento + currentMetadata + .getVersionDocument() + .setServerVersionLastUpdate(this.currentVersion.getCurrenteFromServer()) + .setOriginNameClientLastUpdate(this.userAgentName.getCurrentValue()) + .setOriginVersionClientLastUpdate(this.currentVersion.getCurrentValue()); + + //setando o historic + currentMetadata.getHistoric() + .setLastChangeBy(user) + .setDtChange(new Date()); + + value.setMetadata(currentMetadata); + } + + private Domain generateDoaminByUser(final User user, final Domain domain) { + //Definimos o tipo de dominio baseado no usuário atual + //se o usuário for owner, logo podemos definir o registro como publico, + //caso contrario será privado + + try { + return domain != null ? domain : user.isOwner() ? PUBLIC : PRIVATE; + } catch (RuntimeException exception) { + //se deu erro ao tentar verificar se é um owner logo podemos incarar que é um registro privado + //o erro ocorre pois o usuário é novo e logo não tem info de owner + return PRIVATE; + } + } +} diff --git a/muttley-headers/src/main/resources/META-INF/spring.factories b/muttley-headers/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..ff36fdde --- /dev/null +++ b/muttley-headers/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + br.com.muttley.headers.MuttleyHeaderConfig diff --git a/muttley-hermes-server.pom/.gitignore b/muttley-hermes-server.pom/.gitignore new file mode 100644 index 00000000..a2a3040a --- /dev/null +++ b/muttley-hermes-server.pom/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/muttley-hermes-server.pom/.mvn/wrapper/MavenWrapperDownloader.java b/muttley-hermes-server.pom/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..7f91a56e --- /dev/null +++ b/muttley-hermes-server.pom/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,114 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.Properties; + +public class MavenWrapperDownloader { + + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = + "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: : " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/muttley-hermes-server.pom/.mvn/wrapper/maven-wrapper.jar b/muttley-hermes-server.pom/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..01e67997 Binary files /dev/null and b/muttley-hermes-server.pom/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-hermes-server.pom/.mvn/wrapper/maven-wrapper.properties b/muttley-hermes-server.pom/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..cd0d451c --- /dev/null +++ b/muttley-hermes-server.pom/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip diff --git a/muttley-hermes-server.pom/muttley-hermes-api/.gitignore b/muttley-hermes-server.pom/muttley-hermes-api/.gitignore new file mode 100644 index 00000000..82eca336 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-api/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ \ No newline at end of file diff --git a/muttley-hermes-server.pom/muttley-hermes-api/.mvn/wrapper/maven-wrapper.jar b/muttley-hermes-server.pom/muttley-hermes-api/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..9cc84ea9 Binary files /dev/null and b/muttley-hermes-server.pom/muttley-hermes-api/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-hermes-server.pom/muttley-hermes-api/.mvn/wrapper/maven-wrapper.properties b/muttley-hermes-server.pom/muttley-hermes-api/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..b573bb50 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-api/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip diff --git a/muttley-hermes-server.pom/muttley-hermes-api/mvnw b/muttley-hermes-server.pom/muttley-hermes-api/mvnw new file mode 100755 index 00000000..5bf251c0 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-api/mvnw @@ -0,0 +1,225 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-hermes-server.pom/muttley-hermes-api/mvnw.cmd b/muttley-hermes-server.pom/muttley-hermes-api/mvnw.cmd new file mode 100644 index 00000000..019bd74d --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-api/mvnw.cmd @@ -0,0 +1,143 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/muttley-hermes-server.pom/muttley-hermes-api/pom.xml b/muttley-hermes-server.pom/muttley-hermes-api/pom.xml new file mode 100644 index 00000000..e93eb5d9 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-api/pom.xml @@ -0,0 +1,45 @@ + + + + br.com.muttley + muttley-hermes-server.pom + ${revision} + + 4.0.0 + jar + + muttley-hermes-api + + muttley-hermes-api + Demo project for Spring Boot + + + + br.com.muttley + muttley-model + provided + + + + org.springframework.boot + spring-boot-starter + + + + br.com.muttley + muttley-feign + + + + br.com.muttley + muttley-security + + + + org.springframework.boot + spring-boot-starter-test + test + + + diff --git a/muttley-hermes-server.pom/muttley-hermes-api/src/main/java/br/com/muttley/hermes/api/NotificationClient.java b/muttley-hermes-server.pom/muttley-hermes-api/src/main/java/br/com/muttley/hermes/api/NotificationClient.java new file mode 100644 index 00000000..a736d087 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-api/src/main/java/br/com/muttley/hermes/api/NotificationClient.java @@ -0,0 +1,33 @@ +package br.com.muttley.hermes.api; + +import br.com.muttley.feign.service.config.FeignTimeoutConfig; +import br.com.muttley.model.hermes.notification.onesignal.Notification; +import br.com.muttley.security.infra.security.server.FeignClientConfig; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +@FeignClient(value = "${muttley.hermes.server.name}", path = "/api/v1/tokens-notification", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class}) +public interface NotificationClient { + @RequestMapping(method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + public void sendNotification(@RequestBody final Notification notification); + + @RequestMapping(value = "/{playerId}", method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + public void sendNotification(@PathVariable("playerId") final String playerId, @RequestBody final Notification notification); + + @RequestMapping(value = "/send-by-user/{userId}", method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + public void sendNotificationByUserId(@PathVariable("userId") final String userId, @RequestBody final Notification notification); + + @RequestMapping(value = "/send-by-mobile-user/{userId}", method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + public void sendNotificationMobileByUser(@PathVariable("userId") final String userId, @RequestBody final Notification notification); + + @RequestMapping(value = "/simple-send-by-user/{userId}", method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + public void sendNotificationByUser(@PathVariable("userId") final String userId, @PathVariable(value = "heading", required = false) final String heading, @PathVariable(value = "subtitle", required = false) final String subtitle, @PathVariable(value = "content", required = false) final String content); + + @RequestMapping(value = "/simple-send-by-mobile-user/{userId}", method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + public void sendNotificationMobileByUserId(@PathVariable("userId") final String userId, @PathVariable(value = "heading", required = false) final String heading, @PathVariable(value = "subtitle", required = false) final String subtitle, @PathVariable(value = "content", required = false) final String content); +} diff --git a/muttley-hermes-server.pom/muttley-hermes-api/src/main/java/br/com/muttley/hermes/api/UserTokenNotificationClient.java b/muttley-hermes-server.pom/muttley-hermes-api/src/main/java/br/com/muttley/hermes/api/UserTokenNotificationClient.java new file mode 100644 index 00000000..6e0c08af --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-api/src/main/java/br/com/muttley/hermes/api/UserTokenNotificationClient.java @@ -0,0 +1,20 @@ +package br.com.muttley.hermes.api; + + +import br.com.muttley.feign.service.config.FeignTimeoutConfig; +import br.com.muttley.model.hermes.notification.TokenId; +import br.com.muttley.security.infra.security.server.FeignClientConfig; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +@FeignClient(value = "${muttley.hermes.server.name}", path = "/api/v1/tokens-notification", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class}) +public interface UserTokenNotificationClient { + + @RequestMapping(method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + public void save(@RequestBody TokenId tokenId); + +} diff --git a/muttley-hermes-server.pom/muttley-hermes-api/src/main/java/br/com/muttley/hermes/api/config/MuttleyHermesAPIConfig.java b/muttley-hermes-server.pom/muttley-hermes-api/src/main/java/br/com/muttley/hermes/api/config/MuttleyHermesAPIConfig.java new file mode 100644 index 00000000..79b5e5df --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-api/src/main/java/br/com/muttley/hermes/api/config/MuttleyHermesAPIConfig.java @@ -0,0 +1,11 @@ +package br.com.muttley.hermes.api.config; + +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableFeignClients( + basePackages = "br.com.muttley.hermes.api" +) +public class MuttleyHermesAPIConfig { +} diff --git a/muttley-hermes-server.pom/muttley-hermes-api/src/main/resources/META-INF/spring.factories b/muttley-hermes-server.pom/muttley-hermes-api/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..4f15eb23 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-api/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + br.com.muttley.hermes.api.config.MuttleyHermesAPIConfig diff --git a/muttley-hermes-server.pom/muttley-hermes-server/.gitignore b/muttley-hermes-server.pom/muttley-hermes-server/.gitignore new file mode 100644 index 00000000..2af7cefb --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/.gitignore @@ -0,0 +1,24 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ \ No newline at end of file diff --git a/muttley-hermes-server.pom/muttley-hermes-server/.mvn/wrapper/maven-wrapper.jar b/muttley-hermes-server.pom/muttley-hermes-server/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..9cc84ea9 Binary files /dev/null and b/muttley-hermes-server.pom/muttley-hermes-server/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-hermes-server.pom/muttley-hermes-server/.mvn/wrapper/maven-wrapper.properties b/muttley-hermes-server.pom/muttley-hermes-server/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..9dda3b65 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip diff --git a/muttley-hermes-server.pom/muttley-hermes-server/mvnw b/muttley-hermes-server.pom/muttley-hermes-server/mvnw new file mode 100755 index 00000000..5bf251c0 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/mvnw @@ -0,0 +1,225 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-hermes-server.pom/muttley-hermes-server/mvnw.cmd b/muttley-hermes-server.pom/muttley-hermes-server/mvnw.cmd new file mode 100644 index 00000000..019bd74d --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/mvnw.cmd @@ -0,0 +1,143 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/muttley-hermes-server.pom/muttley-hermes-server/pom.xml b/muttley-hermes-server.pom/muttley-hermes-server/pom.xml new file mode 100644 index 00000000..9f33c525 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/pom.xml @@ -0,0 +1,93 @@ + + + + br.com.muttley + muttley-hermes-server.pom + ${revision} + + 4.0.0 + jar + + muttley-hermes-server + + muttley-hermes-server + Demo project for Spring Boot + + + + + br.com.muttley + muttley-notification + + + + org.projectlombok + lombok + true + + + + br.com.muttley + muttley-domain-service + + + + br.com.muttley + muttley-feign + + + + br.com.muttley + muttley-jackson + + + + br.com.muttley + muttley-model + + + + br.com.muttley + muttley-mongo + + + + br.com.muttley + muttley-security + + + + br.com.muttley + muttley-exception + + + + br.com.muttley + muttley-rest + + + + org.springframework.cloud + spring-cloud-starter-config + + + + org.springframework.cloud + spring-cloud-starter-eureka + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/MuttleyHermesServerApplication.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/MuttleyHermesServerApplication.java new file mode 100644 index 00000000..ab1df981 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/MuttleyHermesServerApplication.java @@ -0,0 +1,26 @@ +package br.com.muttley.hermes.server; + +/*@SpringBootApplication +//Packages onde existem entidades +@EntityScan(basePackages = {"br.com.muttley.model.notification"}) +//Packages onde existem componentes, serviços e configurações +@ComponentScan(basePackages = { + //Injeções internas do projeto + "br.com.muttley.domain.service", + //Configuração de serviços + //"br.com.muttley.security.server", + //Configurações de segurança para o gateway + //"br.com.muttley.security.zuul.gateway.service", + //Configurações de exceptions + "br.com.muttley.exception.service", + //Configurações de serialização + "br.com.muttley.jackson.service" +}) +//@EnableEurekaClient*/ +public class MuttleyHermesServerApplication { + + /*public static void main(String[] args) { + SpringApplication.run(MuttleyHermesServerApplication.class, args); + }*/ + +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/config/MuttleyHermesServerConfig.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/config/MuttleyHermesServerConfig.java new file mode 100644 index 00000000..4e61d12f --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/config/MuttleyHermesServerConfig.java @@ -0,0 +1,14 @@ +package br.com.muttley.hermes.server.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan(basePackages = { + "br.com.muttley.hermes.server", + //"br.com.muttley.hermes.server.config.mongo", + //"br.com.muttley.hermes.server.service", + "br.com.muttley.notification.onesignal.config" +}) +public class MuttleyHermesServerConfig { +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/config/model/DocumentNameConfig.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/config/model/DocumentNameConfig.java new file mode 100644 index 00000000..f0d6ae61 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/config/model/DocumentNameConfig.java @@ -0,0 +1,76 @@ +package br.com.muttley.hermes.server.config.model; + + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +/** + * @author Joel Rodrigues Moreira on 30/04/20. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Configuration(value = "documentNameConfig") +@Getter +public class DocumentNameConfig { + private final String nameCollectionAdminOwner; + private final String nameCollectionOwner; + private final String nameCollectionUser; + private final String nameCollectionPassword; + private final String nameCollectionAdminUserBase; + private final String nameCollectionUserBase; + private final String nameCollectionAccessPlan; + private final String nameCollectionUserPreferences; + private final String nameCollectionUserTokensNotification; + private final String nameCollectionAdminPassaport; + private final String nameCollectionPassaport; + private final String nameCollectionWorkTeam; + + private final String nameCollectionXAPIToken; + private final String nameCollectionUserDataBinding; + private final String nameViewCollectionUser; + private final String nameViewCollectionPassaport; + private final String nameViewCollectionPassaportRolesUser; + + private final String nameViewCollectionWorkTeam; + + public DocumentNameConfig( + @Value("${br.com.muttley.security.server.owner-document:muttley-admin-owners}") final String nameCollectionAdminOwner, + @Value("${br.com.muttley.security.server.owner-document:muttley-owners}") final String nameCollectionOwner, + @Value("${br.com.muttley.security.server.user-document:muttley-users}") final String nameCollectionUser, + @Value("${br.com.muttley.security.server.user-password-document:muttley-users-password}") final String nameCollectionPassword, + @Value("${br.com.muttley.security.server.user-base-document:muttley-admin-users-base}") final String nameCollectionAdminUserBase, + @Value("${br.com.muttley.security.server.user-base-document:muttley-users-base}") final String nameCollectionUserBase, + @Value("${br.com.muttley.security.server.access-plan-document:muttley-access-plans}") final String nameCollectionAccessPlan, + @Value("${br.com.muttley.security.server.user-preference-document:muttley-users-preferences}") final String nameCollectionUserPreferences, + @Value("${br.com.muttley.security.server.user-tokens-notification-document:muttley-users-tokens-notification}") final String nameCollectionUserTokensNotification, + @Value("${br.com.muttley.security.server.admin-passaport-document:muttley-admin-passaports}") final String nameCollectionAdminPassaport, + @Value("${br.com.muttley.security.server.passaport-document:muttley-passaports}") final String nameCollectionPassaport, + @Value("${br.com.muttley.security.server.work-team-document:muttley-work-teams}") final String nameCollectionWorkTeam, + @Value("${br.com.muttley.security.server.x-api-token-document:muttley-x-api-token}") final String nameCollectionXAPIToken, + @Value("${br.com.muttley.security.server.user-data-binding:muttley-users-databinding}") final String nameCollectionUserDataBinding, + @Value("${br.com.muttley.security.server.user-document-view:view-muttley-users}") final String nameViewCollectionUser, + @Value("${br.com.muttley.security.server.passaport-document-view:view-muttley-passaports}") final String nameViewCollectionPassaport, + @Value("${br.com.muttley.security.server.passaport-role-document-view:view-muttley-passaports-roles-user}") final String nameViewCollectionPassaportRolesUser, + @Value("${br.com.muttley.security.server.work-team-document-view:view-muttley-work-teams}") final String nameViewCollectionWorkTeam + ) { + this.nameCollectionAdminOwner = nameCollectionAdminOwner; + this.nameCollectionOwner = nameCollectionOwner; + this.nameCollectionUser = nameCollectionUser; + this.nameCollectionPassword = nameCollectionPassword; + this.nameCollectionAdminUserBase = nameCollectionAdminUserBase; + this.nameCollectionUserBase = nameCollectionUserBase; + this.nameCollectionAccessPlan = nameCollectionAccessPlan; + this.nameCollectionUserPreferences = nameCollectionUserPreferences; + this.nameCollectionUserTokensNotification = nameCollectionUserTokensNotification; + this.nameCollectionAdminPassaport = nameCollectionAdminPassaport; + this.nameCollectionPassaport = nameCollectionPassaport; + this.nameCollectionWorkTeam = nameCollectionWorkTeam; + this.nameCollectionXAPIToken = nameCollectionXAPIToken; + this.nameCollectionUserDataBinding = nameCollectionUserDataBinding; + this.nameViewCollectionUser = nameViewCollectionUser; + this.nameViewCollectionPassaport = nameViewCollectionPassaport; + this.nameViewCollectionPassaportRolesUser = nameViewCollectionPassaportRolesUser; + this.nameViewCollectionWorkTeam = nameViewCollectionWorkTeam; + } +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/config/mongo/MongoConfig.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/config/mongo/MongoConfig.java new file mode 100644 index 00000000..534c5803 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/config/mongo/MongoConfig.java @@ -0,0 +1,40 @@ +package br.com.muttley.hermes.server.config.mongo; + +import br.com.muttley.model.security.converters.KeyUserDataBindingToStringConverter; +import br.com.muttley.model.security.converters.StringToKeyUserDataBindingConverter; +import br.com.muttley.mongo.service.repository.impl.DocumentMongoRepositoryImpl; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +/** + * @author Joel Rodrigues Moreira on 30/04/20. + * e-mail: joel.databox@gmail.com + * @project agrifocus-cloud + *

+ * Realiza a configuração do mongo db + */ +@Configuration +@EnableMongoRepositories(basePackages = {"br.com.muttley.hermes.server.repository"}, repositoryBaseClass = DocumentMongoRepositoryImpl.class) +public class MongoConfig extends br.com.muttley.mongo.service.MongoConfig { + private final ApplicationEventPublisher publisher; + + public MongoConfig(@Value("${spring.data.mongodb.database}") final String dataBaseName, + @Value("${spring.data.mongodb.host}") final String hostDataBase, + @Value("${spring.data.mongodb.port}") final String portDataBase, + @Value("${spring.data.mongodb.username}") final String userName, + @Value("${spring.data.mongodb.password}") final String password, ApplicationEventPublisher publisher) { + super(dataBaseName, hostDataBase, portDataBase, userName, password); + this.publisher = publisher; + } + + @Override + protected Converter[] getConverters() { + return new Converter[]{ + new StringToKeyUserDataBindingConverter(this.publisher), + new KeyUserDataBindingToStringConverter() + }; + } +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/controller/NotificationController.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/controller/NotificationController.java new file mode 100644 index 00000000..e6b94fff --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/controller/NotificationController.java @@ -0,0 +1,55 @@ +package br.com.muttley.hermes.server.controller; + +import br.com.muttley.hermes.server.service.NotificationService; +import br.com.muttley.model.hermes.notification.onesignal.Notification; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +@RestController +@RequestMapping(value = "/api/v1/notifications", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) +public class NotificationController { + + private final NotificationService notificationService; + + @Autowired + public NotificationController(final NotificationService notificationService) { + this.notificationService = notificationService; + } + + @RequestMapping(method = POST) + public void sendNotification(@RequestBody final Notification notification) { + this.notificationService.sendNotification(notification); + } + + @RequestMapping(value = "/{playerId}", method = POST) + public void sendNotification(@PathVariable("playerId") final String playerId, @RequestBody final Notification notification) { + this.notificationService.sendNotification(notification.addPlayers(playerId)); + } + + @RequestMapping(value = "/send-by-user/{userId}", method = POST) + public void sendNotificationByUserId(@PathVariable("userId") final String userId, @RequestBody final Notification notification) { + this.notificationService.sendNotificationByUserId(userId, notification); + } + + @RequestMapping(value = "/send-by-mobile-user/{userId}", method = POST) + public void sendNotificationMobileByUser(@PathVariable("userId") final String userId, @RequestBody final Notification notification) { + this.notificationService.sendNotificationMobileByUserId(userId, notification); + } + + @RequestMapping(value = "/simple-send-by-user/{userId}", method = POST) + public void sendNotificationByUser(@PathVariable("userId") final String userId, @PathVariable(value = "heading", required = false) final String heading, @PathVariable(value = "subtitle", required = false) final String subtitle, @PathVariable(value = "content", required = false) final String content) { + this.notificationService.sendNotificationByUserId(userId, heading, subtitle, content); + } + + @RequestMapping(value = "/simple-send-by-mobile-user/{userId}", method = POST) + public void sendNotificationMobileByUserId(@PathVariable("userId") final String userId, @PathVariable(value = "heading", required = false) final String heading, @PathVariable(value = "subtitle", required = false) final String subtitle, @PathVariable(value = "content", required = false) final String content) { + this.notificationService.sendNotificationMobileByUserId(userId, heading, subtitle, content); + } +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/controller/UserTokenNotificationController.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/controller/UserTokenNotificationController.java new file mode 100644 index 00000000..82379178 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/controller/UserTokenNotificationController.java @@ -0,0 +1,35 @@ +package br.com.muttley.hermes.server.controller; + +import br.com.muttley.hermes.server.service.UserTokensNotificationService; +import br.com.muttley.model.hermes.notification.TokenId; +import br.com.muttley.rest.RestResource; +import br.com.muttley.security.infra.service.AuthService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + + +@RestController +@RequestMapping(value = "/api/v1/tokens-notification", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) +public class UserTokenNotificationController implements RestResource { + private final AuthService authService; + private final UserTokensNotificationService service; + + + @Autowired + public UserTokenNotificationController(final AuthService authService, final UserTokensNotificationService service) { + this.authService = authService; + this.service = service; + } + + @RequestMapping(method = POST, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public void save(@RequestBody TokenId tokenId) { + this.service.addTokenNotification(this.authService.getCurrentUser(), tokenId); + } + +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/liteners/NotificationEventListener.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/liteners/NotificationEventListener.java new file mode 100644 index 00000000..b740053b --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/liteners/NotificationEventListener.java @@ -0,0 +1,22 @@ +package br.com.muttley.hermes.server.liteners; + +import br.com.muttley.model.hermes.notification.onesignal.events.NotificationEvent; +import br.com.muttley.notification.onesignal.service.OneSignalNotificationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +@Component +public class NotificationEventListener implements ApplicationListener { + private final OneSignalNotificationService oneSignalService; + + @Autowired + public NotificationEventListener(final OneSignalNotificationService oneSignalService) { + this.oneSignalService = oneSignalService; + } + + @Override + public void onApplicationEvent(final NotificationEvent notificationEvent) { + this.oneSignalService.sendNotification(notificationEvent.getSource()); + } +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/repository/UserTokensNotificationRepository.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/repository/UserTokensNotificationRepository.java new file mode 100644 index 00000000..6db5f360 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/repository/UserTokensNotificationRepository.java @@ -0,0 +1,24 @@ +package br.com.muttley.hermes.server.repository; + +import br.com.muttley.model.hermes.notification.UserTokensNotification; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserView; +import br.com.muttley.mongo.service.repository.DocumentMongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +/** + * @author Joel Rodrigues Moreira on 30/04/20. + * e-mail: joel.databox@gmail.com + * @project agrifocus-cloud + */ +@Repository +public interface UserTokensNotificationRepository extends DocumentMongoRepository { + UserTokensNotification findByUser(final User user); + + @Query("{'user': {'$ref' : ?#{@documentNameConfig.getNameCollectionUser()}, '$id': ?#{[0].getId()}}}") + UserTokensNotification findByUser(final UserView user); + + @Query("{'user': {'$ref' : ?#{@documentNameConfig.getNameCollectionUser()}, '$id': ?0}}") + UserTokensNotification findByUser(final String userId); +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/NotificationService.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/NotificationService.java new file mode 100644 index 00000000..9255ebf4 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/NotificationService.java @@ -0,0 +1,35 @@ +package br.com.muttley.hermes.server.service; + +import br.com.muttley.model.hermes.notification.onesignal.Content; +import br.com.muttley.model.hermes.notification.onesignal.Notification; +import br.com.muttley.model.security.UserView; + +public interface NotificationService { + void sendNotification(final Notification notification); + + void sendNotification(final String playerId, final Notification notification); + + void sendNotification(final UserView user, final Notification notification); + + void sendNotificationByUserId(final String userId, final Notification notification); + + void sendNotificationMobile(final UserView user, final Notification notification); + + void sendNotificationMobileByUserId(final String userId, final Notification notification); + + void sendNotification(final UserView user, final Content headings, final Content subtitles, final Content contents); + + void sendNotificationByUserId(final String userId, final Content headings, final Content subtitles, final Content contents); + + void sendNotificationMobile(final UserView user, final Content headings, final Content subtitles, final Content contents); + + void sendNotificationMobileByUserId(final String userId, final Content headings, final Content subtitles, final Content contents); + + void sendNotification(final UserView user, final String heading, final String subtitle, final String content); + + void sendNotificationByUserId(final String userId, final String heading, final String subtitle, final String content); + + void sendNotificationMobile(final UserView user, final String heading, final String subtitle, final String content); + + void sendNotificationMobileByUserId(final String userId, final String heading, final String subtitle, final String content); +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/UserTokensNotificationService.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/UserTokensNotificationService.java new file mode 100644 index 00000000..c94b291b --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/UserTokensNotificationService.java @@ -0,0 +1,19 @@ +package br.com.muttley.hermes.server.service; + +import br.com.muttley.domain.service.Service; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.model.hermes.notification.TokenId; +import br.com.muttley.model.hermes.notification.UserTokensNotification; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserView; + + +public interface UserTokensNotificationService extends Service { + UserTokensNotification findByUser(final User user) throws MuttleyNotFoundException; + + UserTokensNotification findByUser(final UserView user) throws MuttleyNotFoundException; + + UserTokensNotification findByUser(final String userId) throws MuttleyNotFoundException; + + void addTokenNotification(final User user, final TokenId tokenId); +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/impl/NotificationServiceImpl.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/impl/NotificationServiceImpl.java new file mode 100644 index 00000000..999aa3ce --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/impl/NotificationServiceImpl.java @@ -0,0 +1,109 @@ +package br.com.muttley.hermes.server.service.impl; + +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.hermes.server.service.NotificationService; +import br.com.muttley.hermes.server.service.UserTokensNotificationService; +import br.com.muttley.model.hermes.notification.onesignal.Content; +import br.com.muttley.model.hermes.notification.onesignal.Notification; +import br.com.muttley.model.security.UserView; +import br.com.muttley.notification.onesignal.service.OneSignalNotificationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import static br.com.muttley.model.hermes.notification.onesignal.MuttleyLanguage.Any; + +@Service +public class NotificationServiceImpl implements NotificationService { + private final OneSignalNotificationService oneSignalNotificationServiceClient; + private final UserTokensNotificationService userTokensNotificationService; + private final Logger logger = LoggerFactory.getLogger(NotificationServiceImpl.class); + + + @Autowired + public NotificationServiceImpl(final OneSignalNotificationService oneSignalNotificationServiceClient, final UserTokensNotificationService userTokensNotificationService) { + this.oneSignalNotificationServiceClient = oneSignalNotificationServiceClient; + this.userTokensNotificationService = userTokensNotificationService; + } + + @Override + public void sendNotification(final Notification notification) { + try { + this.oneSignalNotificationServiceClient.sendNotification(notification); + } catch (Throwable ex) { + logger.error("Erro ao enviar notificação para o serviço do OneSignal", ex); + } + } + + @Override + public void sendNotification(final String playerId, final Notification notification) { + this.sendNotification(notification.addPlayers(playerId)); + } + + @Override + public void sendNotification(final UserView user, final Notification notification) { + try { + this.sendNotification(notification.addPlayers(this.userTokensNotificationService.findByUser(user))); + } catch (final MuttleyNotFoundException ex) { + } + } + + @Override + public void sendNotificationByUserId(final String userId, final Notification notification) { + this.sendNotification(new UserView().setId(userId), notification); + } + + @Override + public void sendNotificationMobile(final UserView user, final Notification notification) { + try { + this.sendNotification(notification.addPlayers(this.userTokensNotificationService.findByUser(user).getTokensMobile())); + } catch (final MuttleyNotFoundException ex) { + } + } + + @Override + public void sendNotificationMobileByUserId(final String userId, final Notification notification) { + this.sendNotificationMobile(new UserView().setId(userId), notification); + } + + @Override + public void sendNotification(final UserView user, final Content headings, final Content subtitles, final Content contents) { + this.sendNotification(user, new Notification().addHeadings(headings).addSubtitles(subtitles).addContent(contents)); + } + + @Override + public void sendNotificationByUserId(final String userId, final Content headings, final Content subtitles, final Content contents) { + this.sendNotification(new UserView().setId(userId), headings, subtitles, contents); + } + + @Override + public void sendNotificationMobile(final UserView user, final Content headings, final Content subtitles, final Content contents) { + this.sendNotificationMobile(user, new Notification().addHeadings(headings).addSubtitles(subtitles).addContent(contents)); + } + + @Override + public void sendNotificationMobileByUserId(final String userId, final Content headings, final Content subtitles, final Content contents) { + this.sendNotificationMobile(new UserView().setId(userId), headings, subtitles, contents); + } + + @Override + public void sendNotification(final UserView user, final String heading, final String subtitle, final String content) { + this.sendNotification(user, new Content(Any, heading), new Content(Any, subtitle), new Content(Any, content)); + } + + @Override + public void sendNotificationByUserId(final String userId, final String heading, final String subtitle, final String content) { + this.sendNotification(new UserView().setId(userId), heading, subtitle, content); + } + + @Override + public void sendNotificationMobile(final UserView user, final String heading, final String subtitle, final String content) { + this.sendNotificationMobile(user, new Content(Any, heading), new Content(Any, subtitle), new Content(Any, content)); + } + + @Override + public void sendNotificationMobileByUserId(final String userId, final String heading, final String subtitle, final String content) { + this.sendNotificationMobile(new UserView().setId(userId), heading, subtitle, content); + } +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/impl/UserTokensNotificationServiceImpl.java b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/impl/UserTokensNotificationServiceImpl.java new file mode 100644 index 00000000..a41fc457 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/java/br/com/muttley/hermes/server/service/impl/UserTokensNotificationServiceImpl.java @@ -0,0 +1,124 @@ +package br.com.muttley.hermes.server.service.impl; + +import br.com.muttley.domain.service.impl.ServiceImpl; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.headers.components.MuttleyUserAgent; +import br.com.muttley.hermes.server.repository.UserTokensNotificationRepository; +import br.com.muttley.hermes.server.service.UserTokensNotificationService; +import br.com.muttley.model.hermes.notification.TokenId; +import br.com.muttley.model.hermes.notification.UserTokensNotification; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserView; +import br.com.muttley.redis.service.RedisService; +import com.mongodb.BasicDBObject; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; + +import static org.springframework.data.mongodb.core.query.Criteria.where; + +@Service +public class UserTokensNotificationServiceImpl extends ServiceImpl implements UserTokensNotificationService { + private static final String KEY_REDIS = "muttley-notification-cache."; + private final UserTokensNotificationRepository repository; + private final RedisService redisService; + @Autowired + private MuttleyUserAgent userAgent; + private final long cachetimeout; + + @Autowired + public UserTokensNotificationServiceImpl( + final UserTokensNotificationRepository repository, + final MongoTemplate mongoTemplate, + final RedisService redisService, + //tempo de validade do token 60 * 60 * 24 * 10 = dias + @Value("${muttley.hermes.notification.cachetimeout:864000}") final long cachetimeout) { + super(repository, mongoTemplate, UserTokensNotification.class); + this.repository = repository; + this.redisService = redisService; + this.cachetimeout = cachetimeout; + } + + @Override + public UserTokensNotification findByUser(final User user) throws MuttleyNotFoundException { + return this.findByUser(user.getId()); + } + + @Override + public UserTokensNotification findByUser(final UserView user) throws MuttleyNotFoundException { + return this.findByUser(user.getId()); + } + + @Override + public UserTokensNotification findByUser(final String userId) throws MuttleyNotFoundException { + //verificando se já tem no cache + if (this.redisService.hasKey(this.generateTokenRedis(userId))) { + return (UserTokensNotification) this.redisService.get(this.generateTokenRedis(userId)); + } + final UserTokensNotification token = this.repository.findByUser(userId); + if (token == null) { + throw new MuttleyNotFoundException(UserTokensNotification.class, "user", "Nenhum registro encontrado"); + } + //se chegou até aqui é sinal que o token ainda não está no cache + //adicionando o token no cache + this.redisService.set(this.generateTokenRedis(userId), token, cachetimeout); + return token; + } + + @Override + public void addTokenNotification(final User user, final TokenId tokenId) { + tokenId.setMobile(this.userAgent.isMobile()); + //se ainda não existir uma coleção para o usuário, devemos criar uma + if (!this.repository.exists("user.$id", new ObjectId(user.getId()))) { + //criando coleção do usuário com o primeiro token + this.save(user, new UserTokensNotification().setUser(user).add(tokenId)); + //se o token é da mobilidade, devemos garantira que outros usuário não terá o mesmo token + } else if (tokenId.isMobile()) { + //garantindo que outros usuários não terão esse token + this.removeTokenIdFromAnotherUsers(user, tokenId); + //salvando o token + this.saveTokenId(user, tokenId); + //se não for um token da mobilidade podemos salvar o token para diversos usuários + } else { + //salvando o token + this.saveTokenId(user, tokenId); + } + this.redisService.delete(this.generateTokenRedis(user.getId())); + } + + private void saveTokenId(final User user, final TokenId tokenId) { + this.mongoTemplate.updateFirst( + new Query(where("user.$id").is(new ObjectId(user.getId()))), + new Update() + .addToSet("tokens", tokenId), + UserTokensNotification.class + ); + } + + /** + * Remove o token de outros usuários + * Isso se faz necessário pois o token está relacionado ao aparelho e por conta disso + * o usuário pode seder o aparelho para outros usuários. + */ + private void removeTokenIdFromAnotherUsers(final User user, final TokenId tokenId) { + this.mongoTemplate.updateFirst( + new Query( + where("user.$id").ne(new ObjectId(user.getId())) + .and("tokens").elemMatch(where("token").is(tokenId.getToken()).and("origin").is(tokenId.getOrigin()).and("mobile").is(true)) + ), + new Update() + .pull("tokens", + new BasicDBObject("origin", tokenId.getOrigin()).append("token", tokenId.getToken()) + ), + UserTokensNotification.class + ); + } + + private String generateTokenRedis(final String userId) { + return KEY_REDIS + userId; + } +} diff --git a/muttley-hermes-server.pom/muttley-hermes-server/src/main/resources/META-INF/spring.factories b/muttley-hermes-server.pom/muttley-hermes-server/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..90340950 --- /dev/null +++ b/muttley-hermes-server.pom/muttley-hermes-server/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + br.com.muttley.hermes.server.config.MuttleyHermesServerConfig diff --git a/muttley-hermes-server.pom/mvnw b/muttley-hermes-server.pom/mvnw new file mode 100755 index 00000000..8b9da3b8 --- /dev/null +++ b/muttley-hermes-server.pom/mvnw @@ -0,0 +1,286 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + wget "$jarUrl" -O "$wrapperJarPath" + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + curl -o "$wrapperJarPath" "$jarUrl" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-hermes-server.pom/mvnw.cmd b/muttley-hermes-server.pom/mvnw.cmd new file mode 100644 index 00000000..fef5a8f7 --- /dev/null +++ b/muttley-hermes-server.pom/mvnw.cmd @@ -0,0 +1,161 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" +FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + echo Found %WRAPPER_JAR% +) else ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/muttley-hermes-server.pom/pom.xml b/muttley-hermes-server.pom/pom.xml new file mode 100644 index 00000000..93726f62 --- /dev/null +++ b/muttley-hermes-server.pom/pom.xml @@ -0,0 +1,21 @@ + + + + br.com.muttley + muttley-cloud + ${revision} + + 4.0.0 + pom + + muttley-hermes-server.pom + + muttley-hermes-server.pom + Demo project for Spring Boot + + + muttley-hermes-server + muttley-hermes-api + + diff --git a/muttley-jackson/pom.xml b/muttley-jackson/pom.xml index 4e431a97..ded35212 100644 --- a/muttley-jackson/pom.xml +++ b/muttley-jackson/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar @@ -21,6 +21,12 @@ provided + + br.com.muttley + muttley-exception + provided + + org.springframework.boot spring-boot-starter-data-mongodb diff --git a/muttley-jackson/src/main/java/br/com/muttley/jackson/service/JacksonConfig.java b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/JacksonConfig.java index 784e2d52..80589071 100644 --- a/muttley-jackson/src/main/java/br/com/muttley/jackson/service/JacksonConfig.java +++ b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/JacksonConfig.java @@ -2,8 +2,13 @@ import br.com.muttley.jackson.service.infra.MuttleyJacksonDeserialize; import br.com.muttley.jackson.service.infra.MuttleyJacksonSerialize; +import br.com.muttley.jackson.service.infra.deserializer.BigDecimalDeserializer; +import br.com.muttley.jackson.service.infra.deserializer.LocalDateDeserializer; import br.com.muttley.jackson.service.infra.deserializer.ObjectIdDeserializer; +import br.com.muttley.jackson.service.infra.deserializer.ZonedDateTimeDeserializer; +import br.com.muttley.jackson.service.infra.serializer.LocalDateSerializer; import br.com.muttley.jackson.service.infra.serializer.ObjectIdSerializer; +import br.com.muttley.jackson.service.infra.serializer.ZonedDateTimeSerializer; import br.com.muttley.model.jackson.DefaultDateFormatConfig; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -14,6 +19,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.ZonedDateTime; + /** * @author Joel Rodrigues Moreira on 13/03/18. @@ -36,8 +45,21 @@ public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization( return new Jackson2ObjectMapperBuilderCustomizer() { @Override public void customize(Jackson2ObjectMapperBuilder mapperBuilder) { + + mapperBuilder.deserializerByType(BigDecimal.class, new BigDecimalDeserializer()); + mapperBuilder.deserializerByType(ObjectId.class, new ObjectIdDeserializer()); mapperBuilder.serializerByType(ObjectId.class, new ObjectIdSerializer()); + + mapperBuilder.deserializerByType(ZonedDateTime.class, new ZonedDateTimeDeserializer(datePattern)); + mapperBuilder.serializerByType(ZonedDateTime.class, new ZonedDateTimeSerializer(datePattern)); + + mapperBuilder.deserializerByType(LocalDate.class, new LocalDateDeserializer()); + mapperBuilder.serializerByType(LocalDate.class, new LocalDateSerializer()); + + //mapperBuilder.deserializers(new OwnerDataDeserializer()); + //mapperBuilder.deserializerByType((Class>) (Class) List.class, new OwnerDataDeserializer()); + //se não existe um formatador de data padrão, devemos adicionar o nosso if (dateFormat == null) { mapperBuilder.dateFormat(new DefaultDateFormatConfig(datePattern)); diff --git a/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/BigDecimalDeserializer.java b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/BigDecimalDeserializer.java new file mode 100644 index 00000000..9998d998 --- /dev/null +++ b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/BigDecimalDeserializer.java @@ -0,0 +1,29 @@ +package br.com.muttley.jackson.service.infra.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.math.BigDecimal; + +import static br.com.muttley.utils.BigDecimalUtils.setDefaultScale; + +/** + * @author Joel Rodrigues Moreira on 12/03/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class BigDecimalDeserializer extends JsonDeserializer { + @Override + public BigDecimal deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + + return setDefaultScale(new BigDecimal(node.asText())); + + } +} diff --git a/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/LocalDateDeserializer.java b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/LocalDateDeserializer.java new file mode 100644 index 00000000..ccfe1786 --- /dev/null +++ b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/LocalDateDeserializer.java @@ -0,0 +1,53 @@ +package br.com.muttley.jackson.service.infra.deserializer; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.regex.Pattern; + +import static br.com.muttley.utils.DateUtils.DEFAULT_ISO_LOCAL_DATE; +import static br.com.muttley.utils.DateUtils.DEFAULT_ISO_ZONED_DATE_TIME; +import static java.util.Arrays.asList; + +/** + * @author Joel Rodrigues Moreira on 17/06/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class LocalDateDeserializer extends JsonDeserializer { + private static final Pattern DATE_PATTERN = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}$"); + + @Override + public LocalDate deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + if (node.isNull()) { + return null; + } + final String value = node.asText(); + try { + if (this.isValideDate(value)) { + return LocalDate.parse(value, DEFAULT_ISO_LOCAL_DATE); + } + return LocalDate.parse(value, DEFAULT_ISO_ZONED_DATE_TIME); + + } catch (DateTimeParseException e) { + throw new MuttleyBadRequestException(null, parser.getCurrentName(), "Informe uma data válida ") + .addDetails("informado", value) + .addDetails("exemplos", asList(LocalDateTime.now().format(DEFAULT_ISO_ZONED_DATE_TIME), LocalDate.now().format(DEFAULT_ISO_LOCAL_DATE))); + } + } + + private boolean isValideDate(final String value) { + return DATE_PATTERN.matcher(value).matches(); + } +} diff --git a/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/OwnerDataDeserializer.java b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/OwnerDataDeserializer.java new file mode 100644 index 00000000..1d21fd9c --- /dev/null +++ b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/OwnerDataDeserializer.java @@ -0,0 +1,39 @@ +package br.com.muttley.jackson.service.infra.deserializer; + +/*import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.OwnerDataImpl;*/ + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira 12/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class OwnerDataDeserializer extends JsonDeserializer { + @Override + public List deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + final List owners = new ArrayList<>(); + node.forEach(it -> { + try { + owners.add(it.traverse(parser.getCodec()).readValueAs(new TypeReference() { + })); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + return owners; + } +} diff --git a/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/ZonedDateTimeDeserializer.java b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/ZonedDateTimeDeserializer.java new file mode 100644 index 00000000..32c96cf5 --- /dev/null +++ b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/deserializer/ZonedDateTimeDeserializer.java @@ -0,0 +1,42 @@ +package br.com.muttley.jackson.service.infra.deserializer; + +import br.com.muttley.utils.DateUtils; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Value; + +import java.io.IOException; +import java.sql.SQLOutput; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Date; + +/** + * @author Joel Rodrigues Moreira on 12/03/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ZonedDateTimeDeserializer extends JsonDeserializer { + + private final String pattern; + + public ZonedDateTimeDeserializer(@Value("${br.com.muttley.jackson.date-pattern:yyyy-MM-dd'T'HH:mm:ss.SSSZ}") final String pattern) { + this.pattern = pattern; + } + + @Override + public ZonedDateTime deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final JsonNode node = parser.getCodec().readTree(parser); + try { + return ZonedDateTime.parse(node.asText(), DateTimeFormatter.ofPattern(pattern)); + } catch (DateTimeParseException exception) { + System.out.println("COBRA O DERCI PRA ARRUMAR SAPORRAKKKKK"); + System.out.println("A DATA FORNECIDA ESTA COM PADRÕES FORA DOS CONFORMES"); + return DateUtils.toZonedDateTime(new Date(Long.valueOf(node.asText()))); + } + } +} diff --git a/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/serializer/LocalDateSerializer.java b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/serializer/LocalDateSerializer.java new file mode 100644 index 00000000..7f34d8d9 --- /dev/null +++ b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/serializer/LocalDateSerializer.java @@ -0,0 +1,27 @@ +package br.com.muttley.jackson.service.infra.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalDate; + +import static br.com.muttley.utils.DateUtils.DEFAULT_ISO_LOCAL_DATE; + +/** + * @author Joel Rodrigues Moreira on 17/06/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class LocalDateSerializer extends JsonSerializer { + @Override + public void serialize(final LocalDate date, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + if (date == null) { + gen.writeNull(); + } + + gen.writeString(date.format(DEFAULT_ISO_LOCAL_DATE)); + } +} diff --git a/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/serializer/ZonedDateTimeSerializer.java b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/serializer/ZonedDateTimeSerializer.java new file mode 100644 index 00000000..4a336177 --- /dev/null +++ b/muttley-jackson/src/main/java/br/com/muttley/jackson/service/infra/serializer/ZonedDateTimeSerializer.java @@ -0,0 +1,30 @@ +package br.com.muttley.jackson.service.infra.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.springframework.beans.factory.annotation.Value; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * @author Joel Rodrigues Moreira on 12/03/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ZonedDateTimeSerializer extends JsonSerializer { + + private final String pattern; + + public ZonedDateTimeSerializer(@Value("${br.com.muttley.jackson.date-pattern:yyyy-MM-dd'T'HH:mm:ss.SSSZ}") final String pattern) { + this.pattern = pattern; + } + + @Override + public void serialize(ZonedDateTime dateTime, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException, JsonProcessingException { + jsonGenerator.writeString(dateTime.format(DateTimeFormatter.ofPattern(pattern))); + } +} diff --git a/muttley-local-cache/.gitignore b/muttley-local-cache/.gitignore new file mode 100644 index 00000000..a2a3040a --- /dev/null +++ b/muttley-local-cache/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/muttley-local-cache/.mvn/wrapper/MavenWrapperDownloader.java b/muttley-local-cache/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..a45eb6ba --- /dev/null +++ b/muttley-local-cache/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/muttley-local-cache/.mvn/wrapper/maven-wrapper.jar b/muttley-local-cache/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..2cc7d4a5 Binary files /dev/null and b/muttley-local-cache/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-local-cache/.mvn/wrapper/maven-wrapper.properties b/muttley-local-cache/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..642d572c --- /dev/null +++ b/muttley-local-cache/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/muttley-local-cache/mvnw b/muttley-local-cache/mvnw new file mode 100755 index 00000000..a16b5431 --- /dev/null +++ b/muttley-local-cache/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-local-cache/mvnw.cmd b/muttley-local-cache/mvnw.cmd new file mode 100644 index 00000000..c8d43372 --- /dev/null +++ b/muttley-local-cache/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/muttley-local-cache/pom.xml b/muttley-local-cache/pom.xml new file mode 100644 index 00000000..bf5ca4d0 --- /dev/null +++ b/muttley-local-cache/pom.xml @@ -0,0 +1,33 @@ + + + + br.com.muttley + muttley-cloud + ${revision} + + 4.0.0 + jar + br.com.muttley + + muttley-local-cache + + muttley-local-cache + Demo project for Spring Boot + + + + + br.com.muttley + muttley-model + provided + + + + br.com.muttley + muttley-redis + + + + + diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalDatabindingService.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalDatabindingService.java new file mode 100644 index 00000000..e414c479 --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalDatabindingService.java @@ -0,0 +1,23 @@ +package br.com.muttley.localcache.services; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.XAPIToken; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira 24/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface LocalDatabindingService { + public static final String BASIC_KEY = "USER-DATABINDING:"; + + List getUserDataBindings(final JwtToken jwtUser, final User user); + + List getUserDataBindings(final XAPIToken token, final User user); + + void expireUserDataBindings(final User user); +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalModelService.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalModelService.java new file mode 100644 index 00000000..1c755340 --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalModelService.java @@ -0,0 +1,34 @@ +package br.com.muttley.localcache.services; + +import br.com.muttley.model.Model; +import br.com.muttley.model.security.User; + +/** + * @author Joel Rodrigues Moreira on 02/09/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface LocalModelService { + + boolean containsInCahce(final User user, final Class clazz, final String key); + + boolean containsReferenceInCahce(final User user, final Class clazz, final String key); + + LocalModelService addCache(final User user, T value, final String key); + + LocalModelService addCache(final User user, T value, final String key, final long timeout); + + LocalModelService addReferenceCache(final User user, T value, final String key); + + LocalModelService addReferenceCache(final User user, T value, final String key, final long timeout); + + T loadModel(final User user, final Class clazz, final String key); + + T loadReference(final User user, final Class clazz, final String key); + + LocalModelService expire(final User user, final Class clazz, final String key); + + LocalModelService refreshExpire(final User user, Class clazz, final String key); + + LocalModelService refreshExpireReference(final User user, Class clazz, final String key); +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalOwnerService.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalOwnerService.java new file mode 100644 index 00000000..fa9e820c --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalOwnerService.java @@ -0,0 +1,23 @@ +package br.com.muttley.localcache.services; + +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + *

+ * Seviço responsável recuperar owners e fazer cache em tempo de requisição de usuário autenticados + */ +public interface LocalOwnerService { + public static final String BASIC_KEY = "OWNER:"; + + OwnerData loadOwnerAny(); + + OwnerData loadOwnerAny(final User user); + + OwnerData loadOwnerById(final String id); + + OwnerData loadOwnerById(final User user, final String id); +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalRSAKeyPairService.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalRSAKeyPairService.java new file mode 100644 index 00000000..781d21b5 --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalRSAKeyPairService.java @@ -0,0 +1,10 @@ +package br.com.muttley.localcache.services; + +public interface LocalRSAKeyPairService { + public static final String BASIC_KEY_PRIVATE = "RSA:PRIVATE"; + public static final String BASIC_KEY_PUBLIC = "RSA:PUBLIC"; + + String encryptMessage(final String message); + + String decryptMessage(final String encryptedMessage); +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalRolesService.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalRolesService.java new file mode 100644 index 00000000..c8979e24 --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalRolesService.java @@ -0,0 +1,24 @@ +package br.com.muttley.localcache.services; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface LocalRolesService { + public static final String BASIC_KEY = "ROLES:"; + + Set loadCurrentRoles(final JwtToken token, final User user); + + Set loadCurrentRoles(final XAPIToken token, final User user); + + void expireRoles(final User user); + +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalUserAuthenticationService.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalUserAuthenticationService.java new file mode 100644 index 00000000..86e21d18 --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalUserAuthenticationService.java @@ -0,0 +1,28 @@ +package br.com.muttley.localcache.services; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.JwtUser; + +/** + * @author Joel Rodrigues Moreira 24/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + * Seviço responsável por válidar e recuperar usuário atravez de tokens de authenticação + */ +public interface LocalUserAuthenticationService { + + JwtUser getJwtUserFrom(final String apiToken); + + JwtUser getJwtUserFrom(final JwtToken token); + + LocalUserAuthenticationService remove(final JwtToken token); + + /** + * Faz a atualização de um determinado token preservando as informações já persistidas + * + * @param currentToken -> token que será atualizado + * @param newToken -> novo token que substituira o antigo + * @return true -> se de fato ocorreu essa atualização + */ + void refreshToken(final JwtToken currentToken, final JwtToken newToken); +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalUserPreferenceService.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalUserPreferenceService.java new file mode 100644 index 00000000..ae03375c --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalUserPreferenceService.java @@ -0,0 +1,21 @@ +package br.com.muttley.localcache.services; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.security.preference.UserPreferences; + +/** + * @author Joel Rodrigues Moreira 24/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface LocalUserPreferenceService { + public static final String BASIC_KEY = "USER-PREFENCES:"; + + UserPreferences getUserPreferences(final JwtToken jwtUser, final User user); + + UserPreferences getUserPreferences(final XAPIToken token, final User user); + + void expireUserPreferences(final User user); +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalWorkTeamService.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalWorkTeamService.java new file mode 100644 index 00000000..c2597198 --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalWorkTeamService.java @@ -0,0 +1,32 @@ +package br.com.muttley.localcache.services; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.workteam.WorkTeamDomain; + +/** + * @author Joel Rodrigues Moreira on 21/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface LocalWorkTeamService { + public static final String BASIC_KEY = "WORKTEAM:"; + + public WorkTeamDomain getWorkTeamDomain(final JwtToken token, final User user); + + public WorkTeamDomain getWorkTeamDomain(final XAPIToken token, final User user); + + public LocalWorkTeamService expire(final User user); + + public LocalWorkTeamService expireByOwner(final User user); + + public static String getBasicKey(final Owner owner, final User user) { + return getBasicKeyExpressionOwner(owner) + user.getId(); + } + + public static String getBasicKeyExpressionOwner(final Owner owner) { + return BASIC_KEY + owner.getId() + ":"; + } +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalXAPITokenService.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalXAPITokenService.java new file mode 100644 index 00000000..106f0203 --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/LocalXAPITokenService.java @@ -0,0 +1,19 @@ +package br.com.muttley.localcache.services; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.XAPIToken; + +public interface LocalXAPITokenService { + static enum Type { + XAPIToken, + JWTToken + } + + public static final String BASIC_KEY = "API-TOKEN:"; + + XAPIToken loadAPIToken(final String token); + + JwtToken loadJwtTokenFrom(final String xAPIToken); + + LocalXAPITokenService expireAPIToken(final String token); +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalDatabindingServiceImpl.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalDatabindingServiceImpl.java new file mode 100644 index 00000000..f0091e9f --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalDatabindingServiceImpl.java @@ -0,0 +1,68 @@ +package br.com.muttley.localcache.services.impl; + +import br.com.muttley.localcache.services.LocalDatabindingService; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.redis.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AbstractLocalDatabindingServiceImpl implements LocalDatabindingService { + + protected final RedisService redisService; + + @Autowired + public AbstractLocalDatabindingServiceImpl(final RedisService redisService) { + this.redisService = redisService; + } + + @Override + public List getUserDataBindings(final JwtToken jwtUser, final User user) { + throw new NotImplementedException(); + } + + @Override + public List getUserDataBindings(XAPIToken token, User user) { + throw new NotImplementedException(); + } + + protected void saveDatabindingsInCache(final JwtToken token, final User user, final List dataBindings) { + this.saveDatabindingsInCache(token.getDtExpiration(), user, dataBindings); + } + + protected void saveDatabindingsInCache(final XAPIToken token, final User user, final List dataBindings) { + this.saveDatabindingsInCache(token.generateDtExpiration(), user, dataBindings); + } + + private void saveDatabindingsInCache(final Date dtExpiration, final User user, final List dataBindings) { + this.redisService.set(this.getBasicKey(user), dataBindings != null ? new ArrayList<>(dataBindings) : dataBindings, dtExpiration); + } + + protected List getDatabinDataBindingsInCache(final JwtToken token, final User user) { + return (List) this.redisService.get(this.getBasicKey(user)); + } + + protected List getDatabinDataBindingsInCache(final XAPIToken token, final User user) { + return (List) this.redisService.get(this.getBasicKey(user)); + } + + @Override + public void expireUserDataBindings(final User user) { + this.redisService.delete(this.getBasicKey(user)); + } + + protected String getBasicKey(final User user) { + return BASIC_KEY + user.getId(); + } +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalModelServiceImpl.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalModelServiceImpl.java new file mode 100644 index 00000000..b6b3508d --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalModelServiceImpl.java @@ -0,0 +1,112 @@ +package br.com.muttley.localcache.services.impl; + +import br.com.muttley.localcache.services.LocalModelService; +import br.com.muttley.model.Model; +import br.com.muttley.model.security.User; +import br.com.muttley.model.util.RedisUtils; +import br.com.muttley.redis.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author Joel Rodrigues Moreira on 02/09/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AbstractLocalModelServiceImpl implements LocalModelService { + private final RedisService redisService; + private final long TIMEOUT = 25L; + + @Autowired + public AbstractLocalModelServiceImpl(final RedisService redisService) { + this.redisService = redisService; + } + + @Override + public boolean containsInCahce(final User user, final Class clazz, final String key) { + return this.redisService.hasKey(this.getBasicKey(user, clazz, key)); + } + + @Override + public boolean containsReferenceInCahce(User user, Class clazz, String key) { + return this.redisService.hasKey(this.getBasicKeyReference(user, clazz, key)); + } + + @Override + public LocalModelService addCache(final User user, final T value, final String key) { + return this.addCache(user, value, key, TIMEOUT); + } + + @Override + public LocalModelService addCache(final User user, final T value, final String key, long timeout) { + this.redisService.set(this.getBasicKey(user, (Class) value.getClass(), key), value, timeout); + return this; + } + + @Override + public LocalModelService addReferenceCache(User user, T value, String key) { + return this.addReferenceCache(user, value, key, TIMEOUT); + } + + @Override + public LocalModelService addReferenceCache(User user, T value, String key, long timeout) { + this.redisService.set(this.getBasicKeyReference(user, (Class) value.getClass(), key), value, timeout); + return this; + } + + @Override + public T loadModel(final User user, final Class clazz, String key) { + final String basickey = this.getBasicKey(user, clazz, key); + final T result; + if (this.containsInCahce(basickey)) { + result = (T) this.redisService.get(basickey); + this.refreshExpire(user, clazz, key); + } else { + result = null; + } + return result; + } + + @Override + public T loadReference(User user, Class clazz, String key) { + final String basickey = this.getBasicKeyReference(user, clazz, key); + final T result; + if (this.containsInCahce(basickey)) { + result = (T) this.redisService.get(basickey); + this.refreshExpire(user, clazz, key); + } else { + result = null; + } + return result; + } + + @Override + public LocalModelService expire(final User user, final Class clazz, final String key) { + this.redisService.delete(this.getBasicKey(user, clazz, key)); + this.redisService.delete(this.getBasicKeyReference(user, clazz, key)); + return this; + } + + @Override + public LocalModelService refreshExpire(User user, Class clazz, String key) { + this.redisService.setExpire(this.getBasicKey(user, clazz, key), TIMEOUT); + return this; + } + + @Override + public LocalModelService refreshExpireReference(User user, Class clazz, String key) { + this.redisService.setExpire(this.getBasicKeyReference(user, clazz, key), TIMEOUT); + return this; + } + + protected boolean containsInCahce(final String key) { + return this.redisService.hasKey(key); + } + + protected String getBasicKey(final User user, final Class clazz, final String key) { + return RedisUtils.createKeyByOwner(user, clazz, key); + } + + protected String getBasicKeyReference(final User user, final Class clazz, final String key) { + return RedisUtils.createKeyByOwner(user, clazz, "REFERENCE:" + key); + } +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalOwnerServiceImpl.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalOwnerServiceImpl.java new file mode 100644 index 00000000..86902e28 --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalOwnerServiceImpl.java @@ -0,0 +1,96 @@ +package br.com.muttley.localcache.services.impl; + +import br.com.muttley.localcache.services.LocalOwnerService; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.OwnerDataImpl; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataImpl; +import br.com.muttley.redis.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AbstractLocalOwnerServiceImpl implements LocalOwnerService { + protected final RedisService redisService; + protected static final long timeout = 60 * 60 * 24;//timeout de 24hr + + @Autowired + public AbstractLocalOwnerServiceImpl(final RedisService redisService) { + this.redisService = redisService; + } + + @Override + public OwnerData loadOwnerAny() { + throw new NotImplementedException(); + } + + @Override + public OwnerData loadOwnerAny(final User user) { + throw new NotImplementedException(); + } + + @Override + public OwnerData loadOwnerById(final String id) { + throw new NotImplementedException(); + } + + @Override + public OwnerData loadOwnerById(final User user, final String id) { + throw new NotImplementedException(); + } + + protected void saveOwnerInCache(final OwnerData owner) { + final Map ownerMap = new HashMap<>(); + ownerMap.put("id", owner.getId()); + ownerMap.put("name", owner.getName()); + ownerMap.put("description", owner.getDescription()); + + if (owner.getUserMaster() != null) { + final Map userMasterMap = new HashMap<>(); + userMasterMap.put("id", owner.getUserMaster().getId()); + userMasterMap.put("name", owner.getUserMaster().getName()); + userMasterMap.put("description", owner.getUserMaster().getDescription()); + userMasterMap.put("userName", owner.getUserMaster().getUserName()); + userMasterMap.put("nickUsers", owner.getUserMaster().getNickUsers()); + userMasterMap.put("email", owner.getUserMaster().getEmail()); + ownerMap.put("userMaster", userMasterMap); + } + this.redisService.set(this.getBasicKey(owner), ownerMap, timeout); + } + + protected OwnerData loadOwerInCache(final String id) { + final Map owerMap = (Map) this.redisService.get(this.getBasicKey(id)); + final OwnerDataImpl owner = new OwnerDataImpl(); + owner.setId(String.valueOf(owerMap.get("id"))); + owner.setDescription(String.valueOf(owerMap.get("description"))); + owner.setName(String.valueOf(owerMap.get("name"))); + if (owerMap.containsKey("userMaster")) { + final UserDataImpl userData = new UserDataImpl(); + final Map userMasterMap = (Map) owerMap.get("userMaster"); + userData.setId(String.valueOf(userMasterMap.get("id"))); + userData.setName(String.valueOf(userMasterMap.get("name"))); + userData.setDescription(String.valueOf(userMasterMap.get("description"))); + userData.setUserName(String.valueOf(userMasterMap.get("userName"))); + userData.setNickUsers((Set) userMasterMap.get("nickUsers")); + userData.setEmail(String.valueOf(userMasterMap.get("email"))); + owner.setUserMaster(userData); + } + return owner; + } + + protected String getBasicKey(final OwnerData ownerData) { + return getBasicKey(ownerData.getId()); + } + + protected String getBasicKey(final String id) { + return LocalOwnerService.BASIC_KEY + id; + } +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalRSAKeyPairService.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalRSAKeyPairService.java new file mode 100644 index 00000000..9f52372a --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalRSAKeyPairService.java @@ -0,0 +1,65 @@ +package br.com.muttley.localcache.services.impl; + +import br.com.muttley.localcache.services.LocalRSAKeyPairService; +import br.com.muttley.model.security.rsa.RSAUtil; +import br.com.muttley.redis.service.RedisService; + +import java.security.PrivateKey; +import java.security.PublicKey; + +import static br.com.muttley.model.security.rsa.RSAUtil.decrypt; +import static br.com.muttley.model.security.rsa.RSAUtil.encrypt; +import static br.com.muttley.model.security.rsa.RSAUtil.readPrivateKeyFromString; +import static br.com.muttley.model.security.rsa.RSAUtil.readPublicKeyFromString; + +public abstract class AbstractLocalRSAKeyPairService implements LocalRSAKeyPairService { + protected PrivateKey privateKey; + protected PublicKey publicKey; + protected final RedisService service; + + protected AbstractLocalRSAKeyPairService(RedisService service) { + this.service = service; + } + + protected String getBasicKeyPublic() { + return BASIC_KEY_PUBLIC; + } + + protected String getBasicKeyPrivate() { + return BASIC_KEY_PRIVATE; + } + + @Override + public String encryptMessage(String message) { + return encrypt(getPrivateKey(), message); + } + + @Override + public String decryptMessage(String encryptedMessage) { + return decrypt(getPublicKey(), encryptedMessage); + } + + protected PrivateKey getPrivateKey() { + if (privateKey == null) { + privateKey = readPrivateKeyFromString((String) this.service.get(this.getBasicKeyPrivate())); + } + return privateKey; + } + + protected void setPrivateKey(final PrivateKey key) { + this.privateKey = key; + this.service.set(this.getBasicKeyPrivate(), RSAUtil.toString(key)); + } + + protected PublicKey getPublicKey() { + if (publicKey == null) { + publicKey = readPublicKeyFromString((String) this.service.get(this.getBasicKeyPublic())); + } + return publicKey; + } + + protected void setPublicKey(final PublicKey key) { + this.publicKey = key; + this.service.set(this.getBasicKeyPublic(), RSAUtil.toString(key)); + } +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalRolesServiceImpl.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalRolesServiceImpl.java new file mode 100644 index 00000000..13b7001a --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalRolesServiceImpl.java @@ -0,0 +1,71 @@ +package br.com.muttley.localcache.services.impl; + +import br.com.muttley.localcache.services.LocalRolesService; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.redis.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import java.util.Date; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AbstractLocalRolesServiceImpl implements LocalRolesService { + protected final RedisService redisService; + + @Autowired + public AbstractLocalRolesServiceImpl(final RedisService redisService) { + this.redisService = redisService; + } + + @Override + public Set loadCurrentRoles(final JwtToken token, final User user) { + throw new NotImplementedException(); + } + + @Override + public Set loadCurrentRoles(final XAPIToken token, final User user) { + throw new NotImplementedException(); + } + + @Override + public void expireRoles(final User user) { + this.redisService.delete(this.getBasicKey(user)); + } + + protected void saveRolesInCache(final JwtToken token, final User user, final Set roles) { + this.saveRolesInCache(token.getDtExpiration(), user, roles); + } + + protected void saveRolesInCache(final XAPIToken token, final User user, final Set roles) { + this.saveRolesInCache(token.generateDtExpiration(), user, roles); + } + + private void saveRolesInCache(final Date dtExpiration, final User user, final Set roles) { + this.redisService.set( + this.getBasicKey(user), + roles.parallelStream() + .map(Role::getRoleName) + .collect(Collectors.toSet()), + dtExpiration); + } + + protected Set loadRolesInCache(final User user) { + return ((Set) this.redisService.get(this.getBasicKey(user))) + .parallelStream() + .map(it -> Role.valueOf(it)) + .collect(Collectors.toSet()); + } + + protected String getBasicKey(final User user) { + return LocalRolesService.BASIC_KEY + user.getId(); + } +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalUserPrefenceServiceImpl.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalUserPrefenceServiceImpl.java new file mode 100644 index 00000000..325c8ed1 --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalUserPrefenceServiceImpl.java @@ -0,0 +1,110 @@ +package br.com.muttley.localcache.services.impl; + +import br.com.muttley.localcache.services.LocalUserPreferenceService; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.security.preference.Preference; +import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.redis.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Joel Rodrigues Moreira 24/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AbstractLocalUserPrefenceServiceImpl implements LocalUserPreferenceService { + protected final RedisService redisService; + protected final ApplicationEventPublisher publisher; + + @Autowired + public AbstractLocalUserPrefenceServiceImpl(final RedisService redisService, ApplicationEventPublisher publisher) { + this.redisService = redisService; + this.publisher = publisher; + } + + @Override + public UserPreferences getUserPreferences(final JwtToken jwtUser, final User user) { + throw new NotImplementedException(); + } + + @Override + public UserPreferences getUserPreferences(XAPIToken token, User user) { + throw new NotImplementedException(); + } + + @Override + public void expireUserPreferences(User user) { + //deletando item do cache + this.redisService.delete(this.getBasicKey(user)); + } + + protected String getBasicKey(final User user) { + return BASIC_KEY + user.getId(); + } + + protected void savePreferenceInCache(final JwtToken token, final User user, final UserPreferences userPreferences) { + savePreferenceInCache(token.getDtExpiration(), user, userPreferences); + } + + protected void savePreferenceInCache(final XAPIToken token, final User user, final UserPreferences userPreferences) { + savePreferenceInCache(token.generateDtExpiration(), user, userPreferences); + } + + protected void savePreferenceInCache(final Date dtExpiration, final User user, final UserPreferences userPreferences) { + final Map userPreferencesMap = new HashMap<>(); + userPreferencesMap.put("id", userPreferences.getId()); + if (!userPreferences.isEmpty()) { + userPreferencesMap.put("preferences", + userPreferences.getPreferences() + .parallelStream() + .map(pre -> { + final Map preferencesItemMap = new HashMap<>(); + preferencesItemMap.put("key", pre.getKey()); + preferencesItemMap.put("value", pre.getValue()); + preferencesItemMap.put("resolved", pre.getResolved()); + return preferencesItemMap; + }).collect(Collectors.toList()) + ); + } + this.redisService.set(this.getBasicKey(user), userPreferencesMap, dtExpiration); + } + + protected UserPreferences getPreferenceInCache(final JwtToken token, final User user) { + return this.getPreferenceInCache(user); + } + + protected UserPreferences getPreferenceInCache(final XAPIToken token, final User user) { + return this.getPreferenceInCache(user); + } + + private UserPreferences getPreferenceInCache(final User user) { + final Map userPreferencesMap = (Map) this.redisService.get(this.getBasicKey(user)); + final UserPreferences preferences = new UserPreferences(); + preferences.setId(String.valueOf(userPreferencesMap.get("id"))); + if (userPreferencesMap.containsKey("preferences")) { + preferences.setPreferences( + ((List>) userPreferencesMap.get("preferences")) + .parallelStream() + .map(mapIt -> { + final Object valeu = mapIt.get("value") == null ? null : mapIt.get("value"); + final Preference preference = new Preference( + String.valueOf(mapIt.get("key")), + mapIt.get("value") + ); + return preference.setResolved(mapIt.get("resolved")); + }).collect(Collectors.toSet()) + ); + } + return preferences; + } +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalWorkTemaServiceImpl.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalWorkTemaServiceImpl.java new file mode 100644 index 00000000..b67a7a12 --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalWorkTemaServiceImpl.java @@ -0,0 +1,68 @@ +package br.com.muttley.localcache.services.impl; + +import br.com.muttley.localcache.services.LocalWorkTeamService; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.workteam.WorkTeamDomain; +import br.com.muttley.redis.service.RedisService; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import java.util.Date; + +import static br.com.muttley.localcache.services.LocalWorkTeamService.getBasicKey; +import static br.com.muttley.localcache.services.LocalWorkTeamService.getBasicKeyExpressionOwner; + +/** + * @author Joel Rodrigues Moreira on 21/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AbstractLocalWorkTemaServiceImpl implements LocalWorkTeamService { + protected final RedisService redisService; + + protected AbstractLocalWorkTemaServiceImpl(RedisService redisService) { + this.redisService = redisService; + } + + @Override + public WorkTeamDomain getWorkTeamDomain(JwtToken token, User user) { + throw new NotImplementedException(); + } + + @Override + public WorkTeamDomain getWorkTeamDomain(XAPIToken token, User user) { + throw new NotImplementedException(); + } + + @Override + public LocalWorkTeamService expire(User user) { + //deletando item do cache + this.redisService.delete(getBasicKey(user.getCurrentOwner(), user)); + return this; + } + + @Override + public LocalWorkTeamService expireByOwner(User user) { + //deletando todos os itens do cache baseado no owner + this.redisService.deleteByExpression(getBasicKeyExpressionOwner(user.getCurrentOwner()) + "*"); + return this; + } + + protected void save(final JwtToken token, final User user, final WorkTeamDomain domain) { + this.save(token.getDtExpiration(), user, domain); + } + + protected void save(final XAPIToken token, final User user, final WorkTeamDomain domain) { + this.save(token.generateDtExpiration(), user, domain); + } + + private void save(final Date dtExpiration, final User user, final WorkTeamDomain domain) { + this.redisService.set(getBasicKey(user.getCurrentOwner(), user), domain, dtExpiration); + } + + protected WorkTeamDomain loadWorkTeamDomainInCache(final User user) { + return (WorkTeamDomain) this.redisService.get(getBasicKey(user.getCurrentOwner(), user)); + } + +} diff --git a/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalXAPITokenServiceImpl.java b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalXAPITokenServiceImpl.java new file mode 100644 index 00000000..fedfca36 --- /dev/null +++ b/muttley-local-cache/src/main/java/br/com/muttley/localcache/services/impl/AbstractLocalXAPITokenServiceImpl.java @@ -0,0 +1,48 @@ +package br.com.muttley.localcache.services.impl; + +import br.com.muttley.localcache.services.LocalXAPITokenService; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.redis.service.RedisService; + +public abstract class AbstractLocalXAPITokenServiceImpl implements LocalXAPITokenService { + protected final RedisService redisService; + private static final long DEFAULT_EXPIRATION = 60 * 60 * 24;//24 horas em segundos + + protected AbstractLocalXAPITokenServiceImpl(RedisService redisService) { + this.redisService = redisService; + } + + protected LocalXAPITokenService saveInCache(final XAPIToken XAPIToken) { + this.redisService.set(this.getBasicKey(XAPIToken.getToken(), Type.XAPIToken), XAPIToken, DEFAULT_EXPIRATION); + return this; + } + + protected LocalXAPITokenService saveInCache(final XAPIToken xapiToken, final JwtToken jwtToken) { + return this.saveInCache(xapiToken.getToken(), jwtToken); + } + + protected LocalXAPITokenService saveInCache(final String xapiToken, final JwtToken jwtToken) { + this.redisService.set(this.getBasicKey(xapiToken, Type.JWTToken), jwtToken, DEFAULT_EXPIRATION); + return this; + } + + public XAPIToken loadAPIToken(final String token) { + return (XAPIToken) this.redisService.get(this.getBasicKey(token, Type.XAPIToken)); + } + + @Override + public JwtToken loadJwtTokenFrom(String xAPIToken) { + return (JwtToken) this.redisService.get(this.getBasicKey(xAPIToken, Type.JWTToken)); + } + + @Override + public LocalXAPITokenService expireAPIToken(String token) { + this.redisService.delete(this.getBasicKey(token, Type.XAPIToken)); + return this; + } + + protected String getBasicKey(final String token, Type type) { + return LocalXAPITokenService.BASIC_KEY + type.name() + ":" + token; + } +} diff --git a/muttley-model/pom.xml b/muttley-model/pom.xml index 3a95012d..092d21b5 100644 --- a/muttley-model/pom.xml +++ b/muttley-model/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar @@ -19,12 +19,28 @@ + + org.projectlombok + lombok + true + + + + org.bouncycastle + bcprov-jdk15on + + br.com.muttley muttley-exception provided + + br.com.muttley + muttley-utils + + commons-beanutils commons-beanutils diff --git a/muttley-model/src/main/java/br/com/muttley/annotations/index/CompoundIndexes.java b/muttley-model/src/main/java/br/com/muttley/annotations/index/CompoundIndexes.java new file mode 100644 index 00000000..c32ef467 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/annotations/index/CompoundIndexes.java @@ -0,0 +1,21 @@ +package br.com.muttley.annotations.index; + +import org.springframework.data.mongodb.core.index.CompoundIndex; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Joel Rodrigues Moreira on 12/06/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface CompoundIndexes { + + CompoundIndex[] value(); + +} diff --git a/muttley-model/src/main/java/br/com/muttley/validator/CheckIndex.java b/muttley-model/src/main/java/br/com/muttley/annotations/valitators/CheckIndex.java similarity index 78% rename from muttley-model/src/main/java/br/com/muttley/validator/CheckIndex.java rename to muttley-model/src/main/java/br/com/muttley/annotations/valitators/CheckIndex.java index 699045ae..c0091f1c 100644 --- a/muttley-model/src/main/java/br/com/muttley/validator/CheckIndex.java +++ b/muttley-model/src/main/java/br/com/muttley/annotations/valitators/CheckIndex.java @@ -1,4 +1,4 @@ -package br.com.muttley.validator; +package br.com.muttley.annotations.valitators; import javax.validation.Constraint; import javax.validation.Payload; @@ -27,13 +27,6 @@ */ String[] fields(); - /** - * Campo que é dono do index. - * Caso der algum erro de index, a exception {@link br.com.muttley.exception.throwables.MuttleyConflictException} - * receberá como campo o valor aqui informado - */ - String fieldOwner(); - Class[] groups() default {}; Class[] payload() default {}; diff --git a/muttley-model/src/main/java/br/com/muttley/validator/CheckIndexValidator.java b/muttley-model/src/main/java/br/com/muttley/annotations/valitators/CheckIndexValidator.java similarity index 96% rename from muttley-model/src/main/java/br/com/muttley/validator/CheckIndexValidator.java rename to muttley-model/src/main/java/br/com/muttley/annotations/valitators/CheckIndexValidator.java index 1bd116cb..a785fff7 100644 --- a/muttley-model/src/main/java/br/com/muttley/validator/CheckIndexValidator.java +++ b/muttley-model/src/main/java/br/com/muttley/annotations/valitators/CheckIndexValidator.java @@ -1,4 +1,4 @@ -package br.com.muttley.validator; +package br.com.muttley.annotations.valitators; import br.com.muttley.exception.throwables.MuttleyConflictException; import org.apache.commons.beanutils.PropertyUtils; @@ -36,7 +36,7 @@ public boolean isValid(final Object value, final ConstraintValidatorContext cont criteria.and(field).is(PropertyUtils.getProperty(value, field)); } if (mongoOperations.exists(new Query(criteria), value.getClass())) { - throw new MuttleyConflictException(value.getClass(), checkIndex.fieldOwner(), checkIndex.message()); + throw new MuttleyConflictException(value.getClass(), "", checkIndex.message()); } //final String cpfCnpj = (String) PropertyUtils.getProperty(value, this.checkIndex.fields()); diff --git a/muttley-model/src/main/java/br/com/muttley/model/BasicAggregateResult.java b/muttley-model/src/main/java/br/com/muttley/model/BasicAggregateResult.java new file mode 100644 index 00000000..267bc44f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/BasicAggregateResult.java @@ -0,0 +1,22 @@ +package br.com.muttley.model; + +import lombok.EqualsAndHashCode; + +/** + * @author Joel Rodrigues Moreira on 19/06/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@EqualsAndHashCode(of = "result") +public class BasicAggregateResult { + private T result; + + public T getResult() { + return result; + } + + public BasicAggregateResult setResult(final T result) { + this.result = result; + return this; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/BasicAggregateResultCount.java b/muttley-model/src/main/java/br/com/muttley/model/BasicAggregateResultCount.java new file mode 100644 index 00000000..c14a6ed8 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/BasicAggregateResultCount.java @@ -0,0 +1,20 @@ +package br.com.muttley.model; + +import lombok.EqualsAndHashCode; + +/** + * @author Joel Rodrigues Moreira on 19/06/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@EqualsAndHashCode(of = "result") +public class BasicAggregateResultCount extends BasicAggregateResult { + public static final BasicAggregateResultCount ZERO = new BasicAggregateResultCount(0l); + + public BasicAggregateResultCount() { + } + + public BasicAggregateResultCount(final long value) { + this.setResult(value); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/Document.java b/muttley-model/src/main/java/br/com/muttley/model/Document.java index abfff474..658ada04 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/Document.java +++ b/muttley-model/src/main/java/br/com/muttley/model/Document.java @@ -1,10 +1,14 @@ package br.com.muttley.model; +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.exception.throwables.MuttleyInvalidObjectIdException; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.beanutils.PropertyUtils; import org.bson.types.ObjectId; import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; import static org.springframework.util.StringUtils.isEmpty; @@ -19,12 +23,23 @@ public interface Document extends Serializable { Document setId(final String id); - Document setHistoric(final Historic historic); + MetadataDocument getMetadata(); + + Document setMetadata(final MetadataDocument metaData); + + @JsonIgnore + default boolean containsMetadata() { + return this.getMetadata() != null; + } @JsonIgnore default ObjectId getObjectId() { if (!isEmpty(getId())) { - return new ObjectId(getId()); + try { + return new ObjectId(getId()); + } catch (IllegalArgumentException ex) { + throw new MuttleyInvalidObjectIdException(this.getClass(), "id", "ObjectId inválido"); + } } return null; } @@ -37,8 +52,6 @@ default boolean contaisObjectId() { return false; } - @JsonIgnore - Historic getHistoric(); default String toJson() { try { @@ -50,4 +63,15 @@ default String toJson() { return null; } } + + static Object getPropertyFrom(final Object instance, final String nameProperty) { + try { + if (instance == null) { + return null; + } + return PropertyUtils.getProperty(instance, nameProperty); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new MuttleyException(e); + } + } } diff --git a/muttley-model/src/main/java/br/com/muttley/model/Historic.java b/muttley-model/src/main/java/br/com/muttley/model/Historic.java index 607315a9..f77bc7c6 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/Historic.java +++ b/muttley-model/src/main/java/br/com/muttley/model/Historic.java @@ -1,11 +1,12 @@ package br.com.muttley.model; import br.com.muttley.model.security.User; +import br.com.muttley.model.security.jackson.UserDeserializer; import br.com.muttley.model.security.jackson.UserSerializer; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.DBRef; import java.util.Date; @@ -15,14 +16,15 @@ * @project muttley-cloud */ public class Historic { - @JsonSerialize(using = UserSerializer.class) @DBRef + @JsonSerialize(using = UserSerializer.class) + @JsonDeserialize(using = UserDeserializer.class) private User createdBy; - @Indexed private Date dtCreate; @DBRef + @JsonSerialize(using = UserSerializer.class) + @JsonDeserialize(using = UserDeserializer.class) private User lastChangeBy; - @Indexed private Date dtChange; public Historic() { @@ -76,4 +78,15 @@ public Historic setDtChange(final Date dtChange) { this.dtChange = dtChange; return this; } + + public static class Builder { + public static Historic createNew(final User user) { + final Date now = new Date(); + return new Historic() + .setCreatedBy(user) + .setDtCreate(now) + .setLastChangeBy(user) + .setDtChange(now); + } + } } diff --git a/muttley-model/src/main/java/br/com/muttley/model/MetadataDocument.java b/muttley-model/src/main/java/br/com/muttley/model/MetadataDocument.java new file mode 100644 index 00000000..2ab067bd --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/MetadataDocument.java @@ -0,0 +1,99 @@ +package br.com.muttley.model; + +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.domain.Domain; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.PersistenceConstructor; + +import static br.com.muttley.model.security.domain.Domain.PRIVATE; + +/** + * @author Joel Rodrigues Moreira on 30/01/20. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class MetadataDocument { + private Domain domain; + private TimeZoneDocument timeZones; + private VersionDocument versionDocument; + private Historic historic; + + public MetadataDocument(final User user) { + this.domain = PRIVATE; + this.timeZones = new TimeZoneDocument(); + this.versionDocument = new VersionDocument(); + this.historic = Historic.Builder.createNew(user); + } + + @JsonCreator + @PersistenceConstructor + public MetadataDocument( + @JsonProperty("domain") final Domain domain, + @JsonProperty("timeZones") final TimeZoneDocument timeZones, + @JsonProperty("versionDocument") final VersionDocument versionDocument, + @JsonProperty("historic") final Historic historic) { + this.domain = domain; + this.timeZones = timeZones; + this.versionDocument = versionDocument; + this.historic = historic; + } + + public boolean containsTimeZones() { + return this.getTimeZones() != null; + } + + public boolean containsVersionDocument() { + return this.getVersionDocument() != null; + } + + public boolean containsHistoric() { + return this.getHistoric() != null; + } + + public boolean containsDomain() { + return this.domain != null; + } + + public static class Builder { + private Domain domain; + private TimeZoneDocument timeZone; + private VersionDocument version; + private Historic historic; + + public static Builder getInstance() { + return new Builder(); + } + + public Builder setDomain(Domain domain) { + this.domain = domain; + return this; + } + + public Builder setTimeZone(final TimeZoneDocument timeZone) { + this.timeZone = timeZone; + return this; + } + + public Builder setVersion(final VersionDocument version) { + this.version = version; + return this; + } + + public Builder setHistoric(Historic historic) { + this.historic = historic; + return this; + } + + public MetadataDocument build() { + return new MetadataDocument(this.domain, this.timeZone, this.version, historic); + } + } + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/Model.java b/muttley-model/src/main/java/br/com/muttley/model/Model.java index db826684..adbc14cc 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/Model.java +++ b/muttley-model/src/main/java/br/com/muttley/model/Model.java @@ -1,20 +1,24 @@ package br.com.muttley.model; -import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; import br.com.muttley.model.security.User; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; /** * @author Joel Rodrigues Moreira on 29/01/18. * @project muttley-cloud */ -public interface Model extends Document { +public interface Model extends Document { default Model setOwner(final User user) { - return this.setOwner(user.getCurrentOwner()); + return this.setOwner((T) user.getCurrentOwner()); } - Model setOwner(final Owner owner); + @JsonProperty("owner") + Model setOwner(final T owner); - Owner getOwner(); + @JsonIgnore + OwnerData getOwner(); } diff --git a/muttley-model/src/main/java/br/com/muttley/model/ModelSync.java b/muttley-model/src/main/java/br/com/muttley/model/ModelSync.java new file mode 100644 index 00000000..4132ef7e --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/ModelSync.java @@ -0,0 +1,34 @@ +package br.com.muttley.model; + +import br.com.muttley.model.security.Owner; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.util.Date; + +import static org.springframework.util.StringUtils.isEmpty; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface ModelSync extends Model { + + /*default ModelSync setId(final String id) { + setId(id); + return this; + }*/ + + String getSync(); + + ModelSync setSync(final String sync); + + Date getDtSync(); + + ModelSync setDtSync(final Date date); + + @JsonIgnore + default boolean containsSync() { + return !isEmpty(this.getSync()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/NameAlias.java b/muttley-model/src/main/java/br/com/muttley/model/NameAlias.java new file mode 100644 index 00000000..876c073a --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/NameAlias.java @@ -0,0 +1,19 @@ +package br.com.muttley.model; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Joel Rodrigues Moreira on 10/10/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface NameAlias { + String singularName() default "[singularName]"; + + String pluralName() default "[pluralName]"; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/SerializeType.java b/muttley-model/src/main/java/br/com/muttley/model/SerializeType.java new file mode 100644 index 00000000..5e048400 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/SerializeType.java @@ -0,0 +1,130 @@ +package br.com.muttley.model; + +import javax.servlet.http.HttpServletRequest; +import java.util.Objects; + +/** + * @author Joel Rodrigues Moreira on 06/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + *

+ * Devemos verificar em cada requisição qual o tipo de serialização a ser utilizada + * SerializeType = 'sync' => devemos serializar o código vindo do serviço do cliente + * SerializeType = 'ObjectId' => devemos serializar o nosso próprio ObjectId + * SerializeType = 'ObjectIdAndSync' => devemos serializar o nosso próprio ObjectId juntamente com o sync + * SerializeType = null => devemos serializar o nosso próprio ObjectId + *

+ */ + +public class SerializeType { + public static final String KEY_FROM_HEADER = "SerializeType"; + public static final String KEY_INTERNAL_FROM_HEADER = "SerializeTypeInternal"; + + public static final String SYNC_TYPE = "sync"; + public static final String OBJECT_ID_TYPE = "ObjectId"; + public static final String OBJECT_ID_AND_SYNC_TYPE = "ObjectIdAndSync"; + + //public static final SerializeType2 INTERNAL = new SerializeType2("true"); + + + private final String value; + private boolean internal; + + private SerializeType(final String value) { + this.value = value; + } + + private SerializeType(final String value, final boolean internal) { + this(value); + this.internal = internal; + } + + public boolean isInternal() { + return internal; + } + + protected SerializeType setInternal(final boolean value) { + this.internal = internal; + return this; + } + + protected SerializeType setInternal(final String value) { + return value == null ? this.setInternal(false) : setInternal("true".equals(value)); + } + + public boolean isSync() { + return SYNC_TYPE.equals(this.value); + } + + public boolean isObjectId() { + return OBJECT_ID_TYPE.equals(this.value); + } + + public boolean isObjectIdAndSync() { + return OBJECT_ID_AND_SYNC_TYPE.equals(this.value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SerializeType that = (SerializeType) o; + return internal == that.internal && Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value, internal); + } + + public static class Builder { + //private HttpServletRequest request; + private String type; + private boolean internal = false; + + private Builder() { + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder setRequest(final HttpServletRequest request) { + return setType(request == null ? null : request.getHeader(KEY_FROM_HEADER)); + } + + public Builder setType(final String type) { + this.type = type; + return this; + } + + private Builder setInternal(final String value) { + this.internal = "true".equals(value); + return this; + } + + public SerializeType build() { + if (this.type == null) { + return new SerializeType(SerializeType.OBJECT_ID_TYPE); + } + final SerializeType serializeType; + switch (type) { + case SerializeType.SYNC_TYPE: + case SerializeType.OBJECT_ID_AND_SYNC_TYPE: + case SerializeType.OBJECT_ID_TYPE: + serializeType = new SerializeType(type, false); + break; + default: + serializeType = new SerializeType(SerializeType.OBJECT_ID_TYPE, false); + } + serializeType.setInternal(this.internal); + return serializeType; + } + + public static SerializeType build(final HttpServletRequest request) { + return Builder.newInstance().setRequest(request).build(); + } + + + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/SyncObjectId.java b/muttley-model/src/main/java/br/com/muttley/model/SyncObjectId.java new file mode 100644 index 00000000..b72c7f8a --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/SyncObjectId.java @@ -0,0 +1,29 @@ +package br.com.muttley.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = {"sync", "id"}) +public class SyncObjectId { + private String sync; + private String id; + + @JsonCreator + public SyncObjectId( + @JsonProperty("sync") final String sync, @JsonProperty("id") final String id) { + this.sync = sync; + this.id = id; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/TimeZoneDocument.java b/muttley-model/src/main/java/br/com/muttley/model/TimeZoneDocument.java new file mode 100644 index 00000000..b665846c --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/TimeZoneDocument.java @@ -0,0 +1,98 @@ +package br.com.muttley.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import static br.com.muttley.utils.TimeZoneUtils.getTimezoneFromId; +import static br.com.muttley.utils.TimeZoneUtils.isValidTimeZone; + +/** + * @author Joel Rodrigues Moreira on 30/01/20. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class TimeZoneDocument { + + /** + * Deve conter informações do timezone corrente do registro + */ + private String currentTimeZone; + + /** + * Deve conter informações do timezone de criação do registro + */ + private String createTimeZone; + + /** + * Deve conter informações do timezone corrente do registro por parte do servidor + */ + private String serverCurrentTimeZone; + + /** + * Deve conter informações do timezone de criação do registro por parte do servidor + */ + private String serverCreteTimeZone; + + public TimeZoneDocument() { + } + + @JsonCreator + public TimeZoneDocument( + @JsonProperty("currentTimeZone") final String currentTimeZone, + @JsonProperty("createTimeZone") final String createTimeZone, + @JsonProperty("serverCurrentTimeZone") final String serverCurrentTimeZone, + @JsonProperty("serverCreteTimeZone") final String serverCreteTimeZone) { + this.setCurrentTimeZone(currentTimeZone); + this.setCreateTimeZone(createTimeZone); + this.setServerCurrentTimeZone(serverCurrentTimeZone); + this.setServerCreteTimeZone(serverCreteTimeZone); + } + + public TimeZoneDocument setCurrentTimeZone(final String currentTimeZone) { + this.currentTimeZone = getTimezoneFromId(currentTimeZone); + return this; + } + + @JsonIgnore + public boolean isValidCurrentTimeZone() { + return isValidTimeZone(this.getCurrentTimeZone()); + } + + public TimeZoneDocument setCreateTimeZone(final String createTimeZone) { + this.createTimeZone = getTimezoneFromId(createTimeZone); + return this; + } + + @JsonIgnore + public boolean isValidCreateTimeZone() { + return isValidTimeZone(this.getCreateTimeZone()); + } + + public TimeZoneDocument setServerCurrentTimeZone(final String serverCurrentTimeZone) { + this.serverCurrentTimeZone = getTimezoneFromId(serverCurrentTimeZone); + return this; + } + + @JsonIgnore + public boolean isValidServerCurrentTimeZone() { + return isValidTimeZone(this.getServerCurrentTimeZone()); + } + + public TimeZoneDocument setServerCreteTimeZone(final String serverCreteTimeZone) { + this.serverCreteTimeZone = getTimezoneFromId(serverCreteTimeZone); + return this; + } + + @JsonIgnore + public boolean isValidServerCreteTimeZone() { + return isValidTimeZone(this.getServerCreteTimeZone()); + } + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/VersionDocument.java b/muttley-model/src/main/java/br/com/muttley/model/VersionDocument.java new file mode 100644 index 00000000..712bbe16 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/VersionDocument.java @@ -0,0 +1,67 @@ +package br.com.muttley.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * @author Joel Rodrigues Moreira on 30/01/20. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class VersionDocument { + + /** + * Nome do cliente que criou o documento + */ + private String originNameClientCreate; + + /** + * Versão do cliente que criou o documento + */ + private String originVersionClientCreate; + + /** + * Versão do servidor em que foi criado o documento + */ + private String serverVersionCreate; + + /** + * Nome do cliente que fez a atualização do documento + */ + private String originNameClientLastUpdate; + + /** + * Versão do cliente que fez a ultima atualização do documento + */ + private String originVersionClientLastUpdate; + + /** + * Versão do servidor em que foi atualizado o documento + */ + private String serverVersionLastUpdate; + + public VersionDocument() { + } + + @JsonCreator + public VersionDocument( + @JsonProperty("originNameClientCreate") final String originNameClientCreate, + @JsonProperty("originVersionClientCreate") final String originVersionClientCreate, + @JsonProperty("serverVersionCreate") final String serverVersionCreate, + @JsonProperty("originNameClientLastUpdate") final String originNameClientLastUpdate, + @JsonProperty("originVersionClientLastUpdate") final String originVersionClientLastUpdate, + @JsonProperty("serverVersionLastUpdate") final String serverVersionLastUpdate) { + this.originNameClientCreate = originNameClientCreate; + this.originVersionClientCreate = originVersionClientCreate; + this.serverVersionCreate = serverVersionCreate; + this.originNameClientLastUpdate = originNameClientLastUpdate; + this.originVersionClientLastUpdate = originVersionClientLastUpdate; + this.serverVersionLastUpdate = serverVersionLastUpdate; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/View.java b/muttley-model/src/main/java/br/com/muttley/model/View.java new file mode 100644 index 00000000..b8240230 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/View.java @@ -0,0 +1,86 @@ +package br.com.muttley.model; + +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.index.CompoundIndexes; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.Objects; + +/** + * @author Joel Rodrigues Moreira on 29/04/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Document(collection = "_view") +@CompoundIndexes({ + @CompoundIndex(name = "name_index_unique", def = "{'name' : 1}", unique = true) +}) +@TypeAlias("view") +public class View { + @Id + private String id; + private String name; + private String version; + private String description; + + public View() { + } + + public View(final String name, final String version, String description) { + this(); + this.name = name; + this.version = version; + this.description = description; + } + + public String getId() { + return id; + } + + public View setId(final String id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public View setName(final String name) { + this.name = name; + return this; + } + + public String getVersion() { + return version; + } + + public View setVersion(final String version) { + this.version = version; + return this; + } + + public String getDescription() { + return description; + } + + public View setDescription(final String description) { + this.description = description; + return this; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof View)) return false; + final View view = (View) o; + return Objects.equals(getId(), view.getId()); + } + + @Override + public int hashCode() { + return Objects.hash(getId()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/admin/AdminOwner.java b/muttley-model/src/main/java/br/com/muttley/model/admin/AdminOwner.java new file mode 100644 index 00000000..1f6024f5 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/admin/AdminOwner.java @@ -0,0 +1,28 @@ +package br.com.muttley.model.admin; + +import br.com.muttley.annotations.index.CompoundIndexes; +import br.com.muttley.model.security.Owner; +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.Document; + +import static br.com.muttley.model.admin.AdminOwner.TYPE_ALIAS; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Document(collection = "#{documentNameConfig.getNameCollectionAdminOwner()}") +@CompoundIndexes({ + @CompoundIndex(name = "userMaster_index_unique", def = "{'userMaster': 1}", unique = true), + @CompoundIndex(name = "name_index", def = "{'name': 1}") +}) +@TypeAlias(TYPE_ALIAS) +public class AdminOwner extends Owner { + @Transient + @JsonIgnore + public static final String TYPE_ALIAS = "admin-owner"; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/admin/AdminPassaport.java b/muttley-model/src/main/java/br/com/muttley/model/admin/AdminPassaport.java new file mode 100644 index 00000000..70912ed2 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/admin/AdminPassaport.java @@ -0,0 +1,27 @@ +package br.com.muttley.model.admin; + +import br.com.muttley.model.security.Passaport; +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.index.CompoundIndexes; + +import static br.com.muttley.model.admin.AdminPassaport.TYPE_ALIAS; + +/** + * @author Joel Rodrigues Moreira 20/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@org.springframework.data.mongodb.core.mapping.Document(collection = "#{documentNameConfig.getNameCollectionAdminPassaport()}") +@CompoundIndexes({ + @CompoundIndex(name = "name_userMaster_index_unique", def = "{'name' : 1, 'userMaster': 1}", unique = true) +}) +@TypeAlias(TYPE_ALIAS) +public class AdminPassaport extends Passaport { + + @Transient + @JsonIgnore + public static final String TYPE_ALIAS = "admin-work-team"; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/admin/AdminUserBase.java b/muttley-model/src/main/java/br/com/muttley/model/admin/AdminUserBase.java new file mode 100644 index 00000000..4924e295 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/admin/AdminUserBase.java @@ -0,0 +1,33 @@ +package br.com.muttley.model.admin; + +import br.com.muttley.model.security.UserBase; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.index.CompoundIndexes; +import org.springframework.data.mongodb.core.mapping.Document; + +import static br.com.muttley.model.admin.AdminUserBase.TYPE_ALIAS; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Document(collection = "#{documentNameConfig.getNameCollectionAdminUserBase()}") +@CompoundIndexes({ + @CompoundIndex(name = "owner_index_unique", def = "{'owner' : 1}", unique = true) +}) +@Getter +@Setter +@Accessors(chain = true) +@TypeAlias(TYPE_ALIAS) +public class AdminUserBase extends UserBase { + @Transient + @JsonIgnore + public static final String TYPE_ALIAS = "admin-user-base"; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/admin/AdminUserDataBinding.java b/muttley-model/src/main/java/br/com/muttley/model/admin/AdminUserDataBinding.java new file mode 100644 index 00000000..eea04e08 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/admin/AdminUserDataBinding.java @@ -0,0 +1,49 @@ +package br.com.muttley.model.admin; + +import br.com.muttley.annotations.index.CompoundIndexes; +import br.com.muttley.model.security.UserDataBinding; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.Document; + +import static br.com.muttley.model.admin.AdminUserDataBinding.TYPE_ALIAS; + +/** + * @author Joel Rodrigues Moreira 27/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Document(collection = "#{documentNameConfig.getNameCollectionAdminUserDataBinding()}") +@CompoundIndexes({ + @CompoundIndex(name = "owner_user_key_index_unique", def = "{'owner': 1, 'user': 1, 'key': 1}", unique = true) +}) +@TypeAlias(TYPE_ALIAS) +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = {"key"}) +public class AdminUserDataBinding extends UserDataBinding { + @Transient + @JsonIgnore + public static final String TYPE_ALIAS = "admin-user-data-binding"; + + public AdminUserDataBinding() { + } + + public AdminUserDataBinding(final UserDataBinding dataBinding) { + this.setId(dataBinding.getId()) + .setOwner(dataBinding.getOwner()) + .setUser(dataBinding.getUser()) + .setKey(dataBinding.getKey()) + .setMetadata(dataBinding.getMetadata()) + .setResolvedValue(dataBinding.getResolvedValue()) + .setResolved(dataBinding.isResolved()) + .setValue(dataBinding.getValue()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/TokenId.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/TokenId.java new file mode 100644 index 00000000..8cba5641 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/TokenId.java @@ -0,0 +1,21 @@ +package br.com.muttley.model.hermes.notification; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.NotBlank; + +import javax.validation.constraints.NotNull; + +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = {"token", "origin"}) +public class TokenId { + @NotBlank(message = "Informe um token válido") + private String token; + private boolean mobile = true; + @NotNull(message = "Informe o origin") + private TokenOrigin origin; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/TokenOrigin.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/TokenOrigin.java new file mode 100644 index 00000000..b649f240 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/TokenOrigin.java @@ -0,0 +1,17 @@ +package br.com.muttley.model.hermes.notification; + +import br.com.muttley.model.hermes.notification.jackson.TokenOriginDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.springframework.util.StringUtils; + +@JsonDeserialize(using = TokenOriginDeserializer.class) +public enum TokenOrigin { + OneSignal; + + public static TokenOrigin getTokenOrigin(final String origin) { + if (StringUtils.isEmpty(origin)) { + return null; + } + return "OneSignal".equalsIgnoreCase(origin) ? OneSignal : null; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/UserTokensNotification.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/UserTokensNotification.java new file mode 100644 index 00000000..ef0e8459 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/UserTokensNotification.java @@ -0,0 +1,52 @@ +package br.com.muttley.model.hermes.notification; + + +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.security.User; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.index.CompoundIndexes; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; + +import javax.validation.constraints.NotNull; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +@Document(collection = "#{documentNameConfig.getNameCollectionUserTokensNotification()}") +@CompoundIndexes({ + @CompoundIndex(name = "user_index_unique", def = "{'user' : 1}", unique = true) +}) +@Getter +@Setter +@Accessors(chain = true) +@TypeAlias("tokens-notification") +public class UserTokensNotification implements br.com.muttley.model.Document { + private String id; + @NotNull(message = "Informe o usuário") + /*@JsonSerialize(using = UserSerializer.class) + @JsonDeserialize(using = UserDeserializer.class)*/ + @DBRef + private User user; + private Set tokens; + private MetadataDocument metadata; + + public UserTokensNotification() { + this.tokens = new HashSet<>(); + } + + public UserTokensNotification add(final TokenId tokenId) { + this.tokens.add(tokenId); + return this; + } + + @JsonIgnore + public Set getTokensMobile() { + return this.tokens.parallelStream().filter(TokenId::isMobile).collect(Collectors.toSet()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/event/OneSignalNotificationEvent.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/event/OneSignalNotificationEvent.java new file mode 100644 index 00000000..629227ee --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/event/OneSignalNotificationEvent.java @@ -0,0 +1,16 @@ +package br.com.muttley.model.hermes.notification.event; + +import br.com.muttley.model.hermes.notification.UserTokensNotification; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira on 01/05/2020. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class OneSignalNotificationEvent extends ApplicationEvent { + + public OneSignalNotificationEvent(UserTokensNotification token) { + super(token); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/infobip/Destination.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/infobip/Destination.java new file mode 100644 index 00000000..4928e012 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/infobip/Destination.java @@ -0,0 +1,19 @@ +package br.com.muttley.model.hermes.notification.infobip; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * @author Joel Rodrigues Moreira on 18/09/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = "to") +public class Destination { + private String to; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/infobip/Messages.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/infobip/Messages.java new file mode 100644 index 00000000..817da115 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/infobip/Messages.java @@ -0,0 +1,21 @@ +package br.com.muttley.model.hermes.notification.infobip; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 18/09/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class Messages { + private Set destinations; + private String from; + private String text; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/infobip/SMSPayload.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/infobip/SMSPayload.java new file mode 100644 index 00000000..6cf9e2f4 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/infobip/SMSPayload.java @@ -0,0 +1,19 @@ +package br.com.muttley.model.hermes.notification.infobip; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 19/09/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class SMSPayload { + private List messages; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/jackson/TokenOriginDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/jackson/TokenOriginDeserializer.java new file mode 100644 index 00000000..fef5780b --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/jackson/TokenOriginDeserializer.java @@ -0,0 +1,20 @@ +package br.com.muttley.model.hermes.notification.jackson; + +import br.com.muttley.model.hermes.notification.TokenOrigin; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; + +public class TokenOriginDeserializer extends JsonDeserializer { + @Override + public TokenOrigin deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + return TokenOrigin.getTokenOrigin(node.asText()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/Content.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/Content.java new file mode 100644 index 00000000..44dccae4 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/Content.java @@ -0,0 +1,37 @@ +package br.com.muttley.model.hermes.notification.onesignal; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.NotBlank; + +import javax.validation.constraints.NotNull; + +import static br.com.muttley.model.hermes.notification.onesignal.MuttleyLanguage.Any; + +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = "language") +public class Content { + @NotNull + private MuttleyLanguage language; + @NotBlank + private String content; + + public Content() { + } + + @JsonCreator + public Content(final @JsonProperty("language") MuttleyLanguage language, final @JsonProperty("content") String content) { + this.language = language; + this.content = content; + } + + public Content(final String content) { + this(Any, content); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/MuttleyLanguage.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/MuttleyLanguage.java new file mode 100644 index 00000000..b92b5803 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/MuttleyLanguage.java @@ -0,0 +1,158 @@ +package br.com.muttley.model.hermes.notification.onesignal; + +import lombok.Getter; +import org.springframework.util.StringUtils; + +import java.util.stream.Stream; + +public enum MuttleyLanguage { + English("en"), + Any("en"), + Arabic("ar"), + Catalan("ca"), + ChineseSimplified("zh-Hans"), + ChineseTraditional("zh-Hant"), + Croatian("hr"), + Czech("cs"), + Danish("da"), + Dutch("nl"), + Estonian("et"), + Finnish("fi"), + French("fr"), + Georgian("ka"), + Bulgarian("bg"), + German("de"), + Greek("el"), + Hindi("hi"), + Hebrew("he"), + Hungarian("hu"), + Indonesian("id"), + Italian("it"), + Japanese("ja"), + Korean("ko"), + Latvian("lv"), + Lithuanian("lt"), + Malay("ms"), + Norwegian("nb"), + Polish("pl"), + Persian("fa"), + Portuguese("pt"), + Romanian("ro"), + Russian("ru"), + Swedish("sv"), + Serbian("sr"), + Slovak("sk"), + Spanish("es"), + Thai("th"), + Turkish("tr"), + Ukrainian("uk"), + Vietnamese("vi"); + + + @Getter + private final String oneSignalValue; + + MuttleyLanguage(final String oneSignalValue) { + this.oneSignalValue = oneSignalValue; + } + + public static MuttleyLanguage getLanguage(final String language) { + if (StringUtils.isEmpty(language)) { + return null; + } + switch (language.toLowerCase()) { + case "english": + return English; + case "any": + return Any; + case "arabic": + return Arabic; + case "catalan": + return Catalan; + case "chinesesimplified": + return ChineseSimplified; + case "chinesetraditional": + return ChineseTraditional; + case "croatian": + return Croatian; + case "czech": + return Czech; + case "danish": + return Danish; + case "dutch": + return Dutch; + case "estonian": + return Estonian; + case "finnish": + return Finnish; + case "french": + return French; + case "georgian": + return Georgian; + case "bulgarian": + return Bulgarian; + case "german": + return German; + case "greek": + return Greek; + case "hindi": + return Hindi; + case "hebrew": + return Hebrew; + case "hungarian": + return Hungarian; + case "indonesian": + return Indonesian; + case "italian": + return Italian; + case "japanese": + return Japanese; + case "korean": + return Korean; + case "latvian": + return Latvian; + case "lithuanian": + return Lithuanian; + case "malay": + return Malay; + case "norwegian": + return Norwegian; + case "polish": + return Polish; + case "persian": + return Persian; + case "portuguese": + return Portuguese; + case "romanian": + return Romanian; + case "russian": + return Russian; + case "swedish": + return Swedish; + case "serbian": + return Serbian; + case "slovak": + return Slovak; + case "spanish": + return Spanish; + case "thai": + return Thai; + case "turkish": + return Turkish; + case "ukrainian": + return Ukrainian; + case "vietnamese": + return Vietnamese; + default: + return Stream.of(MuttleyLanguage.values()) + .filter(it -> it.name().toLowerCase().equals(language.toLowerCase()) || it.oneSignalValue.toLowerCase().equals(language.toLowerCase())) + .findFirst() + .orElse(null); + } + } + + @Override + public String toString() { + return super.toString(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/Notification.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/Notification.java new file mode 100644 index 00000000..41493d2b --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/Notification.java @@ -0,0 +1,159 @@ +package br.com.muttley.model.hermes.notification.onesignal; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.model.hermes.notification.TokenId; +import br.com.muttley.model.hermes.notification.UserTokensNotification; +import br.com.muttley.model.hermes.notification.onesignal.jackson.CollectionContentSerialize; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.NotBlank; +import org.hibernate.validator.constraints.NotEmpty; +import org.springframework.util.CollectionUtils; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; +import static org.springframework.util.StringUtils.isEmpty; + +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = "appId") +public class Notification implements Cloneable { + @NotBlank + @JsonProperty("app_id") + private String appId; + + @NotEmpty + @Valid + @JsonProperty("include_player_ids") + private Set players; + + @JsonProperty("big_picture") + private String picture; + + @Valid + private NotificationData data; + + @Valid + @JsonSerialize(using = CollectionContentSerialize.class) + private Set contents; + + @NotEmpty + @Valid + @JsonSerialize(using = CollectionContentSerialize.class) + private Set headings; + + @Valid + @JsonSerialize(using = CollectionContentSerialize.class) + private Set subtitle; + + public Notification() { + this.players = new HashSet<>(); + this.contents = new HashSet<>(); + this.headings = new HashSet<>(); + this.subtitle = new HashSet<>(); + } + + public Notification addContents(final Collection contents) { + if (!CollectionUtils.isEmpty(contents)) { + this.contents.addAll(contents.parallelStream().filter(it -> !isEmpty(it)).collect(Collectors.toSet())); + } + return this; + } + + public Notification addContent(final Content... contents) { + return this.addContents(asList(contents)); + } + + public Notification addContent(final String content) { + return this.addContent(new Content(content)); + } + + public Notification addContent(final MuttleyLanguage language, final String content) { + return this.addContent(new Content(language, content)); + } + + public Notification addHeadings(final Collection headings) { + if (!CollectionUtils.isEmpty(headings)) { + this.headings.addAll(headings.parallelStream().filter(it -> !isEmpty(it)).collect(Collectors.toSet())); + } + return this; + } + + public Notification addHeadings(final Content... headings) { + this.addHeadings(asList(headings)); + return this; + } + + public Notification addHeading(final String content) { + return this.addHeadings(new Content(content)); + } + + public Notification addHeading(final MuttleyLanguage language, final String content) { + return this.addHeadings(new Content(language, content)); + } + + public Notification addSubtitles(final Collection subtitles) { + if (!CollectionUtils.isEmpty(subtitles)) { + this.subtitle.addAll(subtitles.parallelStream().filter(it -> !isEmpty(it)).collect(Collectors.toSet())); + } + return this; + } + + public Notification addSubtitles(final Content... subtitles) { + this.addSubtitles(asList(subtitles)); + return this; + } + + public Notification addSubtitle(final String content) { + return this.addSubtitles(new Content(content)); + } + + public Notification addSubtitle(final MuttleyLanguage language, final String content) { + return this.addSubtitles(new Content(language, content)); + } + + public Notification addPlayers(final Collection players) { + if (!CollectionUtils.isEmpty(players)) { + this.players.addAll(players.parallelStream().filter(it -> !isEmpty(it)).collect(Collectors.toSet())); + } + return this; + } + + public Notification addPlayers(final String... player) { + return this.addPlayers(asList(player)); + } + + + public Notification addPlayers(final UserTokensNotification userTokensNotification) { + if (userTokensNotification != null) { + return this.addPlayers(userTokensNotification.getTokens()); + } + return this; + } + + public Notification addPlayers(final Set tokens) { + if (tokens != null) { + return this.addPlayers(tokens.parallelStream().map(TokenId::getToken).collect(Collectors.toSet())); + } + return this; + } + + @Override + public Notification clone() { + try { + return (Notification) super.clone(); + } catch (final CloneNotSupportedException ex) { + throw new MuttleyBadRequestException(ex); + } + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/NotificationData.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/NotificationData.java new file mode 100644 index 00000000..e040c570 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/NotificationData.java @@ -0,0 +1,16 @@ +package br.com.muttley.model.hermes.notification.onesignal; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.NotBlank; + +@Getter +@Setter +@Accessors(chain = true) +public class NotificationData { + @NotBlank + private String type; + private Object payload; + private NotificationEventType notificationEventType; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/NotificationEventType.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/NotificationEventType.java new file mode 100644 index 00000000..1898b63d --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/NotificationEventType.java @@ -0,0 +1,17 @@ +package br.com.muttley.model.hermes.notification.onesignal; + +import br.com.muttley.model.hermes.notification.onesignal.jackson.NotificationEnventTypeSerializer; +import br.com.muttley.model.hermes.notification.onesignal.jackson.NotificationEnventTypedeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +/** + * @author Joel Rodrigues Moreira 28/09/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@JsonSerialize(using = NotificationEnventTypeSerializer.class) +@JsonDeserialize(using = NotificationEnventTypedeserializer.class) +public interface NotificationEventType { + String getType(); +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/NotificationEventTypeAvaliable.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/NotificationEventTypeAvaliable.java new file mode 100644 index 00000000..d08ab202 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/NotificationEventTypeAvaliable.java @@ -0,0 +1,45 @@ +package br.com.muttley.model.hermes.notification.onesignal; + +import br.com.muttley.model.hermes.notification.onesignal.jackson.NotificationEnventTypeSerializer; +import br.com.muttley.model.hermes.notification.onesignal.jackson.NotificationEnventTypedeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import java.util.stream.Stream; + +import static org.springframework.util.StringUtils.isEmpty; + +/** + * @author Joel Rodrigues Moreira 28/09/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@JsonSerialize(using = NotificationEnventTypeSerializer.class) +@JsonDeserialize(using = NotificationEnventTypedeserializer.class) +public enum NotificationEventTypeAvaliable implements NotificationEventType { + CREATE, + READ, + UPDATE, + DELETE; + + public static NotificationEventType getNotificationEventType(final String event) { + if (isEmpty(event)) { + return null; + } + return Stream.of(NotificationEventTypeAvaliable.values()) + .parallel() + .filter(n -> n.name().equalsIgnoreCase(event)) + .findFirst() + .orElse(null); + } + + @Override + public String getType() { + return this.name(); + } + + @Override + public String toString() { + return this.getType(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/events/NotificationEvent.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/events/NotificationEvent.java new file mode 100644 index 00000000..2b23f8d6 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/events/NotificationEvent.java @@ -0,0 +1,15 @@ +package br.com.muttley.model.hermes.notification.onesignal.events; + +import br.com.muttley.model.hermes.notification.onesignal.Notification; +import org.springframework.context.ApplicationEvent; + +public class NotificationEvent extends ApplicationEvent { + public NotificationEvent(final Notification notification) { + super(notification); + } + + @Override + public Notification getSource() { + return (Notification) super.getSource(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/jackson/CollectionContentSerialize.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/jackson/CollectionContentSerialize.java new file mode 100644 index 00000000..40b52fc4 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/jackson/CollectionContentSerialize.java @@ -0,0 +1,24 @@ +package br.com.muttley.model.hermes.notification.onesignal.jackson; + +import br.com.muttley.model.hermes.notification.onesignal.Content; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.springframework.util.CollectionUtils; + +import java.io.IOException; +import java.util.Collection; + +public class CollectionContentSerialize extends JsonSerializer> { + @Override + public void serialize(final Collection contents, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + if (!CollectionUtils.isEmpty(contents)) { + for (Content content : contents) { + gen.writeStringField(content.getLanguage().getOneSignalValue(), content.getContent()); + } + } + gen.writeEndObject(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/jackson/NotificationEnventTypeSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/jackson/NotificationEnventTypeSerializer.java new file mode 100644 index 00000000..0fa4891a --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/jackson/NotificationEnventTypeSerializer.java @@ -0,0 +1,25 @@ +package br.com.muttley.model.hermes.notification.onesignal.jackson; + +import br.com.muttley.model.hermes.notification.onesignal.NotificationEventType; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira 28/09/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class NotificationEnventTypeSerializer extends JsonSerializer { + @Override + public void serialize(final NotificationEventType notificationEventType, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + if (notificationEventType == null) { + gen.writeNull(); + } else { + gen.writeString(notificationEventType.getType()); + } + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/jackson/NotificationEnventTypedeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/jackson/NotificationEnventTypedeserializer.java new file mode 100644 index 00000000..3dbdfc64 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/onesignal/jackson/NotificationEnventTypedeserializer.java @@ -0,0 +1,25 @@ +package br.com.muttley.model.hermes.notification.onesignal.jackson; + +import br.com.muttley.model.hermes.notification.onesignal.NotificationEventType; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; + +import static br.com.muttley.model.hermes.notification.onesignal.NotificationEventTypeAvaliable.getNotificationEventType; + +/** + * @author Joel Rodrigues Moreira 28/09/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class NotificationEnventTypedeserializer extends JsonDeserializer { + @Override + public NotificationEventType deserialize(final JsonParser parser, final DeserializationContext ctxt) throws IOException, JsonProcessingException { + final JsonNode node = parser.getCodec().readTree(parser); + return getNotificationEventType(node.asText()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/twilio/SMSPayload.java b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/twilio/SMSPayload.java new file mode 100644 index 00000000..877b71b2 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/hermes/notification/twilio/SMSPayload.java @@ -0,0 +1,45 @@ +package br.com.muttley.model.hermes.notification.twilio; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * @author Joel Rodrigues Moreira on 26/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class SMSPayload { + @JsonProperty("To") + private String to; + @JsonProperty("Body") + private String body; + @JsonProperty("MessagingServiceSid") + private String serviceSid; + + public SMSPayload setTo(String to) { + if (to != null) { + this.to = to.replaceAll("\\D+", ""); + switch (this.to.length()) { + //se tem 13 caractéres logo, devemos add somente o + no inicio + case 13: + this.to = "+" + this.to; + break; + //se tem 111 caractéres logo, devemos add somente o +55 no inicio + case 11: + this.to = "+55" + this.to; + break; + default: + + } + + } else { + this.to = to; + } + return this; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/CollectionModelSyncDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/CollectionModelSyncDeserializer.java new file mode 100644 index 00000000..e2003000 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/CollectionModelSyncDeserializer.java @@ -0,0 +1,76 @@ +package br.com.muttley.model.jackson.converter; + +import br.com.muttley.model.ModelSync; +import br.com.muttley.model.jackson.converter.event.ModelSyncResolverEvent; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +/** + * @author Joel Rodrigues Moreira on 06/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class CollectionModelSyncDeserializer> extends JsonDeserializer { + @Autowired + protected ApplicationEventPublisher eventPublisher; + + + @Override + public C deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + + final List idsOrSync = node.traverse(parser.getCodec()).readValueAs(new TypeReference>() { + }); + + final List deserializedItens = new ArrayList<>(); + //Vamos verificar se o deserializer está no contexto do spring e que o mesmo conseguiu injetar o eventPublisher + if (eventPublisher != null) { + + for (final String idOrSync : idsOrSync) { + //criando evento + final ModelSyncResolverEvent event = this.createEventResolver(idOrSync); + //disparando para alguem ouvir esse evento + this.eventPublisher.publishEvent(event); + //adicionando valor recuperado + deserializedItens.add(event.isResolved() ? event.getValueResolved() : this.newInstance(event.getSource())); + } + return cast(deserializedItens); + } else { + /**provavelmente o deserializer está sendo usado fora do contexto do spring + *ou seja, devemos apenas injetar o ID e nada mais + */ + return node.isNull() ? null : cast(idsOrSync.stream().map(it -> newInstance(it)).collect(toList())); + } + } + + /** + * Deve retornar uma instancia de {@link ModelSyncResolverEvent} + * + * @param idOrSync -> id ou sync do documento + */ + protected abstract ModelSyncResolverEvent createEventResolver(final String idOrSync); + + /** + * Talvez um determinado serviço não tenha um listener para resolver essa dependencia. + * Caso isso ocorra, simplismente devolvemos uma nova instancia com apenas o id ou sync preenchido + */ + protected abstract T newInstance(final String idOrSync); + + protected abstract C cast(final List itens); +} + diff --git a/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/CollectionModelSyncSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/CollectionModelSyncSerializer.java new file mode 100644 index 00000000..a0f2b35c --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/CollectionModelSyncSerializer.java @@ -0,0 +1,60 @@ +package br.com.muttley.model.jackson.converter; + +import br.com.muttley.model.ModelSync; +import br.com.muttley.model.SerializeType; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.CollectionUtils; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; + +/** + * @author Joel Rodrigues Moreira on 06/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class CollectionModelSyncSerializer extends JsonSerializer> { + + @Autowired + private HttpServletRequest request; + + @Override + public void serialize(final Collection collection, final JsonGenerator gen, final SerializerProvider serializerProvider) throws IOException, JsonProcessingException { + if (CollectionUtils.isEmpty(collection)) { + gen.writeNull(); + } else { + final Iterator collectionIterator = collection.iterator(); + final SerializeType serializerType = SerializeType.Builder.build(this.request); + gen.writeStartArray(); + + if (serializerType.isInternal() || serializerType.isObjectId()) { + while (collectionIterator.hasNext()) { + gen.writeString(collectionIterator.next().getId()); + } + } else if (serializerType.isSync()) { + while (collectionIterator.hasNext()) { + gen.writeString(collectionIterator.next().getSync()); + } + } else if (serializerType.isObjectIdAndSync()) { + while (collectionIterator.hasNext()) { + final ModelSync modelSync = collectionIterator.next(); + gen.writeStartObject(); + gen.writeStringField("id", modelSync.getId().toString()); + gen.writeStringField("sync", modelSync.getSync()); + gen.writeEndObject(); + } + } else { + while (collectionIterator.hasNext()) { + gen.writeString(collectionIterator.next().getId()); + } + } + gen.writeEndArray(); + } + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/ListDocumentSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/ListDocumentSerializer.java new file mode 100644 index 00000000..1686eccd --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/ListDocumentSerializer.java @@ -0,0 +1,33 @@ +package br.com.muttley.model.jackson.converter; + +import br.com.muttley.model.Document; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.util.Collection; + +/** + * @author Joel Rodrigues Moreira on 29/03/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ListDocumentSerializer extends JsonSerializer> { + @Override + public void serialize(final Collection documents, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + if (documents != null) { + gen.writeStartArray(); + for (final Document doc : documents) { + gen.writeString(doc.getId()); + } + gen.writeEndArray(); + } else { + gen.writeNull(); + } + + } +} + + diff --git a/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/LocalDateTimeDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/LocalDateTimeDeserializer.java new file mode 100644 index 00000000..094e0229 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/LocalDateTimeDeserializer.java @@ -0,0 +1,70 @@ +package br.com.muttley.model.jackson.converter; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.model.jackson.DefaultDateFormatConfig; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +/** + * @author Joel Rodrigues Moreira on 09/08/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class LocalDateTimeDeserializer extends JsonDeserializer { + public static final DateTimeFormatter dateTimeFormatterDefault = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + private static final DateTimeFormatter dateTimeFormatterOp1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"); + private static final DateTimeFormatter dateTimeFormatterOp2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"); + private static final DateTimeFormatter dateTimeFormatterOp3 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + @Override + public LocalDateTime deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + final String value = node.asText(); + try { + switch (value.length()) { + case 28: + return ZonedDateTime.parse(value, dateTimeFormatterOp3).toLocalDateTime(); + case 21: + return LocalDateTime.parse(value, dateTimeFormatterOp2); + case 22: + return LocalDateTime.parse(value, dateTimeFormatterOp1); + case 23: + return LocalDateTime.parse(value, dateTimeFormatterOp1); + default: + return this.throwsExcpetion(parser); + } + } catch (RuntimeException e) { + return this.throwsExcpetion(parser); + + } + } + + private LocalDateTime throwsExcpetion(final JsonParser parser) throws IOException { + throw new MuttleyBadRequestException(null, parser.getCurrentName(), "Informe uma data válida ") + .addDetails("exemplo", + DefaultDateFormatConfig.createDateOnly().format( + Date.from( + LocalDateTime + .ofInstant(new Date().toInstant(), ZoneId.systemDefault()) + .withHour(0) + .withMinute(0) + .withSecond(0) + .withNano(0) + .atZone(ZoneId.systemDefault()) + .toInstant() + ) + )); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/LocalDateTimeSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/LocalDateTimeSerializer.java new file mode 100644 index 00000000..ab56a998 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/LocalDateTimeSerializer.java @@ -0,0 +1,22 @@ +package br.com.muttley.model.jackson.converter; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalDateTime; + +/** + * @author Joel Rodrigues Moreira on 09/08/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class LocalDateTimeSerializer extends JsonSerializer { + + @Override + public void serialize(LocalDateTime value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException, JsonProcessingException { + jsonGenerator.writeString(value != null ? value.format(LocalDateTimeDeserializer.dateTimeFormatterDefault) : null); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/ModelSyncDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/ModelSyncDeserializer.java new file mode 100644 index 00000000..34ccd856 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/ModelSyncDeserializer.java @@ -0,0 +1,55 @@ +package br.com.muttley.model.jackson.converter; + +import br.com.muttley.model.ModelSync; +import br.com.muttley.model.jackson.converter.event.ModelSyncResolverEvent; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class ModelSyncDeserializer extends DocumentDeserializer { + + @Override + public T deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + //se alguem enviou um objeto completo devemos tentar deserializar o mesmo! + /*if (node.isObject()) { + return this.mapper.readValue(parser, clazz); + }*/ + //Vamos verificar se o deserializer está no contexto do spring e que o mesmo conseguiu injetar o eventPublisher + if (eventPublisher != null) { + //criando evento + final ModelSyncResolverEvent event = this.createEventResolver(node.asText()); + //disparando para alguem ouvir esse evento + this.eventPublisher.publishEvent(event); + //retornando valor recuperado + return event.isResolved() ? event.getValueResolved() : this.newInstance(event.getSource()); + } + /**provavelmente o deserializer está sendo usado fora do contexto do spring + *ou seja, devemos apenas injetar o ID e nada mais + */ + return node.isNull() ? null : this.newInstance(node.asText()); + } + + /** + * Deve retornar uma instancia de {@link ModelSyncResolverEvent} + * + * @param idOrSync -> id ou sync do documento + */ + protected abstract ModelSyncResolverEvent createEventResolver(final String idOrSync); + + /** + * Talvez um determinado serviço não tenha um listener para resolver essa dependencia. + * Caso isso ocorra, simplismente devolvemos uma nova instancia com apenas o id ou sync preenchido + */ + protected abstract T newInstance(final String idOrSync); +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/ModelSyncSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/ModelSyncSerializer.java new file mode 100644 index 00000000..6090d3ed --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/ModelSyncSerializer.java @@ -0,0 +1,56 @@ +package br.com.muttley.model.jackson.converter; + +import br.com.muttley.model.ModelSync; +import br.com.muttley.model.SerializeType; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +import static br.com.muttley.model.SerializeType.Builder.build; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ModelSyncSerializer extends JsonSerializer { + //se no cabeçalho da requição tiver esse informação como true + //devemos então serializar somente o sync + /*private static final String SERIALIZER_TYPE = "SerializeType"; + //valor epserado para configuração do SERIALIZER_INFO + private static final String SYNC_TYPE = "sync"; + private static final String OBJECT_ID_TYPE = "ObjectId"; + private static final String OBJECT_ID_AND_SYNC_TYPE = "ObjectIdAndSync";*/ + + @Autowired + protected HttpServletRequest request; + + @Override + public void serialize(final T value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + if (value == null) { + gen.writeNull(); + } else { + final SerializeType serializerType = build(this.request); + if (serializerType.isInternal()) { + gen.writeString(value.getId()); + } else if (serializerType.isSync()) { + gen.writeString(value.getSync()); + } else if (serializerType.isObjectId()) { + gen.writeString(value.getId()); + } else if (serializerType.isObjectIdAndSync()) { + gen.writeStartObject(); + gen.writeStringField("id", value.getId()); + gen.writeStringField("sync", value.getSync()); + gen.writeEndObject(); + } else { + gen.writeString(value.getId()); + } + + } + } +} \ No newline at end of file diff --git a/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/event/ModelSyncResolverEvent.java b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/event/ModelSyncResolverEvent.java new file mode 100644 index 00000000..ceea7437 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/jackson/converter/event/ModelSyncResolverEvent.java @@ -0,0 +1,19 @@ +package br.com.muttley.model.jackson.converter.event; + +import br.com.muttley.model.ModelSync; + +/** + * @author Joel Rodrigues Moreira on 06/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class ModelSyncResolverEvent extends ModelResolverEvent { + public ModelSyncResolverEvent(String syncOrId) { + super(syncOrId); + } + + public ModelSyncResolverEvent setValueResolved(final T valueResolved) { + super.setValueResolved(valueResolved); + return this; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/jackson/util/NodeUtils.java b/muttley-model/src/main/java/br/com/muttley/model/jackson/util/NodeUtils.java new file mode 100644 index 00000000..63fe6dc3 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/jackson/util/NodeUtils.java @@ -0,0 +1,35 @@ +package br.com.muttley.model.jackson.util; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira 22/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class NodeUtils { + public static String readAsText(final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return node.asText(); + } + + public static String readAsText(final String fieldName, final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return readAsText(node.get(fieldName)); + } + + public static T readNodeAsType(final JsonNode node, final JsonParser parser, TypeReference typeReference) throws IOException { + if (node == null || node.isNull()) { + return null; + } + return node.traverse(parser.getCodec()).readValueAs(typeReference); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonLineString.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonLineString.java new file mode 100644 index 00000000..4a188c4d --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonLineString.java @@ -0,0 +1,50 @@ +package br.com.muttley.model.mercator; + +import br.com.muttley.model.mercator.jackson.GeoJsonDeserialize; +import br.com.muttley.model.mercator.jackson.GeoJsonLineStringSerializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import java.util.Arrays; +import java.util.List; + +import static br.com.muttley.model.mercator.TypeGeoJson.LINESTRING; + + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@JsonSerialize(using = GeoJsonLineStringSerializer.class) +@JsonDeserialize(using = GeoJsonDeserialize.class) +public class GeoJsonLineString extends GeoJsonMultiPoint { + private static final TypeGeoJson TYPE = LINESTRING; + + public GeoJsonLineString() { + super(); + } + + public GeoJsonLineString(final List points) { + super(points); + } + + public GeoJsonLineString(final Point first, final Point second, final Point... others) { + super(first, second, others); + } + + public GeoJsonLineString add(final Point... point) { + super.coordinates.addAll(Arrays.asList(point)); + return this; + } + + public GeoJsonLineString add(final List points) { + super.coordinates.addAll(points); + return this; + } + + @Override + public String getType() { + return TYPE.getType(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonMultiLineString.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonMultiLineString.java new file mode 100644 index 00000000..97dae082 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonMultiLineString.java @@ -0,0 +1,46 @@ +package br.com.muttley.model.mercator; + +import br.com.muttley.model.mercator.jackson.GeoJsonDeserialize; +import br.com.muttley.model.mercator.jackson.GeoJsonMultiLineStringSerializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.springframework.data.mongodb.core.geo.GeoJson; + +import java.util.ArrayList; +import java.util.List; + +import static br.com.muttley.model.mercator.TypeGeoJson.MULTILINESTRING; +import static java.util.Collections.unmodifiableList; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@JsonSerialize(using = GeoJsonMultiLineStringSerializer.class) +@JsonDeserialize(using = GeoJsonDeserialize.class) +public class GeoJsonMultiLineString implements GeoJson> { + private static final TypeGeoJson TYPE = MULTILINESTRING; + + protected List coordinates = new ArrayList<>(); + + public GeoJsonMultiLineString(List... lines) { + for (List line : lines) { + this.coordinates.add(new GeoJsonLineString(line)); + } + } + + public GeoJsonMultiLineString(List lines) { + this.coordinates.addAll(lines); + } + + @Override + public String getType() { + return TYPE.getType(); + } + + @Override + public Iterable getCoordinates() { + return unmodifiableList(this.coordinates); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonMultiPoint.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonMultiPoint.java new file mode 100644 index 00000000..f5eee34c --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonMultiPoint.java @@ -0,0 +1,83 @@ +package br.com.muttley.model.mercator; + + +import br.com.muttley.model.mercator.jackson.GeoJsonDeserialize; +import br.com.muttley.model.mercator.jackson.GeoJsonMultiPointSerializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.mongodb.core.geo.GeoJson; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static br.com.muttley.model.mercator.TypeGeoJson.MULTIPOINT; +import static java.util.Collections.unmodifiableList; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +@JsonSerialize(using = GeoJsonMultiPointSerializer.class) +@JsonDeserialize(using = GeoJsonDeserialize.class) +public class GeoJsonMultiPoint implements GeoJson> { + private static final TypeGeoJson TYPE = MULTIPOINT; + + protected final List coordinates; + + public GeoJsonMultiPoint() { + this.coordinates = new ArrayList<>(); + } + + public GeoJsonMultiPoint(final List coordinates) { + this.coordinates = coordinates; + } + + public GeoJsonMultiPoint(final Point first, final Point second, final Point... others) { + this.coordinates = new ArrayList<>(); + this.coordinates.add(first); + this.coordinates.add(second); + this.coordinates.addAll(Arrays.asList(others)); + } + + public GeoJsonMultiPoint add(final Point... point) { + this.coordinates.addAll(Arrays.asList(point)); + return this; + } + + public GeoJsonMultiPoint add(final Collection points) { + this.coordinates.addAll(points); + return this; + } + + public List getPoints() { + return unmodifiableList(this.coordinates); + } + + @Override + public String getType() { + return TYPE.getType(); + } + + @Override + public Iterable getCoordinates() { + return unmodifiableList(this.coordinates); + } + + public boolean isEmpty() { + return CollectionUtils.isEmpty(this.coordinates); + } + + public Point getCoordinateByIndex(final Long index) { + return this.coordinates.stream().filter(it -> it.getIndex() == index).findFirst().orElse(null); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonMultiPolygon.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonMultiPolygon.java new file mode 100644 index 00000000..e44565ed --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonMultiPolygon.java @@ -0,0 +1,45 @@ +package br.com.muttley.model.mercator; + + +import br.com.muttley.model.mercator.jackson.GeoJsonDeserialize; +import br.com.muttley.model.mercator.jackson.GeoJsonMultiPolygonSerializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.springframework.data.mongodb.core.geo.GeoJson; + +import java.util.ArrayList; +import java.util.List; + +import static br.com.muttley.model.mercator.TypeGeoJson.MULTIPOLYGON; +import static java.util.Collections.unmodifiableList; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@EqualsAndHashCode(of = "coordinates") +@JsonSerialize(using = GeoJsonMultiPolygonSerializer.class) +@JsonDeserialize(using = GeoJsonDeserialize.class) +public class GeoJsonMultiPolygon implements GeoJson> { + private static final TypeGeoJson TYPE = MULTIPOLYGON; + + protected List coordinates = new ArrayList<>(); + + public GeoJsonMultiPolygon(List polygons) { + this.coordinates.addAll(polygons); + } + + @Override + public String getType() { + return TYPE.getType(); + } + + @Override + public Iterable getCoordinates() { + return unmodifiableList(this.coordinates); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonPoint.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonPoint.java new file mode 100644 index 00000000..df264fc7 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonPoint.java @@ -0,0 +1,61 @@ +package br.com.muttley.model.mercator; + +import br.com.muttley.model.mercator.jackson.GeoJsonDeserialize; +import br.com.muttley.model.mercator.jackson.GeoJsonPointSerializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.mongodb.core.geo.GeoJson; + +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.List; + +import static br.com.muttley.model.mercator.TypeGeoJson.POINT; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ + +@Getter +@Setter +@Accessors(chain = true) +@JsonSerialize(using = GeoJsonPointSerializer.class) +@JsonDeserialize(using = GeoJsonDeserialize.class) +public class GeoJsonPoint extends Point implements GeoJson> { + private static final TypeGeoJson TYPE = POINT; + + public GeoJsonPoint(final double longitude, final double latitude, final double elevation, final ZonedDateTime timeStamp) { + super(longitude, latitude, elevation, timeStamp); + } + + public GeoJsonPoint(final double longitude, final double latitude, final double elevation) { + super(longitude, latitude, elevation); + } + + public GeoJsonPoint(final double longitude, final double latitude) { + super(longitude, latitude); + } + + public GeoJsonPoint(final Point point) { + super(point); + } + + @Override + public String getType() { + return TYPE.getType(); + } + + @Override + public List getCoordinates() { + return Arrays.asList(Double.valueOf(getLongitude()), Double.valueOf(getLatitude())); + } + + public Point toPoit() { + return new Point(this.getLongitude(), this.getLatitude(), this.getElevation(), this.getTimeStamp()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonPolygon.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonPolygon.java new file mode 100644 index 00000000..621e6c62 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/GeoJsonPolygon.java @@ -0,0 +1,48 @@ +package br.com.muttley.model.mercator; + + +import br.com.muttley.model.mercator.jackson.GeoJsonDeserialize; +import br.com.muttley.model.mercator.jackson.GeoJsonPolygonSerializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.springframework.data.mongodb.core.geo.GeoJson; + +import java.util.ArrayList; +import java.util.List; + +import static br.com.muttley.model.mercator.TypeGeoJson.POLYGON; +import static java.util.Collections.unmodifiableList; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@JsonSerialize(using = GeoJsonPolygonSerializer.class) +@JsonDeserialize(using = GeoJsonDeserialize.class) +public class GeoJsonPolygon implements GeoJson> { + private static final TypeGeoJson TYPE = POLYGON; + + protected final List coordinates = new ArrayList(); + + public GeoJsonPolygon() { + } + + public GeoJsonPolygon(final Point first, final Point second, final Point third, final Point fourth, final Point... others) { + this.coordinates.add(new GeoJsonLineString(first, second, third, fourth).add(others)); + } + + public GeoJsonPolygon(final List co) { + this.coordinates.addAll(co); + } + + @Override + public String getType() { + return TYPE.getType(); + } + + @Override + public List getCoordinates() { + return unmodifiableList(this.coordinates); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/Point.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/Point.java new file mode 100644 index 00000000..7d4e1ee2 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/Point.java @@ -0,0 +1,105 @@ +package br.com.muttley.model.mercator; + +import br.com.muttley.model.mercator.jackson.PointDeserializer; +import br.com.muttley.model.mercator.jackson.PointSerializer; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Transient; + +import javax.validation.constraints.NotNull; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import static br.com.muttley.utils.TimeZoneUtils.getTimezoneFromId; +import static lombok.AccessLevel.NONE; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +@JsonSerialize(using = PointSerializer.class) +@JsonDeserialize(using = PointDeserializer.class) +public class Point implements Comparable { + @NotNull(message = "informe um valor válido") + protected Double longitude; + @NotNull(message = "informe um valor válido") + protected Double latitude; + protected Double elevation; + protected ZonedDateTime timeStamp; + @Setter(NONE) + protected Long index;//variavel para controle de indice + @JsonIgnore + @Transient + @Setter(NONE) + private String dateAsString;//variavel para controle interno + + public Point(final Double longitude, final Double latitude, final Double elevation, final ZonedDateTime timeStamp, final Long index) { + this.longitude = longitude; + this.latitude = latitude; + this.elevation = elevation; + this.timeStamp = timeStamp; + this.index = index; + this.setDateAsString(this.getTimeStamp()); + } + + public Point(final Double longitude, final Double latitude, final Double elevation, final ZonedDateTime timeStamp) { + this(longitude, latitude, elevation, timeStamp, null); + } + + public Point(final Double longitude, final Double latitude, final Double elevation) { + this(longitude, latitude, elevation, null); + } + + public Point(final Double longitude, final Double latitude) { + this(longitude, latitude, null); + } + + public Point(final Point point) { + if (point != null) { + this.longitude = point.getLongitude(); + this.latitude = point.getLatitude(); + this.elevation = point.getElevation(); + this.timeStamp = point.getTimeStamp(); + this.index = point.getIndex(); + this.setDateAsString(point.getTimeStamp()); + } else { + this.setDateAsString(null); + } + + } + + private Point setDateAsString(final ZonedDateTime date) { + this.dateAsString = date == null ? "" : date.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + return this; + } + + public Object[] toArray() { + return new Object[]{this.longitude, this.latitude, this.elevation, this.getTimeStamp()}; + } + + @JsonIgnore + public String getZoneFromtimeStamp() { + if (this.timeStamp == null) { + return null; + } + return getTimezoneFromId(this.timeStamp.getOffset().getId()); + } + + @JsonIgnore + public boolean containsTimeStamp() { + return this.timeStamp != null; + } + + @Override + public int compareTo(final Point point) { + return this.getTimeStamp().compareTo(point.getTimeStamp()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/TypeGeoJson.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/TypeGeoJson.java new file mode 100644 index 00000000..8f9c1310 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/TypeGeoJson.java @@ -0,0 +1,39 @@ +package br.com.muttley.model.mercator; + +import java.util.stream.Stream; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public enum TypeGeoJson { + POINT("Point"), + MULTIPOINT("MultiPoint"), + LINESTRING("LineString"), + MULTILINESTRING("MultiLineString"), + POLYGON("Polygon"), + MULTIPOLYGON("MultiPolygon"); + + private final String type; + + TypeGeoJson(final String type) { + this.type = type; + } + + public static TypeGeoJson of(final String value) { + return Stream.of(TypeGeoJson.values()) + .filter(it -> it.getType().equals(value) || it.name().equals(value)) + .findFirst() + .orElse(null); + } + + public String getType() { + return type; + } + + @Override + public String toString() { + return getType(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonDeserialize.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonDeserialize.java new file mode 100644 index 00000000..4e8fb4ae --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonDeserialize.java @@ -0,0 +1,60 @@ +package br.com.muttley.model.mercator.jackson; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.model.mercator.GeoJsonPoint; +import br.com.muttley.model.mercator.TypeGeoJson; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.data.mongodb.core.geo.GeoJson; + +import java.io.IOException; + +import static br.com.muttley.model.mercator.jackson.GeoJsonLineStringDeserializer.defaultGeoJsonLineString; +import static br.com.muttley.model.mercator.jackson.GeoJsonMultiLineStringDeserializer.defaultGeoJonMultiLineStringDeserializer; +import static br.com.muttley.model.mercator.jackson.GeoJsonMultiPointDeserializer.defaultGeoJsonMultiPointDeserializer; +import static br.com.muttley.model.mercator.jackson.GeoJsonMultiPolygonDeserializer.defaulGeoJsonMultiPolygonDeserializer; +import static br.com.muttley.model.mercator.jackson.GeoJsonPointDeserialize.defaultGeoJsonPointDeserialize; +import static br.com.muttley.model.mercator.jackson.GeoJsonPolygonDeserializer.defaultGeoJsonPolygon; +import static br.com.muttley.model.mercator.jackson.PointDeserializer.defaultPointDeserializer; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonDeserialize extends JsonDeserializer { + @Override + public Object deserialize(final JsonParser parser, final DeserializationContext ctxt) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + final JsonNode typeNode = node.get("type"); + if (typeNode == null || typeNode.isNull()) { + if (node.isArray()) { + return new GeoJsonPoint(defaultPointDeserializer(parser, node)); + } + throw new MuttleyBadRequestException(GeoJson.class, "type", "Informe um type válido"); + } + + final TypeGeoJson type = TypeGeoJson.of(typeNode.asText()); + + switch (type) { + case POINT: + return defaultGeoJsonPointDeserialize(parser, node.get("coordinates")); + case MULTIPOINT: + return defaultGeoJsonMultiPointDeserializer(parser, node.get("coordinates")); + case LINESTRING: + return defaultGeoJsonLineString(parser, node.get("coordinates")); + case MULTILINESTRING: + return defaultGeoJonMultiLineStringDeserializer(parser, node.get("coordinates")); + case POLYGON: + return defaultGeoJsonPolygon(parser, node.get("coordinates")); + case MULTIPOLYGON: + return defaulGeoJsonMultiPolygonDeserializer(parser, node.get("coordinates")); + } + return null; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonLineStringDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonLineStringDeserializer.java new file mode 100644 index 00000000..cf6a371a --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonLineStringDeserializer.java @@ -0,0 +1,26 @@ +package br.com.muttley.model.mercator.jackson; + + +import br.com.muttley.model.mercator.GeoJsonLineString; +import br.com.muttley.model.mercator.Point; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonLineStringDeserializer { + protected static GeoJsonLineString defaultGeoJsonLineString(final JsonParser parser, final JsonNode node) throws IOException, JsonProcessingException { + return new GeoJsonLineString( + node.traverse(parser.getCodec()).readValueAs(new TypeReference>() { + }) + ); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonLineStringSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonLineStringSerializer.java new file mode 100644 index 00000000..48d1893a --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonLineStringSerializer.java @@ -0,0 +1,29 @@ +package br.com.muttley.model.mercator.jackson; + +import br.com.muttley.model.mercator.GeoJsonLineString; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonLineStringSerializer extends JsonSerializer { + @Override + public void serialize(final GeoJsonLineString geoJsonLineString, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + + gen.writeStringField("type", geoJsonLineString.getType()); + gen.writeFieldName("coordinates"); + + gen.writeObject(geoJsonLineString.getCoordinates()); + + + gen.writeEndObject(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiLineStringDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiLineStringDeserializer.java new file mode 100644 index 00000000..22bfb05f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiLineStringDeserializer.java @@ -0,0 +1,26 @@ +package br.com.muttley.model.mercator.jackson; + + +import br.com.muttley.model.mercator.GeoJsonLineString; +import br.com.muttley.model.mercator.GeoJsonMultiLineString; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonMultiLineStringDeserializer { + protected static GeoJsonMultiLineString defaultGeoJonMultiLineStringDeserializer(final JsonParser parser, final JsonNode node) throws IOException, JsonProcessingException { + return new GeoJsonMultiLineString( + (List) node.traverse(parser.getCodec()).readValueAs(new TypeReference>() { + }) + ); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiLineStringSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiLineStringSerializer.java new file mode 100644 index 00000000..6be91205 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiLineStringSerializer.java @@ -0,0 +1,33 @@ +package br.com.muttley.model.mercator.jackson; + +import br.com.muttley.model.mercator.GeoJsonMultiLineString; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonMultiLineStringSerializer extends JsonSerializer { + @Override + public void serialize(final GeoJsonMultiLineString geoJsonMultiLineString, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + + gen.writeStringField("type", geoJsonMultiLineString.getType()); + gen.writeFieldName("coordinates"); + + gen.writeObject(geoJsonMultiLineString.getCoordinates()); + + /*gen.writeStartArray(); + + gen.writeob + defaultPointSerializer(geoJsonMultiPoint.get'', gen); + gen.writeEndArray();*/ + gen.writeEndObject(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPointDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPointDeserializer.java new file mode 100644 index 00000000..cd4b1f6d --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPointDeserializer.java @@ -0,0 +1,24 @@ +package br.com.muttley.model.mercator.jackson; + + +import br.com.muttley.model.mercator.GeoJsonMultiPoint; +import br.com.muttley.model.mercator.Point; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonMultiPointDeserializer { + protected static GeoJsonMultiPoint defaultGeoJsonMultiPointDeserializer(final JsonParser parser, final JsonNode node) throws IOException, JsonProcessingException { + return new GeoJsonMultiPoint(node.traverse(parser.getCodec()).readValueAs(new TypeReference>() { + })); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPointSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPointSerializer.java new file mode 100644 index 00000000..932a7874 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPointSerializer.java @@ -0,0 +1,33 @@ +package br.com.muttley.model.mercator.jackson; + +import br.com.muttley.model.mercator.GeoJsonMultiPoint; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonMultiPointSerializer extends JsonSerializer { + @Override + public void serialize(final GeoJsonMultiPoint geoJsonMultiPoint, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + + gen.writeStringField("type", geoJsonMultiPoint.getType()); + gen.writeFieldName("coordinates"); + + gen.writeObject(geoJsonMultiPoint.getCoordinates()); + + /*gen.writeStartArray(); + + gen.writeob + defaultPointSerializer(geoJsonMultiPoint.get'', gen); + gen.writeEndArray();*/ + gen.writeEndObject(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPolygonDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPolygonDeserializer.java new file mode 100644 index 00000000..28f282f5 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPolygonDeserializer.java @@ -0,0 +1,26 @@ +package br.com.muttley.model.mercator.jackson; + + +import br.com.muttley.model.mercator.GeoJsonMultiPolygon; +import br.com.muttley.model.mercator.GeoJsonPolygon; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonMultiPolygonDeserializer { + protected static GeoJsonMultiPolygon defaulGeoJsonMultiPolygonDeserializer(final JsonParser parser, final JsonNode node) throws IOException, JsonProcessingException { + return new GeoJsonMultiPolygon( + node.traverse(parser.getCodec()).readValueAs(new TypeReference>() { + }) + ); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPolygonSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPolygonSerializer.java new file mode 100644 index 00000000..81dbc74f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonMultiPolygonSerializer.java @@ -0,0 +1,33 @@ +package br.com.muttley.model.mercator.jackson; + +import br.com.muttley.model.mercator.GeoJsonMultiPolygon; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonMultiPolygonSerializer extends JsonSerializer { + @Override + public void serialize(final GeoJsonMultiPolygon geoJsonMultiPolygon, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + + gen.writeStringField("type", geoJsonMultiPolygon.getType()); + gen.writeFieldName("coordinates"); + + gen.writeObject(geoJsonMultiPolygon.getCoordinates()); + + /*gen.writeStartArray(); + + gen.writeob + defaultPointSerializer(geoJsonMultiPoint.get'', gen); + gen.writeEndArray();*/ + gen.writeEndObject(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPointDeserialize.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPointDeserialize.java new file mode 100644 index 00000000..50d5c0cf --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPointDeserialize.java @@ -0,0 +1,24 @@ +package br.com.muttley.model.mercator.jackson; + +import br.com.muttley.model.mercator.GeoJsonPoint; +import br.com.muttley.model.mercator.Point; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonPointDeserialize { + protected static GeoJsonPoint defaultGeoJsonPointDeserialize(final JsonParser parser, final JsonNode node) throws IOException, JsonProcessingException { + final Point point = PointDeserializer.defaultPointDeserializer(parser, node); + if (point == null) { + return null; + } + return new GeoJsonPoint(point); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPointSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPointSerializer.java new file mode 100644 index 00000000..f0e63842 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPointSerializer.java @@ -0,0 +1,30 @@ +package br.com.muttley.model.mercator.jackson; + +import br.com.muttley.model.mercator.GeoJsonPoint; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +import static br.com.muttley.model.mercator.jackson.PointSerializer.defaultPointSerializer; + + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonPointSerializer extends JsonSerializer { + @Override + public void serialize(final GeoJsonPoint geoJsonPoint, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + + gen.writeStringField("type", geoJsonPoint.getType()); + gen.writeFieldName("coordinates"); + defaultPointSerializer(geoJsonPoint, gen); + + gen.writeEndObject(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPolygonDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPolygonDeserializer.java new file mode 100644 index 00000000..ea4ba075 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPolygonDeserializer.java @@ -0,0 +1,23 @@ +package br.com.muttley.model.mercator.jackson; + +import br.com.muttley.model.mercator.GeoJsonLineString; +import br.com.muttley.model.mercator.GeoJsonPolygon; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonPolygonDeserializer { + protected static GeoJsonPolygon defaultGeoJsonPolygon(final JsonParser parser, final JsonNode node) throws IOException, JsonProcessingException { + return new GeoJsonPolygon(node.traverse(parser.getCodec()).readValueAs(new TypeReference>() { + })); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPolygonSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPolygonSerializer.java new file mode 100644 index 00000000..12fbb77b --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/GeoJsonPolygonSerializer.java @@ -0,0 +1,29 @@ +package br.com.muttley.model.mercator.jackson; + +import br.com.muttley.model.mercator.GeoJsonPolygon; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class GeoJsonPolygonSerializer extends JsonSerializer { + @Override + public void serialize(final GeoJsonPolygon geoJsonPolygon, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + + gen.writeStringField("type", geoJsonPolygon.getType()); + gen.writeFieldName("coordinates"); + + gen.writeObject(geoJsonPolygon.getCoordinates()); + + + gen.writeEndObject(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/PointDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/PointDeserializer.java new file mode 100644 index 00000000..d949e8ad --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/PointDeserializer.java @@ -0,0 +1,61 @@ +package br.com.muttley.model.mercator.jackson; + +import br.com.muttley.model.mercator.Point; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.time.ZonedDateTime; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class PointDeserializer extends JsonDeserializer { + + protected static Point pointDeserializerByLatitudeAndLongitude(final JsonParser parser, final JsonNode node) throws IOException, JsonProcessingException { + + if (node.isNull() || ((!node.has("latitude") || !node.has("longitude")) || (node.get("latitude").isNull() || node.get("longitude").isNull()))) { + return null; + } else { + return new Point(node.get("longitude").asDouble(), node.get("latitude").asDouble()); + } + } + + protected static Point defaultPointDeserializer(final JsonParser parser, final JsonNode node) throws IOException, JsonProcessingException { + + if (node.isNull() || node.size() == 0) { + return null; + } + + final JsonNode jsonLongitude = node.get(0); + final JsonNode jsonLatitude = node.get(1); + + final Point point = new Point(jsonLongitude != null ? jsonLongitude.asDouble() : null, jsonLatitude != null ? jsonLatitude.asDouble() : null); + if (node.size() == 3) { + if (node.get(2).isNumber()) { + point.setElevation(node.get(2).asDouble()); + } else { + point.setTimeStamp(node.get(2).traverse(parser.getCodec()).readValueAs(ZonedDateTime.class)); + } + } else if (node.size() == 4) { + point.setElevation(node.get(2).asDouble()); + point.setTimeStamp(node.get(3).traverse(parser.getCodec()).readValueAs(ZonedDateTime.class)); + } + return point; + } + + @Override + public Point deserialize(final JsonParser parser, final DeserializationContext ctxt) throws IOException, JsonProcessingException { + final ObjectCodec objectCodec = parser.getCodec(); + + final JsonNode node = objectCodec.readTree(parser); + + return defaultPointDeserializer(parser, node); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/PointSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/PointSerializer.java new file mode 100644 index 00000000..68647acc --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/mercator/jackson/PointSerializer.java @@ -0,0 +1,39 @@ +package br.com.muttley.model.mercator.jackson; + +import br.com.muttley.model.mercator.Point; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class PointSerializer extends JsonSerializer { + + protected static void defaultPointSerializer(final Point point, final JsonGenerator gen) throws IOException { + gen.writeStartArray(); + gen.writeNumber(point.getLongitude()); + gen.writeNumber(point.getLatitude()); + if (point.getElevation() != null) { + gen.writeNumber(point.getElevation()); + } + if (point.getTimeStamp() != null) { + gen.writeObject(point.getTimeStamp()); + } + if (point.getIndex() != null) { + gen.writeNumber(point.getIndex()); + } + gen.writeEndArray(); + } + + @Override + public void serialize(final Point point, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + defaultPointSerializer(point, gen); + } + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/recoveryPassword/ResetPasswordRequest.java b/muttley-model/src/main/java/br/com/muttley/model/recoveryPassword/ResetPasswordRequest.java new file mode 100644 index 00000000..8fecf63d --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/recoveryPassword/ResetPasswordRequest.java @@ -0,0 +1,14 @@ +package br.com.muttley.model.recoveryPassword; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ResetPasswordRequest { + + private String token; + private String newPassword; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/recoveryPassword/UserForgotPasswordRequest.java b/muttley-model/src/main/java/br/com/muttley/model/recoveryPassword/UserForgotPasswordRequest.java new file mode 100644 index 00000000..e46dcad6 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/recoveryPassword/UserForgotPasswordRequest.java @@ -0,0 +1,15 @@ +package br.com.muttley.model.recoveryPassword; + +public class UserForgotPasswordRequest { + + private String email; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/AccessPlan.java b/muttley-model/src/main/java/br/com/muttley/model/security/AccessPlan.java index 21d4bdb6..1b426703 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/AccessPlan.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/AccessPlan.java @@ -1,7 +1,7 @@ package br.com.muttley.model.security; import br.com.muttley.model.Document; -import br.com.muttley.model.Historic; +import br.com.muttley.model.MetadataDocument; import org.hibernate.validator.constraints.NotBlank; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.CompoundIndex; @@ -23,7 +23,7 @@ public class AccessPlan implements Document { @Id private String id; - private Historic historic; + private MetadataDocument metadata; @NotBlank(message = "Informe um nome válido") private String name; @Min(value = 1, message = "É necessário ter ao menos 1 usuário!") @@ -42,13 +42,13 @@ public AccessPlan setId(final String id) { } @Override - public Historic getHistoric() { - return this.historic; + public MetadataDocument getMetadata() { + return metadata; } @Override - public AccessPlan setHistoric(Historic historic) { - this.historic = historic; + public AccessPlan setMetadata(final MetadataDocument metadata) { + this.metadata = metadata; return this; } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/AuthoritiesUtils.java b/muttley-model/src/main/java/br/com/muttley/model/security/AuthoritiesUtils.java new file mode 100644 index 00000000..a3618bd8 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/AuthoritiesUtils.java @@ -0,0 +1,31 @@ +package br.com.muttley.model.security; + + +import java.util.HashSet; +import java.util.Set; + +import static br.com.muttley.model.security.Role.ROLE_OWNER; +import static br.com.muttley.model.security.Role.ROLE_ROOT; +import static java.util.Arrays.asList; + +public class AuthoritiesUtils { + public static final Authority AUTHORITY_ROLE_OWNER = new AuthorityImpl(ROLE_OWNER, "Permissões de dono da base de dados"); + public static final Authority AUTHORITY_ROLE_ROOT = new AuthorityImpl(ROLE_ROOT, "Permissões de root do sistema"); + + protected static final Set authorities = new HashSet(asList( + AUTHORITY_ROLE_OWNER, + AUTHORITY_ROLE_ROOT + )); + + public static Authority getAuthority(final Role role) { + return getAllAuthorities() + .parallelStream() + .filter(it -> it.getRole().equals(role)) + .findAny() + .orElse(null); + } + + public static Set getAllAuthorities() { + return authorities; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/Authority.java b/muttley-model/src/main/java/br/com/muttley/model/security/Authority.java index 3667373d..9a670aee 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/Authority.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/Authority.java @@ -1,51 +1,19 @@ package br.com.muttley.model.security; +import org.springframework.security.core.GrantedAuthority; + public interface Authority { - String getName(); + Role getRole(); String getDescription(); - /*private String name; - private String descricao; - - public Authority() { - } - - public Authority(final String name) { - this(); - this.name = name; - } - - public Authority(final Authorities name) { - this(name.name()); - } - - public String getName() { - return name; + default GrantedAuthority toGrantedAuthority() { + return new GrantedAuthority() { + @Override + public String getAuthority() { + return getRole().toString(); + } + }; } - - public Authority setName(final Authorities name) { - this.name = name.getDescription(); - return this; - } - - @JsonIgnore - @Transient - public String getDescricao() { - return this.name; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (!(o instanceof Authority)) return false; - final Authority authority = (Authority) o; - return name == authority.name; - } - - @Override - public int hashCode() { - return Objects.hash(name, 87); - }*/ } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/AuthorityImpl.java b/muttley-model/src/main/java/br/com/muttley/model/security/AuthorityImpl.java index 36c61929..77d960a2 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/AuthorityImpl.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/AuthorityImpl.java @@ -1,5 +1,8 @@ package br.com.muttley.model.security; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.Objects; /** @@ -8,29 +11,39 @@ * @project muttley-cloud */ public class AuthorityImpl implements Authority { - private String name; + private Role role; private String description; public AuthorityImpl() { } - public AuthorityImpl(final String name) { + public AuthorityImpl(final Role role) { this(); - this.name = name; + this.role = role; + } + + public AuthorityImpl(final String role) { + this(Role.valueOf(role)); + } + + @JsonCreator + public AuthorityImpl(@JsonProperty("role") final Role role, @JsonProperty("description") final String description) { + this(role); + this.description = description; } - public AuthorityImpl(final String name, final String description) { - this(name); + public AuthorityImpl(final String role, final String description) { + this(role); this.description = description; } @Override - public String getName() { - return name; + public Role getRole() { + return role; } - public AuthorityImpl setName(final String name) { - this.name = name; + public AuthorityImpl setRole(final Role role) { + this.role = role; return this; } @@ -49,11 +62,11 @@ public boolean equals(final Object o) { if (this == o) return true; if ((!(o instanceof AuthorityImpl)) || (!(o instanceof Authority))) return false; final Authority authority = (Authority) o; - return Objects.equals(name, authority.getName()); + return Objects.equals(role, authority.getRole()); } @Override public int hashCode() { - return Objects.hash(name, 81); + return Objects.hash(role, 81); } } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/JwtToken.java b/muttley-model/src/main/java/br/com/muttley/model/security/JwtToken.java index 587bdff5..cf9f103a 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/JwtToken.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/JwtToken.java @@ -3,16 +3,28 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.time.Instant; +import java.util.Date; +import java.util.Map; import static com.google.common.base.Strings.isNullOrEmpty; +import static java.util.Base64.getDecoder; /** * @author Joel Rodrigues Moreira on 12/01/18. * @project spring-cloud */ public class JwtToken implements Serializable { + @JsonIgnore + private Map payload; + private final String token; @JsonCreator @@ -28,4 +40,53 @@ public String getToken() { public boolean isEmpty() { return isNullOrEmpty(this.token); } + + @JsonIgnore + public long getExpiration() { + decodeToken(); + return Long.valueOf(String.valueOf(payload.get("exp"))); + } + + @JsonIgnore + public Date getDtCreated() { + decodeToken(); + return Date.from(Instant.ofEpochSecond(Long.valueOf(String.valueOf(this.payload.get("created"))))); + } + + @JsonIgnore + public Date getDtExpiration() { + return Date.from(Instant.ofEpochSecond(this.getExpiration())); + } + + @JsonIgnore + public String getUsername() { + decodeToken(); + return payload.get("sub"); + } + + private void decodeToken() { + + if (payload == null) { + try { + //separando os blocos de informações + final String[] blocksEncond = this.token.split("\\."); + //array com json de informações do token + final String[] paylodString = new String[]{new String(getDecoder().decode(blocksEncond[0]), "UTF-8"), new String(getDecoder().decode(blocksEncond[1]), "UTF-8")}; + //deserializando + final ObjectMapper mapper = new ObjectMapper(); + + payload = mapper.readValue(paylodString[0], Map.class); + payload.putAll(mapper.readValue(paylodString[1], Map.class)); + + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } catch (JsonParseException e) { + e.printStackTrace(); + } catch (JsonMappingException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/JwtUser.java b/muttley-model/src/main/java/br/com/muttley/model/security/JwtUser.java index 89216019..08f5ad76 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/JwtUser.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/JwtUser.java @@ -4,9 +4,11 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import io.jsonwebtoken.lang.Collections; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.util.StringUtils; import java.util.Collection; import java.util.Date; @@ -22,42 +24,31 @@ public class JwtUser implements UserDetails { private final String id; private final String name; - private final String password; - private final String email; + private final Password password; + private final String username; private final Collection authorities; private final boolean enabled; private final Date lastPasswordResetDate; /*@Value("${springboot..security.jwt.issuer}") - private String issuer;*/ + private String issuer ;*/ private final User originUser; - public JwtUser(final User user) { - this.id = user.getId(); - this.name = user.getName(); - this.password = user.getPasswd(); - this.email = user.getEmail(); - this.authorities = mapToGrantedAuthorities(user.getAuthorities()); - this.enabled = user.isEnable(); - this.lastPasswordResetDate = user.getLastPasswordResetDate(); - this.originUser = user; - } - - public JwtUser(final UserBuilder userBuilder) { - this.id = userBuilder.id; - this.name = userBuilder.name; - this.password = userBuilder.password; - this.email = userBuilder.email; - this.authorities = userBuilder.authorities; - this.enabled = userBuilder.enabled; - this.lastPasswordResetDate = userBuilder.lastPasswordResetDate; - this.originUser = userBuilder.originUser; + public JwtUser(final Builder builder) { + this.id = builder.id; + this.name = builder.name; + this.password = builder.password; + this.username = builder.userName; + this.authorities = builder.authorities; + this.enabled = builder.enabled; + this.lastPasswordResetDate = builder.lastPasswordResetDate; + this.originUser = builder.originUser; } @JsonCreator public JwtUser( @JsonProperty("id") final String id, @JsonProperty("name") final String name, - @JsonProperty("password") final String password, + @JsonProperty("password") final Password password, @JsonProperty("username") final String userName, @JsonProperty("authorities") final Collection authorities, @JsonProperty("enabled") final boolean enabled, @@ -66,7 +57,7 @@ public JwtUser( this.id = id; this.name = name; this.password = password; - this.email = userName; + this.username = userName; this.authorities = authorities; this.enabled = enabled; this.lastPasswordResetDate = lastPasswordResetDate; @@ -85,7 +76,7 @@ public String getId() { @Override public String getUsername() { - return email; + return username; } @JsonIgnore @@ -106,18 +97,17 @@ public boolean isCredentialsNonExpired() { return true; } - public String getEmail() { - return email; - } - @JsonIgnore @Override public String getPassword() { - return password; + return password.getPassword(); } @Override public Collection getAuthorities() { + if (Collections.isEmpty(this.authorities)) { + return mapToGrantedAuthorities(this.originUser.getAuthorities()); + } return authorities; } @@ -132,8 +122,9 @@ public Date getLastPasswordResetDate() { } private static final List mapToGrantedAuthorities(final Collection authorities) { - return authorities.stream() - .map(authority -> new SimpleGrantedAuthority(authority.getName())) + return authorities.parallelStream() + .filter(autority -> autority != null && !StringUtils.isEmpty(autority.getRole())) + .map(authority -> new SimpleGrantedAuthority(authority.getRole().toString())) .collect(Collectors.toList()); } @@ -141,61 +132,75 @@ public String toJson() { return JsonHelper.toJson(this); } - public static class UserBuilder { + public static class Builder { private String id; private String name; - - private String password; - private String email; + private Password password; + private String userName; private Collection authorities; private boolean enabled; private Date lastPasswordResetDate; - /*@Value("${springboot..security.jwt.issuer}") - private String issuer;*/ private User originUser; - public UserBuilder setId(final String id) { + private Builder() { + + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder setId(final String id) { this.id = id; return this; } - public UserBuilder setName(final String name) { + public Builder setName(final String name) { this.name = name; return this; } - public UserBuilder setPassword(final String password) { + public Builder setPassword(final Password password) { this.password = password; return this; } - public UserBuilder setEmail(final String email) { - this.email = email; + public Builder setUserName(final String userName) { + this.userName = userName; return this; } - public UserBuilder setAuthorities(final Collection authorities) { + public Builder setAuthorities(final Collection authorities) { this.authorities = authorities; return this; } - public UserBuilder setEnabled(final boolean enabled) { + public Builder setEnabled(final boolean enabled) { this.enabled = enabled; return this; } - public UserBuilder setLastPasswordResetDate(final Date lastPasswordResetDate) { + public Builder setLastPasswordResetDate(final Date lastPasswordResetDate) { this.lastPasswordResetDate = lastPasswordResetDate; return this; } - public UserBuilder setOriginUser(final User originUser) { + public Builder setOriginUser(final User originUser) { this.originUser = originUser; return this; } + public Builder set(final User user) { + return this.setId(user.getId()) + .setName(user.getName()) + .setUserName(user.getUserName()) + .setAuthorities(user.getAuthorities().parallelStream().map(Authority::toGrantedAuthority).collect(Collectors.toList())) + .setEnabled(user.isEnable()) + .setOriginUser(user); + } + public JwtUser build() { - return new JwtUser(this); + return new JwtUser(this.id, this.name, this.password, this.userName, this.authorities, this.enabled, this.lastPasswordResetDate, this.originUser); } } -} \ No newline at end of file +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/KeyUserDataBinding.java b/muttley-model/src/main/java/br/com/muttley/model/security/KeyUserDataBinding.java new file mode 100644 index 00000000..9f9922a6 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/KeyUserDataBinding.java @@ -0,0 +1,56 @@ +package br.com.muttley.model.security; + +import br.com.muttley.model.security.jackson.KeyUserDataBindingDeserializer; +import br.com.muttley.model.security.jackson.KeyUserDataBindingSerializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 10/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@JsonSerialize(using = KeyUserDataBindingSerializer.class) +@JsonDeserialize(using = KeyUserDataBindingDeserializer.class) +public interface KeyUserDataBinding { + + String getDisplayKey(); + + /** + * Chave a ser usada + */ + String getKey(); + + /** + * Indica se a chave pode esta correlacionada a mais de um usuário por owner + */ + boolean isUnique(); + + static KeyUserDataBinding from(final String value) { + return Avaliable.from(value); + } + + public static class Avaliable { + private static final Set cache = new HashSet<>(); + + public static void add(final KeyUserDataBinding value) { + if (value != null) { + cache.add(value); + } + } + + public static Set getValues() { + return Avaliable.cache; + } + + public static KeyUserDataBinding from(final String value) { + return getValues().parallelStream() + .filter(it -> it.getKey().equals(value)) + .findFirst() + .orElse(null); + } + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/Owner.java b/muttley-model/src/main/java/br/com/muttley/model/security/Owner.java index b3095e11..75079214 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/Owner.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/Owner.java @@ -1,22 +1,29 @@ package br.com.muttley.model.security; -import br.com.muttley.model.Historic; +import br.com.muttley.annotations.index.CompoundIndexes; +import br.com.muttley.exception.throwables.MuttleyInvalidObjectIdException; +import br.com.muttley.model.MetadataDocument; import br.com.muttley.model.jackson.converter.DocumentSerializer; import br.com.muttley.model.security.jackson.AccessPlanDeserializer; import br.com.muttley.model.security.jackson.UserDeserializer; import br.com.muttley.model.security.jackson.UserSerializer; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.base.Objects; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; import org.springframework.data.mongodb.core.index.CompoundIndex; -import org.springframework.data.mongodb.core.index.CompoundIndexes; -import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import javax.validation.constraints.NotNull; +import static br.com.muttley.model.security.Owner.TYPE_ALIAS; +import static org.springframework.util.StringUtils.isEmpty; + /** * @author Joel Rodrigues Moreira on 24/04/18. * e-mail: joel.databox@gmail.com @@ -24,12 +31,17 @@ */ @Document(collection = "#{documentNameConfig.getNameCollectionOwner()}") @CompoundIndexes({ - @CompoundIndex(name = "name_userMaster_index_unique", def = "{'name' : 1, 'userMaster': 1}", unique = true) + @CompoundIndex(name = "userMaster_index_unique", def = "{'userMaster': 1}", unique = true), + @CompoundIndex(name = "name_index", def = "{'name': 1}") }) -public class Owner implements br.com.muttley.model.Document { +@TypeAlias(TYPE_ALIAS) +public class Owner implements br.com.muttley.model.Document, OwnerData { + @Transient + @JsonIgnore + public static final String TYPE_ALIAS = "owner"; + @Id protected String id; - @Indexed protected String name; protected String description; @NotNull(message = "Informe o usuário master") @@ -41,7 +53,7 @@ public class Owner implements br.com.muttley.model.Document { @JsonDeserialize(using = AccessPlanDeserializer.class) @DBRef protected AccessPlan accessPlan; - protected Historic historic; + protected MetadataDocument metadata; @Override public String getId() { @@ -54,6 +66,7 @@ public Owner setId(final String id) { return this; } + @Override public String getName() { return name; } @@ -63,6 +76,7 @@ public Owner setName(final String name) { return this; } + @Override public String getDescription() { return description; } @@ -72,6 +86,7 @@ public Owner setDescription(final String description) { return this; } + @Override public User getUserMaster() { return userMaster; } @@ -90,13 +105,15 @@ public Owner setAccessPlan(final AccessPlan accessPlan) { return this; } - public Owner setHistoric(final Historic historic) { - this.historic = historic; - return this; + @Override + public MetadataDocument getMetadata() { + return metadata; } - public Historic getHistoric() { - return this.historic; + @Override + public Owner setMetadata(final MetadataDocument metaData) { + this.metadata = metaData; + return this; } @Override @@ -111,4 +128,20 @@ public boolean equals(final Object o) { public int hashCode() { return Objects.hashCode(id, 93); } + + public OwnerData toOwnerData() { + return new OwnerDataImpl(this); + } + + @Override + public ObjectId getObjectId() { + if (!isEmpty(getId())) { + try { + return new ObjectId(getId()); + } catch (IllegalArgumentException ex) { + throw new MuttleyInvalidObjectIdException(this.getClass(), "id", "ObjectId inválido"); + } + } + return null; + } } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/OwnerData.java b/muttley-model/src/main/java/br/com/muttley/model/security/OwnerData.java new file mode 100644 index 00000000..b2dbaae5 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/OwnerData.java @@ -0,0 +1,39 @@ +package br.com.muttley.model.security; + +import br.com.muttley.exception.throwables.MuttleyInvalidObjectIdException; +import br.com.muttley.model.security.jackson.OwnerDataDeserializerDefault; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.bson.types.ObjectId; + +import static org.springframework.util.StringUtils.isEmpty; + +/** + * @author Joel Rodrigues Moreira 29/12/2020 + * joel.databox@gmail.com + * @project muttley-cloud + *

+ * Interface criada para Evitar o vazamente do informações a respeito do owner para terceiros + */ +@JsonDeserialize(using = OwnerDataDeserializerDefault.class) +public interface OwnerData { + String getId(); + + String getName(); + + String getDescription(); + + UserData getUserMaster(); + + @JsonIgnore + default ObjectId getObjectId() { + if (!isEmpty(getId())) { + try { + return new ObjectId(getId()); + } catch (IllegalArgumentException ex) { + throw new MuttleyInvalidObjectIdException(this.getClass(), "id", "ObjectId inválido"); + } + } + return null; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/OwnerDataImpl.java b/muttley-model/src/main/java/br/com/muttley/model/security/OwnerDataImpl.java new file mode 100644 index 00000000..4ff5ded0 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/OwnerDataImpl.java @@ -0,0 +1,48 @@ +package br.com.muttley.model.security; + +import br.com.muttley.model.security.jackson.UserDataDeserializer; +import br.com.muttley.model.security.jackson.UserDataSerializer; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * @author Joel Rodrigues Moreira 29/12/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class OwnerDataImpl implements OwnerData { + private String id; + private String name; + private String description; + @JsonSerialize(using = UserDataSerializer.class) + @JsonDeserialize(using = UserDataDeserializer.class) + private UserData userMaster; + + public OwnerDataImpl() { + } + + @JsonCreator + public OwnerDataImpl( + @JsonProperty("id") final String id, + @JsonProperty("name") final String name, + @JsonProperty("description") final String description, + @JsonProperty("userMaster") final UserData userMaster) { + this(); + this.id = id; + this.name = name; + this.description = description; + this.userMaster = userMaster; + } + + public OwnerDataImpl(final OwnerData owner) { + this(owner.getId(), owner.getName(), owner.getDescription(), owner.getUserMaster()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/Passaport.java b/muttley-model/src/main/java/br/com/muttley/model/security/Passaport.java new file mode 100644 index 00000000..65c6fbcd --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/Passaport.java @@ -0,0 +1,208 @@ +package br.com.muttley.model.security; + +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.Model; +import br.com.muttley.model.jackson.converter.DocumentSerializer; +import br.com.muttley.model.security.jackson.OwnerDeserializer; +import br.com.muttley.model.security.jackson.UserCollectionSerializer; +import br.com.muttley.model.security.jackson.UserDeserializer; +import br.com.muttley.model.security.jackson.UserSerializer; +import br.com.muttley.model.security.jackson.UserSetDeserializer; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.base.Objects; +import org.hibernate.validator.constraints.NotBlank; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.index.CompoundIndexes; +import org.springframework.data.mongodb.core.mapping.DBRef; + +import javax.validation.constraints.NotNull; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Stream; + +import static br.com.muttley.model.security.Passaport.TYPE_ALIAS; +import static org.springframework.util.CollectionUtils.isEmpty; + +/** + * @author Joel Rodrigues Moreira on 24/04/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@org.springframework.data.mongodb.core.mapping.Document(collection = "#{documentNameConfig.getNameCollectionPassaport()}") +@CompoundIndexes({ + @CompoundIndex(name = "name_userMaster_index_unique", def = "{'name' : 1, 'userMaster': 1}", unique = true) +}) +@TypeAlias(TYPE_ALIAS) +public class Passaport implements Model { + @Transient + @JsonIgnore + public static final String TYPE_ALIAS = "passaport"; + + @Id + protected String id; + @NotBlank(message = "Informe um nome válido para o grupo") + protected String name; + protected String description; + @NotNull(message = "É nécessário ter um usuário master no grupo de trabalho") + @JsonSerialize(using = UserSerializer.class) + @JsonDeserialize(using = UserDeserializer.class) + @DBRef + protected User userMaster; + /*@NotNull(message = "É nécessário informar quem é o owner do grupo de trabalho") + @DBRef*/ + @JsonSerialize(using = DocumentSerializer.class) + @JsonDeserialize(using = OwnerDeserializer.class) + @DBRef + protected Owner owner; + @DBRef + @JsonSerialize(using = UserCollectionSerializer.class) + @JsonDeserialize(using = UserSetDeserializer.class) + protected Set members; + protected MetadataDocument metadata; + protected Set roles; + + public Passaport() { + this.members = new LinkedHashSet<>(); + this.roles = new LinkedHashSet<>(); + } + + @Override + public String getId() { + return id; + } + + @Override + public Passaport setId(final String id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public Passaport setName(final String name) { + this.name = name; + return this; + } + + + public String getDescription() { + return description; + } + + public Passaport setDescription(final String description) { + this.description = description; + return this; + } + + + public User getUserMaster() { + return userMaster; + } + + public Passaport setUserMaster(final User userMaster) { + this.userMaster = userMaster; + this.addMember(this.userMaster); + return this; + } + + + public Owner getOwner() { + return owner; + } + + public Passaport setOwner(final Owner owner) { + this.owner = owner; + return this; + } + + + public Set getMembers() { + return members; + } + + public Passaport setMembers(final Set members) { + this.members = members; + return this; + } + + public Passaport addMember(final User user) { + this.members.add(user); + return this; + } + + @Override + public MetadataDocument getMetadata() { + return metadata; + } + + @Override + public Passaport setMetadata(final MetadataDocument metaData) { + this.metadata = metaData; + return this; + } + + public Set getRoles() { + return roles; + } + + public Passaport setRoles(final Set roles) { + this.roles = roles; + return this; + } + + public Passaport addRole(final Authority authority) { + if (authority != null) { + this.roles.add(authority.getRole()); + } + return this; + } + + public Passaport addRole(final Role role) { + if (role != null) { + this.roles.add(role); + } + return this; + } + + public Passaport addRoles(final Role... roles) { + if (roles != null) { + Stream.of(roles).filter(java.util.Objects::nonNull).forEach(it -> this.roles.add(it)); + } + return this; + } + + public Passaport addRoles(final Collection roles) { + if (roles != null) { + roles.parallelStream().filter(java.util.Objects::nonNull).forEach(it -> this.roles.add(it)); + } + return this; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof Passaport)) return false; + final Passaport passaport = (Passaport) o; + return Objects.equal(id, passaport.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, -33); + } + + public boolean containsRole(final Role role) { + if (isEmpty(this.roles)) { + return false; + } + return this.roles.contains(role); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/Passwd.java b/muttley-model/src/main/java/br/com/muttley/model/security/PasswdPayload.java similarity index 50% rename from muttley-model/src/main/java/br/com/muttley/model/security/Passwd.java rename to muttley-model/src/main/java/br/com/muttley/model/security/PasswdPayload.java index 73ac6f77..4dd786f2 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/Passwd.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/PasswdPayload.java @@ -9,52 +9,52 @@ /** * Classe para auxiliar a atulização da senha */ -public class Passwd { +public class PasswdPayload { private JwtToken token; @NotNull(message = "Informe a senha atual!") @NotEmpty(message = "Informe a senha atual!") - private String actualPasswd; + private String actualPassword; @NotNull(message = "Informe uma nova senha valida!") @NotEmpty(message = "Informe uma nova senha valida!") - private String newPasswd; + private String newPassword; - public Passwd() { + public PasswdPayload() { } @JsonCreator - public Passwd( + public PasswdPayload( @JsonProperty("token") final JwtToken token, - @JsonProperty("actualPasswd") final String actualPasswd, - @JsonProperty("newPasswd") final String newPasswd) { + @JsonProperty("actualPassword") final String actualPassword, + @JsonProperty("newPassword") final String newPassword) { this.token = token; - this.actualPasswd = actualPasswd; - this.newPasswd = newPasswd; + this.actualPassword = actualPassword; + this.newPassword = newPassword; } public JwtToken getToken() { return token; } - public Passwd setToken(final JwtToken token) { + public PasswdPayload setToken(final JwtToken token) { this.token = token; return this; } - public String getActualPasswd() { - return actualPasswd; + public String getActualPassword() { + return actualPassword; } - public void setActualPasswd(final String actualPasswd) { - this.actualPasswd = actualPasswd; + public void setActualPassword(final String actualPassword) { + this.actualPassword = actualPassword; } - public String getNewPasswd() { - return newPasswd; + public String getNewPassword() { + return newPassword; } - public void setNewPasswd(final String newPasswd) { - this.newPasswd = newPasswd; + public void setNewPassword(final String newPassword) { + this.newPassword = newPassword; } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/Password.java b/muttley-model/src/main/java/br/com/muttley/model/security/Password.java new file mode 100644 index 00000000..64c7c45b --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/Password.java @@ -0,0 +1,191 @@ +package br.com.muttley.model.security; + +import br.com.muttley.annotations.index.CompoundIndexes; +import br.com.muttley.exception.throwables.security.MuttleySecurityBadRequestException; +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.security.jackson.UserDeserializer; +import br.com.muttley.model.security.jackson.UserSerializer; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.NotBlank; +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.http.HttpStatus; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.util.StringUtils; + +import javax.validation.constraints.NotNull; +import java.security.SecureRandom; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import static br.com.muttley.model.security.Password.BuilderPasswordEncoder.getPasswordEncoder; +import static io.jsonwebtoken.SignatureAlgorithm.HS512; +import static io.jsonwebtoken.impl.crypto.MacProvider.generateKey; + +/** + * @author Joel Rodrigues Moreira 12/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Document(collection = "#{documentNameConfig.getNameCollectionPassword()}") +@CompoundIndexes({ + @CompoundIndex(name = "user_index_unique", def = "{'user': 1}") +}) +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = "id") +public class Password implements br.com.muttley.model.Document { + /*@Transient + @JsonIgnore + private static final int SALT = 10; + private static final SecureRandom RANDOM = new SecureRandom(generateKey(HS512).getEncoded());*/ + + + @JsonIgnore + private String id; + @JsonIgnore + @NotNull + @DBRef + @JsonSerialize(using = UserSerializer.class) + @JsonDeserialize(using = UserDeserializer.class) + private User user; + @NotBlank(message = "Informe uma senha valida!") + private String password; + @NotNull(message = "Informe uma senha valida!") + private Date lastDatePasswordChanges; + @JsonIgnore + private List oldPasswords; + @JsonIgnore + private MetadataDocument metadata; + + protected Password() { + this.oldPasswords = new LinkedList<>(); + } + + @PersistenceConstructor + @JsonCreator + public Password( + @JsonProperty("id") final String id, + @JsonProperty("user") final User user, + @JsonProperty("password") final String password, + @JsonProperty("lastDatePasswordChanges") Date lastDatePasswordChanges, + @JsonProperty("oldPasswords") final List oldPasswords, + @JsonProperty("metadata") final MetadataDocument metadata) { + this.id = id; + this.user = user; + this.password = password; + this.lastDatePasswordChanges = lastDatePasswordChanges; + this.oldPasswords = oldPasswords; + this.metadata = metadata; + } + + public Password setPassword(final String password) { + if (password == null || password.trim().isEmpty()) { + throw new MuttleySecurityBadRequestException(User.class, "passwd", "Informe uma senha valida!"); + } + if (!StringUtils.isEmpty(this.password)) { + //gerando historico das senhas + this.addOldPassword(new PasswordItem(this)); + } + this.password = getPasswordEncoder().encode(password); + this.setLastDatePasswordChanges(new Date()); + return this; + } + + public Password setPassword(final PasswdPayload passwdPayload) { + if (!checkPasswd(passwdPayload.getActualPassword())) { + throw new MuttleySecurityBadRequestException(User.class, "passwd", "A senha atual informada é invalida!").setStatus(HttpStatus.NOT_ACCEPTABLE); + } + if (!StringUtils.isEmpty(this.password)) { + //gerando historico das senhas + this.addOldPassword(new PasswordItem(this)); + } + this.password = getPasswordEncoder().encode(passwdPayload.getNewPassword()); + this.setLastDatePasswordChanges(new Date()); + return this; + } + + public List getOldPasswords() { + if (this.oldPasswords == null) { + this.oldPasswords = new LinkedList<>(); + } + return oldPasswords; + } + + private Password addOldPassword(final PasswordItem passwordItem) { + this.getOldPasswords().add(0, passwordItem); + if (this.oldPasswords.size() > 5) { + this.setOldPasswords(this.oldPasswords.subList(0, 5)); + } + return this; + } + + public Password setPasswd(final Password password) { + this.password = password.password; + return this; + } + + public boolean checkPasswd(final String passwd) { + return checkPasswd(passwd, this.password); + } + + @JsonIgnore + public boolean isValidPasswd() { + return this.password != null && !this.password.isEmpty(); + } + + public boolean checkPasswd(final String passwd, String cryptedPasswd) { + return getPasswordEncoder().matches(passwd, cryptedPasswd); + } + + public static class Builder { + private User user; + private String password; + + public static Builder newInstance() { + return new Builder(); + } + + public Builder setPassword(final String password) { + this.password = password; + return this; + } + + public Builder setPassword(final PasswdPayload password) { + return this.setPassword(password.getNewPassword()); + } + + public Password builder() { + return new Password() + .setUser(this.user) + .setLastDatePasswordChanges(new Date()) + .setPassword(this.password); + } + + public Builder setUser(final User user) { + this.user = user; + return this; + } + } + + public static class BuilderPasswordEncoder { + private static final int SALT = 10; + private static final SecureRandom RANDOM = new SecureRandom(generateKey(HS512).getEncoded()); + + public static BCryptPasswordEncoder getPasswordEncoder() { + return new BCryptPasswordEncoder(SALT, RANDOM); + } + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/PasswordItem.java b/muttley-model/src/main/java/br/com/muttley/model/security/PasswordItem.java new file mode 100644 index 00000000..342f8b21 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/PasswordItem.java @@ -0,0 +1,30 @@ +package br.com.muttley.model.security; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import org.springframework.data.annotation.PersistenceConstructor; + +import java.util.Date; + +/** + * @author Joel Rodrigues Moreira 12/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +public class PasswordItem { + private final String password; + private final Date dtCreate; + + @PersistenceConstructor + @JsonCreator + public PasswordItem(@JsonProperty("password") final String password, @JsonProperty("dtCreate") final Date dtCreate) { + this.password = password; + this.dtCreate = dtCreate; + } + + public PasswordItem(final Password password) { + this(password.getPassword(), password.getLastDatePasswordChanges()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/RecoveryPasswordResponse.java b/muttley-model/src/main/java/br/com/muttley/model/security/RecoveryPasswordResponse.java new file mode 100644 index 00000000..0129fd8f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/RecoveryPasswordResponse.java @@ -0,0 +1,18 @@ +package br.com.muttley.model.security; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * @author Joel Rodrigues Moreira on 01/11/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class RecoveryPasswordResponse { + private String password; + private String fone; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/RecoveryPayload.java b/muttley-model/src/main/java/br/com/muttley/model/security/RecoveryPayload.java new file mode 100644 index 00000000..3251678b --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/RecoveryPayload.java @@ -0,0 +1,23 @@ +package br.com.muttley.model.security; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * @author Joel Rodrigues Moreira on 27/10/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class RecoveryPayload { + @JsonIgnore + private String fone;//fone do user + private String email; + private boolean renewCode; + private String codeVerification; + private String seedVerification; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/ResetPasswordRequest.java b/muttley-model/src/main/java/br/com/muttley/model/security/ResetPasswordRequest.java new file mode 100644 index 00000000..f1007893 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/ResetPasswordRequest.java @@ -0,0 +1,13 @@ +package br.com.muttley.model.security; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +@Data +@Getter +@Setter +public class ResetPasswordRequest { + private String token; + private String newPassword; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/Role.java b/muttley-model/src/main/java/br/com/muttley/model/security/Role.java new file mode 100644 index 00000000..a0b1ee0a --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/Role.java @@ -0,0 +1,354 @@ +package br.com.muttley.model.security; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Joel Rodrigues Moreira on 10/05/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@TypeAlias("role") +public class Role { + private final String roleName; + + @JsonIgnore + protected static final Set values = new HashSet<>(); + + @JsonIgnore + public static final Role ROLE_OWNER = new Role("ROLE_OWNER"); + @JsonIgnore + public static final Role ROLE_ROOT = new Role("ROLE_ROOT"); + @JsonIgnore + public static final Role ROLE_ROOT_READ = new Role("ROLE_ROOT_READ"); + @JsonIgnore + public static final Role ROLE_ODIN_USER = new Role("ROLE_ODIN_USER"); + + @JsonIgnore + public static final Role ROLE_ACCESS_PLAN_CREATE = new Role("ROLE_ACCESS_PLAN_CREATE"); + @JsonIgnore + public static final Role ROLE_ACCESS_PLAN_READ = new Role("ROLE_ACCESS_PLAN_READ"); + @JsonIgnore + public static final Role ROLE_ACCESS_PLAN_UPDATE = new Role("ROLE_ACCESS_PLAN_UPDATE"); + @JsonIgnore + public static final Role ROLE_ACCESS_PLAN_DELETE = new Role("ROLE_ACCESS_PLAN_DELETE"); + @JsonIgnore + public static final Role ROLE_ACCESS_PLAN_SIMPLE_USE = new Role("ROLE_ACCESS_PLAN_SIMPLE_USE"); + + @JsonIgnore + public static final Role ROLE_MOBILE_ACCESS_PLAN_CREATE = new Role("ROLE_MOBILE_ACCESS_PLAN_CREATE"); + @JsonIgnore + public static final Role ROLE_MOBILE_ACCESS_PLAN_READ = new Role("ROLE_MOBILE_ACCESS_PLAN_READ"); + @JsonIgnore + public static final Role ROLE_MOBILE_ACCESS_PLAN_UPDATE = new Role("ROLE_MOBILE_ACCESS_PLAN_UPDATE"); + @JsonIgnore + public static final Role ROLE_MOBILE_ACCESS_PLAN_DELETE = new Role("ROLE_MOBILE_ACCESS_PLAN_DELETE"); + + @JsonIgnore + public static final Role ROLE_OWNER_CREATE = new Role("ROLE_OWNER_CREATE"); + @JsonIgnore + public static final Role ROLE_OWNER_READ = new Role("ROLE_OWNER_READ"); + @JsonIgnore + public static final Role ROLE_OWNER_UPDATE = new Role("ROLE_OWNER_UPDATE"); + @JsonIgnore + public static final Role ROLE_OWNER_DELETE = new Role("ROLE_OWNER_DELETE"); + @JsonIgnore + public static final Role ROLE_OWNER_SIMPLE_USE = new Role("ROLE_OWNER_SIMPLE_USE"); + + @JsonIgnore + public static final Role ROLE_USER_BASE_CREATE = new Role("ROLE_USER_BASE_CREATE"); + @JsonIgnore + public static final Role ROLE_USER_BASE_READ = new Role("ROLE_USER_BASE_READ"); + @JsonIgnore + public static final Role ROLE_USER_BASE_UPDATE = new Role("ROLE_USER_BASE_UPDATE"); + @JsonIgnore + public static final Role ROLE_USER_BASE_DELETE = new Role("ROLE_USER_BASE_DELETE"); + @JsonIgnore + public static final Role ROLE_USER_BASE_SIMPLE_USE = new Role("ROLE_USER_BASE_SIMPLE_USE"); + + @JsonIgnore + public static final Role ROLE_USER_VIEW_CREATE = new Role("ROLE_USER_VIEW_CREATE"); + @JsonIgnore + public static final Role ROLE_USER_VIEW_READ = new Role("ROLE_USER_VIEW_READ"); + @JsonIgnore + public static final Role ROLE_USER_VIEW_UPDATE = new Role("ROLE_USER_VIEW_UPDATE"); + @JsonIgnore + public static final Role ROLE_USER_VIEW_DELETE = new Role("ROLE_USER_VIEW_DELETE"); + @JsonIgnore + public static final Role ROLE_USER_SIMPLE_USE = new Role("ROLE_USER_SIMPLE_USE"); + + @JsonIgnore + public static final Role ROLE_PASSAPORT_CREATE = new Role("ROLE_PASSAPORT_CREATE"); + @JsonIgnore + public static final Role ROLE_PASSAPORT_READ = new Role("ROLE_PASSAPORT_READ"); + @JsonIgnore + public static final Role ROLE_PASSAPORT_UPDATE = new Role("ROLE_PASSAPORT_UPDATE"); + @JsonIgnore + public static final Role ROLE_PASSAPORT_DELETE = new Role("ROLE_PASSAPORT_DELETE"); + @JsonIgnore + public static final Role ROLE_PASSAPORT_SIMPLE_USE = new Role("ROLE_PASSAPORT_SIMPLE_USE"); + + @JsonIgnore + public static final Role ROLE_MOBILE_PASSAPORT_CREATE = new Role("ROLE_MOBILE_PASSAPORT_CREATE"); + @JsonIgnore + public static final Role ROLE_MOBILE_PASSAPORT_READ = new Role("ROLE_MOBILE_PASSAPORT_READ"); + @JsonIgnore + public static final Role ROLE_MOBILE_PASSAPORT_UPDATE = new Role("ROLE_MOBILE_PASSAPORT_UPDATE"); + @JsonIgnore + public static final Role ROLE_MOBILE_PASSAPORT_DELETE = new Role("ROLE_MOBILE_PASSAPORT_DELETE"); + + @JsonIgnore + public static final Role ROLE_WORK_TEAM_CREATE = new Role("ROLE_WORK_TEAM_CREATE"); + @JsonIgnore + public static final Role ROLE_WORK_TEAM_READ = new Role("ROLE_WORK_TEAM_READ"); + @JsonIgnore + public static final Role ROLE_WORK_TEAM_UPDATE = new Role("ROLE_WORK_TEAM_UPDATE"); + @JsonIgnore + public static final Role ROLE_WORK_TEAM_DELETE = new Role("ROLE_WORK_TEAM_DELETE"); + @JsonIgnore + public static final Role ROLE_WORK_TEAM_SIMPLE_USE = new Role("ROLE_WORK_TEAM_SIMPLE_USE"); + + @JsonIgnore + public static final Role ROLE_MOBILE_WORK_TEAM_CREATE = new Role("ROLE_MOBILE_WORK_TEAM_CREATE"); + @JsonIgnore + public static final Role ROLE_MOBILE_WORK_TEAM_READ = new Role("ROLE_MOBILE_WORK_TEAM_READ"); + @JsonIgnore + public static final Role ROLE_MOBILE_WORK_TEAM_UPDATE = new Role("ROLE_MOBILE_WORK_TEAM_UPDATE"); + @JsonIgnore + public static final Role ROLE_MOBILE_WORK_TEAM_DELETE = new Role("ROLE_MOBILE_WORK_TEAM_DELETE"); + + @JsonIgnore + public static final Role ROLE_USER_DATA_BINDING_CREATE = new Role("ROLE_USER_DATA_BINDING_CREATE"); + @JsonIgnore + public static final Role ROLE_USER_DATA_BINDING_READ = new Role("ROLE_USER_DATA_BINDING_READ"); + @JsonIgnore + public static final Role ROLE_USER_DATA_BINDING_UPDATE = new Role("ROLE_USER_DATA_BINDING_UPDATE"); + @JsonIgnore + public static final Role ROLE_USER_DATA_BINDING_DELETE = new Role("ROLE_USER_DATA_BINDING_DELETE"); + @JsonIgnore + public static final Role ROLE_USER_DATA_BINDING_SIMPLE_USE = new Role("ROLE_USER_DATA_BINDING_SIMPLE_USE"); + @JsonIgnore + public static final Role ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE = new Role("ROLE_USER_DATA_BINDING_SIMPLE_USE"); + + @JsonIgnore + public static final Role ROLE_MOBILE_USER_DATA_BINDING_CREATE = new Role("ROLE_MOBILE_USER_DATA_BINDING_CREATE"); + @JsonIgnore + public static final Role ROLE_MOBILE_USER_DATA_BINDING_READ = new Role("ROLE_MOBILE_USER_DATA_BINDING_READ"); + @JsonIgnore + public static final Role ROLE_MOBILE_USER_DATA_BINDING_UPDATE = new Role("ROLE_MOBILE_USER_DATA_BINDING_UPDATE"); + @JsonIgnore + public static final Role ROLE_MOBILE_USER_DATA_BINDING_DELETE = new Role("ROLE_MOBILE_USER_DATA_BINDING_DELETE"); + @JsonIgnore + public static final Role ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE = new Role("ROLE_MOBILE_USER_DATA_BINDING_SIMPLE_USE"); + + @JsonCreator + public Role(@JsonProperty("roleName") final String roleName) { + if (!roleName.toUpperCase().startsWith("ROLE_")) { + this.roleName = "ROLE_" + roleName.toUpperCase(); + } else { + this.roleName = roleName.toUpperCase(); + } + values.add(this); + } + + public String getRoleName() { + return roleName; + } + + /** + * Retorno o nome de maneira simples da role. + * Por exemplo, considere a role 'ROLE_WOKR_TEAM_READ' + * o simple name dela será 'WORK_TEAM' + */ + @JsonIgnore + public String getSimpleName() { + return this.getRoleName().replace("ROLE_", "") + .replace("_CREATE", "") + .replace("_READ", "") + .replace("_UPDATE", "") + .replace("_DELETE", "") + .replace("_SIMPLE_USE", ""); + } + + @JsonIgnore + @Transient + public static Set getValues() { + return Role.values; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof Role)) return false; + final Role role = (Role) o; + return Objects.equals(roleName, role.roleName); + } + + @Override + public int hashCode() { + return Objects.hash(roleName); + } + + @Override + public String toString() { + return roleName; + } + + /** + * Com base na String passada por parametro, + * o metodo retornará uma instancia {@link Role} caso a mesma seja válida + * + * @param value -> nameRole a ser procurado + * @return {@link Role} caso seja encontrado + */ + @JsonIgnore + public static final Role valueOf(final String value) { + return values + .parallelStream() + .filter(it -> it.getRoleName().equalsIgnoreCase(value)) + .findAny() + .orElse(null); + } + + /** + * Com base nas String's passadas por parametro, + * o metodo retornará um array de instancias de {@link Role} caso possua alguma String válidaseja válida + * + * @param values -> vetor com nameRole a ser procurados + * @return {@link Role[]} caso seja encontrado + */ + @JsonIgnore + public static final Role[] valueOf(final String... values) { + final Object[] roles = Role.values + .parallelStream() + .filter(it -> { + for (final String v : values) { + if (it.getRoleName().equalsIgnoreCase(v)) { + return true; + } + } + return false; + }) + .collect(Collectors.toSet()) + .toArray(); + if (roles == null || roles.length == 0) { + return null; + } + return (Role[]) roles; + } + + /** + * Com base na String's passada por parametro, + * o metodo retorna uma roleName padronizada + *
+ * Exemplo: role = "Notas", endNameRole = read + * resultado = ROLE_NOTAS_READ + * + * @param role -> nome básico da role; + * @param endNameRole -> sufixo da role; + * @return {@link String} + */ + @JsonIgnore + public static final String toPatternRole(final String endNameRole, final String role) { + final StringBuilder sbuilder = new StringBuilder(); + if (!role.startsWith("ROLE_")) { + sbuilder.append("ROLE_").append(role); + } + if (!role.endsWith("_" + endNameRole)) { + sbuilder.append("_").append(endNameRole); + } + return sbuilder.toString().toUpperCase(); + } + + /** + * Com base nas String's passadas por parametro, + * o metodo retorna as roleNames padronizadas + *
+ * Exemplo: role = "Notas", endNameRole = read + * resultado = [ROLE_NOTAS_READ] + * + * @param roles -> nomes básicos de role; + * @param endNameRole -> sufixo das role; + * @return {@link String} + */ + @JsonIgnore + public static final String[] toPatternRole(final String endNameRole, final String... roles) { + if (roles == null) return null; + return Stream.of(roles) + .parallel() + .map(r -> Role.toPatternRole(endNameRole, r)) + .toArray(String[]::new); + } + + public static void initializeRoles() { + Role.values.addAll( + Arrays.asList( + Role.ROLE_OWNER, + Role.ROLE_ROOT, + Role.ROLE_ODIN_USER, + Role.ROLE_ACCESS_PLAN_CREATE, + Role.ROLE_ACCESS_PLAN_READ, + Role.ROLE_ACCESS_PLAN_UPDATE, + Role.ROLE_ACCESS_PLAN_DELETE, + Role.ROLE_ACCESS_PLAN_SIMPLE_USE, + Role.ROLE_MOBILE_ACCESS_PLAN_CREATE, + Role.ROLE_MOBILE_ACCESS_PLAN_READ, + Role.ROLE_MOBILE_ACCESS_PLAN_UPDATE, + Role.ROLE_MOBILE_ACCESS_PLAN_DELETE, + Role.ROLE_OWNER_CREATE, + Role.ROLE_OWNER_READ, + Role.ROLE_OWNER_UPDATE, + Role.ROLE_OWNER_DELETE, + Role.ROLE_OWNER_SIMPLE_USE, + Role.ROLE_USER_BASE_CREATE, + Role.ROLE_USER_BASE_READ, + Role.ROLE_USER_BASE_UPDATE, + Role.ROLE_USER_BASE_DELETE, + Role.ROLE_USER_BASE_SIMPLE_USE, + Role.ROLE_USER_VIEW_CREATE, + Role.ROLE_USER_VIEW_READ, + Role.ROLE_USER_VIEW_UPDATE, + Role.ROLE_USER_VIEW_DELETE, + Role.ROLE_USER_SIMPLE_USE, + Role.ROLE_PASSAPORT_CREATE, + Role.ROLE_PASSAPORT_READ, + Role.ROLE_PASSAPORT_UPDATE, + Role.ROLE_PASSAPORT_DELETE, + Role.ROLE_PASSAPORT_SIMPLE_USE, + Role.ROLE_MOBILE_PASSAPORT_CREATE, + Role.ROLE_MOBILE_PASSAPORT_READ, + Role.ROLE_MOBILE_PASSAPORT_UPDATE, + Role.ROLE_MOBILE_PASSAPORT_DELETE, + Role.ROLE_WORK_TEAM_CREATE, + Role.ROLE_WORK_TEAM_READ, + Role.ROLE_WORK_TEAM_UPDATE, + Role.ROLE_WORK_TEAM_DELETE, + Role.ROLE_WORK_TEAM_SIMPLE_USE, + Role.ROLE_MOBILE_WORK_TEAM_CREATE, + Role.ROLE_MOBILE_WORK_TEAM_READ, + Role.ROLE_MOBILE_WORK_TEAM_UPDATE, + Role.ROLE_MOBILE_WORK_TEAM_DELETE, + Role.ROLE_USER_DATA_BINDING_CREATE, + Role.ROLE_USER_DATA_BINDING_READ, + Role.ROLE_USER_DATA_BINDING_UPDATE, + Role.ROLE_USER_DATA_BINDING_DELETE, + Role.ROLE_USER_DATA_BINDING_SIMPLE_USE, + Role.ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE, + Role.ROLE_MOBILE_USER_DATA_BINDING_CREATE, + Role.ROLE_MOBILE_USER_DATA_BINDING_READ, + Role.ROLE_MOBILE_USER_DATA_BINDING_UPDATE, + Role.ROLE_MOBILE_USER_DATA_BINDING_DELETE, + Role.ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE + )); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/User.java b/muttley-model/src/main/java/br/com/muttley/model/security/User.java index 3c6bd4f9..c92e2d9f 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/User.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/User.java @@ -1,11 +1,17 @@ package br.com.muttley.model.security; +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.exception.throwables.MuttleyInvalidObjectIdException; import br.com.muttley.exception.throwables.security.MuttleySecurityBadRequestException; import br.com.muttley.model.jackson.JsonHelper; +import br.com.muttley.model.security.preference.Foto; import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.model.workteam.WorkTeamDomain; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import io.jsonwebtoken.lang.Collections; +import org.bson.types.ObjectId; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotBlank; import org.springframework.data.annotation.Id; @@ -13,35 +19,35 @@ import org.springframework.data.mongodb.core.index.CompoundIndex; import org.springframework.data.mongodb.core.index.CompoundIndexes; import org.springframework.data.mongodb.core.mapping.Document; -import org.springframework.http.HttpStatus; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.util.CollectionUtils; import javax.validation.constraints.Size; import java.io.Serializable; -import java.time.Instant; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Objects; -import java.util.Set; +import java.time.LocalDateTime; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; -import static java.util.Objects.isNull; +import static java.util.regex.Pattern.CASE_INSENSITIVE; +import static java.util.stream.Collectors.toSet; +import static org.springframework.util.StringUtils.isEmpty; /** * Created by joel on 16/01/17. */ @Document(collection = "#{documentNameConfig.getNameCollectionUser()}") @CompoundIndexes({ - @CompoundIndex(name = "email_index_unique", def = "{'email' : 1}", unique = true) + @CompoundIndex(name = "userName_index_unique", def = "{'userName' : 1}", unique = true), + @CompoundIndex(name = "email_index", def = "{'email' : 1}"), + @CompoundIndex(name = "nickUsers_index", def = "{'nickUsers' : 1}") }) -public class User implements Serializable { - @Transient +public class User implements Serializable, UserData { + /*@Transient @JsonIgnore - private static final int SALT = 8; + private static final int SALT = 8;*/ + + @Transient @JsonIgnore private static final String EMAIL_PATTERN = "\\b(^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@([A-Za-z0-9-])+(\\.[A-Za-z0-9-]+)*((\\.[A-Za-z0-9]{2,})|(\\.[A-Za-z0-9]{2,}\\.[A-Za-z0-9]{2,}))$)\\b"; @@ -49,62 +55,113 @@ public class User implements Serializable { @Id private String id; @Transient - private Set workTeams;//os workteam devem ser carregados separadamente - @Transient - private WorkTeam currentWorkTeam; + private Owner currentOwner; @NotBlank(message = "O campo nome não pode ser nulo!") @Size(min = 4, max = 200, message = "O campo nome deve ter de 4 a 200 caracteres!") private String name; - @NotBlank(message = "Informe um email válido!") + private String description; @Email(message = "Informe um email válido!") private String email; - @NotBlank(message = "Informe uma senha valida!") - private String passwd; - private Date lastPasswordResetDate; + @Email(message = "Informe um email secundário válido!") + private String emailSecundario; + @NotBlank(message = "Informe um userName válido") + private String userName; + private Foto foto; + private Set nickUsers = new HashSet<>(); + //@NotBlank(message = "Informe uma senha valida!") + //private String passwd; + //private Date lastPasswordResetDate; private Boolean enable; + + @JsonIgnore + private LocalDateTime resetTokenExpiryDate; + + @JsonIgnore + private String resetToken; @Transient - private Set authorities;//Os authorities devem ser repassado pelo workteam corrente + private Set authorities;//Os authorities devem ser repassado pelo passaport corrente @Transient private UserPreferences preferences; + @Transient + private WorkTeamDomain workTeamDomain; + private List dataBindings; + //Define se o usuário é do odin ou de algum outro owner + private boolean odinUser = false; + private String fone; + public User() { this.authorities = new LinkedHashSet(); this.enable = true; - this.lastPasswordResetDate = Date.from(Instant.now()); - this.workTeams = new HashSet(); + //this.lastPasswordResetDate = Date.from(Instant.now()); } @JsonCreator public User( @JsonProperty("id") final String id, - @JsonProperty("workTeams") final Set workTeams, - @JsonProperty("currentWorkTeam") final WorkTeam currentWorkTeam, + @JsonProperty("currentOwner") final Owner currentOwner, @JsonProperty("name") final String name, + @JsonProperty("description") final String description, + @JsonProperty("userName") final String userName, + @JsonProperty("foto") final Foto foto, @JsonProperty("email") final String email, - @JsonProperty("passwd") final String passwd, - @JsonProperty("lastPasswordResetDate") final Date lastPasswordResetDate, + @JsonProperty("emailSecundario") final String emailSecundario, + @JsonProperty("nickUsers") final Set nickUsers, @JsonProperty("enable") final Boolean enable, @JsonProperty("authorities") final Set authorities, - @JsonProperty("preferences") final UserPreferences preferences) { + @JsonProperty("preferences") final UserPreferences preferences, + @JsonProperty("workTeamDomain") final WorkTeamDomain workTeamDomain, + @JsonProperty("dataBindings") final List dataBindings, + @JsonProperty("fone") String fone) { this.id = id; - this.workTeams = workTeams; - this.currentWorkTeam = currentWorkTeam; + this.currentOwner = currentOwner; this.name = name; + this.description = description; + this.userName = userName; + this.foto = foto; this.email = email; - this.passwd = passwd; - this.lastPasswordResetDate = lastPasswordResetDate; + this.emailSecundario = emailSecundario; + this.fone = fone; + this.setNickUsers(nickUsers); this.enable = enable; this.authorities = authorities; this.preferences = preferences; + this.workTeamDomain = workTeamDomain; + this.dataBindings = dataBindings; } public User(final UserPayLoad payLoad) { this(); this.setName(payLoad.getName()); + this.setDescription(payLoad.getDescription()); + this.setUserName(payLoad.getUserName()); + this.setUserFoto(payLoad.getFoto()); this.setEmail(payLoad.getEmail()); - this.setPasswd(payLoad.getPasswd()); + this.setNickUsers(payLoad.getNickUsers()); + this.setOdinUser(payLoad.isOdinUser()); + this.setFone(payLoad.getFone()); + /*if (!isEmpty(payLoad.getPasswd())) { + this.setPasswd(payLoad.getPasswd()); + }*/ } + public void setFoto(Foto foto) { + this.foto = foto; + } + + public String getResetToken() { + return resetToken; + } + + + public void setResetToken(String token) { + this.resetToken = token; + this.resetTokenExpiryDate = LocalDateTime.now().plusHours(1); // Expira em 1 hora. + } + + + + @Override public String getId() { return id; } @@ -114,44 +171,62 @@ public User setId(final String id) { return this; } - public Set getWorkTeams() { - return workTeams; - } - - public User setWorkTeams(final Collection workTeams) { - this.workTeams = new HashSet<>(workTeams); - return this; + public ObjectId getObjectId() { + if (!isEmpty(getId())) { + try { + return new ObjectId(getId()); + } catch (IllegalArgumentException ex) { + throw new MuttleyInvalidObjectIdException(this.getClass(), "id", "ObjectId inválido"); + } + } + return null; } - public User addWorkTeam(final Collection workTeams) { - this.workTeams.addAll(workTeams); - return this; + public LocalDateTime getResetTokenExpiryDate() { + return resetTokenExpiryDate; } - public User addWorkTeam(final WorkTeam... workTeams) { - for (WorkTeam work : workTeams) { - this.workTeams.add(work); - } - return this; + public void setResetTokenExpiryDate(LocalDateTime resetTokenExpiryDate) { + this.resetTokenExpiryDate = resetTokenExpiryDate; } @JsonIgnore public Owner getCurrentOwner() { - if (isNull(getCurrentWorkTeam())) { - return null; - } - return getCurrentWorkTeam().getOwner(); + return this.currentOwner; } - public WorkTeam getCurrentWorkTeam() { - return currentWorkTeam; + @JsonProperty("currentOwner") + public User setCurrentOwner(final Owner currentOwner) { + this.currentOwner = currentOwner; + return this; } - public User setCurrentWorkTeam(final WorkTeam currentWorkTeam) { - this.currentWorkTeam = currentWorkTeam; - return this; + public User setCurrentOwner(final OwnerData owner) { + if (owner != null) { + final User user; + if (owner.getUserMaster() != null) { + user = new User() + .setId(owner.getUserMaster().getId()) + .setDescription(owner.getUserMaster().getDescription()) + .setEmail(owner.getUserMaster().getEmail()) + .setUserName(owner.getUserMaster().getUserName()) + .setName(owner.getUserMaster().getName()) + .setNickUsers(owner.getUserMaster().getNickUsers()); + } else { + user = null; + } + return this.setCurrentOwner( + new Owner() + .setId(owner.getId()) + .setName(owner.getName()) + .setDescription(owner.getDescription()) + .setUserMaster(user) + ); + } + return this.setCurrentOwner((Owner) null); } + @Override public String getName() { return name; } @@ -161,16 +236,85 @@ public User setName(final String name) { return this; } + @Override + public String getDescription() { + return description; + } + + public User setDescription(final String description) { + this.description = description; + return this; + } + + @Override + public String getUserName() { + return userName; + } + + public User setUserName(final String userName) { + this.userName = userName; + return this; + } + + + public Foto getFoto() { + return foto; + } + + public User setUserFoto(final Foto foto) { + this.foto = foto; + return this; + } + + @Override public String getEmail() { return email; } + @Override + public String getEmailSecundario() { + return emailSecundario; + } + public User setEmail(final String email) { - this.email = email; + this.email = email != null ? email.toLowerCase() : email; return this; } - @JsonIgnore + @Override + public Set getNickUsers() { + return nickUsers; + } + + public User setNickUsers(final Set nickUsers) { + if (nickUsers != null) { + this.nickUsers = nickUsers.parallelStream().map(String::toLowerCase).collect(toSet()); + } + return this; + } + + public String getFone() { + return fone; + } + + public User setFone(String fone) { + this.fone = fone; + return this; + } + + public User addNickUsers(final String nick) { + if (nick != null) { + this.nickUsers.add(nick.toLowerCase()); + } + return this; + } + + public User addNickUsers(final String... nick) { + this.nickUsers.addAll(Stream.of(nick).filter(it -> it != null).map(String::toLowerCase).collect(toSet())); + return this; + } + + /*@JsonIgnore public String getPasswd() { return passwd; } @@ -182,33 +326,27 @@ public User setPasswd(final String passwd) { } this.passwd = new BCryptPasswordEncoder(SALT).encode(passwd); return this; - } - + }*/ +/* public User setPasswd(final User user) { this.passwd = user.passwd; return this; - } + }*/ - public User setPasswd(final Passwd passwd) { - if (!checkPasswd(passwd.getActualPasswd())) { + /*public User setPasswd(final PasswdPayload passwdPayload) { + if (!checkPasswd(passwdPayload.getActualPasswd())) { throw new MuttleySecurityBadRequestException(User.class, "passwd", "A senha atual informada é invalida!").setStatus(HttpStatus.NOT_ACCEPTABLE); } - this.passwd = new BCryptPasswordEncoder(SALT).encode(passwd.getNewPasswd()); + this.passwd = new BCryptPasswordEncoder(SALT).encode(passwdPayload.getNewPasswd()); return this; - } + }*/ - public boolean checkPasswd(final String passwd) { - return checkPasswd(passwd, this.passwd); - } - public boolean checkPasswd(final String passwd, String cryptPasswd) { - return new BCryptPasswordEncoder().matches(passwd, cryptPasswd); - } - @JsonIgnore +/* @JsonIgnore public boolean isValidPasswd() { return this.passwd != null && !this.passwd.isEmpty(); - } + }*/ public Boolean isEnable() { return enable; @@ -223,12 +361,18 @@ public Set getAuthorities() { return authorities; } + @JsonProperty public User setAuthorities(final Set authorities) { authorities.forEach(a -> checkAuthority(a)); this.authorities = authorities; return this; } + public User setAuthorities(final Collection roles) { + this.authorities = roles.parallelStream().map(it -> new AuthorityImpl(it)).collect(toSet()); + return this; + } + public User addAuthority(final Authority authority) { checkAuthority(authority); this.authorities.add(authority); @@ -240,7 +384,7 @@ public User addAuthorities(final Collection authorities) { this.authorities.addAll(authorities); return this; } - +/* public Date getLastPasswordResetDate() { return lastPasswordResetDate; } @@ -248,7 +392,7 @@ public Date getLastPasswordResetDate() { public User setLastPasswordResetDate(final Date lastPasswordResetDate) { this.lastPasswordResetDate = lastPasswordResetDate; return this; - } + }*/ public final boolean inRole(final String role) { try { @@ -263,9 +407,11 @@ public final boolean inRole(final Authority role) { } public final boolean inAnyRole(final String... roles) { - return inAnyRole( - Stream.of(roles).map(r -> new AuthorityImpl(r)) - ); + return inAnyRole(Stream.of(roles).map(AuthorityImpl::new)); + } + + public final boolean inAnyRole(final Role... roles) { + return this.inAnyRole(Stream.of(roles).map(AuthorityImpl::new)); } public final boolean inAnyRole(final Authority... roles) { @@ -273,8 +419,7 @@ public final boolean inAnyRole(final Authority... roles) { } public final boolean inAnyRole(final Stream roles) { - return roles - .anyMatch(getAuthorities()::contains); + return roles.filter(it -> it != null).anyMatch(getAuthorities()::contains); } public UserPreferences getPreferences() { @@ -286,21 +431,103 @@ public User setPreferences(final UserPreferences preferences) { return this; } + public List getDataBindings() { + return dataBindings; + } + + public User setDataBindings(final List dataBindings) { + this.dataBindings = dataBindings; + return this; + } + + public User setWorkTeamDomain(final WorkTeamDomain workTeamDomain) { + this.workTeamDomain = workTeamDomain; + return this; + } + + public WorkTeamDomain getWorkTeamDomain() { + return this.workTeamDomain; + } + + @JsonIgnore + public boolean containsDatabinding(final String key) { + if (this.dataBindingsIsEmpty()) { + return false; + } + return this.dataBindings + .parallelStream() + .filter(it -> it.getKey().toString().equals(key)) + .count() > 0; + } + + @JsonIgnore + public boolean containsDatabinding(final KeyUserDataBinding key) { + if (this.dataBindingsIsEmpty()) { + return false; + } + return this.dataBindings + .parallelStream() + .filter(it -> it.getKey().equals(key)) + .count() > 0; + } + + public boolean dataBindingsIsEmpty() { + return Collections.isEmpty(this.dataBindings); + } + + public UserDataBinding getDataBinding(final String key) { + if (isEmpty(key) || dataBindingsIsEmpty()) { + return null; + } + return this.dataBindings + .parallelStream() + .filter(it -> it.getKey().toString().equals(key)) + .findFirst() + .orElse(null); + } + + public UserDataBinding getDataBinding(final KeyUserDataBinding key) { + if (key == null || dataBindingsIsEmpty()) { + return null; + } + return this.dataBindings + .parallelStream() + .filter(it -> it.getKey().equals(key)) + .findFirst() + .orElse(null); + } + + + public boolean isOdinUser() { + return odinUser; + } + + public User setOdinUser(final boolean odinUser) { + this.odinUser = odinUser; + return this; + } + + @JsonIgnore public boolean containsPreference(final String keyPreference) { return this.preferences.contains(keyPreference); } + @JsonIgnore + public boolean preferencesIsEmpty() { + return this.preferences != null ? this.preferences.isEmpty() : true; + } + @Override public boolean equals(final Object o) { if (this == o) return true; if (!(o instanceof User)) return false; final User user = (User) o; - return Objects.equals(getId(), user.getId()); + return Objects.equals(getId(), user.getId()) && Objects.equals(getEmail(), user.getEmail()) && Objects.equals(getUserName(), user.getUserName()); } @Override public int hashCode() { - return Objects.hash(getId(), "dfg"); + return Objects.hash(getId(), getEmail(), getUserName()); } public String toJson() { @@ -308,14 +535,54 @@ public String toJson() { } @JsonIgnore - public boolean isValidEmail() { + private boolean isValidEmail() { + return User.isValidEmail(this.email); + } + + @JsonIgnore + public static boolean isValidEmail(final String email) { if ((email == null) || (email.trim().isEmpty())) return false; - final Pattern pattern = Pattern.compile(EMAIL_PATTERN, Pattern.CASE_INSENSITIVE); + final Pattern pattern = Pattern.compile(EMAIL_PATTERN, CASE_INSENSITIVE); final Matcher matcher = pattern.matcher(email); return matcher.matches(); } + private boolean isValidUserName() { + return isValidUserName(this.userName); + } + + /** + * realiza validações basicar para o email, userName e nickNames + */ + public void validateBasicInfoForLogin() { + if (!isEmpty(this.email)) { + if (!this.isValidEmail()) { + throw new MuttleySecurityBadRequestException(User.class, "email", "Informe um email válido!"); + } + } + + if (!isEmpty(this.emailSecundario)) { + if (!this.isValidEmail()) { + throw new MuttleySecurityBadRequestException(User.class, "email secundário", "Informe um email secundário válido!"); + } + } + + if (!CollectionUtils.isEmpty(this.nickUsers)) { + + this.nickUsers.forEach(it -> { + if (!isValidUserName(it)) { + new MuttleySecurityBadRequestException(User.class, "nickUsers", "Informe nickUser válidos") + .addDetails("informed", this.nickUsers); + } + }); + } + if (!isValidUserName(this.userName)) { + new MuttleySecurityBadRequestException(User.class, "nickUsers", "Informe nickUser válidos") + .addDetails("informed", this.nickUsers); + } + } + /** * Para evitar bugs com o Mongodb, é necessário garantirmos que não sera salvo um enum como Authority */ @@ -324,4 +591,94 @@ private void checkAuthority(Authority authority) { throw new IllegalArgumentException("Um authority não pode ser instancia de Enum: [" + authority.toString() + "]"); } } + + public static boolean isValidUserName(final String userName) { + return UserNameValidator.isValid(4, 70, userName); + } + + @JsonIgnore + public boolean isOwner() { + if (this.getCurrentOwner() == null) { + throw new MuttleyException("O USUÁRIO ATUAL ESTÁ SEM INFORMAÇÃO DE OWNER"); + } + //verificando se o userMaster do ownerAtual tem tedas as infos + //caso contrario comparamos apenas pelo userName + final User userMaster = this.getCurrentOwner().getUserMaster(); + return this.getUserName().equals(userMaster.getUserName()); + /*return !isEmpty(userMaster.getId()) && !isEmpty(userMaster.getEmail()) && !isEmpty(userMaster.getUserName()) ? + this.equals(userMaster) : this.getUserName().equals(userMaster.getUserName());*/ + } + + public void setEmailSecundario(String emailSecundario) { + this.emailSecundario = emailSecundario; + } + + private static final class UserNameValidator { + private static final char[] SUPPORT_SYMBOLS_CHAR = {'.', '_', '-'}; + //private static final char[] SUPPORT_SYMBOLS_CHAR = {'_', '-'}; + + public static boolean isValid(final int minLength, final int maxLength, final String userName) { + + // check empty + if (userName == null || userName.length() == 0) { + return false; + } + + // check length + if (userName.length() < minLength || userName.length() > maxLength) { + return false; + } + + return isValidUsername(userName.toCharArray()); + } + + private static boolean isValidUsername(final char[] userName) { + + int currentPosition = 0; + boolean valid = true; + + // check char by char + for (char c : userName) { + + // if alphanumeric char, no need check, process next + if (!Character.isLetterOrDigit(c)) { + + // for non-alphanumeric char, also not a supported symbol, break + if (!isSupportedSymbols(c)) { + valid = false; + break; + } + + // ensures first and last char not a supported symbol + if (userName[0] == c || userName[userName.length - 1] == c) { + valid = false; + break; + } + + // ensure supported symbol does not appear consecutively + // is next position also a supported symbol? + if (isSupportedSymbols(userName[currentPosition + 1])) { + valid = false; + break; + } + + } + + currentPosition++; + } + + return valid; + + } + + private static boolean isSupportedSymbols(final char symbol) { + for (char temp : SUPPORT_SYMBOLS_CHAR) { + if (temp == symbol) { + return true; + } + } + return false; + } + + } } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/UserBase.java b/muttley-model/src/main/java/br/com/muttley/model/security/UserBase.java new file mode 100644 index 00000000..81b1197c --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/UserBase.java @@ -0,0 +1,71 @@ +package br.com.muttley.model.security; + +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.Model; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.index.CompoundIndexes; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; + +import javax.validation.Valid; +import javax.validation.constraints.Size; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 25/11/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Document(collection = "#{documentNameConfig.getNameCollectionUserBase()}") +@CompoundIndexes({ + @CompoundIndex(name = "owner_index_unique", def = "{'owner' : 1}", unique = true) +}) +@Getter +@Setter +@Accessors(chain = true) +@TypeAlias(UserBase.TYPE_ALIAS) +public class UserBase implements Model { + @Transient + @JsonIgnore + public static final String TYPE_ALIAS = "user-base"; + + @Id + private String id; + + @JsonIgnore + @DBRef + private Owner owner; + + @Size.List({@Size(min = 1, message = "Informe pelo menos um usuário")}) + @Valid + private Set users; + + private MetadataDocument metadata; + + public UserBase() { + this.users = new HashSet<>(); + } + + public UserBase addUser(final UserBaseItem userBaseItem) { + if (userBaseItem != null) { + this.users.add(userBaseItem); + } + return this; + } + + public UserBase addUser(final UserData currentUser, final UserData user) { + if (user != null) { + this.addUser(new UserBaseItem(currentUser, user, null, new Date(), true, null)); + } + return this; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/UserBaseItem.java b/muttley-model/src/main/java/br/com/muttley/model/security/UserBaseItem.java new file mode 100644 index 00000000..e2945d16 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/UserBaseItem.java @@ -0,0 +1,81 @@ +package br.com.muttley.model.security; + +import br.com.muttley.model.security.jackson.UserDataDeserializer; +import br.com.muttley.model.security.jackson.UserDataSerializer; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Transient; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.util.CollectionUtils; + +import javax.validation.constraints.NotNull; +import java.util.Date; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 04/12/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = "user") +public class UserBaseItem { + @DBRef + @NotNull(message = "Informe o usuário que está efetuando essa operação") + @JsonSerialize(using = UserDataSerializer.class) + @JsonDeserialize(using = UserDataDeserializer.class) + private UserData addedBy; + + @DBRef + @NotNull(message = "Informe o usuário participante da base") + @JsonSerialize(using = UserDataSerializer.class) + @JsonDeserialize(using = UserDataDeserializer.class) + private UserData user; + + @Transient + private UserPayLoad userInfoForMerge; + + @NotNull + private Date dtCreate; + + private boolean status; + + /** + * Variável para apenas transacionar os item de usuário para databindings + */ + @Transient + private Set dataBindings; + + public UserBaseItem() { + this.status = true; + } + + @JsonCreator + public UserBaseItem( + @JsonProperty("addedBy") final UserData addedBy, + @JsonProperty("user") final UserData user, + @JsonProperty("userInfoForMerge") final UserPayLoad userInfoForMerge, + @JsonProperty("dtCreate") final Date dtCreate, + @JsonProperty("status") final boolean status, + @JsonProperty("dataBindings") Set dataBindings) { + this(); + this.addedBy = addedBy; + this.user = user; + this.userInfoForMerge = userInfoForMerge; + this.dtCreate = dtCreate; + this.status = status; + this.dataBindings = dataBindings; + } + + public boolean dataBindingsIsEmpty() { + return CollectionUtils.isEmpty(this.dataBindings); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/UserData.java b/muttley-model/src/main/java/br/com/muttley/model/security/UserData.java new file mode 100644 index 00000000..259d0488 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/UserData.java @@ -0,0 +1,30 @@ +package br.com.muttley.model.security; + +import br.com.muttley.model.security.preference.Foto; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 28/12/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@JsonDeserialize(as = User.class) +public interface UserData { + String getId(); + + String getName(); + + String getDescription(); + + String getUserName(); + + Foto getFoto(); + + String getEmail(); + + String getEmailSecundario(); + + Set getNickUsers(); +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/UserDataBinding.java b/muttley-model/src/main/java/br/com/muttley/model/security/UserDataBinding.java new file mode 100644 index 00000000..97a6d08f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/UserDataBinding.java @@ -0,0 +1,64 @@ +package br.com.muttley.model.security; + +import br.com.muttley.annotations.index.CompoundIndexes; +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.Model; +import br.com.muttley.model.jackson.converter.DocumentSerializer; +import br.com.muttley.model.security.jackson.OwnerDeserializer; +import br.com.muttley.model.security.jackson.UserDataDeserializer; +import br.com.muttley.model.security.jackson.UserDataSerializer; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; + +import javax.validation.constraints.NotNull; + +/** + * @author Joel Rodrigues Moreira 12/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + * Armazena dados que devem ser lincados ao usuário por owners + */ +@Document(collection = "#{documentNameConfig.getNameCollectionUserDataBinding()}") +@CompoundIndexes({ + @CompoundIndex(name = "owner_user_key_index_unique", def = "{'owner': 1, 'user': 1, 'key': 1}", unique = true) +}) +@TypeAlias(UserDataBinding.TYPE_ALIAS) +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = {"key"}) +public class UserDataBinding implements Model { + @Transient + @JsonIgnore + public static final String TYPE_ALIAS = "user-data-binding"; + + private String id; + @JsonSerialize(using = DocumentSerializer.class) + @JsonDeserialize(using = OwnerDeserializer.class) + @DBRef + private Owner owner; + @NotNull(message = "Informe um usuário") + @JsonSerialize(using = UserDataSerializer.class) + @JsonDeserialize(using = UserDataDeserializer.class) + @DBRef + private UserData user; + private KeyUserDataBinding key; + private String value; + @Transient + @JsonIgnore + protected Object resolvedValue; + @Transient + @JsonIgnore + protected boolean resolved = false; + private MetadataDocument metadata; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/UserDataImpl.java b/muttley-model/src/main/java/br/com/muttley/model/security/UserDataImpl.java new file mode 100644 index 00000000..de5f187f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/UserDataImpl.java @@ -0,0 +1,53 @@ +package br.com.muttley.model.security; + +import br.com.muttley.model.security.preference.Foto; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.Email; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toSet; + +/** + * @author Joel Rodrigues Moreira 20/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class UserDataImpl implements UserData { + protected String id; + protected String name; + protected String userName; + protected Foto foto; + protected String description; + protected String email; + @Email(message = "Informe um email secundário válido!") + protected String emailSecundario; + protected Set nickUsers = new HashSet<>(); + + public UserDataImpl setNickUsers(final Set nickUsers) { + if (nickUsers != null) { + this.nickUsers = nickUsers.parallelStream().map(String::toLowerCase).collect(toSet()); + } + return this; + } + + public UserDataImpl addNickUsers(final String nick) { + if (nick != null) { + this.nickUsers.add(nick.toLowerCase()); + } + return this; + } + + public UserDataImpl addNickUsers(final String... nick) { + this.nickUsers.addAll(Stream.of(nick).filter(it -> it != null).map(String::toLowerCase).collect(toSet())); + return this; + } + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/UserPayLoad.java b/muttley-model/src/main/java/br/com/muttley/model/security/UserPayLoad.java index 08b85196..560cbd17 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/UserPayLoad.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/UserPayLoad.java @@ -1,48 +1,283 @@ package br.com.muttley.model.security; +import br.com.muttley.model.security.preference.Foto; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotBlank; +import org.springframework.util.ObjectUtils; import javax.validation.constraints.Size; import java.io.Serializable; +import java.util.Objects; +import java.util.Set; /** * @author Joel Rodrigues Moreira on 23/04/18. * e-mail: joel.databox@gmail.com * @project muttley-cloud */ +@EqualsAndHashCode(of = {"email", "userName"}) public class UserPayLoad implements Serializable { @NotBlank(message = "O campo nome não pode ser nulo!") @Size(min = 4, max = 200, message = "O campo nome deve ter de 4 a 200 caracteres!") private String name; - @NotBlank(message = "Informe um email válido!") + private String description; @Email(message = "Informe um email válido!") private String email; + @Email(message = "Informe um email secundário válido!") + private String emailSecundario; + + private String userName; + private Foto foto; + private Set nickUsers; @NotBlank(message = "Informe uma senha valida!") private String passwd; + private String fone; + private boolean odinUser = false; + + //numero enviado por sms para confirma o numero telefonico + private String seedVerification; + + //numero que mobilidade receber e que deve ser comparado com o seed + private String codeVerification; + + private boolean renewCode = false; + //private Set dataBindings; @JsonCreator public UserPayLoad( @JsonProperty("name") final String name, + @JsonProperty("description") final String description, @JsonProperty("email") final String email, - @JsonProperty("passwd") final String passwd) { + @JsonProperty("emailSecundario") final String emailSecundario, + @JsonProperty("userName") final String userName, + @JsonProperty("foto") final Foto foto, + @JsonProperty("nickUsers") final Set nickUsers, + @JsonProperty("passwd") final String passwd, + @JsonProperty("fone") String fone, + @JsonProperty("odinUser") boolean odinUser, + @JsonProperty("seedVerification") String seedVerification, + @JsonProperty("codeVerification") String codeVerification, + @JsonProperty("renewCode") boolean renewCode + + /*@JsonProperty("dataBindings") final Set dataBindings*/) { this.name = name; + this.description = description; this.email = email; + this.emailSecundario = emailSecundario; + this.userName = userName; + this.foto = foto; + this.nickUsers = nickUsers; this.passwd = passwd; + this.fone = fone; + this.odinUser = odinUser; + this.seedVerification = seedVerification; + this.codeVerification = codeVerification; + this.renewCode = renewCode; + //this.dataBindings = dataBindings; } public String getName() { return name; } + public String getDescription() { + return description; + } + public String getEmail() { return email; } + public String getEmailSecundario() { + return emailSecundario; + } + + + public String getUserName() { + return userName; + } + + public Foto getFoto() { + return foto; + } + + public Set getNickUsers() { + return nickUsers; + } + public String getPasswd() { return passwd; } + + public String getFone() { + return fone; + } + + public UserPayLoad setFone(String fone) { + this.fone = fone; + return this; + } + + public boolean isOdinUser() { + return odinUser; + } + + public UserPayLoad setOdinUser(boolean odinUser) { + this.odinUser = odinUser; + return this; + } + + public String getSeedVerification() { + return seedVerification; + } + + public UserPayLoad setSeedVerification(String seedVerification) { + this.seedVerification = seedVerification; + return this; + } + + public String getCodeVerification() { + return codeVerification; + } + + public UserPayLoad setCodeVerification(String codeVerification) { + this.codeVerification = codeVerification; + return this; + } + + public boolean seedHasBeeVerificate() { + if (this.getSeedVerification() == null) { + return false; + } + return Objects.equals(this.getSeedVerification(), this.getCodeVerification()); + } + + public boolean seedVerificationIsEmpty() { + return ObjectUtils.isEmpty(this.getSeedVerification()); + } + + public boolean codeVerificationIsEmpty() { + return ObjectUtils.isEmpty(this.getCodeVerification()); + } + + public boolean isRenewCode() { + return renewCode; + } + + public UserPayLoad setRenewCode(boolean renewCode) { + this.renewCode = renewCode; + return this; + } + + public static final class Builder { + private String name; + private String description; + private String email; + private String emailSecundario; + private String userName; + private Foto foto; + private Set nickUsers; + private String passwd; + private String fone; + + private boolean odinUser; + + //numero enviado por sms para confirma o numero telefonico + private String seedVerification; + //numero que mobilidade receber e que deve ser comparado com o seed + private String codeVerification; + private boolean renewCode = false; + + private Builder() { + } + + public Builder setName(final String name) { + this.name = name; + return this; + } + + public Builder setDescription(final String description) { + this.description = description; + return this; + } + + public Builder setEmail(final String email) { + this.email = email; + return this; + } + + public Builder setEmailSecundario(final String emailSecundario) { + this.emailSecundario = emailSecundario; + return this; + } + + + public Builder setUserName(final String userName) { + this.userName = userName; + return this; + } + + public Builder setFoto(final Foto foto) { + this.foto = foto; + return this; + } + + public Builder setNickUsers(final Set nickUsers) { + this.nickUsers = nickUsers; + return this; + } + + public Builder setPasswd(final String passwd) { + this.passwd = passwd; + return this; + } + + public Builder setFone(String fone) { + this.fone = fone; + return this; + } + + public Builder setOdinUser(boolean odinUser) { + this.odinUser = odinUser; + return this; + } + + public Builder setSeedVerification(String seedVerification) { + this.seedVerification = seedVerification; + return this; + } + + public Builder setCodeVerification(String codeVerification) { + this.codeVerification = codeVerification; + return this; + } + + public Builder setRenewCode(boolean renewCode) { + this.renewCode = renewCode; + return this; + } + + public Builder set(final User user) { + return this.setName(user.getName()) + .setDescription(user.getDescription()) + .setEmail(user.getEmail()) + .setEmailSecundario(user.getEmailSecundario()) + .setUserName(user.getUserName()) + .setFoto(user.getFoto()) + .setNickUsers(user.getNickUsers()) + .setOdinUser(user.isOdinUser()); + } + + public static Builder newInstance() { + return new Builder(); + } + + public UserPayLoad build() { + return new UserPayLoad(this.name, this.description, this.email, this.emailSecundario, this.userName, this.foto, this.nickUsers, this.passwd, this.fone, this.odinUser, this.seedVerification, this.codeVerification, this.renewCode); + } + } } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/UserView.java b/muttley-model/src/main/java/br/com/muttley/model/security/UserView.java new file mode 100644 index 00000000..47ded7de --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/UserView.java @@ -0,0 +1,69 @@ +package br.com.muttley.model.security; + +import br.com.muttley.model.Document; +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.security.preference.Foto; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.Email; +import org.springframework.data.mongodb.core.mapping.DBRef; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 29/04/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = {"id", "email", "userName"}) +public class UserView implements UserData, Document { + private String id; + private String name; + private String description; + private String userName; + private Foto foto; + private String email; + @Email(message = "Informe um email secundário válido!") + private String emailSecundario; + private Set nickUsers; + private boolean status; + private String fone; + @DBRef + @JsonIgnore + private Owner owner; + private MetadataDocument metadata; + + public UserView() { + } + + public UserView(final User user) { + this.setId(user.getId()) + .setName(user.getName()) + .setDescription(user.getDescription()) + .setUserName(user.getUserName()) + .setFoto(user.getFoto()) + .setEmail(user.getEmail()) + .setEmailSecundario(user.getEmailSecundario()) + .setNickUsers(user.getNickUsers()) + .setFone(user.getFone()) + .setOwner(user.getCurrentOwner()); + } + + private UserView setOwner(final Passaport passaport) { + if (passaport != null) { + this.setOwner(passaport.getOwner()); + } + return this; + } + + public UserView setOwner(final Owner owner) { + this.owner = owner; + return this; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/WorkTeam.java b/muttley-model/src/main/java/br/com/muttley/model/security/WorkTeam.java deleted file mode 100644 index 5010e50f..00000000 --- a/muttley-model/src/main/java/br/com/muttley/model/security/WorkTeam.java +++ /dev/null @@ -1,151 +0,0 @@ -package br.com.muttley.model.security; - -import br.com.muttley.model.Document; -import br.com.muttley.model.Historic; -import com.google.common.base.Objects; -import org.hibernate.validator.constraints.NotBlank; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.index.CompoundIndex; -import org.springframework.data.mongodb.core.index.CompoundIndexes; -import org.springframework.data.mongodb.core.mapping.DBRef; - -import javax.validation.constraints.NotNull; -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * @author Joel Rodrigues Moreira on 24/04/18. - * e-mail: joel.databox@gmail.com - * @project muttley-cloud - */ -@org.springframework.data.mongodb.core.mapping.Document(collection = "#{documentNameConfig.getNameCollectionWorkTeam()}") -@CompoundIndexes({ - @CompoundIndex(name = "name_userMaster_index_unique", def = "{'name' : 1, 'userMaster': 1}", unique = true) -}) -public class WorkTeam implements Document { - @Id - protected String id; - @NotBlank(message = "Informe um nome válido para o grupo") - protected String name; - protected String description; - @NotNull(message = "É nécessário ter um usuário master no grupo de trabalho") - @DBRef - protected User userMaster; - @NotNull(message = "É nécessário informar quem é o owner do grupo de trabalho") - @DBRef - protected Owner owner; - @DBRef - protected Set members; - protected Historic historic; - protected Set authorities; - - public WorkTeam() { - this.members = new LinkedHashSet<>(); - this.authorities = new LinkedHashSet<>(); - } - - @Override - public String getId() { - return id; - } - - @Override - public WorkTeam setId(final String id) { - this.id = id; - return this; - } - - public String getName() { - return name; - } - - public WorkTeam setName(final String name) { - this.name = name; - return this; - } - - - public String getDescription() { - return description; - } - - public WorkTeam setDescription(final String description) { - this.description = description; - return this; - } - - - public User getUserMaster() { - return userMaster; - } - - public WorkTeam setUserMaster(final User userMaster) { - this.userMaster = userMaster; - this.addMember(this.userMaster); - return this; - } - - - public Owner getOwner() { - return owner; - } - - public WorkTeam setOwner(final Owner owner) { - this.owner = owner; - return this; - } - - - public Set getMembers() { - return members; - } - - public WorkTeam setMembers(final Set members) { - this.members = members; - return this; - } - - public WorkTeam addMember(final User user) { - this.members.add(user); - return this; - } - - @Override - public Historic getHistoric() { - return historic; - } - - @Override - public WorkTeam setHistoric(final Historic historic) { - this.historic = historic; - return this; - } - - - public Set getAuthorities() { - return authorities; - } - - public WorkTeam setAuthorities(final Set authorities) { - this.authorities = authorities; - return this; - } - - public WorkTeam addAuthority(final Authority authority) { - this.authorities.add(authority); - return this; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (!(o instanceof WorkTeam)) return false; - WorkTeam workTeam = (WorkTeam) o; - return Objects.equal(id, workTeam.id); - } - - @Override - public int hashCode() { - return Objects.hashCode(id, -33); - } -} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/XAPIToken.java b/muttley-model/src/main/java/br/com/muttley/model/security/XAPIToken.java new file mode 100644 index 00000000..995fdb55 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/XAPIToken.java @@ -0,0 +1,134 @@ +package br.com.muttley.model.security; + +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.Model; +import br.com.muttley.model.jackson.converter.DocumentSerializer; +import br.com.muttley.model.security.jackson.OwnerDeserializer; +import br.com.muttley.model.security.jackson.UserDeserializer; +import br.com.muttley.model.security.jackson.UserSerializer; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.NotEmpty; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.index.CompoundIndexes; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.util.ObjectUtils; + +import javax.validation.constraints.NotNull; +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira on 08/08/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@org.springframework.data.mongodb.core.mapping.Document(collection = "#{documentNameConfig.getNameCollectionXAPIToken()}") +@CompoundIndexes({ + @CompoundIndex(name = "owner_index", def = "{'owner' : 1}"), + @CompoundIndex(name = "owner.id_index", def = "{'owner.$id' : 1}"), + @CompoundIndex(name = "owner.id_user.$id_index", def = "{'owner.$id' : 1, 'user.$id' : 1}"), + @CompoundIndex(name = "token.index", def = "{'token' : 1}") +}) +@Getter +@Setter +@Accessors(chain = true) +public class XAPIToken implements Model, UserDetails { + @Id + private String id; + @DBRef + @JsonSerialize(using = DocumentSerializer.class) + @JsonDeserialize(using = OwnerDeserializer.class) + private Owner owner; + @NotNull(message = "É nécessário ter um usuário master no grupo de trabalho") + @JsonSerialize(using = UserSerializer.class) + @JsonDeserialize(using = UserDeserializer.class) + @DBRef + private User user; + @NotNull + private Date dtCreate; + @NotNull + private String version; + @NotNull(message = "Informe uma descrição válida") + private String description; + @NotEmpty(message = "É necessário ter um token devidamente gerado") + private String token; + private String locaSeed; + private MetadataDocument metadata; + + @JsonIgnore + public String generateSeedHash() { + final Map map = new HashMap<>(6); + map.put("owner", this.owner != null ? owner.getId() : null); + map.put("user", this.user != null ? user.getId() : null); + map.put("dtCreate", this.dtCreate); + map.put("version", this.version); + map.put("description", this.description); + map.put("localSeed", this.locaSeed); + + try { + return new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(map); + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return null; + } + + @Override + public boolean isAccountNonExpired() { + return false; + } + + @Override + public boolean isAccountNonLocked() { + return false; + } + + @Override + public boolean isCredentialsNonExpired() { + return false; + } + + @Override + public boolean isEnabled() { + return false; + } + + public Date generateDtExpiration() { + return Date.from(Instant.now().plus(Duration.ofHours(2))); + } + + public boolean isEmpty() { + return ObjectUtils.isEmpty(this.getToken()); + } + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/authentication/Authentication.java b/muttley-model/src/main/java/br/com/muttley/model/security/authentication/Authentication.java new file mode 100644 index 00000000..83835219 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/authentication/Authentication.java @@ -0,0 +1,51 @@ +package br.com.muttley.model.security.authentication; + +import br.com.muttley.model.security.Authority; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.model.workteam.WorkTeamDomain; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 19/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface Authentication { + User getCurrentUser(); + + Authentication setCurrentUser(final User user); + + Owner getCurrentOwner(); + + Authentication setCurrentOwner(final Owner currentOwner); + + Set getAuthorities(); + + Authentication setAuthorities(final Set authorities); + + Authentication setAuthorities(final Collection roles); + + WorkTeamDomain getWorkTeam(); + + Authentication setWorkTeam(final WorkTeamDomain domain); + + UserPreferences getPreferences(); + + Authentication setPreferences(final UserPreferences preferences); + + List getDataBindings(); + + Authentication setDataBindings(final List dataBindings); + + boolean isOdiUser(); + + Authentication setOdinUser(final boolean odinUser); + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/authentication/AuthenticationImpl.java b/muttley-model/src/main/java/br/com/muttley/model/security/authentication/AuthenticationImpl.java new file mode 100644 index 00000000..03a73b93 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/authentication/AuthenticationImpl.java @@ -0,0 +1,145 @@ +package br.com.muttley.model.security.authentication; + +import br.com.muttley.model.security.Authority; +import br.com.muttley.model.security.AuthorityImpl; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.security.jackson.UserDeserializer; +import br.com.muttley.model.security.jackson.UserSerializer; +import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.model.workteam.WorkTeamDomain; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Transient; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toSet; + +/** + * @author Joel Rodrigues Moreira on 19/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class AuthenticationImpl implements Authentication { + @JsonSerialize(using = UserSerializer.class) + @JsonDeserialize(using = UserDeserializer.class) + private User currentUser; + @Transient + private Owner currentOwner; + @Transient + private WorkTeamDomain workTeam; + @Transient + private Set authorities; + @Transient + private List dataBindings; + @Transient + private UserPreferences preferences; + private boolean odinUser = false; + + public AuthenticationImpl() { + this.authorities = new HashSet<>(); + this.dataBindings = new ArrayList<>(); + } + + public AuthenticationImpl(final XAPIToken xapiToken) { + this.currentUser = xapiToken.getUser(); + this.currentOwner = xapiToken.getOwner(); + //this.workTeam = xapiToken.get + this.authorities = new HashSet<>(); + this.dataBindings = new ArrayList<>(); + } + + + public final boolean inRole(final Authority role) { + return getAuthorities().contains(role); + } + + public final boolean inAnyRole(final String... roles) { + return inAnyRole(Stream.of(roles).map(AuthorityImpl::new)); + } + + public final boolean inAnyRole(final Role... roles) { + return this.inAnyRole(Stream.of(roles).map(AuthorityImpl::new)); + } + + public final boolean inAnyRole(final Authority... roles) { + return inAnyRole(Stream.of(roles)); + } + + public final boolean inAnyRole(final Stream roles) { + return roles.filter(it -> it != null).anyMatch(getAuthorities()::contains); + } + + @Override + public Authentication setAuthorities(Set authorities) { + this.authorities = authorities; + return this; + } + + @Override + public Authentication setAuthorities(final Collection roles) { + this.authorities = roles.parallelStream().map(it -> new AuthorityImpl(it)).collect(toSet()); + return this; + } + + @Override + public boolean isOdiUser() { + return odinUser; + } + + @Override + public Set getAuthorities() { + return authorities; + } + + ///////////////////////////////// + /*@Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return this.getCurrentUser().getUserName(); + } + + @Override + public boolean isAccountNonExpired() { + return false; + } + + @Override + public boolean isAccountNonLocked() { + return false; + } + + @Override + public boolean isCredentialsNonExpired() { + return false; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean isOdiUser() { + return false; + }*/ +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/converters/KeyUserDataBindingToStringConverter.java b/muttley-model/src/main/java/br/com/muttley/model/security/converters/KeyUserDataBindingToStringConverter.java new file mode 100644 index 00000000..6ef36466 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/converters/KeyUserDataBindingToStringConverter.java @@ -0,0 +1,18 @@ +package br.com.muttley.model.security.converters; + +import br.com.muttley.model.security.KeyUserDataBinding; +import org.springframework.core.convert.converter.Converter; + +/** + * @author Joel Rodrigues Moreira 10/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class KeyUserDataBindingToStringConverter implements Converter { + + + @Override + public String convert(final KeyUserDataBinding source) { + return source == null ? null : source.getKey(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/converters/StringToKeyUserDataBindingConverter.java b/muttley-model/src/main/java/br/com/muttley/model/security/converters/StringToKeyUserDataBindingConverter.java new file mode 100644 index 00000000..3e3bf103 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/converters/StringToKeyUserDataBindingConverter.java @@ -0,0 +1,39 @@ +package br.com.muttley.model.security.converters; + +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.model.security.KeyUserDataBinding; +import br.com.muttley.model.security.events.KeyUserDataBindingResolverEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.core.convert.converter.Converter; + +/** + * @author Joel Rodrigues Moreira 10/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class StringToKeyUserDataBindingConverter implements Converter { + private final ApplicationEventPublisher publisher; + + @Autowired + public StringToKeyUserDataBindingConverter(final ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + @Override + public KeyUserDataBinding convert(final String source) { + if (source == null) { + return null; + } + final KeyUserDataBindingResolverEvent event = new KeyUserDataBindingResolverEvent(source); + this.publisher.publishEvent(event); + if (!event.isResolved()) { + final KeyUserDataBinding result = KeyUserDataBinding.from(source); + if (result == null) { + throw new MuttleyException("Crie um listener pra resolver isso"); + } + return result; + } + return event.getResolvedValue(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/domain/Domain.java b/muttley-model/src/main/java/br/com/muttley/model/security/domain/Domain.java new file mode 100644 index 00000000..86b31615 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/domain/Domain.java @@ -0,0 +1,21 @@ +package br.com.muttley.model.security.domain; + +/** + * @author Joel Rodrigues Moreira on 16/02/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public enum Domain { + /** + * Qualquer registro que estiver marcado como {@link PRIVATE} somente quem criou e seus supervisores poderá visualizar + */ + PRIVATE, + /** + * Qualquer registro que estiver marcado como {@link RESTRICTED} somente quem criou e participantes do grupo de trabalho e seus supervisores poderá visualizar + */ + RESTRICTED, + /** + * Qualquer registro que estiver marcado como {@link PUBLIC} qualquer pessoa poderá visualizar o registro + */ + PUBLIC; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/enumeration/Authorities.java b/muttley-model/src/main/java/br/com/muttley/model/security/enumeration/Authorities.java deleted file mode 100644 index 5d531e0f..00000000 --- a/muttley-model/src/main/java/br/com/muttley/model/security/enumeration/Authorities.java +++ /dev/null @@ -1,25 +0,0 @@ -package br.com.muttley.model.security.enumeration; - - -import br.com.muttley.model.security.Authority; - -public enum Authorities implements Authority { - ROLE_USER("Permissão simples de usuário"), - ROLE_ADMIN("Permissão de administrador"); - - private final String description; - - Authorities(final String description) { - this.description = description; - } - - @Override - public String getName() { - return this.name(); - } - - @Override - public String getDescription() { - return this.description; - } -} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/AbstractValidateOwnerEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/AbstractValidateOwnerEvent.java new file mode 100644 index 00000000..e6f2939b --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/AbstractValidateOwnerEvent.java @@ -0,0 +1,30 @@ +package br.com.muttley.model.security.events; + +import br.com.muttley.model.security.User; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * Evento disparado toda vez que se tenta inserir um novo registro + * O mesmo se faz necessário para não haver vazamento de segurança. + * + * @author Joel Rodrigues Moreira on 08/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class AbstractValidateOwnerEvent extends ApplicationEvent { + @Getter + private final User currenteUserFromRequest; + private final T value; + + public AbstractValidateOwnerEvent(final User currenteUserFromRequest, final T source) { + super(source); + this.value = source; + this.currenteUserFromRequest = currenteUserFromRequest; + } + + @Override + public T getSource() { + return this.value; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/DeserializeUserPreferencesEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/DeserializeUserPreferencesEvent.java new file mode 100644 index 00000000..b24fb1b5 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/DeserializeUserPreferencesEvent.java @@ -0,0 +1,45 @@ +package br.com.muttley.model.security.events; + +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.preference.UserPreferences; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira on 25/03/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + *

+ * Evento é disparado toda vez que se carrega as preferencias do usuário logado + * O ouvinte será responsável por carregar detalhes da mesma caso necessário como + * por exemplo o owner e tudo mais + */ +public class DeserializeUserPreferencesEvent extends ApplicationEvent { + public DeserializeUserPreferencesEvent(final UserPreferencesResolverEventItem preferences) { + super(preferences); + } + + @Override + public UserPreferencesResolverEventItem getSource() { + return (UserPreferencesResolverEventItem) super.getSource(); + } + + public User getUser() { + return this.getSource().getUser(); + } + + public UserPreferences getPreferences() { + return this.getSource().getUserPreferences(); + } + + @Getter + public static class UserPreferencesResolverEventItem { + private final User user; + private final UserPreferences userPreferences; + + public UserPreferencesResolverEventItem(User user, UserPreferences userPreferences) { + this.user = user; + this.userPreferences = userPreferences; + } + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/KeyUserDataBindingResolverEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/KeyUserDataBindingResolverEvent.java new file mode 100644 index 00000000..5d75b073 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/KeyUserDataBindingResolverEvent.java @@ -0,0 +1,35 @@ +package br.com.muttley.model.security.events; + +import br.com.muttley.model.security.KeyUserDataBinding; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira 10/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class KeyUserDataBindingResolverEvent extends ApplicationEvent { + private KeyUserDataBinding resolvedValue; + + public KeyUserDataBindingResolverEvent(final String key) { + super(key); + } + + @Override + public String getSource() { + return (String) super.getSource(); + } + + public KeyUserDataBinding getResolvedValue() { + return resolvedValue; + } + + public KeyUserDataBindingResolverEvent setResolvedValue(final KeyUserDataBinding resolvedValue) { + this.resolvedValue = resolvedValue; + return this; + } + + public boolean isResolved() { + return resolvedValue != null; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/PassaportResolverEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/PassaportResolverEvent.java new file mode 100644 index 00000000..c20dcb03 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/PassaportResolverEvent.java @@ -0,0 +1,15 @@ +package br.com.muttley.model.security.events; + +import br.com.muttley.model.jackson.converter.event.ModelResolverEvent; +import br.com.muttley.model.security.Passaport; + +/** + * @author Joel Rodrigues Moreira on 07/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class PassaportResolverEvent extends ModelResolverEvent { + public PassaportResolverEvent(String id) { + super(id); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/SendNewPasswordRecoveredEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/SendNewPasswordRecoveredEvent.java new file mode 100644 index 00000000..4bf276f5 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/SendNewPasswordRecoveredEvent.java @@ -0,0 +1,28 @@ +package br.com.muttley.model.security.events; + +import br.com.muttley.model.security.Password; +import br.com.muttley.model.security.User; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira on 28/10/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class SendNewPasswordRecoveredEvent extends ApplicationEvent { + private final User user; + @Getter + private final String hallPassword; + + public SendNewPasswordRecoveredEvent(User source, final String hallPassword) { + super(source); + this.user = source; + this.hallPassword = hallPassword; + } + + @Override + public User getSource() { + return this.user; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/UserAfterCacheLoadEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/UserAfterCacheLoadEvent.java index 92ee15d2..97da8246 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/events/UserAfterCacheLoadEvent.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/UserAfterCacheLoadEvent.java @@ -1,6 +1,8 @@ package br.com.muttley.model.security.events; +import br.com.muttley.model.security.JwtToken; import br.com.muttley.model.security.User; +import lombok.Getter; import org.springframework.context.ApplicationEvent; /** @@ -18,12 +20,31 @@ public class UserAfterCacheLoadEvent extends ApplicationEvent { * * @param source the object on which the event initially occurred (never {@code null}) */ - public UserAfterCacheLoadEvent(final User source) { - super(source); + public UserAfterCacheLoadEvent(final JwtToken token, final User user) { + super(new UserAfterCacheLoadEventSource(token, user)); } @Override - public User getSource() { - return (User) super.getSource(); + public UserAfterCacheLoadEventSource getSource() { + return (UserAfterCacheLoadEventSource) super.getSource(); + } + + public User getUser() { + return this.getSource().getUser(); + } + + public JwtToken getJwtToken() { + return this.getSource().getToken(); + } + + @Getter + private static class UserAfterCacheLoadEventSource { + private final JwtToken token; + private final User user; + + public UserAfterCacheLoadEventSource(final JwtToken token, final User user) { + this.token = token; + this.user = user; + } } } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/UserLoggedEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/UserLoggedEvent.java index 979bb1df..e97e806f 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/events/UserLoggedEvent.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/UserLoggedEvent.java @@ -1,6 +1,8 @@ package br.com.muttley.model.security.events; +import br.com.muttley.model.security.JwtToken; import br.com.muttley.model.security.User; +import lombok.Getter; import org.springframework.context.ApplicationEvent; /** @@ -11,17 +13,24 @@ * Evento lançado toda vez que um usuário se loga no sistema. */ public class UserLoggedEvent extends ApplicationEvent { + @Getter + private final User user; + @Getter + private final JwtToken token; + /** * Create a new ApplicationEvent. * * @param source the object on which the event initially occurred (never {@code null}) */ - public UserLoggedEvent(final User source) { + public UserLoggedEvent(final JwtToken token, final User source) { super(source); + this.token = token; + this.user = source; } @Override public User getSource() { - return (User) super.getSource(); + return user; } } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/UserResolverEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/UserResolverEvent.java index b040f67e..8737a34e 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/events/UserResolverEvent.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/UserResolverEvent.java @@ -9,12 +9,12 @@ * @project muttley-cloud */ public class UserResolverEvent extends ApplicationEvent { - final String email; + final String userName; protected User valueResolved; - public UserResolverEvent(final String email) { - super(email); - this.email = email; + public UserResolverEvent(final String userName) { + super(userName); + this.userName = userName; } public User getUserResolver() { @@ -26,8 +26,8 @@ public UserResolverEvent setValueResolved(final User valueResolved) { return this; } - public String getEmail() { - return email; + public String getUserName() { + return userName; } public boolean isResolved() { diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/UserResolverFromJWTEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/UserResolverFromJWTEvent.java new file mode 100644 index 00000000..71b12d48 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/UserResolverFromJWTEvent.java @@ -0,0 +1,33 @@ +package br.com.muttley.model.security.events; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira 12/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class UserResolverFromJWTEvent extends ApplicationEvent { + protected final JwtToken token; + @Getter + @Setter + protected User userResolved; + + public UserResolverFromJWTEvent(final JwtToken token) { + super(token); + this.token = token; + } + + @Override + public JwtToken getSource() { + return this.token; + } + + public boolean isResolved() { + return this.userResolved != null; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidadeUserForeCreateEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidadeUserForeCreateEvent.java new file mode 100644 index 00000000..da9ae5ef --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidadeUserForeCreateEvent.java @@ -0,0 +1,35 @@ +package br.com.muttley.model.security.events; + +import br.com.muttley.model.security.UserPayLoad; +import org.springframework.context.ApplicationEvent; +import org.springframework.util.ObjectUtils; + +import java.util.Objects; +import java.util.Random; + +/** + * @author Joel Rodrigues Moreira on 29/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + *

+ * Evento criado para implementar lógica de validação de numero telefonico + */ +public class ValidadeUserForeCreateEvent extends ApplicationEvent { + private final UserPayLoad userPayLoad; + + public ValidadeUserForeCreateEvent(final UserPayLoad userPayLoad) { + super(userPayLoad); + this.userPayLoad = userPayLoad; + + } + + @Override + public UserPayLoad getSource() { + return this.userPayLoad; + } + + + public boolean numberIsValid() { + return Objects.equals(this.userPayLoad.getSeedVerification(), this.userPayLoad.getCodeVerification()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidateOwnerEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidateOwnerEvent.java new file mode 100644 index 00000000..e6869352 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidateOwnerEvent.java @@ -0,0 +1,21 @@ +package br.com.muttley.model.security.events; + +import br.com.muttley.model.Model; +import br.com.muttley.model.security.User; + +/** + * Evento disparado toda vez que se tenta inserir um novo registro do tipo {@link Model} + * O mesmo se faz necessário para não haver vazamento de segurança. + *

+ * Por padrão só iremos aceitar owner caso arequisição venha do servidor odin, + * caso contrario pegaremos o owner da requisição corrente + * + * @author Joel Rodrigues Moreira on 08/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ValidateOwnerEvent extends AbstractValidateOwnerEvent { + public ValidateOwnerEvent(User currenteUserFromRequest, Model source) { + super(currenteUserFromRequest, source); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidateOwnerInWorkGroupEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidateOwnerInWorkGroupEvent.java new file mode 100644 index 00000000..f52e38cd --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidateOwnerInWorkGroupEvent.java @@ -0,0 +1,21 @@ +package br.com.muttley.model.security.events; + +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.User; + +/** + * Evento disparado toda vez que se tenta inserir um novo grupo de trabalho + * O mesmo se faz necessário para não haver vazamento de segurança. + *

+ * Por padrão só iremos aceitar owner caso arequisição venha do servidor odin, + * caso contrario pegaremos o owner da requisição corrente + * + * @author Joel Rodrigues Moreira on 24/04/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ValidateOwnerInWorkGroupEvent extends AbstractValidateOwnerEvent { + public ValidateOwnerInWorkGroupEvent(final User currenteUserFromRequest, final Passaport passaport) { + super(currenteUserFromRequest, passaport); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidatePasswordRecoveryEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidatePasswordRecoveryEvent.java new file mode 100644 index 00000000..7f1daad2 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/ValidatePasswordRecoveryEvent.java @@ -0,0 +1,31 @@ +package br.com.muttley.model.security.events; + +import br.com.muttley.model.security.RecoveryPayload; +import org.springframework.context.ApplicationEvent; + +import java.util.Objects; + +/** + * @author Joel Rodrigues Moreira on 27/10/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ValidatePasswordRecoveryEvent extends ApplicationEvent { + private final RecoveryPayload payload; + + public ValidatePasswordRecoveryEvent(final RecoveryPayload payload) { + super(payload); + this.payload = payload; + + } + + @Override + public RecoveryPayload getSource() { + return this.payload; + } + + + public boolean numberIsValid() { + return Objects.equals(this.payload.getSeedVerification(), this.payload.getCodeVerification()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/events/authentication/AuthenticationAfterCacheLoadEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/events/authentication/AuthenticationAfterCacheLoadEvent.java new file mode 100644 index 00000000..d1a2e06a --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/events/authentication/AuthenticationAfterCacheLoadEvent.java @@ -0,0 +1,45 @@ +package br.com.muttley.model.security.events.authentication; + +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.security.authentication.Authentication; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira on 19/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + * Evento lançado toda vez que um usuário é recuperado no cache do redis. + * Deve-se utilizar esse evento para saber a hora certa de se carregar detalhes como, + * preferencias de usuário; + */ +public class AuthenticationAfterCacheLoadEvent extends ApplicationEvent { + + public AuthenticationAfterCacheLoadEvent(final XAPIToken token, final Authentication authentication) { + super(new AuthenticationAfterCacheLoadEventSource(token, authentication)); + } + + @Override + public AuthenticationAfterCacheLoadEventSource getSource() { + return (AuthenticationAfterCacheLoadEventSource) super.getSource(); + } + + public XAPIToken getToken() { + return this.getSource().getXAPIToken(); + } + + public Authentication getAuthentication() { + return this.getSource().getAuthentication(); + } + + @Getter + private static class AuthenticationAfterCacheLoadEventSource { + private final XAPIToken XAPIToken; + private final Authentication authentication; + + private AuthenticationAfterCacheLoadEventSource(XAPIToken XAPIToken, Authentication authentication) { + this.XAPIToken = XAPIToken; + this.authentication = authentication; + } + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/expanders/KeyUserDataBindingExpander.java b/muttley-model/src/main/java/br/com/muttley/model/security/expanders/KeyUserDataBindingExpander.java new file mode 100644 index 00000000..f91994b9 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/expanders/KeyUserDataBindingExpander.java @@ -0,0 +1,19 @@ +package br.com.muttley.model.security.expanders; + +import br.com.muttley.model.security.KeyUserDataBinding; +import feign.Param; + +/** + * @author Joel Rodrigues Moreira 12/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class KeyUserDataBindingExpander implements Param.Expander { + @Override + public String expand(final Object key) { + if (key == null) { + return null; + } + return ((KeyUserDataBinding) key).getKey(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/KeyUserDataBindingDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/KeyUserDataBindingDeserializer.java new file mode 100644 index 00000000..ed39eb7f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/KeyUserDataBindingDeserializer.java @@ -0,0 +1,42 @@ +package br.com.muttley.model.security.jackson; + +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.model.security.KeyUserDataBinding; +import br.com.muttley.model.security.events.KeyUserDataBindingResolverEvent; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira 10/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class KeyUserDataBindingDeserializer extends JsonDeserializer { + @Autowired + protected ApplicationEventPublisher eventPublisher; + + @Override + public KeyUserDataBinding deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final JsonNode node = parser.getCodec().readTree(parser); + if (node.isNull()) { + return null; + } + if (eventPublisher == null) { + final KeyUserDataBinding result = KeyUserDataBinding.from(node.asText()); + if (result == null) { + throw new MuttleyException("Não foi possivel resolver por falta do publisher e falta no cache"); + } + return result; + } + final KeyUserDataBindingResolverEvent event = new KeyUserDataBindingResolverEvent(node.asText()); + eventPublisher.publishEvent(event); + return event.getResolvedValue(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/KeyUserDataBindingSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/KeyUserDataBindingSerializer.java new file mode 100644 index 00000000..313932c4 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/KeyUserDataBindingSerializer.java @@ -0,0 +1,25 @@ +package br.com.muttley.model.security.jackson; + +import br.com.muttley.model.security.KeyUserDataBinding; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira 10/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class KeyUserDataBindingSerializer extends JsonSerializer { + + @Override + public void serialize(final KeyUserDataBinding key, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + if (key == null) { + gen.writeNull(); + } + gen.writeString(key.getKey()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/OwnerDataDeserializerDefault.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/OwnerDataDeserializerDefault.java new file mode 100644 index 00000000..88b8e912 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/OwnerDataDeserializerDefault.java @@ -0,0 +1,135 @@ +package br.com.muttley.model.security.jackson; + +import br.com.muttley.model.Historic; +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.security.AccessPlan; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.OwnerDataImpl; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.events.AccessPlanResolver; +import br.com.muttley.model.security.events.OwnerResolverEvent; +import br.com.muttley.model.security.events.UserResolverEvent; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; + +import java.io.IOException; + +import static br.com.muttley.model.jackson.util.NodeUtils.readAsText; +import static br.com.muttley.model.jackson.util.NodeUtils.readNodeAsType; + +/** + * @author Joel Rodrigues Moreira 13/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class OwnerDataDeserializerDefault extends JsonDeserializer { + @Autowired + protected ApplicationEventPublisher eventPublisher; + + @Override + public OwnerData deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final JsonNode node = parser.getCodec().readTree(parser); + if (node.isObject()) { + //se o node tiver algum desses campos quer dizer que devemos desserializar uma + //instancia de Owner completa + if (node.has("accessPlan") || node.has("historic") || node.has("metadata")) { + final Owner owner = new Owner(); + owner.setId(readAsText("id", node)); + owner.setDescription(readAsText("description", node)); + owner.setName(readAsText("name", node)); + + owner.setUserMaster( + this.readUserMaster(node.get("userMaster"), parser) + ); + owner.setAccessPlan( + this.readAccessPlan(node.get("accessPlan"), parser) + ); + owner.setMetadata( + this.readMetadata(node.get("metadata"), parser) + ); + return owner; + } else { + return new OwnerDataImpl( + readAsText("id", node), + readAsText("name", node), + readAsText("description", node), + this.readUserMaster(node.get("userMaster"), parser) + ); + } + //Vamos verificar se o deserializer está no contexto do spring e que o mesmo conseguiu injetar o eventPublisher + } else if (eventPublisher != null) { + //criando evento + final OwnerResolverEvent event = new OwnerResolverEvent(node.asText()); + //disparando para alguem ouvir esse evento + this.eventPublisher.publishEvent(event); + //retornando valor recuperado + return event.isResolved() ? event.getValueResolved() : new Owner().setId(event.getSource()); + } else { + /**provavelmente o deserializer está sendo usado fora do contexto do spring + *ou seja, devemos apenas injetar o ID e nada mais + */ + + return node.isNull() ? null : new Owner().setId(node.asText()); + } + } + + private User readUserMaster(final JsonNode node, final JsonParser parser) throws IOException { + if (node != null && !node.isNull()) { + if (node.isObject()) { + return readNodeAsType(node, parser, new TypeReference() { + }); + } else + //Vamos verificar se o deserializer está no contexto do spring e que o mesmo conseguiu injetar o eventPublisher + if (eventPublisher != null) { + //criando evento + final UserResolverEvent event = new UserResolverEvent(node.asText()); + //disparando para alguem ouvir esse evento + this.eventPublisher.publishEvent(event); + //retornando valor recuperado + return event.isResolved() ? event.getUserResolver() : new User().setUserName(event.getUserName()); + } else { + return ObjectId.isValid(node.asText()) ? new User().setId(node.asText()) : new User().setUserName(node.asText()); + } + } + return null; + } + + private AccessPlan readAccessPlan(final JsonNode node, final JsonParser parser) throws IOException { + if (node != null && !node.isNull()) { + if (node.isObject()) { + return readNodeAsType(node, parser, new TypeReference() { + }); + } else + //Vamos verificar se o deserializer está no contexto do spring e que o mesmo conseguiu injetar o eventPublisher + if (eventPublisher != null) { + //criando evento + final AccessPlanResolver event = new AccessPlanResolver(node.asText()); + //disparando para alguem ouvir esse evento + this.eventPublisher.publishEvent(event); + //retornando valor recuperado + return event.isResolved() ? event.getValueResolved() : new AccessPlan().setId(event.getSource()); + } + } + return null; + } + + private Historic readHistoric(final JsonNode node, final JsonParser parser) throws IOException { + return readNodeAsType(node, parser, new TypeReference() { + }); + } + + private MetadataDocument readMetadata(final JsonNode node, final JsonParser parser) throws IOException { + return readNodeAsType(node, parser, new TypeReference() { + }); + } + + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/PassaportDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/PassaportDeserializer.java new file mode 100644 index 00000000..4f23f5ff --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/PassaportDeserializer.java @@ -0,0 +1,24 @@ +package br.com.muttley.model.security.jackson; + +import br.com.muttley.model.jackson.converter.DocumentDeserializer; +import br.com.muttley.model.jackson.converter.event.DocumentResolverEvent; +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.events.PassaportResolverEvent; + +/** + * @author Joel Rodrigues Moreira on 07/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class PassaportDeserializer extends DocumentDeserializer { + + @Override + protected DocumentResolverEvent createEventResolver(String id) { + return new PassaportResolverEvent(id); + } + + @Override + protected Passaport newInstance(String id) { + return new Passaport().setId(id); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/RoleSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/RoleSerializer.java new file mode 100644 index 00000000..a935be4d --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/RoleSerializer.java @@ -0,0 +1,24 @@ +package br.com.muttley.model.security.jackson; + +import br.com.muttley.model.security.Role; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira on 15/05/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class RoleSerializer extends JsonSerializer { + + @Override + public void serialize(final Role role, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + gen.writeStringField("roleName", role.getRoleName()); + gen.writeEndObject(); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserCollectionSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserCollectionSerializer.java new file mode 100644 index 00000000..4406903c --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserCollectionSerializer.java @@ -0,0 +1,33 @@ +package br.com.muttley.model.security.jackson; + +import br.com.muttley.model.security.User; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.util.Collection; + +/** + * @author Joel Rodrigues Moreira on 24/04/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class UserCollectionSerializer extends JsonSerializer> { + @Override + public void serialize(final Collection user, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + if (user == null) { + gen.writeNull(); + } else { + gen.writeStartArray(); + for (final User currentUser : user) { + if (currentUser != null && !StringUtils.isEmpty(currentUser.getUserName())) { + gen.writeString(currentUser != null ? currentUser.getUserName() : null); + } + } + gen.writeEndArray(); + } + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserDataDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserDataDeserializer.java new file mode 100644 index 00000000..47b96006 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserDataDeserializer.java @@ -0,0 +1,43 @@ +package br.com.muttley.model.security.jackson; + +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.UserView; +import br.com.muttley.model.security.events.UserResolverEvent; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira on 24/04/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class UserDataDeserializer extends JsonDeserializer { + @Autowired + protected ApplicationEventPublisher eventPublisher; + + @Override + public UserData deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + //verificando se o eventPublisher foi injetado no contexto do spring + if (this.eventPublisher != null) { + final UserResolverEvent event = new UserResolverEvent(node.asText()); + //disparando para alguem ouvir esse evento + this.eventPublisher.publishEvent(event); + //retornando valor recuperado + return event.isResolved() ? event.getUserResolver() : new UserView().setUserName(event.getUserName()); + } + //deserializando o usuário com apenas o username mesmo + return node.isNull() ? null : new UserView().setUserName(node.asText()); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserDataSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserDataSerializer.java new file mode 100644 index 00000000..9767e2a5 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserDataSerializer.java @@ -0,0 +1,21 @@ +package br.com.muttley.model.security.jackson; + +import br.com.muttley.model.security.UserData; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * @author Joel Rodrigues Moreira on 24/12/2020. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class UserDataSerializer extends JsonSerializer { + @Override + public void serialize(final UserData user, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeString(user != null ? user.getUserName() : null); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserDeserializer.java index 513e0fda..2502ee9b 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserDeserializer.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserDeserializer.java @@ -28,15 +28,20 @@ public class UserDeserializer extends JsonDeserializer { public User deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { final ObjectCodec oc = parser.getCodec(); final JsonNode node = oc.readTree(parser); + final String username = node.asText(); + //se vier uma string vazia, logo, podemos retornar null + if (username == null || username.trim().isEmpty()) { + return null; + } //verificando se o eventPublisher foi injetado no contexto do spring if (this.eventPublisher != null) { final UserResolverEvent event = new UserResolverEvent(node.asText()); //disparando para alguem ouvir esse evento this.eventPublisher.publishEvent(event); //retornando valor recuperado - return event.isResolved() ? event.getUserResolver() : new User().setEmail(event.getEmail()); + return event.isResolved() ? event.getUserResolver() : new User().setUserName(event.getUserName()); } - //deserializando o usuário com apenas o email mesmo - return node.isNull() ? null : new User().setEmail(node.asText()); + //deserializando o usuário com apenas o username mesmo + return node.isNull() ? null : new User().setUserName(node.asText()); } } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserListDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserListDeserializer.java new file mode 100644 index 00000000..7dd703af --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserListDeserializer.java @@ -0,0 +1,66 @@ +package br.com.muttley.model.security.jackson; + +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.events.UserResolverEvent; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +/** + * @author Joel Rodrigues Moreira on 24/04/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class UserListDeserializer extends JsonDeserializer> { + @Autowired + protected ApplicationEventPublisher eventPublisher; + + @Override + public Collection deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + + if (node.isNull()) { + return null; + } + final Collection users = new ArrayList<>(); + deserializerCollection(node, users); + return users; + } + + protected void deserializerCollection(final JsonNode node, final Collection users) { + final Iterator nodeIterator = node.iterator(); + + while (nodeIterator.hasNext()) { + final JsonNode currentNode = nodeIterator.next(); + if (!StringUtils.isEmpty(currentNode.asText())) { + //verificando se o eventPublisher foi injetado no contexto do spring + if (this.eventPublisher != null) { + final UserResolverEvent event = new UserResolverEvent(currentNode.asText()); + //disparando para alguem ouvir esse evento + this.eventPublisher.publishEvent(event); + //retornando valor recuperado + users.add(event.isResolved() ? event.getUserResolver() : new User().setUserName(event.getUserName())); + } else { + //deserializando o usuário com apenas o userName mesmo + users.add(node.isNull() ? null : new User().setUserName(node.asText())); + } + } + } + } + + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserSerializer.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserSerializer.java index e2cc6d39..5ec2eb87 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserSerializer.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserSerializer.java @@ -16,6 +16,15 @@ public class UserSerializer extends JsonSerializer { @Override public void serialize(final User user, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { - gen.writeString(user != null ? user.getEmail() : null); + gen.writeString(user != null ? user.getUserName() : null); } + + + /*@Override + public void serializeWithType(final User value, final JsonGenerator gen, final SerializerProvider serializers, final TypeSerializer typeSer) throws IOException { + //super.serializeWithType(value, gen, serializers, typeSer); + typeSer.writeTypePrefixForScalar(this, gen, User.class); + serialize(value, gen, serializers); + typeSer.writeTypeSuffixForScalar(this, gen); + }*/ } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserSetDeserializer.java b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserSetDeserializer.java new file mode 100644 index 00000000..965c19ab --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/jackson/UserSetDeserializer.java @@ -0,0 +1,41 @@ +package br.com.muttley.model.security.jackson; + +import br.com.muttley.model.security.User; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; + +/** + * @author Joel Rodrigues Moreira on 24/04/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class UserSetDeserializer extends UserListDeserializer { + @Autowired + protected ApplicationEventPublisher eventPublisher; + + @Override + public Collection deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + + if (node.isNull()) { + return null; + } + final Collection users = new HashSet<>(); + deserializerCollection(node, users); + return users; + } + + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/merge/MergedUserBaseItemResponse.java b/muttley-model/src/main/java/br/com/muttley/model/security/merge/MergedUserBaseItemResponse.java new file mode 100644 index 00000000..5d657f82 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/merge/MergedUserBaseItemResponse.java @@ -0,0 +1,56 @@ +package br.com.muttley.model.security.merge; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira on 10/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = "email") +public class MergedUserBaseItemResponse { + private String email; + private Status status; + private Map details; + + public MergedUserBaseItemResponse() { + } + + public MergedUserBaseItemResponse(final String email) { + this.email = email; + } + + public MergedUserBaseItemResponse(final String email, final Status status) { + this(email); + this.status = status; + this.details = new HashMap<>(); + } + + @JsonCreator + public MergedUserBaseItemResponse( + @JsonProperty("email") final String email, + @JsonProperty("status") final Status status, + @JsonProperty("details") final Map details) { + this(email, status); + this.details = details; + } + + public MergedUserBaseItemResponse addDetails(final String key, final String details) { + if (this.details == null) { + this.details = new HashMap<>(); + } + this.details.put(key, details); + return this; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/merge/MergedUserBaseResponse.java b/muttley-model/src/main/java/br/com/muttley/model/security/merge/MergedUserBaseResponse.java new file mode 100644 index 00000000..1f46ae2f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/merge/MergedUserBaseResponse.java @@ -0,0 +1,43 @@ +package br.com.muttley.model.security.merge; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 10/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +public class MergedUserBaseResponse { + private List itens; + + + public MergedUserBaseResponse() { + } + + @JsonCreator + public MergedUserBaseResponse(@JsonProperty("itens") final List itens) { + this.itens = itens; + } + + public MergedUserBaseResponse add(final MergedUserBaseItemResponse item) { + this.prepareItens(); + this.itens.add(item); + return this; + } + + private MergedUserBaseResponse add(final String email, final Status status) { + return this.add(new MergedUserBaseItemResponse(email, status)); + } + + private void prepareItens() { + if (itens == null) { + this.itens = new ArrayList<>(); + } + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/merge/Status.java b/muttley-model/src/main/java/br/com/muttley/model/security/merge/Status.java new file mode 100644 index 00000000..34162fa6 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/merge/Status.java @@ -0,0 +1,12 @@ +package br.com.muttley.model.security.merge; + +/** + * @author Joel Rodrigues Moreira on 10/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public enum Status { + OK, + ERROR, + CONFLICT; +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/preference/Foto.java b/muttley-model/src/main/java/br/com/muttley/model/security/preference/Foto.java new file mode 100644 index 00000000..714d2ca1 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/preference/Foto.java @@ -0,0 +1,43 @@ +package br.com.muttley.model.security.preference; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.URL; + +import javax.validation.constraints.Size; + +/** + * @author Carol Cedro on 15/07/2024. + * e-mail: ana.carolina@maxxsoft.com.br + */ + +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = "link") +public class Foto { + + + @Size(max = 140, message = "A legenda pode ter no máximo 140 caracteres!") + private String legenda; + @URL(message = "Informe uma URL válida!") + private String link; + + public Foto() { + + } + + @JsonCreator + public Foto(@JsonProperty("legenda") final String legenda, @JsonProperty("link") final String link) { + this(); + this.legenda = legenda; + this.link = link; + } + + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/preference/Preference.java b/muttley-model/src/main/java/br/com/muttley/model/security/preference/Preference.java index 5b65a12d..00729058 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/preference/Preference.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/preference/Preference.java @@ -3,7 +3,11 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Objects; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Transient; import static org.springframework.util.StringUtils.isEmpty; @@ -12,9 +16,16 @@ * e-mail: joel.databox@gmail.com * @project muttley-cloud */ +@EqualsAndHashCode(of = "key") +@Getter public class Preference { protected final String key; protected final Object value; + @Transient + @JsonIgnore + @Setter + @Accessors(chain = true) + protected Object resolved; @JsonCreator public Preference( @@ -32,17 +43,9 @@ public Object getValue() { return value; } - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (!(o instanceof Preference)) return false; - final Preference that = (Preference) o; - return Objects.equal(key, that.key); - } - - @Override - public int hashCode() { - return Objects.hashCode(key, 2, 3); + @JsonIgnore + public boolean isResolved() { + return this.resolved != null; } /** diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/preference/UserPreferences.java b/muttley-model/src/main/java/br/com/muttley/model/security/preference/UserPreferences.java index aa523a50..4f604891 100644 --- a/muttley-model/src/main/java/br/com/muttley/model/security/preference/UserPreferences.java +++ b/muttley-model/src/main/java/br/com/muttley/model/security/preference/UserPreferences.java @@ -1,7 +1,7 @@ package br.com.muttley.model.security.preference; import br.com.muttley.model.Document; -import br.com.muttley.model.Historic; +import br.com.muttley.model.MetadataDocument; import br.com.muttley.model.security.User; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -10,6 +10,7 @@ import org.springframework.data.mongodb.core.index.CompoundIndex; import org.springframework.data.mongodb.core.index.CompoundIndexes; import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.util.CollectionUtils; import java.util.HashSet; import java.util.Set; @@ -24,23 +25,25 @@ */ @org.springframework.data.mongodb.core.mapping.Document(collection = "#{documentNameConfig.getNameCollectionUserPreferences()}") @CompoundIndexes({ - @CompoundIndex(name = "user_index_unique", def = "{'user' : 1}", unique = true) + @CompoundIndex(name = "user_index_unique", def = "{'user' : 1}", unique = true), + @CompoundIndex(name = "preferences_key_index", def = "{'preferences.key': 1}"), + @CompoundIndex(name = "preferences_key_value_index", def = "{'preferences.key': 1, 'preferences.value':1}") }) public class UserPreferences implements Document { - public static final String WORK_TEAM_PREFERENCE = "WorkTeamPreference"; + public static final String OWNER_PREFERENCE = "OWNER_PREFERENCE"; @Id private String id; @JsonIgnore @DBRef private User user; - private Historic historic; + private MetadataDocument metadata; private Set preferences; public UserPreferences() { this.preferences = new HashSet<>(); } - public UserPreferences(Preference... preferences) { + public UserPreferences(final Preference... preferences) { notNull(preferences, "preferences is null"); this.preferences = new HashSet<>(); Stream.of(preferences) @@ -54,14 +57,14 @@ public UserPreferences(Preference... preferences) { @JsonCreator public UserPreferences( - @JsonProperty("id") String id, - @JsonProperty("user") User user, - @JsonProperty("historic") Historic historic, - @JsonProperty("preferences") Set preferences) { + final @JsonProperty("id") String id, + final @JsonProperty("user") User user, + final @JsonProperty("preferences") Set preferences, + final @JsonProperty("metadata") MetadataDocument metadata) { this.id = id; this.user = user; - this.historic = historic; this.preferences = preferences; + this.metadata = metadata; } @Override @@ -76,14 +79,14 @@ public UserPreferences setId(final String id) { } @Override - public UserPreferences setHistoric(final Historic historic) { - this.historic = historic; - return this; + public MetadataDocument getMetadata() { + return metadata; } @Override - public Historic getHistoric() { - return this.historic; + public UserPreferences setMetadata(final MetadataDocument metaData) { + this.metadata = metaData; + return this; } public boolean contains(final String key) { @@ -91,6 +94,11 @@ public boolean contains(final String key) { return this.preferences.contains(p); } + @JsonIgnore + public boolean isEmpty() { + return CollectionUtils.isEmpty(this.preferences); + } + public User getUser() { return user; } @@ -110,23 +118,26 @@ public UserPreferences setPreferences(final Set preferences) { } public Preference get(final String key) { - return this.preferences.stream() + return this.preferences.parallelStream() .filter(p -> p.key.equals(key)) .findAny() - .get(); + .orElse(null); } public UserPreferences set(final Preference preference) { + this.remove(preference.getKey()); this.preferences.add(preference); return this; } public UserPreferences set(final String key, final Object value) { + this.remove(key); this.preferences.add(new Preference(key, value)); return this; } public UserPreferences set(final String key, final Document value) { + this.remove(key); this.preferences.add(new Preference(key, value.getId())); return this; } diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/AvaliableRoles.java b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/AvaliableRoles.java new file mode 100644 index 00000000..f96da70f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/AvaliableRoles.java @@ -0,0 +1,116 @@ +package br.com.muttley.model.security.rolesconfig; + +import br.com.muttley.model.security.Role; +import lombok.Getter; +import lombok.experimental.Accessors; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import static br.com.muttley.model.security.rolesconfig.ViewRoleDefinition.newRoleDefinition; +import static java.util.Arrays.asList; +import static java.util.Arrays.stream; + +/** + * @author Joel Rodrigues Moreira on 02/07/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Accessors(chain = true) +public class AvaliableRoles { + private static int idsCounter = 0; + private final Set viewRoleDefinitions; + + protected AvaliableRoles(final Collection viewRoleDefinitions) { + this.viewRoleDefinitions = new HashSet<>(viewRoleDefinitions); + } + + protected AvaliableRoles(final ViewRoleDefinition... viewRoleDefinitions) { + this(asList(viewRoleDefinitions)); + } + + public Set getDependenciesRolesFrom(final Set currentRoles) { + return currentRoles.stream().map(it -> getDependenciesRolesFrom(it)) + .reduce((acc, roles) -> { + acc.addAll(roles); + return acc; + }) + .orElse(new HashSet<>(0)); + + } + + public Set getDependenciesRolesFrom(final Role role) { + return this.viewRoleDefinitions.stream() + .filter(it -> it.contains(role)) + .map(it -> it.getDependencies()) + .reduce((acc, roles) -> { + acc.addAll(roles); + return acc; + }).orElse(new HashSet<>(0)); + } + + public static ViewRoleDefinition newViewRoleDefinition(final String title, final String description, final RoleDefinition... roleDefinitions) { + idsCounter++; + return new ViewRoleDefinition(idsCounter, null, title, description, roleDefinitions); + } + + public static ViewRoleDefinition newViewRoleDefinition(final String title, final String description, final Role... roles) { + idsCounter++; + return new ViewRoleDefinition(idsCounter, null, title, description, + stream(roles).map(it -> newRoleDefinition(it)).toArray(RoleDefinition[]::new) + ); + } + + public static ViewRoleDefinition newViewRoleDefinition(final IconMenu iconMenu, final String title, final String description, final Role... roles) { + idsCounter++; + return new ViewRoleDefinition(idsCounter, iconMenu, title, description, + stream(roles).map(it -> newRoleDefinition(it)).toArray(RoleDefinition[]::new) + ); + } + + public static ViewRoleDefinition newViewRoleDefinition(final String icon, final String title, final String description, final Role... roles) { + idsCounter++; + final IconMenu iconMenu = new IconMenu(icon); + return new ViewRoleDefinition(idsCounter, iconMenu, title, description, + stream(roles).map(it -> newRoleDefinition(it)).toArray(RoleDefinition[]::new) + ); + } + + public static ViewRoleDefinition newViewRoleDefinition(final String icon, final String title, final String description, final RoleDefinition... roleDefinitions) { + idsCounter++; + final IconMenu iconMenu = new IconMenu(icon); + return new ViewRoleDefinition(idsCounter, iconMenu, title, description, roleDefinitions); + } + + public static ViewRoleDefinition newViewRoleDefinition(final String icon, final FontSet fontSet, final String title, final String description, final Role... roles) { + idsCounter++; + final IconMenu iconMenu = new IconMenu(icon, fontSet); + return new ViewRoleDefinition(idsCounter, iconMenu, title, description, + stream(roles).map(it -> newRoleDefinition(it)).toArray(RoleDefinition[]::new) + ); + } + + public static ViewRoleDefinition newViewRoleDefinition(final String icon, final FontSet fontSet, final String title, final String description, final RoleDefinition... roleDefinitions) { + idsCounter++; + final IconMenu iconMenu = new IconMenu(icon, fontSet); + return new ViewRoleDefinition(idsCounter, iconMenu, title, description, roleDefinitions); + } + + public static ViewRoleDefinition newViewRoleDefinition(final String title, final String description) { + idsCounter++; + return new ViewRoleDefinition(idsCounter, null, title, description, (RoleDefinition) null); + } + + public static AvaliableRoles newAvaliableRoles(final Collection viewRoleDefinitions) { + idsCounter = 0; + return new AvaliableRoles(viewRoleDefinitions); + } + + public static AvaliableRoles newAvaliableRoles(final ViewRoleDefinition... viewRoleDefinitions) { + idsCounter = 0; + ViewRoleDefinition.idsCounter = 0; + return new AvaliableRoles(viewRoleDefinitions); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/FontSet.java b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/FontSet.java new file mode 100644 index 00000000..4ccd992c --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/FontSet.java @@ -0,0 +1,11 @@ +package br.com.muttley.model.security.rolesconfig; + +/** + * @author Joel Rodrigues Moreira on 06/12/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public enum FontSet { + MDI, + FONT_AWESOME +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/IconMenu.java b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/IconMenu.java new file mode 100644 index 00000000..b477b122 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/IconMenu.java @@ -0,0 +1,44 @@ +package br.com.muttley.model.security.rolesconfig; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * @author Joel Rodrigues Moreira on 06/12/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode +public class IconMenu { + private String icon; + private FontSet fontSet; + + public IconMenu(final String icon) { + this(icon, null); + } + + public IconMenu(final String icon, final FontSet fontSet) { + this.icon = icon; + this.fontSet = fontSet; + } + + public String getIcon() { + if (fontSet != null && icon != null) { + if (fontSet.equals(FontSet.FONT_AWESOME)) { + return this.icon.startsWith("fa-") ? this.icon : "fa-" + this.icon; + } else if (fontSet.equals(FontSet.MDI)) { + return this.icon.startsWith("mdi-") ? this.icon : "mdi-" + this.icon; + } + } + return icon; + } + + public FontSet getFontSet() { + return fontSet; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/RoleDefinition.java b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/RoleDefinition.java new file mode 100644 index 00000000..8d063033 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/RoleDefinition.java @@ -0,0 +1,28 @@ +package br.com.muttley.model.security.rolesconfig; + +import br.com.muttley.model.security.Role; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +/** + * @author Joel Rodrigues Moreira on 02/07/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@EqualsAndHashCode(of = "typeRole") +public class RoleDefinition { + private final int id; + private final Role typeRole; + private final String description; + + protected RoleDefinition(final int id, final Role typeRole, final String description) { + this.id = id; + this.typeRole = typeRole; + this.description = description; + } + + protected RoleDefinition(final int id, final Role typeRole) { + this(id, typeRole, null); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/ViewRoleDefinition.java b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/ViewRoleDefinition.java new file mode 100644 index 00000000..2061645f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/ViewRoleDefinition.java @@ -0,0 +1,92 @@ +package br.com.muttley.model.security.rolesconfig; + +import br.com.muttley.model.security.Role; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import static java.util.Arrays.asList; +import static java.util.Arrays.stream; + +/** + * @author Joel Rodrigues Moreira on 02/07/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@EqualsAndHashCode(of = "title") +public class ViewRoleDefinition { + @JsonIgnore + protected static int idsCounter = 0; + private final int id; + private final IconMenu icon; + private final String title; + private final String description; + private final Set roleDefinitions; + private final Set dependencies = new HashSet<>(); + + protected ViewRoleDefinition(final int id, final IconMenu icon, final String title, final String description, final RoleDefinition... roleDefinitions) { + this.id = id; + this.icon = icon; + this.title = title; + this.description = description; + if (roleDefinitions == null) { + this.roleDefinitions = new HashSet<>(); + } else { + this.roleDefinitions = new HashSet<>(asList(roleDefinitions)); + } + } + + protected ViewRoleDefinition(final int id, final IconMenu icon, final String title, final String description, final Role... roles) { + this(id, icon, title, description, stream(roles).map(it -> { + idsCounter++; + return new RoleDefinition(idsCounter, it); + }).toArray(RoleDefinition[]::new)); + } + + protected ViewRoleDefinition(final int id, final IconMenu icon, final String title, final String description) { + this(id, icon, title, description, (RoleDefinition) null); + } + + protected ViewRoleDefinition add(RoleDefinition roleDefinition) { + this.roleDefinitions.add(roleDefinition); + return this; + } + + public ViewRoleDefinition add(RoleDefinition... roleDefinitions) { + this.roleDefinitions.addAll(asList(roleDefinitions)); + return this; + } + + public ViewRoleDefinition add(Collection roleDefinitions) { + this.roleDefinitions.addAll(roleDefinitions); + return this; + } + + public ViewRoleDefinition addDependencies(final Role... dependencies) { + this.dependencies.addAll(asList(dependencies)); + return this; + } + + public boolean contains(final RoleDefinition role) { + return this.roleDefinitions.contains(role); + } + + public boolean contains(final Role role) { + return this.contains(new RoleDefinition(0, role)); + } + + public static RoleDefinition newRoleDefinition(final Role typeRole, final String description) { + idsCounter++; + return new RoleDefinition(idsCounter, typeRole, description); + } + + public static RoleDefinition newRoleDefinition(final Role typeRole) { + idsCounter++; + return new RoleDefinition(idsCounter, typeRole); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/event/AvaliableRolesEvent.java b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/event/AvaliableRolesEvent.java new file mode 100644 index 00000000..0c0a41a8 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/rolesconfig/event/AvaliableRolesEvent.java @@ -0,0 +1,38 @@ +package br.com.muttley.model.security.rolesconfig.event; + +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.rolesconfig.AvaliableRoles; +import br.com.muttley.model.security.rolesconfig.ViewRoleDefinition; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +import static java.util.Arrays.asList; + +/** + * @author Joel Rodrigues Moreira on 02/07/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +public class AvaliableRolesEvent extends ApplicationEvent { + private final User user; + private final AvaliableRoles source; + + public AvaliableRolesEvent(final User user, final AvaliableRoles source) { + super(source); + this.user = user; + this.source = source; + } + + public AvaliableRolesEvent add(final ViewRoleDefinition... viewRoleDefinitions) { + this.source.getViewRoleDefinitions().addAll(asList(viewRoleDefinitions)); + return this; + } + + @Override + public AvaliableRoles getSource() { + return this.source; + } + + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/security/rsa/RSAUtil.java b/muttley-model/src/main/java/br/com/muttley/model/security/rsa/RSAUtil.java new file mode 100644 index 00000000..bfa9d35c --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/security/rsa/RSAUtil.java @@ -0,0 +1,237 @@ +package br.com.muttley.model.security.rsa; + +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.bouncycastle.util.io.pem.PemWriter; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.io.*; +import java.nio.charset.Charset; +import java.security.*; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.Random; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.crypto.Cipher.ENCRYPT_MODE; + +/** + * @author Joel Rodrigues Moreira on 08/08/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class RSAUtil { + private static final String PUBLIC_KEY = "PUBLIC KEY"; + private static final String PRIVATE_KEY = "PRIVATE KEY"; + + public static KeyPair createKeyPair() { + return createKeyPair(1024); + } + + public static KeyPair createKeyPair(final int size) { + return createKeyPair(size, generateRandomString(1024)); + } + + public static KeyPair createKeyPair(final int size, final String seed) { + try { + final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + final SecureRandom random = new SecureRandom(); + + random.setSeed(seed.getBytes()); + keyGen.initialize(size, random); //Specify the RSA key length + KeyPair keyPair = keyGen.generateKeyPair(); + + return keyPair; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public static void write(final String fileName, final PrivateKey privateKey) { + write(fileName, PRIVATE_KEY, privateKey); + } + + public static void write(final File file, final PrivateKey privateKey) { + write(file, PRIVATE_KEY, privateKey); + } + + public static void write(final String fileName, final PublicKey publicKey) { + write(fileName, PUBLIC_KEY, publicKey); + } + + public static void write(final File file, final PublicKey publicKey) { + write(file, PUBLIC_KEY, publicKey); + } + + private static void write(final String fileName, final String description, final Key key) { + write(new File(fileName), description, key); + } + + private static void write(final File file, final String description, final Key key) { + file.getParentFile().mkdirs(); + try (final PemWriter pemWriter = new PemWriter(new OutputStreamWriter(new FileOutputStream(file)))) { + pemWriter.writeObject(new PemObject(description, key.getEncoded())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String generateRandomString(final int size) { + final int leftLimit = 48; // numeral '0' + final int rightLimit = 122; // letter 'z' + + return new Random() + .ints(leftLimit, rightLimit + 1) + .parallel() + .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)) + .limit(size) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + } + + private static byte[] parsePEMFile(File pemFile) { + if (!pemFile.isFile() || !pemFile.exists()) { + throw new RuntimeException(String.format("The file '%s' doesn't exist.", pemFile.getAbsolutePath())); + } + try { + return parsePEMFile(new FileReader(pemFile)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static byte[] parsePEMFile(final InputStream pemFile) { + return parsePEMFile(new InputStreamReader(pemFile)); + } + + private static byte[] parsePEMFile(final Reader pemFile) { + try (final PemReader reader = new PemReader(pemFile)) { + return reader.readPemObject().getContent(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static byte[] parsePEMFile(final String description, final String value) { + return new PemObject(description, value.getBytes()).getContent(); + } + + private static PublicKey getPublicKey(byte[] keyBytes, String algorithm) { + PublicKey publicKey = null; + try { + final KeyFactory kf = KeyFactory.getInstance(algorithm); + final EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + publicKey = kf.generatePublic(keySpec); + } catch (NoSuchAlgorithmException e) { + System.out.println("Could not reconstruct the public key, the given algorithm could not be found."); + } catch (InvalidKeySpecException e) { + System.out.println("Could not reconstruct the public key"); + } + + return publicKey; + } + + private static PrivateKey getPrivateKey(byte[] keyBytes, String algorithm) { + PrivateKey privateKey = null; + try { + final KeyFactory kf = KeyFactory.getInstance(algorithm); + final EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + privateKey = kf.generatePrivate(keySpec); + } catch (NoSuchAlgorithmException e) { + System.out.println("Could not reconstruct the private key, the given algorithm could not be found."); + } catch (InvalidKeySpecException e) { + System.out.println("Could not reconstruct the private key"); + } + + return privateKey; + } + + public static PublicKey readPublicKeyFromString(String value) { + return getPublicKey(parsePEMFile(PUBLIC_KEY, value), "RSA"); + } + + public static PublicKey readPublicKeyFromFile(String filepath) { + return getPublicKey(parsePEMFile(new File(filepath)), "RSA"); + } + + public static PrivateKey readPrivateKeyFromString(final String value) { + return getPrivateKey(parsePEMFile(PRIVATE_KEY, value), "RSA"); + } + + public static PrivateKey readPrivateKeyFromFile(final InputStream reader) { + return getPrivateKey(parsePEMFile(reader), "RSA"); + } + + public static PrivateKey readPrivateKeyFromFile(String filepath) { + return getPrivateKey(parsePEMFile(new File(filepath)), "RSA"); + } + + public static String encrypt(final Key key, final String message) { + return encrypt(key, message.getBytes(UTF_8)); + } + + public static String encrypt(final Key key, final byte[] content) { + try { + final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(ENCRYPT_MODE, key); + return Base64.getEncoder().encodeToString(cipher.doFinal(content)); + } catch (NoSuchPaddingException e) { + throw new RuntimeException(e); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } catch (IllegalBlockSizeException e) { + throw new RuntimeException(e); + } catch (BadPaddingException e) { + throw new RuntimeException(e); + } catch (InvalidKeyException e) { + throw new RuntimeException(e); + } + } + + public static String decrypt(final Key key, final String encryptedMessage) { + return decrypt(key, encryptedMessage, UTF_8); + } + + + public static String decrypt(final Key key, final String encryptedMessage, final Charset charset) { + try { + final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, key); + return new String(cipher.doFinal(Base64.getDecoder().decode(encryptedMessage)), charset); + } catch (NoSuchPaddingException e) { + throw new RuntimeException(e); + } catch (IllegalBlockSizeException e) { + throw new RuntimeException(e); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } catch (BadPaddingException e) { + throw new RuntimeException(e); + } catch (InvalidKeyException e) { + throw new RuntimeException(e); + } + } + + public static String toString(final PrivateKey key) { + return toString(PRIVATE_KEY, key); + } + + public static String toString(final PublicKey key) { + return toString(PUBLIC_KEY, key); + } + + public static String toString(final String description, final Key key) { + try (final StringWriter stringWriter = new StringWriter(); final PemWriter pemWriter = new PemWriter(stringWriter)) { + pemWriter.writeObject(new PemObject(description, key.getEncoded())); + pemWriter.flush(); + return stringWriter.toString(); + } catch (IOException e) { + throw new RuntimeException("Cannot format certificate to PEM format", e); + } + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/userManager/IncludeSecundaryEmail.java b/muttley-model/src/main/java/br/com/muttley/model/userManager/IncludeSecundaryEmail.java new file mode 100644 index 00000000..5cfd217a --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/userManager/IncludeSecundaryEmail.java @@ -0,0 +1,18 @@ +package br.com.muttley.model.userManager; + +import br.com.muttley.model.security.User; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Email; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class IncludeSecundaryEmail { + + + @Email(message = "Informe um email secundário válido!") + private String emailSecundary; + +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/util/DateUtils.java b/muttley-model/src/main/java/br/com/muttley/model/util/DateUtils.java deleted file mode 100644 index aea38bb9..00000000 --- a/muttley-model/src/main/java/br/com/muttley/model/util/DateUtils.java +++ /dev/null @@ -1,20 +0,0 @@ -package br.com.muttley.model.util; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Date; - -/** - * @author Joel Rodrigues Moreira on 18/06/18. - * e-mail: joel.databox@gmail.com - * @project muttley-cloud - */ -public class DateUtils { - public static LocalDateTime toLocalDateTime(final Date date) { - return toLocalDateTime(date, ZoneId.systemDefault()); - } - - public static LocalDateTime toLocalDateTime(final Date date, final ZoneId zoneId) { - return LocalDateTime.ofInstant(date.toInstant(), zoneId); - } -} diff --git a/muttley-model/src/main/java/br/com/muttley/model/util/JacksonUtil.java b/muttley-model/src/main/java/br/com/muttley/model/util/JacksonUtil.java new file mode 100644 index 00000000..f1170eab --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/util/JacksonUtil.java @@ -0,0 +1,137 @@ +package br.com.muttley.model.util; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * @author Joel Rodrigues Moreira on 02/02/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class JacksonUtil { + public static String extractString(final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return node.asText(); + } + + public static String extractString(final String path, final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return extractString(node.get(path)); + } + + public static Long extractLong(final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return node.asLong(); + } + + public static Long extractLong(final String path, final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return extractLong(node.get(path)); + } + + public static Integer extractInteger(final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return node.asInt(); + } + + public static Integer extractInteger(final String path, final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return extractInteger(node.get(path)); + } + + public static Double extractDouble(final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return node.asDouble(); + } + + public static Double extractDouble(final String path, final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return extractDouble(node.get(path)); + } + + public static boolean extractBoolean(final JsonNode node) { + if (node == null || node.isNull()) { + return false; + } + return node.asBoolean(); + } + + public static boolean extractBoolean(final String path, final JsonNode node) { + if (node == null || node.isNull()) { + return false; + } + return extractBoolean(node.get(path)); + } + + public static LocalDateTime extractLocalDateTime(final String patthern, final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return LocalDateTime.parse(node.asText(), DateTimeFormatter.ofPattern(patthern)); + } + + public static LocalDateTime extractLocalDateTime(final String path, final String patthern, final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + return extractLocalDateTime(patthern, node.get(path)); + } + + public static void writeNumberField(final JsonGenerator gen, final String field, final Long value) throws IOException { + if (value != null) { + gen.writeNumberField(field, value); + } + } + + public static void writeBooleanField(final JsonGenerator gen, final String field, final Boolean value) throws IOException { + if (value != null) { + gen.writeBooleanField(field, value); + } + } + + public static void writeStringField(final JsonGenerator gen, final String field, final Object value) throws IOException { + if (value != null) { + gen.writeStringField(field, value.toString()); + } + } + + public static void writeDateField(final JsonGenerator gen, final String field, final LocalDate value, final String pattern) throws IOException { + if (value != null) { + gen.writeStringField(field, value.format(DateTimeFormatter.ofPattern(pattern))); + } + } + + public static void writeDateField(final JsonGenerator gen, final String field, final LocalDateTime value, final String pattern) throws IOException { + if (value != null) { + gen.writeStringField(field, value.format(DateTimeFormatter.ofPattern(pattern))); + } + } + + public static void writeDateField(final JsonGenerator gen, final String field, final ZonedDateTime value, final String pattern) throws IOException { + if (value != null) { + gen.writeStringField(field, value.format(DateTimeFormatter.ofPattern(pattern))); + } + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/util/RedisUtils.java b/muttley-model/src/main/java/br/com/muttley/model/util/RedisUtils.java new file mode 100644 index 00000000..6aee578f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/util/RedisUtils.java @@ -0,0 +1,14 @@ +package br.com.muttley.model.util; + +import br.com.muttley.model.security.User; + +/** + * @author Joel Rodrigues Moreira on 02/09/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class RedisUtils { + public static String createKeyByOwner(final User user, final Class clazz, final String key) { + return clazz.getName().toUpperCase() + ":" + user.getCurrentOwner().getId() + ":" + key; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/validation/CollectionReferences.java b/muttley-model/src/main/java/br/com/muttley/model/validation/CollectionReferences.java new file mode 100644 index 00000000..01161956 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/validation/CollectionReferences.java @@ -0,0 +1,27 @@ +package br.com.muttley.model.validation; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 13/01/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = "id") +public class CollectionReferences { + private String collection; + private List records; + + public CollectionReferences(String collection, List records) { + this.collection = collection; + this.records = records; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/validation/HasBeenReferencedEvent.java b/muttley-model/src/main/java/br/com/muttley/model/validation/HasBeenReferencedEvent.java new file mode 100644 index 00000000..4380ae58 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/validation/HasBeenReferencedEvent.java @@ -0,0 +1,46 @@ +package br.com.muttley.model.validation; + +import br.com.muttley.model.Model; +import org.springframework.context.ApplicationEvent; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 13/01/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class HasBeenReferencedEvent extends ApplicationEvent { + + protected final Reference value; + private List references; + + public HasBeenReferencedEvent(Reference reference) { + super(reference); + this.value = reference; + + } + + @Override + public Reference getSource() { + return this.value; + } + + public List getReferences() { + return this.references; + } + + public HasBeenReferencedEvent addReference(CollectionReferences references) { + if (this.references == null) { + this.references = new ArrayList<>(); + } + this.references.add(references); + return this; + } + + public boolean containsReference() { + return !CollectionUtils.isEmpty(this.references); + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/validation/Reference.java b/muttley-model/src/main/java/br/com/muttley/model/validation/Reference.java new file mode 100644 index 00000000..1b3be829 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/validation/Reference.java @@ -0,0 +1,28 @@ +package br.com.muttley.model.validation; + +import br.com.muttley.model.Model; +import lombok.Getter; + +/** + * @author Joel Rodrigues Moreira on 13/01/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +public class Reference { + private String id; + private T value; + + public Reference(String id) { + this.id = id; + } + + public Reference(T value) { + this(value.getId()); + this.value = value; + } + + public boolean modelIsNull() { + return this.value == null; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/workteam/WorkTeam.java b/muttley-model/src/main/java/br/com/muttley/model/workteam/WorkTeam.java new file mode 100644 index 00000000..c7b28e5b --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/workteam/WorkTeam.java @@ -0,0 +1,115 @@ +package br.com.muttley.model.workteam; + +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.Model; +import br.com.muttley.model.jackson.converter.DocumentSerializer; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.jackson.OwnerDeserializer; +import br.com.muttley.model.security.jackson.UserCollectionSerializer; +import br.com.muttley.model.security.jackson.UserSetDeserializer; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.NotBlank; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.index.CompoundIndexes; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.util.CollectionUtils; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.HashSet; +import java.util.Set; + +import static br.com.muttley.model.workteam.WorkTeam.TYPE_ALIAS; + + +/** + * @author Joel Rodrigues Moreira on 02/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@org.springframework.data.mongodb.core.mapping.Document(collection = "#{documentNameConfig.getNameCollectionWorkTeam()}") +@CompoundIndexes({ + @CompoundIndex(name = "owner_index", def = "{'owner' : 1}"), + @CompoundIndex(name = "owner.id_index", def = "{'owner.$id' : 1}"), + @CompoundIndex(name = "usersMaster.id_index", def = "{'usersMaster.$id' : 1}"), + @CompoundIndex(name = "owner_usersMaster_index", def = "{'owner' : 1, 'usersMaster' : 1}"), + @CompoundIndex(name = "owner.id_usersMaster.id_index", def = "{'owner.$id' : 1, 'usersMaster.$id' : 1}") +}) +@TypeAlias(TYPE_ALIAS) +@Getter +@Setter +@Accessors(chain = true) +public class WorkTeam implements Model { + @Transient + @JsonIgnore + public static final String TYPE_ALIAS = "work-team"; + + @Id + protected String id; + @NotBlank(message = "Informe um nome válido para o grupo") + protected String name; + protected String description; + @NotNull(message = "É nécessário ter pelo usuário master no grupo de trabalho") + @Size(min = 1, message = "É nécessário ter pelo usuário master no grupo de trabalho") + @JsonSerialize(using = UserCollectionSerializer.class) + @JsonDeserialize(using = UserSetDeserializer.class) + @DBRef + protected Set usersMaster; + /*@NotNull(message = "É nécessário informar quem é o owner do grupo de trabalho") + @DBRef*/ + @JsonSerialize(using = DocumentSerializer.class) + @JsonDeserialize(using = OwnerDeserializer.class) + @DBRef + protected Owner owner; + @DBRef + @JsonSerialize(using = UserCollectionSerializer.class) + @JsonDeserialize(using = UserSetDeserializer.class) + protected Set members; + protected MetadataDocument metadata; + protected boolean editDataFromMembers = false; + + public boolean containsMember(final User userMaster) { + if (this.membersIsEmpty()) { + return false; + } + return this.getMembers() + .parallelStream() + .filter(user -> user.equals(userMaster)) + .count() > 0; + } + + public boolean membersIsEmpty() { + return CollectionUtils.isEmpty(this.members); + } + + public boolean usersMasterIsEmpty() { + return CollectionUtils.isEmpty(this.usersMaster); + } + + /** + * Verifica se tem algum membro presente no workteam + * incluindo o usermaster + */ + public boolean containsAnyUser() { + return (!this.usersMasterIsEmpty()) || (!this.membersIsEmpty()); + } + + @JsonIgnore + public Set getAllUsers() { + final Set users = this.membersIsEmpty() ? new HashSet<>() : new HashSet<>(this.getMembers()); + + if (!this.usersMasterIsEmpty()) { + users.addAll(this.getUsersMaster()); + } + return users; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/workteam/WorkTeamDomain.java b/muttley-model/src/main/java/br/com/muttley/model/workteam/WorkTeamDomain.java new file mode 100644 index 00000000..a94f6391 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/workteam/WorkTeamDomain.java @@ -0,0 +1,104 @@ +package br.com.muttley.model.workteam; + +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.jackson.UserDeserializer; +import br.com.muttley.model.security.jackson.UserSerializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.bson.types.ObjectId; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static br.com.muttley.model.security.domain.Domain.PUBLIC; +import static br.com.muttley.model.security.domain.Domain.RESTRICTED; + +/** + * @author Joel Rodrigues Moreira on 08/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +public class WorkTeamDomain { + /*@JsonSerialize(using = UserSerializer.class) + @JsonDeserialize(using = UserDeserializer.class)*/ + private User userMaster; + /*@JsonSerialize(using = UserCollectionSerializer.class) + @JsonDeserialize(using = UserSetDeserializer.class)*/ + private Set supervisors; + private Set colleagues; + private Set subordinates; + + public WorkTeamDomain() { + this.supervisors = new HashSet<>(); + this.colleagues = new HashSet<>(); + this.subordinates = new HashSet<>(); + } + + public Set getAllUsers() { + final Set users = Arrays.asList( + this.supervisors, + this.colleagues, + this.subordinates + ).parallelStream() + .filter(Objects::nonNull) + .map(it -> + it.parallelStream() + .map(WorkTeamMember::getUser) + .collect(Collectors.toSet()) + ).reduce((acc, item) -> { + acc.addAll(item); + return acc; + }).orElseGet(HashSet::new); + + if (userMaster != null) { + users.add(userMaster); + } + return users; + } + + public Set getIdsOfAllUsers() { + return this.getAllUsers() + .parallelStream() + .map(it -> it.getObjectId()) + .collect(Collectors.toSet()); + } + + public WorkTeamDomain addSupervisors(final WorkTeamMember workTeamMember) { + this.supervisors.add(workTeamMember); + return this; + } + + public WorkTeamDomain addSupervisors(final User user) { + this.supervisors.add(new WorkTeamMember(user, PUBLIC)); + return this; + } + + public WorkTeamDomain addColleagues(final WorkTeamMember workTeamMember) { + this.colleagues.add(workTeamMember); + return this; + } + + public WorkTeamDomain addColleagues(final User user) { + this.colleagues.add(new WorkTeamMember(user, RESTRICTED)); + return this; + } + + public WorkTeamDomain addSubordinates(final WorkTeamMember workTeamMember) { + this.subordinates.add(workTeamMember); + return this; + } + + public WorkTeamDomain addSubordinates(final User user) { + this.subordinates.add(new WorkTeamMember(user, null)); + return this; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/model/workteam/WorkTeamMember.java b/muttley-model/src/main/java/br/com/muttley/model/workteam/WorkTeamMember.java new file mode 100644 index 00000000..69268d9f --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/model/workteam/WorkTeamMember.java @@ -0,0 +1,34 @@ +package br.com.muttley.model.workteam; + +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.domain.Domain; +import br.com.muttley.model.security.jackson.UserDeserializer; +import br.com.muttley.model.security.jackson.UserSerializer; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.springframework.data.annotation.PersistenceConstructor; + +/** + * @author Joel Rodrigues Moreira on 22/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@EqualsAndHashCode(of = "user") +public class WorkTeamMember { + /*@JsonSerialize(using = UserSerializer.class) + @JsonDeserialize(using = UserDeserializer.class)*/ + private final User user; + private final Domain domain; + + @JsonCreator + @PersistenceConstructor + public WorkTeamMember(@JsonProperty("user") User user, @JsonProperty("domain") Domain domain) { + this.user = user; + this.domain = domain; + } +} diff --git a/muttley-model/src/main/java/br/com/muttley/versioning/Versioning.java b/muttley-model/src/main/java/br/com/muttley/versioning/Versioning.java new file mode 100644 index 00000000..e0194cd2 --- /dev/null +++ b/muttley-model/src/main/java/br/com/muttley/versioning/Versioning.java @@ -0,0 +1,10 @@ +package br.com.muttley.versioning; + +/** + * @author Joel Rodrigues Moreira 17/08/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface Versioning { + String getVersion(); +} diff --git a/muttley-mongo/pom.xml b/muttley-mongo/pom.xml index 6050297d..a250b2dc 100644 --- a/muttley-mongo/pom.xml +++ b/muttley-mongo/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar @@ -15,6 +15,23 @@ Demo project for Spring Boot + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + + org.apache.commons + commons-lang3 + + br.com.muttley muttley-exception diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/MongoConfig.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/MongoConfig.java index 952f61bf..e1f67da1 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/MongoConfig.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/MongoConfig.java @@ -1,10 +1,21 @@ package br.com.muttley.mongo.service; +import br.com.muttley.mongo.service.codec.BigDecimalCodecProvider; +import br.com.muttley.mongo.service.codec.BigDecimalTransformer; +import br.com.muttley.mongo.service.codec.ZonedDateTimeCodecProvider; +import br.com.muttley.mongo.service.codec.ZonedDateTimeTransformer; import br.com.muttley.mongo.service.converters.BigDecimalToDecimal128Converter; +import br.com.muttley.mongo.service.converters.BsonDocumentToZonedDateTimeConverter; import br.com.muttley.mongo.service.converters.Decimal128ToBigDecimalConverter; +import br.com.muttley.mongo.service.converters.ZonedDateTimeToBsonDocumentConverter; import com.mongodb.Mongo; import com.mongodb.MongoClient; +import com.mongodb.MongoClientOptions; import com.mongodb.ServerAddress; +import org.bson.BSON; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.core.convert.converter.Converter; @@ -12,10 +23,13 @@ import org.springframework.data.mongodb.core.convert.CustomConversions; import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory; +import java.math.BigDecimal; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import static com.mongodb.MongoCredential.createCredential; +import static java.util.Arrays.asList; import static java.util.Collections.singletonList; /** @@ -73,12 +87,38 @@ protected UserCredentials getUserCredentials() { return super.getUserCredentials(); }*/ + @Override @Bean public Mongo mongo() { + BSON.addEncodingHook(BigDecimal.class, new BigDecimalTransformer()); + BSON.addEncodingHook(ZonedDateTime.class, new ZonedDateTimeTransformer()); + + final List codecRegistries = new ArrayList<>(asList( + CodecRegistries.fromProviders(new BigDecimalCodecProvider()), + CodecRegistries.fromProviders(new ZonedDateTimeCodecProvider()), + MongoClient.getDefaultCodecRegistry() + )); + + final List codecProviders = this.getCodecProviders(); + if (codecProviders != null) { + codecProviders.stream().map(it -> CodecRegistries.fromProviders(it)).forEach(it -> codecRegistries.add(it)); + } + + final CodecRegistry condecRegistry = CodecRegistries.fromRegistries(codecRegistries); + + final MongoClientOptions.Builder builder = MongoClientOptions.builder().codecRegistry(condecRegistry); + + return new MongoClient( singletonList(new ServerAddress(this.hostDataBase, Integer.valueOf(this.portDataBase))), - singletonList(createCredential(this.userName, this.dataBaseName, password.toCharArray()))); + singletonList(createCredential(this.userName, this.dataBaseName, password.toCharArray())), + builder.build() + ); + } + + protected List getCodecProviders() { + return null; } /** @@ -92,6 +132,8 @@ public final CustomConversions customConversions() { final List converters = new ArrayList(2); converters.add(new BigDecimalToDecimal128Converter()); converters.add(new Decimal128ToBigDecimalConverter()); + converters.add(new ZonedDateTimeToBsonDocumentConverter()); + converters.add(new BsonDocumentToZonedDateTimeConverter()); //pegando o conversores customizados final Converter[] customConversions = getConverters(); diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/BigDecimalCodec.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/BigDecimalCodec.java new file mode 100644 index 00000000..1706af52 --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/BigDecimalCodec.java @@ -0,0 +1,33 @@ +package br.com.muttley.mongo.service.codec; + +import br.com.muttley.mongo.service.converters.BigDecimalToDecimal128Converter; +import br.com.muttley.mongo.service.converters.Decimal128ToBigDecimalConverter; +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; + +import java.math.BigDecimal; + +/** + * @author Joel Rodrigues Moreira on 22/08/2019. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class BigDecimalCodec implements Codec { + @Override + public BigDecimal decode(final BsonReader reader, final DecoderContext context) { + return new Decimal128ToBigDecimalConverter().convert(reader.readDecimal128()); + } + + @Override + public void encode(final BsonWriter writer, final BigDecimal value, final EncoderContext context) { + writer.writeDecimal128(new BigDecimalToDecimal128Converter().convert(value)); + } + + @Override + public Class getEncoderClass() { + return BigDecimal.class; + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/BigDecimalCodecProvider.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/BigDecimalCodecProvider.java new file mode 100644 index 00000000..8cdcb828 --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/BigDecimalCodecProvider.java @@ -0,0 +1,22 @@ +package br.com.muttley.mongo.service.codec; + +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; + +import java.math.BigDecimal; + +/** + * @author Joel Rodrigues Moreira on 22/08/2019. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class BigDecimalCodecProvider implements CodecProvider { + @Override + public Codec get(final Class aClass, final CodecRegistry codecRegistry) { + if (BigDecimal.class.isAssignableFrom(aClass)) { + return (Codec) new BigDecimalCodec(); + } + return null; + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/BigDecimalTransformer.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/BigDecimalTransformer.java new file mode 100644 index 00000000..a69f2e5c --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/BigDecimalTransformer.java @@ -0,0 +1,18 @@ +package br.com.muttley.mongo.service.codec; + +import br.com.muttley.mongo.service.converters.BigDecimalToDecimal128Converter; +import org.bson.Transformer; + +import java.math.BigDecimal; + +/** + * @author Joel Rodrigues Moreira on 22/08/2019. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class BigDecimalTransformer implements Transformer { + @Override + public Object transform(final Object object) { + return new BigDecimalToDecimal128Converter().convert((BigDecimal) object); + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/ZonedDateTimeCodec.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/ZonedDateTimeCodec.java new file mode 100644 index 00000000..eb16331e --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/ZonedDateTimeCodec.java @@ -0,0 +1,56 @@ +package br.com.muttley.mongo.service.codec; + +import br.com.muttley.mongo.service.converters.BsonDocumentToZonedDateTimeConverter; +import br.com.muttley.mongo.service.converters.ZonedDateTimeToBsonDocumentConverter; +import org.bson.BasicBSONObject; +import org.bson.BsonDateTime; +import org.bson.BsonDocument; +import org.bson.BsonReader; +import org.bson.BsonString; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; + +import java.time.ZonedDateTime; + +/** + * @author Joel Rodrigues Moreira on 22/08/2019. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ZonedDateTimeCodec implements Codec { + + @Override + public ZonedDateTime decode(final BsonReader reader, final DecoderContext context) { + + final BasicBSONObject document = new BasicBSONObject(); + + reader.readStartDocument(); + + document.put("date", new BsonDateTime(reader.readDateTime("date"))); + document.put("offset", new BsonString(reader.readString("offset"))); + + reader.readEndDocument(); + return new BsonDocumentToZonedDateTimeConverter().convert(document); + } + + @Override + public void encode(final BsonWriter writer, final ZonedDateTime value, final EncoderContext context) { + final BsonDocument document = new ZonedDateTimeToBsonDocumentConverter().convert(value); + + writer.writeStartDocument(); + + writer.writeDateTime("date", document.get("date").asDateTime().getValue()); + writer.writeString("offset", document.get("offset").asString().getValue()); + + writer.writeEndDocument(); + } + + @Override + public Class getEncoderClass() { + return ZonedDateTime.class; + } + + +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/ZonedDateTimeCodecProvider.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/ZonedDateTimeCodecProvider.java new file mode 100644 index 00000000..a3aac68e --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/ZonedDateTimeCodecProvider.java @@ -0,0 +1,22 @@ +package br.com.muttley.mongo.service.codec; + +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; + +import java.time.OffsetDateTime; + +/** + * @author Joel Rodrigues Moreira on 22/08/2019. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ZonedDateTimeCodecProvider implements CodecProvider { + @Override + public Codec get(final Class aClass, final CodecRegistry codecRegistry) { + if (OffsetDateTime.class.isAssignableFrom(aClass)) { + return (Codec) new ZonedDateTimeCodec(); + } + return null; + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/ZonedDateTimeTransformer.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/ZonedDateTimeTransformer.java new file mode 100644 index 00000000..010017d2 --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/codec/ZonedDateTimeTransformer.java @@ -0,0 +1,20 @@ +package br.com.muttley.mongo.service.codec; + +import br.com.muttley.mongo.service.converters.ZonedDateTimeToBsonDocumentConverter; +import org.bson.Transformer; + +import java.time.ZonedDateTime; + +/** + * @author Joel Rodrigues Moreira on 22/08/2019. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ZonedDateTimeTransformer implements Transformer { + + @Override + public Object transform(final Object zonedDateTime) { + return new ZonedDateTimeToBsonDocumentConverter().convert((ZonedDateTime) zonedDateTime); + } + +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/config/ConfigViews.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/config/ConfigViews.java index 2291379e..b5b9a636 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/config/ConfigViews.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/config/ConfigViews.java @@ -1,14 +1,20 @@ package br.com.muttley.mongo.service.config; -import br.com.muttley.mongo.service.config.source.ViewRepository; +import br.com.muttley.model.View; import br.com.muttley.mongo.service.config.source.ViewSource; +import br.com.muttley.mongo.service.event.ConfigViewContextEvent; import com.mongodb.MongoClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Component; +import static org.springframework.data.mongodb.core.query.Criteria.where; + /** * @author Joel Rodrigues Moreira on 18/06/18. * e-mail: joel.databox@gmail.com @@ -16,19 +22,24 @@ */ @Component public class ConfigViews implements ApplicationListener { - private final String dbName; - private final ViewRepository viewRepository; + @Value("${spring.data.mongodb.database}") + private String dbName; + private final MongoTemplate template; private final MongoClient client; + private final ApplicationEventPublisher publisher; @Autowired - public ConfigViews(@Value("${spring.data.mongodb.database}") final String dbName, final ViewRepository viewRepository, final MongoClient client) { - this.dbName = dbName; - this.viewRepository = viewRepository; + public ConfigViews(MongoTemplate template, MongoClient client, ApplicationEventPublisher publisher) { + this.template = template; this.client = client; + this.publisher = publisher; } @Override public void onApplicationEvent(final ContextRefreshedEvent event) { + final ConfigViewContextEvent configViewContextEvent = new ConfigViewContextEvent(); + publisher.publishEvent(configViewContextEvent); + configViewContextEvent.getSource().forEach(it -> this.createView(it)); } @@ -61,5 +72,37 @@ private void createView(final ViewSource source) { this.repository.save(view.updateInfo(source)); } }*/ + final Query query = new Query(); + query.addCriteria(where("name").is(source.getViewName())); + //verificando se a view existe + final View view = this.template.findOne(new Query(where("name").is(source.getViewName())), View.class); + + if (view == null) { + //a view não existe, logo devemos criar a mesma + this.client + .getDatabase(this.dbName) + .createView(source.getViewName(), source.getViewOn(), source.getPipeline()); + //salvando informações da view criada + this.template.save(new View(source.getViewName(), source.getVersion(), source.getDescription())); + } else { + //se a view já existe devemos verificar a versão da mesma + //se a versão for diferente devemos dropar essa view + if (!view.getVersion().equals(source.getVersion())) { + this.client + .getDatabase(this.dbName) + .getCollection(source.getViewName()) + .drop(); + + //adicionando novamente a view + this.client + .getDatabase(this.dbName) + .createView(source.getViewName(), source.getViewOn(), source.getPipeline()); + + //atualizando info da view + //this.template.save(view.updateInfo(source)); + this.template.save(view.setDescription(source.getDescription()) + .setVersion(source.getVersion())); + } + } } } diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/config/source/ViewRepository.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/config/source/ViewRepository.java deleted file mode 100644 index 6a83d98e..00000000 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/config/source/ViewRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package br.com.muttley.mongo.service.config.source; - -/** - * @author Joel Rodrigues Moreira on 18/06/18. - * e-mail: joel.databox@gmail.com - * @project muttley-cloud - */ -public interface ViewRepository { - AbstractView findByName(final String name); - - AbstractView save(AbstractView view); -} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/BigDecimalToDecimal128Converter.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/BigDecimalToDecimal128Converter.java index 8faa9059..6b446ff1 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/BigDecimalToDecimal128Converter.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/BigDecimalToDecimal128Converter.java @@ -3,6 +3,7 @@ import org.bson.types.Decimal128; import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.WritingConverter; import org.springframework.stereotype.Component; import java.math.BigDecimal; @@ -11,10 +12,11 @@ * Created by master on 16/06/17. */ @Component +@WritingConverter public class BigDecimalToDecimal128Converter implements Converter { @Override public Decimal128 convert(final BigDecimal source) { return source == null ? null : Decimal128.parse(source.toPlainString()); } -} \ No newline at end of file +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/BsonDocumentToZonedDateTimeConverter.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/BsonDocumentToZonedDateTimeConverter.java new file mode 100644 index 00000000..0391f24e --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/BsonDocumentToZonedDateTimeConverter.java @@ -0,0 +1,26 @@ +package br.com.muttley.mongo.service.converters; + +/** + * Created by master on 16/06/17. + */ + +import org.bson.BasicBSONObject; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.stereotype.Component; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +@Component +@ReadingConverter +public class BsonDocumentToZonedDateTimeConverter implements Converter { + + @Override + public ZonedDateTime convert(final BasicBSONObject source) { + if (source == null || source.isEmpty()) { + return null; + } + return ZonedDateTime.ofInstant(source.getDate("date").toInstant(), ZoneOffset.of(source.getString("offset"))); + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/Decimal128ToBigDecimalConverter.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/Decimal128ToBigDecimalConverter.java index d79a60c3..d5d0a6e8 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/Decimal128ToBigDecimalConverter.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/Decimal128ToBigDecimalConverter.java @@ -6,15 +6,19 @@ import org.bson.types.Decimal128; import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; import org.springframework.stereotype.Component; import java.math.BigDecimal; +import static br.com.muttley.utils.BigDecimalUtils.setDefaultScale; + @Component +@ReadingConverter public class Decimal128ToBigDecimalConverter implements Converter { @Override public BigDecimal convert(final Decimal128 source) { - return source == null ? null : source.bigDecimalValue(); + return source == null ? null : setDefaultScale(source.bigDecimalValue()); } -} \ No newline at end of file +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/DefaultAuthorityToDocumentConverter.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/DefaultAuthorityToDocumentConverter.java index 4eee547f..e258e10c 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/DefaultAuthorityToDocumentConverter.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/DefaultAuthorityToDocumentConverter.java @@ -3,6 +3,7 @@ import br.com.muttley.model.security.Authority; import org.bson.Document; import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.WritingConverter; /** * @author Joel Rodrigues Moreira on 28/02/18. @@ -11,11 +12,12 @@ *

* Converter padrão para a interface Authority */ +@WritingConverter public class DefaultAuthorityToDocumentConverter implements Converter { @Override public Document convert(final Authority authority) { final Document document = new Document(); - document.put("name", authority.getName()); + document.put("name", authority.getRole().toString()); document.put("description", authority.getDescription()); return document; } diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/DefaultDocumentToAuthorityConverter.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/DefaultDocumentToAuthorityConverter.java index d9ed4e64..7866fe93 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/DefaultDocumentToAuthorityConverter.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/DefaultDocumentToAuthorityConverter.java @@ -4,6 +4,7 @@ import br.com.muttley.model.security.AuthorityImpl; import org.bson.Document; import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; /** * @author Joel Rodrigues Moreira on 28/02/18. @@ -12,6 +13,7 @@ *

* Converter padrão para a interface Authority */ +@ReadingConverter public class DefaultDocumentToAuthorityConverter implements Converter { @Override public Authority convert(final Document source) { diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/ZonedDateTimeToBsonDocumentConverter.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/ZonedDateTimeToBsonDocumentConverter.java new file mode 100644 index 00000000..9239eb03 --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/converters/ZonedDateTimeToBsonDocumentConverter.java @@ -0,0 +1,58 @@ +package br.com.muttley.mongo.service.converters; + + +import br.com.muttley.model.TimeZoneDocument; +import org.bson.BsonDateTime; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.WritingConverter; +import org.springframework.stereotype.Component; + +import java.time.LocalTime; +import java.time.ZonedDateTime; +import java.util.Date; + +import static br.com.muttley.utils.TimeZoneUtils.getTimezoneFromId; + +/** + * Created by master on 16/06/17. + */ +@Component +@WritingConverter +public class ZonedDateTimeToBsonDocumentConverter implements Converter { + + @Override + public BsonDocument convert(final ZonedDateTime zonedDateTime) { + if (zonedDateTime == null) { + return null; + } + final BsonDocument document = new BsonDocument("date", new BsonDateTime( + Date.from(zonedDateTime.toInstant()).getTime() + )); + + final String offset = getTimezoneFromId(zonedDateTime.getOffset().toString()); + + document.put("offset", new BsonString(offset)); + + final LocalTime hour = LocalTime.parse(offset.replaceAll("[-+]", "")); + + document.put("virtual", + new BsonDateTime( + offset.startsWith("-") ? + Date.from( + zonedDateTime + .minusHours(hour.getHour()) + .minusMinutes(hour.getMinute()) + .toInstant() + ).getTime() : + Date.from( + zonedDateTime + .plusHours(hour.getHour()) + .plusMinutes(hour.getMinute()) + .toInstant() + ).getTime() + )); + return document; + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/event/ConfigViewContextEvent.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/event/ConfigViewContextEvent.java new file mode 100644 index 00000000..bfe5dfd2 --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/event/ConfigViewContextEvent.java @@ -0,0 +1,31 @@ +package br.com.muttley.mongo.service.event; + +import br.com.muttley.mongo.service.config.source.ViewSource; +import org.springframework.context.ApplicationEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 08/09/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ConfigViewContextEvent extends ApplicationEvent { + private List views; + + public ConfigViewContextEvent() { + super(""); + this.views = new ArrayList(); + } + + public ConfigViewContextEvent add(ViewSource source) { + this.views.add(source); + return this; + } + + @Override + public List getSource() { + return this.views; + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/Aggregate.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/Aggregate.java index be7b54bb..04d687b0 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/Aggregate.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/Aggregate.java @@ -47,7 +47,7 @@ public static final List createAggregationsCount(final Cla } private static final List createAggregationsDefault(final boolean isCount, final Class clazz, final Map queryParams) { - final Map> trimap = checkCriterios(queryParams); + final Map> trimap = checkCriterios(queryParams); final List aggregations = new ArrayList<>(5); //verificando se é necessário fazer gabiarra do lookup if (isRequiredProject(queryParams)) { @@ -58,8 +58,8 @@ private static final List createAggregationsDefault(final SortOperation sortOperationAsc = null; SortOperation sortOperationDesc = null; for (final String key : trimap.keySet()) { - final Map map = trimap.get(key); - for (final Operators operation : map.keySet()) { + final Map map = trimap.get(key); + for (final Operator operation : map.keySet()) { final Object value = map.get(operation); switch (operation) { case SKIP: @@ -148,12 +148,14 @@ public static final boolean isRequiredProject(Map queryParams) { /** * Cria criteria com base nos parametros */ - private static final Criteria extractCriteria(final Operators operator, final String key, final Object value) { + private static final Criteria extractCriteria(final Operator operator, final String key, final Object value) { switch (operator) { case CONTAINS: return new Criteria(key).regex(value.toString(), "si"); case GTE: return new Criteria(key).gte(value); + case LTE: + return new Criteria(key).lte(value); case LT: return new Criteria(key).lt(value); case IN: @@ -172,10 +174,10 @@ private static final Criteria extractCriteria(final Operators operator, final St //evitando que algum animal passe uma string vazia if (expr.length > 1) { //extraindo os criterios do or - criterionsOr[i] = extractCriteria(Operators.of(expr[0]), createKey(expr[0]), expr[1]); + criterionsOr[i] = extractCriteria(Operator.of(expr[0]), createKey(expr[0]), expr[1]); } else { //string ta vazia mano - criterionsOr[i] = extractCriteria(Operators.of(expr[0]), createKey(expr[0]), ""); + criterionsOr[i] = extractCriteria(Operator.of(expr[0]), createKey(expr[0]), ""); } } return new Criteria().orOperator(criterionsOr); @@ -188,47 +190,50 @@ private static final Criteria extractCriteria(final Operators operator, final St /** * Organiza as operação necessarias em um trimap */ - private static final Map> checkCriterios(final Map queryParams) { - final Map> trimap = new HashMap<>(); + private static final Map> checkCriterios(final Map queryParams) { + final Map> trimap = new HashMap<>(); //organizando os parametros queryParams.forEach((key, value) -> { final String keyTrimap = createKey(key); - Operators operator = Operators.of(key); + Operator operator = Operator.of(key); if (operator == null) { - operator = Operators.IS; + operator = Operator.IS; LogFactory.getLog(Aggregate.class).error("Atenção, operador de agregação não encontrado. Será adicionado o .$is"); } switch (operator) { case CONTAINS: - addParam(trimap, keyTrimap, Operators.CONTAINS, value); + addParam(trimap, keyTrimap, Operator.CONTAINS, value); break; case GTE: - addParam(trimap, keyTrimap, Operators.GTE, value); + addParam(trimap, keyTrimap, Operator.GTE, value); + break; + case LTE: + addParam(trimap, keyTrimap, Operator.LTE, value); break; case LT: - addParam(trimap, keyTrimap, Operators.LT, value); + addParam(trimap, keyTrimap, Operator.LT, value); break; case IN: - addParam(trimap, keyTrimap, Operators.IN, split(String.valueOf(value))); + addParam(trimap, keyTrimap, Operator.IN, split(String.valueOf(value))); break; case IS: - addParam(trimap, keyTrimap, Operators.IS, value); + addParam(trimap, keyTrimap, Operator.IS, value); break; case OR: - addParam(trimap, keyTrimap, Operators.OR, value); + addParam(trimap, keyTrimap, Operator.OR, value); break; case SKIP: - addParam(trimap, keyTrimap, Operators.SKIP, value); + addParam(trimap, keyTrimap, Operator.SKIP, value); break; case LIMIT: - addParam(trimap, keyTrimap, Operators.LIMIT, value); + addParam(trimap, keyTrimap, Operator.LIMIT, value); break; case ORDER_BY_ASC: - addParam(trimap, keyTrimap, Operators.ORDER_BY_ASC, split(String.valueOf(value))); + addParam(trimap, keyTrimap, Operator.ORDER_BY_ASC, split(String.valueOf(value))); break; case ORDER_BY_DESC: - addParam(trimap, keyTrimap, Operators.ORDER_BY_DESC, split(String.valueOf(value))); + addParam(trimap, keyTrimap, Operator.ORDER_BY_DESC, split(String.valueOf(value))); break; default: throw new MuttleyBadRequestException(null, null, "A requisição contem criterios inválidos"); @@ -241,17 +246,17 @@ private static final Map> checkCriterios(final Ma * Cria uma chave para o trimap que organiza a agregação a ser executada */ private static final String createKey(String key) { - for (final Operators o : Operators.values()) { + for (final Operator o : Operator.values()) { key = key.replace(o.toString(), ""); } return key; } - private static final void addParam(final Map> trimap, final String key, final Operators operator, final Object value) { + private static final void addParam(final Map> trimap, final String key, final Operator operator, final Object value) { if (trimap.containsKey(key)) { trimap.get(key).put(operator, value); } else { - final HashMap mp = new HashMap<>(); + final HashMap mp = new HashMap<>(); mp.put(operator, value); trimap.put(key, mp); } @@ -284,4 +289,4 @@ private static final String[] split(final String regex, final String value) { private static final String getCollectionName(final Class clazz) { return ((Document) clazz.getAnnotation(Document.class)).collection(); } -} \ No newline at end of file +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/AggregationUtils.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/AggregationUtils.java new file mode 100644 index 00000000..9ff73aad --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/AggregationUtils.java @@ -0,0 +1,425 @@ +package br.com.muttley.mongo.service.infra; + + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.mongo.service.infra.metadata.EntityMetaData; +import io.jsonwebtoken.lang.Collections; +import lombok.Getter; +import org.apache.commons.logging.LogFactory; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.LimitOperation; +import org.springframework.data.mongodb.core.aggregation.SkipOperation; +import org.springframework.data.mongodb.core.aggregation.SortOperation; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import static br.com.muttley.mongo.service.infra.Operator.CONTAINS; +import static br.com.muttley.mongo.service.infra.Operator.GT; +import static br.com.muttley.mongo.service.infra.Operator.GTE; +import static br.com.muttley.mongo.service.infra.Operator.IN; +import static br.com.muttley.mongo.service.infra.Operator.IS; +import static br.com.muttley.mongo.service.infra.Operator.LIMIT; +import static br.com.muttley.mongo.service.infra.Operator.LT; +import static br.com.muttley.mongo.service.infra.Operator.LTE; +import static br.com.muttley.mongo.service.infra.Operator.OR; +import static br.com.muttley.mongo.service.infra.Operator.ORDER_BY_ASC; +import static br.com.muttley.mongo.service.infra.Operator.ORDER_BY_DESC; +import static br.com.muttley.mongo.service.infra.Operator.SKIP; +import static br.com.muttley.mongo.service.infra.Operator.of; +import static br.com.muttley.mongo.service.infra.Operator.values; +import static java.util.Arrays.asList; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.count; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.skip; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.sort; +import static org.springframework.util.CollectionUtils.isEmpty; + +/** + * @author Joel Rodrigues Moreira on 04/06/19. + * e-mail: joel.databox@gmail.com + * @project invistate-cloud + */ +public class AggregationUtils { + + public static final List createAggregations(final EntityMetaData entityMetaData, final List pipelines, final Map queryParams) { + return createAggregationsDefault(entityMetaData, pipelines, false, queryParams); + } + + public static final List createAggregationsCount(final EntityMetaData entityMetaData, final List pipelines, final Map queryParams) { + return createAggregationsCount(entityMetaData, pipelines, queryParams, "count"); + } + + public static final List createAggregationsCount(final EntityMetaData entityMetaData, final List pipelines, final Map queryParams, final String nameFieldResult) { + final List list = createAggregationsDefault(entityMetaData, pipelines, true, queryParams); + list.add(count().as(nameFieldResult)); + return list; + } + + private static List createAggregationsDefault(final EntityMetaData entityMetaData, final List pipelines, final boolean isCount, final Map queryParams) { + final Map> triMap = processCriterions(queryParams); + final List aggregations = isEmpty(pipelines) ? new LinkedList() : new LinkedList(pipelines); + + + SkipOperation skipOperation = null; + LimitOperation limitOperation = null; + SortOperation sortOperationAsc = null; + SortOperation sortOperationDesc = null; + + //armazena a keys que fez lookup + //isso é necessário para evitarmos de refazer lookup + final Set keysLookUp = new LinkedHashSet<>(); + + for (final String key : triMap.keySet()) { + final Map map = triMap.get(key); + for (final Operator operation : map.keySet()) { + final Object value = map.get(operation); + + switch (operation) { + case SKIP: + if (!isCount) + skipOperation = skip(Long.valueOf(value.toString())); + break; + case LIMIT: + if (!isCount) + limitOperation = limit(Long.valueOf(value.toString())); + break; + case ORDER_BY_ASC: + if (!isCount) + sortOperationAsc = sort(Sort.Direction.ASC, (String[]) value); + break; + case ORDER_BY_DESC: + if (!isCount) + sortOperationDesc = sort(Sort.Direction.DESC, (String[]) value); + break; + default: { + + boolean lookup = false; + final List pipes; + //sofaremos a verificação para lookup so a key não referenciar um id + if (!(StringUtils.countOccurrencesOf(key, ".") == 1 && key.endsWith(".$id"))) { + //verificando se é já tem lookup a ser gerado + final String[] other = EntityMetaData.generateCascadKeys(key); + //se tiver mais do que 1 item é cinal que estamos trabalhando com níveis + if (other.length > 1) { + //verificando se do primeiro até o penultimo nível se faz necessário fazer lookup + for (int i = 0; i <= other.length - 2; i++) { + if (entityMetaData.getFieldByName(other[i]).isDBRef()) { + if (!keysLookUp.contains(other[i])) { + keysLookUp.add(other[i]); + lookup = true; + } + } + } + } + pipes = extractCriteria(entityMetaData, isEmpty(pipelines) ? lookup : false, operation, key, value); + } else { + pipes = extractCriteria(entityMetaData, isEmpty(pipelines), operation, key, value); + } + + if (pipes != null) { + pipes.forEach(it -> { + if (!Collections.isEmpty(it.getPipelines())) { + aggregations.addAll(it.getPipelines()); + } + aggregations.add(match(it.getCriteria())); + }); + } + } + } + + } + } + if (!isCount) { + if (sortOperationAsc != null) aggregations.add(sortOperationAsc); + if (sortOperationDesc != null) aggregations.add(sortOperationDesc); + if (skipOperation != null) aggregations.add(skipOperation); + if (limitOperation != null) aggregations.add(limitOperation); + } + if (aggregations.isEmpty()) { + aggregations.add(skip(0L)); + } + return aggregations; + } + + /** + * Organiza as operações necessárias para cada parametro passado + */ + private static final Map> processCriterions(final Map queryParams) { + final Map> triMap = new LinkedHashMap<>(); + + //percorrendo todos os itens + queryParams.forEach((final String key, final String value) -> { + Operator operator = of(key); + final String keyTrimap = replaceAllOperators(key); + if (operator == null) { + operator = IS; + LogFactory.getLog(AggregationUtils.class).error("Atenção, operador de agregação não encontrado. Será adicionado o .$is"); + } + switch (operator) { + case CONTAINS: + addParamInTriMap(triMap, keyTrimap, CONTAINS, value); + break; + case LTE: + addParamInTriMap(triMap, keyTrimap, LTE, value); + break; + case GTE: + addParamInTriMap(triMap, keyTrimap, GTE, value); + break; + case LT: + addParamInTriMap(triMap, keyTrimap, LT, value); + break; + case GT: + addParamInTriMap(triMap, keyTrimap, GT, value); + break; + case IN: + addParamInTriMap(triMap, keyTrimap, IN, split(String.valueOf(value))); + break; + case IS: + addParamInTriMap(triMap, keyTrimap, IS, value); + break; + case OR: + addParamInTriMap(triMap, keyTrimap, OR, value); + break; + case SKIP: + addParamInTriMap(triMap, keyTrimap, SKIP, value); + break; + case LIMIT: + addParamInTriMap(triMap, keyTrimap, LIMIT, value); + break; + case ORDER_BY_ASC: + addParamInTriMap(triMap, keyTrimap, ORDER_BY_ASC, split(String.valueOf(value))); + break; + case ORDER_BY_DESC: + addParamInTriMap(triMap, keyTrimap, ORDER_BY_DESC, split(String.valueOf(value))); + break; + default: + throw new MuttleyBadRequestException(null, null, "A requisição contem criterios inválidos"); + } + }); + return triMap; + } + + /** + * Remove qualquer operador presente em uma stringo + * por exemplo: + * + * @param value => "pessoa.nome.$is" + * @return "pessoa.nome" + */ + private static final String replaceAllOperators(final String value) { + //lista de widcards + final String[] operators = Stream.of(values()).map(Operator::toString).toArray(String[]::new); + String result = value; + //removendo tudo + for (final String widcard : operators) { + result = result.replace(widcard, ""); + } + return result; + } + + private static final void addParamInTriMap(Map> triMap, final String key, final Operator operator, final Object value) { + if (triMap.containsKey(key)) { + triMap.get(key).put(operator, value); + } else { + final HashMap mp = new HashMap<>(); + mp.put(operator, value); + triMap.put(key, mp); + } + } + + private static final String[] split(String value) { + if (value != null && !value.isEmpty()) { + final String first = value.substring(0, 1); + if (first.equals(";") || first.equals("|")) { + value = value.substring(1); + } + + if (value.contains(";")) + return value.split(";"); + if (value.contains("|")) + return value.split("|"); + + return new String[]{value}; + } + return null; + } + + private static final String[] split(final String regex, final String value) { + if (!StringUtils.isEmpty(value)) { + return value.split(regex); + } + return null; + } + + private static final List extractCriteria(final EntityMetaData entityMetaData, final boolean generatePipelines, final Operator operator, final String key, final Object value) { + switch (operator) { + case IS: + return asList(new CustomCriteria(entityMetaData, key, extractIS(entityMetaData, key, value)).build(generatePipelines)); + case CONTAINS: + return asList(new CustomCriteria(entityMetaData, key, extractCONTAINS(entityMetaData, key, value)).build(generatePipelines)); + case GTE: + return asList(new CustomCriteria(entityMetaData, key, extractGTE(entityMetaData, key, value)).build(generatePipelines)); + case LTE: + return asList(new CustomCriteria(entityMetaData, key, extractLTE(entityMetaData, key, value)).build(generatePipelines)); + case GT: + return asList(new CustomCriteria(entityMetaData, key, extractGT(entityMetaData, key, value)).build(generatePipelines)); + case LT: + return asList(new CustomCriteria(entityMetaData, key, extractLT(entityMetaData, key, value)).build(generatePipelines)); + case IN: + return asList(new CustomCriteria(entityMetaData, key, extractIN(entityMetaData, key, value)).build(generatePipelines)); + case OR: { + return extractOR(entityMetaData, generatePipelines, key, value); + } + default: + throw new IllegalArgumentException("Case not foud"); + } + } + + private static final Criteria extractIS(final EntityMetaData entityMetaData, final String key, final Object value) { + final EntityMetaData m = entityMetaData.getFieldByName(key); + if (m != null) { + return new Criteria(key).is(m.converteValue(value)); + } + return new Criteria(key).is(value); + } + + private static final Criteria extractGTE(final EntityMetaData entityMetaData, final String key, final Object value) { + final EntityMetaData m = entityMetaData.getFieldByName(key); + if (m != null) { + return new Criteria(key).gte(m.converteValue(value)); + } + return new Criteria(key).gte(value); + } + + private static final Criteria extractLTE(final EntityMetaData entityMetaData, final String key, final Object value) { + final EntityMetaData m = entityMetaData.getFieldByName(key); + if (m != null) { + return new Criteria(key).lte(m.converteValue(value)); + } + return new Criteria(key).lte(value); + } + + private static final Criteria extractGT(final EntityMetaData entityMetaData, final String key, final Object value) { + final EntityMetaData m = entityMetaData.getFieldByName(key); + if (m != null) { + return new Criteria(key).gt(m.converteValue(value)); + } + return new Criteria(key).gt(value); + } + + private static final Criteria extractLT(final EntityMetaData entityMetaData, final String key, final Object value) { + final EntityMetaData m = entityMetaData.getFieldByName(key); + if (m != null) { + return new Criteria(key).lt(m.converteValue(value)); + } + return new Criteria(key).lt(value); + } + + private static final Criteria extractIN(final EntityMetaData entityMetaData, final String key, final Object value) { + final EntityMetaData m = entityMetaData.getFieldByName(key); + if (m != null) { + return new Criteria(key).in(m.converteValue(value)); + } + return new Criteria(key).in(value); + } + + private static final Criteria extractCONTAINS(final EntityMetaData entityMetaData, final String key, final Object value) { + final EntityMetaData m = entityMetaData.getFieldByName(key); + if (m != null) { + return new Criteria(key).regex(m.converteValue(value).toString(), "si"); + } + return new Criteria(key).regex(value.toString(), "si"); + } + + private static final List extractOR(final EntityMetaData entityMetaData, final boolean generatePipelines, final String key, final Object values) { + + final String text = values.toString(); + if (text == null || text.length() <= 2) { + throw new MuttleyBadRequestException(null, "$or", "Erro ao executar a consulta").addDetails("$or", "informe algum parametro valido para o operador $or"); + } + final String[] allCriterions = split(";;", text.substring(1, text.length() - 1)); + final List pipelines = new ArrayList<>(); + for (int i = 0; i < allCriterions.length; i++) { + final String expr[] = split("=", allCriterions[i]); + //evitando que algum animal passe uma string vazia + if (expr.length > 1) { + //extraindo os criterios do or + pipelines.addAll(extractCriteria(entityMetaData, generatePipelines, of(expr[0]), replaceAllOperators(expr[0]), expr[1])); + //criterionsOr[i] = extractCriteria(entityMetaData, of(expr[0]), replaceAllOperators(expr[0]), expr[1]); + } else { + //string ta vazia mano + pipelines.addAll(extractCriteria(entityMetaData, generatePipelines, of(expr[0]), replaceAllOperators(expr[0]), "")); + //criterionsOr[i] = extractCriteria(entityMetaData, of(expr[0]), replaceAllOperators(expr[0]), ""); + } + } + return asList(new Pipelines(new Criteria().orOperator(pipelines.stream().map(it -> it.getCriteria()).toArray(Criteria[]::new)))); + //return pipelines; + + } + + @Getter + private static class CustomCriteria { + private final EntityMetaData entityMetaData; + private final String key; + private final Criteria criteria; + + public CustomCriteria(final EntityMetaData entityMetaData, final String key, final Criteria criteria) { + this.entityMetaData = entityMetaData; + this.key = key; + this.criteria = criteria; + //this.build(); + } + + + public Pipelines build(final boolean generatePipelines) { + if (generatePipelines) { + final List aggregations = entityMetaData.createProjectFor(key); + if (Collections.isEmpty(aggregations)) { + return new Pipelines(criteria); + } else { + return new Pipelines(false, aggregations, criteria); + } + } + return new Pipelines(criteria); + } + + + private String resolveExpression(final String expression) { + if (expression.startsWith("#")) { + return (String) new SpelExpressionParser().parseExpression(expression).getValue(); + } + return expression; + } + + } + + @Getter + private static class Pipelines { + private final boolean containsProject; + private final List pipelines; + private final Criteria criteria; + + public Pipelines(final boolean containsProject, final List pipelines, final Criteria criteria) { + this.containsProject = containsProject; + this.pipelines = pipelines; + this.criteria = criteria; + } + + public Pipelines(Criteria criteria) { + this(false, null, criteria); + } + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/Operator.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/Operator.java new file mode 100644 index 00000000..2bf41289 --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/Operator.java @@ -0,0 +1,150 @@ +package br.com.muttley.mongo.service.infra; + +public enum Operator { + GTE(".$gte"), + LTE(".$lte"), + GT(".$gt"), + LT(".$lt"), + IN(".$in") { + @Override + public boolean isRequiredArray() { + return true; + } + }, + CONTAINS(".$contains"), + IS(".$is"), + SKIP("$skip"), + LIMIT("$limit"), + OR(".$or") { + @Override + public boolean isRequiredArray() { + return true; + } + }, + ORDER_BY_ASC("$orderByAsc") { + @Override + public boolean isRequiredArray() { + return true; + } + }, + ORDER_BY_DESC("$orderByDesc") { + @Override + public boolean isRequiredArray() { + return true; + } + }; + + private final String widcard; + + private Operator(String widcard) { + this.widcard = widcard; + } + + @Override + public String toString() { + return this.widcard; + } + + public boolean isRequiredArray() { + return false; + } + + public static Operator of(String value) { + if (value.contains(".$id")) { + value = value.replace(".$id", ""); + } + if (value.contains(".$")) { + value = value.substring(value.indexOf(".$")); + } else if (value.contains("$")) { + value = value.substring(value.indexOf("$")); + } + + switch (value.toLowerCase()) { + case ".$gte": + case "$gte": + return GTE; + case ".$lte": + case "$lte": + return LTE; + case ".$gt": + case "$gt": + return GT; + case ".$lt": + case "$lt": + return LT; + case ".$in": + case "$in": + return IN; + case ".$contains": + case "$contains": + return CONTAINS; + case ".$is": + case "$is": + return IS; + case "$skip": + return SKIP; + case "$limit": + return LIMIT; + case ".$or": + case "$or": + return OR; + case "$orderByAsc": + case "$orderbyasc": + return ORDER_BY_ASC; + case "$orderByDesc": + case "$orderbydesc": + return ORDER_BY_DESC; + default: + return IS; + + } + } + + public static boolean containsOperator(final String value) { + return value.contains(".$gte") || + value.contains("$gte") || + value.contains(".$lte") || + value.contains("$lte") || + value.contains(".$gt") || + value.contains("$gt") || + value.contains(".$lt") || + value.contains("$lt") || + value.contains(".$in") || + value.contains("$in") || + value.contains(".$contains") || + value.contains("$contains") || + value.contains(".$is") || + value.contains("$is") || + value.contains("$skip") || + value.contains("$limit") || + value.contains(".$or") || + value.contains("$or") || + value.contains("$orderByAsc") || + value.contains("$orderByDesc"); + + } + + public static boolean isOperator(final String value) { + return value.equalsIgnoreCase(".$gte") || + value.equalsIgnoreCase("$gte") || + value.equalsIgnoreCase(".$lte") || + value.equalsIgnoreCase("$lte") || + value.equalsIgnoreCase(".$gt") || + value.equalsIgnoreCase("$gt") || + value.equalsIgnoreCase(".$lt") || + value.equalsIgnoreCase("$lt") || + value.equalsIgnoreCase(".$in") || + value.equalsIgnoreCase("$in") || + value.equalsIgnoreCase(".$contains") || + value.equalsIgnoreCase("$contains") || + value.equalsIgnoreCase(".$is") || + value.equalsIgnoreCase("$is") || + value.equalsIgnoreCase("$skip") || + value.equalsIgnoreCase("$limit") || + value.equalsIgnoreCase(".$or") || + value.equalsIgnoreCase("$or") || + value.equalsIgnoreCase("$orderByAsc") || + value.equalsIgnoreCase("$orderByDesc"); + + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/Operators.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/Operators.java deleted file mode 100644 index 3b3ea265..00000000 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/Operators.java +++ /dev/null @@ -1,86 +0,0 @@ -package br.com.muttley.mongo.service.infra; - -public enum Operators { - GTE(".$gte"), - LT(".$lt"), - IN(".$in") { - @Override - public boolean isSplit() { - return true; - } - }, - CONTAINS(".$contains"), - IS(".$is"), - SKIP("$skip"), - LIMIT("$limit"), - OR(".$or") { - @Override - public boolean isSplit() { - return true; - } - }, - ORDER_BY_ASC("$orderByAsc") { - @Override - public boolean isSplit() { - return true; - } - }, - ORDER_BY_DESC("$orderByDesc") { - @Override - public boolean isSplit() { - return true; - } - }; - - private final String widcard; - - private Operators(String widcard) { - this.widcard = widcard; - } - - @Override - public String toString() { - return this.widcard; - } - - public boolean isSplit() { - return false; - } - - public static Operators of(String value) { - if (value.contains(".$id")) { - value = value.replace(".$id", ""); - } - if (value.contains(".$")) { - value = value.substring(value.indexOf(".$")); - } else if (value.contains("$")) { - value = value.substring(value.indexOf("$")); - } - - switch (value) { - case ".$gte": - return GTE; - case ".$lt": - return LT; - case ".$in": - return IN; - case ".$contains": - return CONTAINS; - case ".$is": - return IS; - case "$skip": - return SKIP; - case "$limit": - return LIMIT; - case ".$or": - return OR; - case "$orderByAsc": - return ORDER_BY_ASC; - case "$orderByDesc": - return ORDER_BY_DESC; - default: - return null; - - } - } -} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/QueryBuilder.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/QueryBuilder.java index 97c85079..229a30cf 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/QueryBuilder.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/QueryBuilder.java @@ -13,43 +13,43 @@ public class QueryBuilder { public static Query createQuery(final Map queryParams) { - final Map> trimap = new HashMap<>(); + final Map> trimap = new HashMap<>(); queryParams.forEach((key, value) -> { final String k = key(key); - if (key.endsWith(Operators.CONTAINS.toString())) { - addParam(trimap, k, Operators.CONTAINS, value); - } else if (key.endsWith(Operators.GTE.toString())) { - addParam(trimap, k, Operators.GTE, value); - } else if (key.endsWith(Operators.LT.toString())) { - addParam(trimap, k, Operators.LT, value); - } else if (key.endsWith(Operators.IN.toString())) { - addParam(trimap, k, Operators.IN, split(String.valueOf(value))); - } else if (key.endsWith(Operators.IS.toString()) || !key.contains("$")) { - addParam(trimap, k, Operators.IS, value); - } else if (key.endsWith(Operators.OR.toString())) { - addParam(trimap, k, Operators.OR, value); - } else if (key.endsWith(Operators.SKIP.toString())) { - addParam(trimap, k, Operators.SKIP, value); - } else if (key.endsWith(Operators.LIMIT.toString())) { - addParam(trimap, k, Operators.LIMIT, value); - } else if (key.endsWith(Operators.ORDER_BY_ASC.toString())) { - addParam(trimap, k, Operators.ORDER_BY_ASC, split(String.valueOf(value))); - } else if (key.endsWith(Operators.ORDER_BY_DESC.toString())) { - addParam(trimap, k, Operators.ORDER_BY_DESC, split(String.valueOf(value))); + if (key.endsWith(Operator.CONTAINS.toString())) { + addParam(trimap, k, Operator.CONTAINS, value); + } else if (key.endsWith(Operator.GTE.toString())) { + addParam(trimap, k, Operator.GTE, value); + } else if (key.endsWith(Operator.LT.toString())) { + addParam(trimap, k, Operator.LT, value); + } else if (key.endsWith(Operator.IN.toString())) { + addParam(trimap, k, Operator.IN, split(String.valueOf(value))); + } else if (key.endsWith(Operator.IS.toString()) || !key.contains("$")) { + addParam(trimap, k, Operator.IS, value); + } else if (key.endsWith(Operator.OR.toString())) { + addParam(trimap, k, Operator.OR, value); + } else if (key.endsWith(Operator.SKIP.toString())) { + addParam(trimap, k, Operator.SKIP, value); + } else if (key.endsWith(Operator.LIMIT.toString())) { + addParam(trimap, k, Operator.LIMIT, value); + } else if (key.endsWith(Operator.ORDER_BY_ASC.toString())) { + addParam(trimap, k, Operator.ORDER_BY_ASC, split(String.valueOf(value))); + } else if (key.endsWith(Operator.ORDER_BY_DESC.toString())) { + addParam(trimap, k, Operator.ORDER_BY_DESC, split(String.valueOf(value))); } }); final Query query = new Query(); trimap.forEach((key, map) -> { //Criteria cri = where(key); map.forEach((operation, value) -> { - if (Operators.SKIP.equals(operation)) { + if (Operator.SKIP.equals(operation)) { query.skip(Integer.valueOf(value.toString())); - } else if (Operators.LIMIT.equals(operation)) { + } else if (Operator.LIMIT.equals(operation)) { query.limit(Integer.valueOf(value.toString())); - } else if (Operators.ORDER_BY_ASC.equals(operation)) { + } else if (Operator.ORDER_BY_ASC.equals(operation)) { query.with(new Sort(Sort.Direction.ASC, (String[]) value)); - } else if (Operators.ORDER_BY_DESC.equals(operation)) { + } else if (Operator.ORDER_BY_DESC.equals(operation)) { query.with(new Sort(Sort.Direction.DESC, (String[]) value)); } else { query.addCriteria(extractCriteria(operation, key, value)); @@ -60,7 +60,7 @@ public static Query createQuery(final Map queryParams) { return query; } - private static Criteria extractCriteria(final Operators operator, final String key, final Object value) { + private static Criteria extractCriteria(final Operator operator, final String key, final Object value) { switch (operator) { case CONTAINS: return new Criteria(key).regex(value.toString(), "si"); @@ -80,10 +80,10 @@ private static Criteria extractCriteria(final Operators operator, final String k final String expr[] = split("=", allCriterions[i]); //evitando que algum animal passe uma string vazia if (expr.length > 1) { - criterionsOr[i] = extractCriteria(Operators.of(expr[0]), key(expr[0]), expr[1]); + criterionsOr[i] = extractCriteria(Operator.of(expr[0]), key(expr[0]), expr[1]); } else { //string ta vazia mano - criterionsOr[i] = extractCriteria(Operators.of(expr[0]), key(expr[0]), ""); + criterionsOr[i] = extractCriteria(Operator.of(expr[0]), key(expr[0]), ""); } } return new Criteria().orOperator(criterionsOr); @@ -93,18 +93,18 @@ private static Criteria extractCriteria(final Operators operator, final String k } } - private static void addParam(final Map> trimap, final String key, final Operators operator, final Object value) { + private static void addParam(final Map> trimap, final String key, final Operator operator, final Object value) { if (trimap.containsKey(key)) { trimap.get(key).put(operator, value); } else { - HashMap mp = new HashMap<>(); + HashMap mp = new HashMap<>(); mp.put(operator, value); trimap.put(key, mp); } } private static String key(String key) { - for (final Operators o : Operators.values()) { + for (final Operator o : Operator.values()) { key = key.replace(o.toString(), ""); } return key; diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/metadata/EntityMetaData.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/metadata/EntityMetaData.java new file mode 100644 index 00000000..f3580139 --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/metadata/EntityMetaData.java @@ -0,0 +1,415 @@ +package br.com.muttley.mongo.service.infra.metadata; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.model.security.Owner; +import br.com.muttley.utils.DateUtils; +import com.mongodb.BasicDBObject; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.Fields; +import org.springframework.data.mongodb.core.aggregation.ProjectionOperation; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.beans.Transient; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import static br.com.muttley.utils.DateUtils.DATE_REGEX; +import static br.com.muttley.utils.DateUtils.DATE_TIME_REGEX; +import static java.util.Arrays.asList; +import static org.apache.commons.lang3.reflect.FieldUtils.getAllFields; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.lookup; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; + +/** + * @author Joel Rodrigues Moreira on 25/05/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(of = "nameField") +@ToString +public class EntityMetaData implements Cloneable { + private static final String DATE_PATTERN = "yyyy-MM-dd"; + private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + //private static final String DATE_REGEX = "(\\d{4}|\\d{5}|\\d{6}|\\d{7})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|[3][01])"; + //private static final String DATE_TIME_REGEX = "(\\d{4}|\\d{5}|\\d{6}|\\d{7})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|[3][01])T(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23):(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59):(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59)([.])\\d{3}([+-])\\d{4}"; + private static final Map cache = new LinkedHashMap<>(); + private String nameField; + private Class classType; + private boolean id; + private EntityMetaDataType type; + private Set fields; + private String collection; + + public EntityMetaData() { + } + + public EntityMetaData addFields(Collection values) { + if (this.fields == null) { + this.fields = new HashSet<>(); + } + this.fields.addAll(values); + return this; + } + + public EntityMetaData addField(final EntityMetaData entityMetaData) { + if (this.fields == null) { + this.fields = new HashSet(); + } + this.fields.add(entityMetaData); + return this; + } + + public boolean isDBRef() { + return EntityMetaDataType.DBREF.equals(this.type); + } + + public static EntityMetaData of(final Class type) { + return of(type.getName(), type); + } + + public static EntityMetaData of(final String name, final Class type) { + if (isBasicObject(type)) { + return new EntityMetaData().setNameField(name); + } else { + if (EntityMetaData.cache.containsKey(type.getName())) { + return EntityMetaData.cache.get(type.getName()).clone().setNameField(name); + } + } + final EntityMetaData metaData = new EntityMetaData().setNameField(type.getName()); + EntityMetaData.cache.put(type.getName(), metaData); + + final Document document = (Document) type.getAnnotation(Document.class); + if (document != null) { + metaData.setCollection(document.collection()); + } + Stream.of(getAllFields(type)) + .filter(field -> + !Modifier.isStatic(field.getModifiers()) && (field.getDeclaredAnnotation(Transient.class) == null && field.getDeclaredAnnotation(org.springframework.data.annotation.Transient.class) == null) + ) + .map(field -> { + final EntityMetaData ent = EntityMetaData.of(field.getName(), field.getType()).setType(EntityMetaDataType.of(field)).setClassType(field.getType()); + if (field.getAnnotation(Id.class) != null) { + ent.setId(true); + } + return ent; + }) + .forEach(entityMetaData -> { + metaData.addField(entityMetaData); + }); + + + return metaData.clone().setNameField(name); + } + + private static EntityMetaData getFieldByName(final String nameField, final EntityMetaData entityMetaData) { + //se o nome tiver . é necessário fazezr recursividade1 + if (nameField.contains(".")) { + //pegando o primeiro nome + final String currentPath = nameField.substring(0, nameField.indexOf(".")); + + final EntityMetaData currentEntityMetaData = entityMetaData.getFields().stream().filter(it -> it.nameField.equals(currentPath)) + .findFirst() + .orElse(null); + if (currentEntityMetaData != null) { + return getFieldByName(nameField.replace(currentPath + ".", ""), currentEntityMetaData); + } + return null; + } else { + if (entityMetaData.getFields() == null) { + return null; + } + return entityMetaData + .getFields() + .stream() + .filter(it -> { + final String referencedName = "$" + it.nameField; + return it.nameField.equals(nameField) || referencedName.equals(nameField); + }) + .findFirst() + .orElse(null); + } + } + + public EntityMetaData getFieldByName(final String nameField) { + return getFieldByName(nameField, this); + } + + private Set getAllFieldFromLevelOf(final String nameField, final EntityMetaData entityMetaData) { + //se o nome tiver . é necessário fazer recursividade + if (nameField.contains(".")) { + //pegando o primeiro nome + final String currentPath = nameField.substring(0, nameField.indexOf(".")); + + final EntityMetaData currentEntityMetaData = entityMetaData.getFields().stream().filter(it -> it.nameField.equals(currentPath)) + .findFirst() + .orElse(null); + if (currentEntityMetaData != null) { + return getAllFieldFromLevelOf(nameField.replace(currentPath + ".", ""), currentEntityMetaData); + } + return null; + } else { + return Collections.unmodifiableSet(entityMetaData.getFields()); + } + } + + public Set getAllFieldFromLevelOf(final String nameField) { + return getAllFieldFromLevelOf(nameField, this); + } + + public Object converteValue(Object object) { + if (object == null || this.classType == object.getClass()) { + if (isId()) { + return new ObjectId(object.toString()); + } + return object; + } + switch (this.type) { + case STRING: + case ARRAY: + case DBREF: + case OTHER: + return object; + case BOOLEAN: + final String objectSTR = object.toString(); + if ((objectSTR.equalsIgnoreCase("true") || objectSTR.equalsIgnoreCase("false"))) { + return Boolean.valueOf(objectSTR); + } + return object; + case DATE: { + try { + final String objectStr = object.toString(); + if (objectStr.matches(DATE_REGEX)) { + return new SimpleDateFormat(DATE_PATTERN).parse(objectStr); + } else if (objectStr.matches(DATE_TIME_REGEX)) { + return new SimpleDateFormat(DATE_TIME_PATTERN).parse(objectStr); + } + } catch (ParseException e) { + } + return object; + } + case NUMBER: { + if ((this.classType == int.class) || (this.classType == Integer.class)) { + return Integer.valueOf(object.toString()); + } + if ((this.classType == long.class) || (this.classType == Long.class)) { + return Long.valueOf(object.toString()); + } + if ((this.classType == float.class) || (this.classType == Float.class)) { + return Float.valueOf(object.toString()); + } + if ((this.classType == double.class) || (this.classType == Double.class)) { + return Double.valueOf(object.toString()); + } + if (this.classType == BigDecimal.class) { + return new BigDecimal(object.toString()); + } + if ((this.classType == short.class) || (this.classType == Short.class)) { + return Short.valueOf(object.toString()); + } + if ((this.classType == BigInteger.class)) { + return BigInteger.valueOf(Long.valueOf(object.toString())); + } + return object; + } + default: + return object; + } + } + + private static boolean isBasicObject(final Class clazz) { + + if ((clazz == byte.class) || + (clazz.isPrimitive()) || + (clazz == String.class) || + (clazz == Byte.class) || + (clazz == Short.class) || + (clazz == Integer.class) || + (clazz == Long.class) || + (clazz == Float.class) || + (clazz == Double.class) || + (clazz == Boolean.class) || + (clazz == Date.class) || + (clazz == BigDecimal.class) || + (clazz == BigInteger.class) || + (clazz.isEnum()) || + (clazz.isArray()) || + (clazz == Set.class) || + (clazz == Collection.class) || + (clazz == Object.class)) { + return true; + } else { + return false; + } + } + + public List createProjectFor(final String key) { + //verificando se tem niveis de navegação no objeto + if (!key.contains(".")) { + //recuperando o campo espécifico + EntityMetaData field = getFieldByName(key, this); + //se não for DBRef podemos retornar null + if (!field.isDBRef()) { + return null; + } + + //criando as operações necessárias + return asList( + project(key).and(context -> new BasicDBObject("$objectToArray", "$" + field.getNameField())).as(field.getNameField()), + project(key).and(context -> new BasicDBObject("$arrayElemAt", asList("$" + field.getNameField() + ".v", 1))).as(field.getNameField()), + lookup(field.getCollection(), field.getNameField(), "_id", field.getNameField()), + unwind("$" + field.getNameField()) + ); + } else { + /*//gerando um array de keys + final String[] basicKeys = key.split("\\."); + //gerando as keys necessárias para se buscar cada campo + //cada key deve ser precedida ple key anterior + final String[] keys = new String[basicKeys.length]; + //inserindo a primeira key + keys[0] = basicKeys[0]; + //gerando as demais keys + //como o primeiro item já foi inserido, podemos pular o mesmo + for (int i = 1; i < basicKeys.length; i++) { + //pegando o indice anterior e concatenando com o atual + keys[i] = keys[i - 1] + "." + basicKeys[i]; + }*/ + + return createOperation(generateCascadKeys(key), this); + } + } + + public static String[] generateCascadKeys(final String key) { + //gerando um array de keys + final String[] basicKeys = key.split("\\."); + //gerando as keys necessárias para se buscar cada campo + //cada key deve ser precedida ple key anterior + final String[] keys = new String[basicKeys.length]; + //inserindo a primeira key + keys[0] = basicKeys[0]; + //gerando as demais keys + //como o primeiro item já foi inserido, podemos pular o mesmo + for (int i = 1; i < basicKeys.length; i++) { + //pegando o indice anterior e concatenando com o atual + keys[i] = keys[i - 1] + "." + basicKeys[i]; + } + return keys; + } + + private List createOperation(final String[] keyEntityMetaData, final EntityMetaData entityMetaData) { + if (keyEntityMetaData == null || keyEntityMetaData.length == 0 || (keyEntityMetaData.length == 2 && keyEntityMetaData[1].endsWith(".$id"))) { + return asList(); + } + final List result = new ArrayList<>(); + for (int i = 0; i < keyEntityMetaData.length; i++) { + final EntityMetaData currentField = entityMetaData.getFieldByName(keyEntityMetaData[i]); + if (currentField != null && currentField.isDBRef()) { + if (currentField.getClassType() == Owner.class) { + throw new MuttleyBadRequestException(currentField.getClassType(), currentField.getNameField(), "Acesso indevido a propriedade"); + } + //auxilia na concatenação + final int aux = i; + if (i == 0) { + //pegando todos os campos da classe para adicionar no project + final String[] keysForProject = getKeyForProject(keyEntityMetaData[i], entityMetaData); + + result.addAll( + asList( + project(keysForProject).and(context -> new BasicDBObject("$objectToArray", "$" + currentField.getNameField())).as(currentField.getNameField()), + project(keysForProject).and(context -> new BasicDBObject("$arrayElemAt", asList("$" + currentField.getNameField() + ".v", 1))).as(currentField.getNameField()), + lookup(currentField.getCollection(), currentField.getNameField(), "_id", currentField.getNameField()), + unwind("$" + currentField.getNameField()) + ) + ); + } else { + //pengando o nome de variavle de cada classe + final List keysForProject = new ArrayList<>(i); + for (int b = 0; b <= i; b++) { + keysForProject.add(getKeyForProject(keyEntityMetaData[b], entityMetaData)); + } + ProjectionOperation projectToArray = null; + ProjectionOperation projectElemAt = null; + //gerando os projects + for (int b = 0; b < keysForProject.size(); b++) { + if (projectToArray == null) { + projectToArray = project(keysForProject.get(b)); + projectElemAt = project(keysForProject.get(b)); + } else { + final int aux1 = b; + final String[] keysNested = Stream.of(keysForProject.get(b)).map(it -> "$" + keyEntityMetaData[aux1 - 1] + "." + it) + .filter(it -> !it.contains("$" + keyEntityMetaData[aux1])) + .toArray(String[]::new); + + projectToArray = projectToArray.and(keyEntityMetaData[b - 1]).nested(Fields.fields(keysNested)); + projectElemAt = projectElemAt.and(keyEntityMetaData[b - 1]).nested(Fields.fields(keysNested)); + } + } + //adicionando as informações necessárias para lookup + result.addAll( + asList( + projectToArray.and(context -> new BasicDBObject("$objectToArray", "$" + keyEntityMetaData[aux])).as(keyEntityMetaData[aux]), + projectElemAt.and(context -> new BasicDBObject("$arrayElemAt", asList("$" + keyEntityMetaData[aux] + ".v", 1))).as(keyEntityMetaData[aux]), + lookup(currentField.getCollection(), keyEntityMetaData[aux], "_id", keyEntityMetaData[aux]), + unwind("$" + keyEntityMetaData[aux]) + ) + ); + } + } + } + return result; + } + + /** + * retorn todos os campos da classe para adicionar no project + */ + private String[] getKeyForProject(final String key, final EntityMetaData entityMetaData) { + + return entityMetaData.getAllFieldFromLevelOf(key).stream().map(it -> it.getNameField()).toArray(String[]::new); + + } + + @Getter + @EqualsAndHashCode(of = "key") + private class KeyEntityMetaData { + private final String key; + private final EntityMetaData entityMetaData; + + public KeyEntityMetaData(String key, EntityMetaData entityMetaData) { + this.key = key; + this.entityMetaData = entityMetaData; + } + + } + + @Override + protected EntityMetaData clone() { + try { + return (EntityMetaData) super.clone(); + } catch (final CloneNotSupportedException ex) { + throw new MuttleyBadRequestException(ex); + } + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/metadata/EntityMetaDataType.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/metadata/EntityMetaDataType.java new file mode 100644 index 00000000..53564904 --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/metadata/EntityMetaDataType.java @@ -0,0 +1,59 @@ +package br.com.muttley.mongo.service.infra.metadata; + +import org.springframework.data.mongodb.core.mapping.DBRef; + +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 25/05/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public enum EntityMetaDataType { + STRING, + NUMBER, + DBREF, + BOOLEAN, + DATE, + ARRAY, + OTHER; + + public static EntityMetaDataType of(Field field) { + if (field.getType() == String.class) { + return STRING; + } + if (field.getType() == boolean.class || field.getType() == Boolean.class) { + return BOOLEAN; + } + if (field.getType() == Date.class) { + return DATE; + } + if (field.getAnnotation(DBRef.class) != null) { + return DBREF; + } + if (field.getType() == List.class || field.getType() == Set.class || field.getType() == Collection.class) { + return ARRAY; + } + if ((field.getType() == short.class) || + (field.getType() == int.class) || + (field.getType() == long.class) || + (field.getType() == float.class) || + (field.getType() == double.class) || + (field.getType() == Short.class) || + (field.getType() == Integer.class) || + (field.getType() == Long.class) || + (field.getType() == Float.class) || + (field.getType() == Double.class) || + (field.getType() == BigInteger.class) || + (field.getType() == BigDecimal.class)) { + return NUMBER; + } + return OTHER; + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/util/ListReduceBuilder.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/util/ListReduceBuilder.java new file mode 100644 index 00000000..4c1f6c68 --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/util/ListReduceBuilder.java @@ -0,0 +1,25 @@ +package br.com.muttley.mongo.service.infra.util; + +import org.springframework.data.mongodb.core.aggregation.AggregationExpression; +import org.springframework.data.mongodb.core.aggregation.ArrayOperators; + +import java.util.Collection; + +/** + * @author Joel Rodrigues Moreira on 09/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ListReduceBuilder { + public static ArrayOperators.Reduce reduce(final String array, final Collection initialValue, final ArrayOperators.Reduce.PropertyExpression... expressions) { + return reduce(array).withInitialValue(initialValue).reduce(expressions); + } + + public static ArrayOperators.Reduce reduce(final String array, final Collection initialValue, final AggregationExpression expression) { + return reduce(array).withInitialValue(initialValue).reduce(expression); + } + + public static ArrayOperators.Reduce.InitialValueBuilder reduce(final String array) { + return ArrayOperators.Reduce.arrayOf(array); + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/util/MapBuilder.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/util/MapBuilder.java new file mode 100644 index 00000000..f5b75ec3 --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/util/MapBuilder.java @@ -0,0 +1,26 @@ +package br.com.muttley.mongo.service.infra.util; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import org.springframework.data.mongodb.core.aggregation.AggregationExpression; +import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; + +/** + * @author Joel Rodrigues Moreira on 22/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class MapBuilder { + public static AggregationExpression map(final String input, final String as, final AggregationExpression in) { + return new AggregationExpression() { + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return new BasicDBObject("$map", + new BasicDBObject("input", input) + .append("as", as) + .append("in", in.toDbObject(context)) + ); + } + }; + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/util/SetUnionBuilder.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/util/SetUnionBuilder.java new file mode 100644 index 00000000..d63ed89d --- /dev/null +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/infra/util/SetUnionBuilder.java @@ -0,0 +1,27 @@ +package br.com.muttley.mongo.service.infra.util; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import org.springframework.data.mongodb.core.aggregation.AggregationExpression; +import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; + +/** + * @author Joel Rodrigues Moreira on 09/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class SetUnionBuilder { + public static AggregationExpression setUnion(final Object... arrays) { + return new AggregationExpression() { + + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return new BasicDBObject("$setUnion", arrays); + } + }; + } + + public static AggregationExpression setUnion(final String... arrays) { + return setUnion((Object[]) arrays); + } +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/CustomMongoRepository.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/CustomMongoRepository.java index fa374326..e3015958 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/CustomMongoRepository.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/CustomMongoRepository.java @@ -1,10 +1,12 @@ package br.com.muttley.mongo.service.repository; -import br.com.muttley.model.Historic; +import br.com.muttley.model.MetadataDocument; import br.com.muttley.model.security.Owner; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; public interface CustomMongoRepository extends DocumentMongoRepository { /** @@ -15,6 +17,14 @@ public interface CustomMongoRepository extends DocumentMongoRepository { */ T save(final Owner owner, final T value); + /** + * Sava um colleção de registro registro + * + * @param owner -> dono do registro + * @param values -> collection de objetos a serem salvos + */ + Collection save(final Owner owner, final Collection values); + /** * Busca um simples registro * @@ -23,6 +33,14 @@ public interface CustomMongoRepository extends DocumentMongoRepository { */ T findOne(final Owner owner, final String id); + /** + * Busca vários registros simples + * + * @param owner -> dono do registro + * @param ids -> ids dos registros desejado + */ + Set findMulti(final Owner owner, final String ids[]); + /** * Busca o primeiro registro qualquer de uma colection * @@ -52,7 +70,7 @@ public interface CustomMongoRepository extends DocumentMongoRepository { * @param owner -> dono do registro * @param queryParams -> parametros para criterios */ - List findAll(final Owner owner, final Map queryParams); + List findAll(final Owner owner, final Map queryParams); /** * Conta registros de uma determinada collection @@ -60,7 +78,7 @@ public interface CustomMongoRepository extends DocumentMongoRepository { * @param owner -> dono do registro * @param queryParams -> parametros para criterios */ - long count(final Owner owner, final Map queryParams); + long count(final Owner owner, final Map queryParams); /** * Verifica se existe um determinado registro no banco de dados @@ -95,18 +113,18 @@ public interface CustomMongoRepository extends DocumentMongoRepository { boolean exists(final Owner owner, final Object... filter); /** - * Carrega o historico de um determinado registro + * Carrega o metadata de um determinado registro * * @param owner -> dono do registro * @param value -> registro a ser carregado */ - Historic loadHistoric(final Owner owner, final T value); + MetadataDocument loadMetaData(final Owner owner, final T value); /** - * Carrega o historico de um determinado registro + * Carrega o metadata de um determinado registro * * @param owner -> dono do registro * @param id -> id do registro a ser carregado */ - Historic loadHistoric(final Owner owner, final String id); -} \ No newline at end of file + MetadataDocument loadMetaData(final Owner owner, final String id); +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/DocumentMongoRepository.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/DocumentMongoRepository.java index 4f8b8cf4..ca194048 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/DocumentMongoRepository.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/DocumentMongoRepository.java @@ -1,13 +1,21 @@ package br.com.muttley.mongo.service.repository; -import br.com.muttley.model.Historic; +import br.com.muttley.model.MetadataDocument; import org.springframework.data.mongodb.repository.MongoRepository; import java.util.List; import java.util.Map; +import java.util.Set; public interface DocumentMongoRepository extends MongoRepository { + /** + * Busca vários registros simples + * + * @param ids -> ids dos registros desejado + */ + Set findMulti(final String ids[]); + /** * Busca o primeiro registro qualquer de uma colection */ @@ -18,14 +26,14 @@ public interface DocumentMongoRepository extends MongoRepository { * * @param queryParams -> parametros para criterios */ - List findAll(final Map queryParams); + List findAll(final Map queryParams); /** * Conta registros de uma determinada collection * * @param queryParams -> parametros para criterios */ - long count(final Map queryParams); + long count(final Map queryParams); /** * Verifica se existe um determinado registro no banco de dados @@ -49,16 +57,16 @@ public interface DocumentMongoRepository extends MongoRepository { boolean exists(final Object... filter); /** - * Carrega o historico de um determinado registro + * Carrega o metadata de um determinado registro * * @param value -> registro a ser carregado */ - Historic loadHistoric(final T value); + MetadataDocument loadMetadata(final T value); /** - * Carrega o historico de um determinado registro + * Carrega o metadata de um determinado registro * * @param id -> id do registro a ser carregado */ - Historic loadHistoric(final String id); -} \ No newline at end of file + MetadataDocument loadMetadata(final String id); +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/impl/CustomMongoRepositoryImpl.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/impl/CustomMongoRepositoryImpl.java index 75734b39..d907777a 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/impl/CustomMongoRepositoryImpl.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/impl/CustomMongoRepositoryImpl.java @@ -1,20 +1,29 @@ package br.com.muttley.mongo.service.repository.impl; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.exception.throwables.repository.MuttleyRepositoryInvalidIdException; import br.com.muttley.exception.throwables.repository.MuttleyRepositoryOwnerNotInformedException; -import br.com.muttley.model.Historic; +import br.com.muttley.model.MetadataDocument; import br.com.muttley.model.Model; import br.com.muttley.model.security.Owner; +import br.com.muttley.mongo.service.infra.AggregationUtils; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.query.MongoEntityInformation; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; -import java.util.HashMap; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; -import static br.com.muttley.mongo.service.infra.Aggregate.createAggregations; -import static br.com.muttley.mongo.service.infra.Aggregate.createAggregationsCount; +import static java.util.stream.Stream.of; import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; @@ -33,20 +42,71 @@ public final T save(final Owner owner, final T value) { return super.save(value); } + @Override + public Collection save(final Owner owner, final Collection values) { + if (!CollectionUtils.isEmpty(values)) { + validateOwner(owner); + values.parallelStream().forEach(it -> it.setOwner(owner)); + return super.save(values); + } + return values; + } + @Override public final T findOne(final Owner owner, final String id) { + try { + validateOwner(owner); + final ObjectId objectId = newObjectId(id); + return operations.findOne( + new Query( + where("owner.$id").is(owner.getObjectId()) + .and("id").is(newObjectId(id)) + ), CLASS + ); + } catch (MuttleyRepositoryInvalidIdException ex) { + throw new MuttleyNotFoundException(CLASS, "id", "Registro não encontrado"); + } + } + + @Override + public Set findMulti(final Owner owner, final String[] ids) { + validateOwner(owner); - return operations.findOne( - new Query( - where("owner.$id").is(owner.getObjectId()) - .and("id").is(newObjectId(id)) - ), CLASS - ); + + //criando um array de ObjecIds + final ObjectId[] objectIds = of(ids) + .map(id -> { + try { + return newObjectId(id); + } catch (MuttleyRepositoryInvalidIdException ex) { + return null; + } + //pegando apenas ids válidos + }).filter(Objects::nonNull).toArray(ObjectId[]::new); + + //filtrando os ids válidos + if (!ObjectUtils.isEmpty(objectIds)) { + final List records = operations.find( + new Query( + where("owner.$id").is(owner.getObjectId()) + .and("id").in(objectIds) + ), CLASS + ); + + if (CollectionUtils.isEmpty(records)) { + return null; + } + + return new HashSet<>(records); + } + + return null; } @Override public T findFirst(final Owner owner) { validateOwner(owner); + return operations .findOne( new Query( @@ -57,13 +117,17 @@ public T findFirst(final Owner owner) { @Override public final void delete(final Owner owner, final String id) { - validateOwner(owner); - operations.remove( - new Query( - where("owner.$id").is(owner.getObjectId()) - .and("id").is(newObjectId(id)) - ), CLASS - ); + try { + validateOwner(owner); + operations.remove( + new Query( + where("owner.$id").is(owner.getObjectId()) + .and("id").is(newObjectId(id)) + ), CLASS + ); + } catch (MuttleyRepositoryInvalidIdException ex) { + throw new MuttleyNotFoundException(CLASS, "id", "Registro não encontrado"); + } } @Override @@ -79,28 +143,27 @@ public final void delete(final Owner owner, final T value) { } @Override - - public final List findAll(final Owner owner, final Map queryParams) { + public final List findAll(final Owner owner, final Map queryParams) { validateOwner(owner); return operations.aggregate( - newAggregation( - createAggregations( - CLASS, - new HashMap<>(addOwnerQueryParam(owner, ((queryParams != null && !queryParams.isEmpty()) ? queryParams : new HashMap<>()))) - ) - ), - COLLECTION, CLASS) + newAggregation( + AggregationUtils.createAggregations( + this.entityMetaData, getBasicPipelines(this.CLASS), + + addOwnerQueryParam(owner, queryParams) + ) + ), + COLLECTION, CLASS) .getMappedResults(); } @Override - public final long count(final Owner owner, final Map queryParams) { + public final long count(final Owner owner, final Map queryParams) { validateOwner(owner); final AggregationResults result = operations.aggregate( newAggregation( - createAggregationsCount( - CLASS, - new HashMap<>(addOwnerQueryParam(owner, ((queryParams != null && !queryParams.isEmpty()) ? queryParams : new HashMap<>()))) + AggregationUtils.createAggregationsCount(this.entityMetaData, getBasicPipelines(this.CLASS), + addOwnerQueryParam(owner, queryParams) )), COLLECTION, ResultCount.class); return result.getUniqueMappedResult() != null ? ((ResultCount) result.getUniqueMappedResult()).getCount() : 0; @@ -113,13 +176,17 @@ public final boolean exists(final Owner owner, final T value) { @Override public final boolean exists(final Owner owner, final String id) { - validateOwner(owner); - return operations.exists( - new Query( - where("owner.$id").is(owner.getObjectId()) - .and("id").is(newObjectId(id)) - ), CLASS - ); + try { + validateOwner(owner); + return operations.exists( + new Query( + where("owner.$id").is(owner.getObjectId()) + .and("id").is(newObjectId(id)) + ), CLASS + ); + } catch (MuttleyRepositoryInvalidIdException ex) { + throw new MuttleyNotFoundException(CLASS, "id", "Registro não encontrado"); + } } @Override @@ -140,31 +207,31 @@ public boolean exists(final Owner owner, final Object... filter) { } @Override - public Historic loadHistoric(final Owner owner, final T value) { + public MetadataDocument loadMetaData(final Owner owner, final T value) { final AggregationResults result = operations.aggregate( newAggregation( match(where("owner.$id").is(owner.getObjectId()) .and("_id").is(value.getObjectId()) - ), project().and("$historic.createdBy").as("createdBy") - .and("$historic.dtCreate").as("dtCreate") - .and("$historic.dtChange").as("dtChange") - ), COLLECTION, Historic.class); + ), project().and("$metadata.timeZones").as("timeZones") + .and("$metadata.versionDocument").as("versionDocument") + .and("$metadata.historic").as("historic") + ), COLLECTION, MetadataDocument.class); - return result.getUniqueMappedResult() != null ? ((Historic) result.getUniqueMappedResult()) : null; + return result.getUniqueMappedResult() != null ? ((MetadataDocument) result.getUniqueMappedResult()) : null; } @Override - public Historic loadHistoric(final Owner owner, final String id) { + public MetadataDocument loadMetaData(final Owner owner, final String id) { final AggregationResults result = operations.aggregate( newAggregation( match(where("owner.$id").is(owner.getObjectId()) .and("_id").is(newObjectId(id)) - ), project().and("$historic.createdBy").as("createdBy") - .and("$historic.dtCreate").as("dtCreate") - .and("$historic.dtChange").as("dtChange") - ), COLLECTION, Historic.class); + ), project().and("$metadata.timeZones").as("timeZones") + .and("$metadata.versionDocument").as("versionDocument") + .and("$metadata.historic").as("historic") + ), COLLECTION, MetadataDocument.class); - return result.getUniqueMappedResult() != null ? ((Historic) result.getUniqueMappedResult()) : null; + return result.getUniqueMappedResult() != null ? ((MetadataDocument) result.getUniqueMappedResult()) : null; } private final void validateOwner(final Owner owner) { @@ -173,12 +240,12 @@ private final void validateOwner(final Owner owner) { } } - private final Map addOwnerQueryParam(final Owner owner, final Map queryParams) { - final Map query = new HashMap<>(1); - query.put("owner.$id.$is", owner.getObjectId()); + private final Map addOwnerQueryParam(final Owner owner, final Map queryParams) { + final Map query = new LinkedHashMap<>(1); + query.put("owner.$id.$is", owner.getObjectId().toString()); if (queryParams != null) { query.putAll(queryParams); } return query; } -} \ No newline at end of file +} diff --git a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/impl/DocumentMongoRepositoryImpl.java b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/impl/DocumentMongoRepositoryImpl.java index c9c513a0..4933a597 100644 --- a/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/impl/DocumentMongoRepositoryImpl.java +++ b/muttley-mongo/src/main/java/br/com/muttley/mongo/service/repository/impl/DocumentMongoRepositoryImpl.java @@ -1,24 +1,44 @@ package br.com.muttley.mongo.service.repository.impl; +import br.com.muttley.annotations.index.CompoundIndexes; import br.com.muttley.exception.throwables.MuttleyException; import br.com.muttley.exception.throwables.repository.MuttleyRepositoryIdIsNullException; import br.com.muttley.exception.throwables.repository.MuttleyRepositoryInvalidIdException; import br.com.muttley.model.Document; -import br.com.muttley.model.Historic; +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.mongo.service.infra.AggregationUtils; +import br.com.muttley.mongo.service.infra.metadata.EntityMetaData; +import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.bson.types.ObjectId; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.index.CompoundIndex; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.query.MongoEntityInformation; import org.springframework.data.mongodb.repository.support.SimpleMongoRepository; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import static br.com.muttley.mongo.service.infra.Aggregate.createAggregations; -import static br.com.muttley.mongo.service.infra.Aggregate.createAggregationsCount; +import static java.util.stream.Stream.of; import static org.bson.types.ObjectId.isValid; import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; @@ -29,14 +49,49 @@ public class DocumentMongoRepositoryImpl extends SimpleMongo protected final MongoOperations operations; protected final Class CLASS; protected final String COLLECTION; + protected final EntityMetaData entityMetaData; + protected final Log log = LogFactory.getLog(this.getClass()); public DocumentMongoRepositoryImpl(final MongoEntityInformation metadata, final MongoOperations mongoOperations) { super(metadata, mongoOperations); this.operations = mongoOperations; this.CLASS = metadata.getJavaType(); this.COLLECTION = metadata.getCollectionName(); + this.entityMetaData = EntityMetaData.of(metadata.getJavaType()); + this.createIndexes(metadata); } + @Override + public Set findMulti(final String[] ids) { + + //criando um array de ObjecIds + final ObjectId[] objectIds = of(ids) + .map(id -> { + try { + return newObjectId(id); + } catch (MuttleyRepositoryInvalidIdException ex) { + return null; + } + //pegando apenas ids válidos + }).filter(Objects::nonNull).toArray(ObjectId[]::new); + + //filtrando os ids válidos + if (!ObjectUtils.isEmpty(objectIds)) { + final List records = operations.find( + new Query( + where("id").in(objectIds) + ), CLASS + ); + + if (CollectionUtils.isEmpty(records)) { + return null; + } + + return new HashSet<>(records); + } + + return null; + } @Override public T findFirst() { @@ -44,12 +99,12 @@ public T findFirst() { } @Override - public List findAll(final Map queryParams) { + public List findAll(final Map queryParams) { return operations .aggregate( newAggregation( - createAggregations(CLASS, - ((queryParams != null && !queryParams.isEmpty()) ? queryParams : new HashMap<>()) + AggregationUtils.createAggregations(this.entityMetaData, getBasicPipelines(this.CLASS), + queryParams != null ? queryParams : new LinkedHashMap<>() ) ), COLLECTION, CLASS @@ -58,12 +113,12 @@ public List findAll(final Map queryParams) { } @Override - public long count(final Map queryParams) { + public long count(final Map queryParams) { final AggregationResults result = operations.aggregate( newAggregation( - createAggregationsCount( - CLASS, - ((queryParams != null && !queryParams.isEmpty()) ? queryParams : new HashMap<>())) + AggregationUtils.createAggregationsCount(this.entityMetaData, getBasicPipelines(this.CLASS), + ((queryParams != null && !queryParams.isEmpty()) ? queryParams : new HashMap<>()) + ) ), COLLECTION, ResultCount.class); return result.getUniqueMappedResult() != null ? ((ResultCount) result.getUniqueMappedResult()).count : 0; @@ -100,37 +155,38 @@ public boolean exists(final Object... filter) { } @Override - public Historic loadHistoric(final T value) { + public MetadataDocument loadMetadata(final T value) { final AggregationResults result = operations.aggregate( newAggregation( match( where("_id").is(value.getObjectId()) - ), project().and("$historic.createdBy").as("createdBy") - .and("$historic.dtCreate").as("dtCreate") - .and("$historic.dtChange").as("dtChange") - .and("$historic.lastChangeBy").as("lastChangeBy") + ), project().and("$metadata.timeZones").as("timeZones") + .and("$metadata.versionDocument").as("versionDocument") + .and("$metadata.historic").as("historic") + ), COLLECTION, MetadataDocument.class); - - ), COLLECTION, Historic.class); - - return result.getUniqueMappedResult() != null ? ((Historic) result.getUniqueMappedResult()) : null; + return result.getUniqueMappedResult() != null ? ((MetadataDocument) result.getUniqueMappedResult()) : null; } @Override - public Historic loadHistoric(final String id) { + public MetadataDocument loadMetadata(final String id) { final AggregationResults result = operations.aggregate( newAggregation( match( where("_id").is(newObjectId(id)) - ), project().and("$historic.createdBy").as("createdBy") - .and("$historic.dtCreate").as("dtCreate") - .and("$historic.dtChange").as("dtChange") - .and("$historic.lastChangeBy").as("lastChangeBy") - + ), project().and("$metadata.timeZones").as("timeZones") + .and("$metadata.versionDocument").as("versionDocument") + .and("$metadata.historic").as("historic") + ), COLLECTION, MetadataDocument.class); - ), COLLECTION, Historic.class); + return result.getUniqueMappedResult() != null ? ((MetadataDocument) result.getUniqueMappedResult()) : null; + } - return result.getUniqueMappedResult() != null ? ((Historic) result.getUniqueMappedResult()) : null; + /** + * Retorna uma lista de pipelines para agregação + */ + List getBasicPipelines(final Class clazz) { + return null; } protected final void validateId(final String id) { @@ -159,4 +215,155 @@ public ResultCount setCount(final Long count) { return this; } } -} \ No newline at end of file + + private void createIndexes(final MongoEntityInformation metadata) { + final CompoundIndexes compoundIndexes = metadata.getJavaType().getAnnotation(CompoundIndexes.class); + if (compoundIndexes != null) { + final Supplier> compoundIndexesValues = () -> Stream.of(compoundIndexes.value()); + //verificando se tem algum indices nas anotações com nome ou definições iguais + final List duplicateds = compoundIndexesValues.get() + .parallel() + .filter(it -> + compoundIndexesValues + .get() + .parallel() + .filter(itt -> + itt.name().equals(it.name()) || + ( + this.isEqualDefinitionIndex(BasicDBObject.parse(itt.def()), BasicDBObject.parse(it.def())) && + itt.unique() && + it.unique() + ) + ).count() > 1 + ).collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(duplicateds)) { + throw new MuttleyException("Existe definições de indices duplicados na classe -> " + this.CLASS.getCanonicalName()) + .addDetails("duplicateds", duplicateds); + } + + final List currentIdexies = this.operations.getCollection(metadata.getCollectionName()).getIndexInfo(); + final List currentIndexiesName = currentIdexies + .parallelStream() + .map(index -> index.get("name")) + .map(Object::toString) + .collect(Collectors.toList()); + final DBCollection collection = this.operations.getCollection(metadata.getCollectionName()); + compoundIndexesValues.get() + .forEach(compoundIndex -> { + //verificando se já existe o indice pelo nome + if (currentIndexiesName.contains(compoundIndex.name())) { + //se chegou até aqui é sinal que existe, + //logo devemos verificar se a definição de ambos são equivalente + //caso contrario devemos atualizar o indice + if (!this.isEqualDefinitionIndex( + BasicDBObject.parse(compoundIndex.def()), + currentIdexies.parallelStream() + .filter(it -> it.get("name").equals(compoundIndex.name())) + .map(it -> (DBObject) it.get("key")) + .findFirst() + .orElse(null) + )) { + //se chegou até aqui logo o indice existe no banco, porem com parametros diferente + // por conta disso devemos dropar esse indice e criar um novo + this.dropIndex(collection, compoundIndex.name()); + this.createIndex(collection, compoundIndex); + } else { + log.info("The index \"" + compoundIndex.name() + "\" already exists for collection \"" + COLLECTION + "\""); + } + } else { + //se chegou até aqui é sinal que não existe um indice com o mesmo nome, + //logo devemos verificar se exite algum indice com nome diferente, porem com a definição igual + //se existir definição igual, logo devemos renomear o mesmo + //se não existir devemos criar o mesmo + final DBObject existingItem = currentIdexies + .parallelStream() + //.map(it -> (DBObject) it.get("key")) + .filter(it -> + + this.isEqualDefinitionIndex(BasicDBObject.parse(compoundIndex.def()), (DBObject) it.get("key")) && + Objects.equals(compoundIndex.unique(), (it.get("unique") == null ? false : it.get("unique"))) + + ).findAny() + .orElse(null); + + if (existingItem != null) { + this.dropIndex(collection, existingItem.get("name").toString()); + } + this.createIndex(collection, compoundIndex); + } + + }); + + //dropando indices que exista somente no banco de dados + //pegando todos os indices + final List currentIndexies = collection.getIndexInfo(); + //interando todos os indices e pegando somente os que não deveria existir + currentIndexies.parallelStream() + .map(it -> it.get("name").toString()) + .filter(name -> !name.equals("_id_")) + .filter(name -> compoundIndexesValues.get() + .parallel() + .map(CompoundIndex::name) + .filter(it -> it.equals(name)) + .count() == 0 + ).forEach(name -> { + this.dropIndex(collection, name); + }); + } + } + + /** + * Checa se uma nova definição de um indice é equivalente a um indice já existente + */ + private boolean isEqualDefinitionIndex(final DBObject newIndex, final DBObject currentIndex) { + if ((newIndex != null && currentIndex == null) || (newIndex == null && currentIndex != null)) { + return false; + } + final List keySetNewIndex = new LinkedList<>(newIndex.keySet()); + final List keySetCurrentIndex = new LinkedList<>(currentIndex.keySet()); + if (keySetNewIndex.size() != keySetCurrentIndex.size()) { + return false; + } + for (int i = 0; i < keySetNewIndex.size(); i++) { + if ((!keySetNewIndex.get(i).equals(keySetCurrentIndex.get(i))) || + (!(Double.valueOf(newIndex.get(keySetNewIndex.get(i)).toString()).intValue() == Double.valueOf(currentIndex.get(keySetCurrentIndex.get(i)).toString()).intValue()))) { + return false; + } + } + return true; + } + + private void dropIndex(final DBCollection collection, final String name) { + if (!"_id_".equals(name)) { + collection.dropIndexes(name); + log.info("The index \"" + name + "\" has been removed from collection \"" + COLLECTION + "\""); + } + } + + private void createIndex(final DBCollection collection, final CompoundIndex compoundIndex) { + final DBObject indexDefinition = BasicDBObject.parse(compoundIndex.def()); + final DBObject options = new BasicDBObject(); + + if (compoundIndex.background()) { + options.put("background", 1); + } + + if (compoundIndex.unique()) { + options.put("unique", 1); + } + + if (!StringUtils.isEmpty(compoundIndex.name())) { + options.put("name", compoundIndex.name()); + } + + if (compoundIndex.sparse()) { + options.put("sparse", compoundIndex.sparse()); + } + + collection.createIndex(indexDefinition, options); + + log.info("Created index \"" + compoundIndex.name() + "\" for collection \"" + COLLECTION + "\""); + } + + +} diff --git a/muttley-notification/.gitignore b/muttley-notification/.gitignore new file mode 100644 index 00000000..a2a3040a --- /dev/null +++ b/muttley-notification/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/muttley-notification/.mvn/wrapper/MavenWrapperDownloader.java b/muttley-notification/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..a45eb6ba --- /dev/null +++ b/muttley-notification/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/muttley-notification/.mvn/wrapper/maven-wrapper.jar b/muttley-notification/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..2cc7d4a5 Binary files /dev/null and b/muttley-notification/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-notification/.mvn/wrapper/maven-wrapper.properties b/muttley-notification/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..642d572c --- /dev/null +++ b/muttley-notification/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/muttley-notification/mvnw b/muttley-notification/mvnw new file mode 100755 index 00000000..a16b5431 --- /dev/null +++ b/muttley-notification/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-notification/mvnw.cmd b/muttley-notification/mvnw.cmd new file mode 100644 index 00000000..c8d43372 --- /dev/null +++ b/muttley-notification/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/muttley-notification/pom.xml b/muttley-notification/pom.xml new file mode 100644 index 00000000..c3ceacc0 --- /dev/null +++ b/muttley-notification/pom.xml @@ -0,0 +1,57 @@ + + + + br.com.muttley + muttley-cloud + ${revision} + + 4.0.0 + jar + br.com.muttley + + muttley-notification + + muttley-notification + Demo project for Spring Boot + + + + + + org.projectlombok + lombok + true + + + + br.com.muttley + muttley-feign + + + + br.com.muttley + muttley-model + provided + + + + br.com.muttley + muttley-exception + provided + + + + org.springframework.boot + spring-boot-starter + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/infobip/config/InfobipConfig.java b/muttley-notification/src/main/java/br/com/muttley/notification/infobip/config/InfobipConfig.java new file mode 100644 index 00000000..23121085 --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/infobip/config/InfobipConfig.java @@ -0,0 +1,16 @@ +package br.com.muttley.notification.infobip.config; + +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * @author Joel Rodrigues Moreira on 18/09/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Configuration +@ComponentScan(basePackages = "br.com.muttley.notification.infobip.service") +@EnableFeignClients(basePackages = "br.com.muttley.notification.infobip.service.impl") +public class InfobipConfig { +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/InfoBitNotificationService.java b/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/InfoBitNotificationService.java new file mode 100644 index 00000000..19f8ed9b --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/InfoBitNotificationService.java @@ -0,0 +1,12 @@ +package br.com.muttley.notification.infobip.service; + +import br.com.muttley.model.hermes.notification.infobip.SMSPayload; + +/** + * @author Joel Rodrigues Moreira on 18/09/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface InfoBitNotificationService { + void sendNotification(final SMSPayload payload); +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/impl/InfoBitBasicAuthorizationRequestInterceptor.java b/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/impl/InfoBitBasicAuthorizationRequestInterceptor.java new file mode 100644 index 00000000..128a9209 --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/impl/InfoBitBasicAuthorizationRequestInterceptor.java @@ -0,0 +1,36 @@ +package br.com.muttley.notification.infobip.service.impl; + +import feign.RequestInterceptor; +import feign.RequestTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; + +/** + * @author Joel Rodrigues Moreira on 18/09/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class InfoBitBasicAuthorizationRequestInterceptor implements RequestInterceptor { + private final String AUTHENTICATION_TOKEN; + private final Logger log; + + + public InfoBitBasicAuthorizationRequestInterceptor(@Value("${muttley.notification.infobip.token:INVALID}") final String token) { + this.AUTHENTICATION_TOKEN = token; + if ("INVALID".equalsIgnoreCase(token)) { + this.log = LoggerFactory.getLogger(InfoBitBasicAuthorizationRequestInterceptor.class); + this.log.error("\n\n NÃO FOI INFORMADO UM TOKEN VÁLIDO PARA O INFOBIT \n\n"); + } else { + this.log = null; + } + } + + @Override + public void apply(RequestTemplate template) { + template.header("Authorization", this.AUTHENTICATION_TOKEN); + if ("INVALID".equalsIgnoreCase(this.AUTHENTICATION_TOKEN)) { + this.log.error("\n\n NÃO FOI INFORMADO UM TOKEN VÁLIDO PARA O INFOBIT \n\n"); + } + } +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/impl/InfoBitNotificationServiceClient.java b/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/impl/InfoBitNotificationServiceClient.java new file mode 100644 index 00000000..0c2a9b7c --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/impl/InfoBitNotificationServiceClient.java @@ -0,0 +1,19 @@ +package br.com.muttley.notification.infobip.service.impl; + +import br.com.muttley.model.hermes.notification.infobip.SMSPayload; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +/** + * @author Joel Rodrigues Moreira on 18/09/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@FeignClient(url = "${muttley.notification.infobip.url:https://5y83wx.api.infobip.com/sms/2/text/advanced}", value = "infobit", configuration = {InfoBitBasicAuthorizationRequestInterceptor.class}) +public interface InfoBitNotificationServiceClient { + @RequestMapping(method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + void sendSMS(SMSPayload payload); +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/impl/InfoBitNotificationServiceImpl.java b/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/impl/InfoBitNotificationServiceImpl.java new file mode 100644 index 00000000..f606abeb --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/infobip/service/impl/InfoBitNotificationServiceImpl.java @@ -0,0 +1,27 @@ +package br.com.muttley.notification.infobip.service.impl; + +import br.com.muttley.model.hermes.notification.infobip.Messages; +import br.com.muttley.model.hermes.notification.infobip.SMSPayload; +import br.com.muttley.notification.infobip.service.InfoBitNotificationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author Joel Rodrigues Moreira on 18/09/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class InfoBitNotificationServiceImpl implements InfoBitNotificationService { + private final InfoBitNotificationServiceClient client; + + @Autowired + public InfoBitNotificationServiceImpl(InfoBitNotificationServiceClient client) { + this.client = client; + } + + @Override + public void sendNotification(SMSPayload payload) { + this.client.sendSMS(payload); + } +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/config/OneSignalConfig.java b/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/config/OneSignalConfig.java new file mode 100644 index 00000000..85e0fb89 --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/config/OneSignalConfig.java @@ -0,0 +1,13 @@ +package br.com.muttley.notification.onesignal.config; + +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan(basePackages = "br.com.muttley.notification.onesignal.service") +@EnableFeignClients( + basePackages = "br.com.muttley.notification.onesignal.service.impl" +) +public class OneSignalConfig { +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/OneSignalNotificationService.java b/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/OneSignalNotificationService.java new file mode 100644 index 00000000..ac05ddf4 --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/OneSignalNotificationService.java @@ -0,0 +1,7 @@ +package br.com.muttley.notification.onesignal.service; + +import br.com.muttley.model.hermes.notification.onesignal.Notification; + +public interface OneSignalNotificationService { + public void sendNotification(final Notification notification); +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/impl/OneSignalBasicAuthorizationJWTRequestInterceptor.java b/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/impl/OneSignalBasicAuthorizationJWTRequestInterceptor.java new file mode 100644 index 00000000..9601eec9 --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/impl/OneSignalBasicAuthorizationJWTRequestInterceptor.java @@ -0,0 +1,35 @@ +package br.com.muttley.notification.onesignal.service.impl; + +import feign.RequestInterceptor; +import feign.RequestTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; + +public class OneSignalBasicAuthorizationJWTRequestInterceptor implements RequestInterceptor { + private final String tokenHeader; + private final String tokenValue; + private final Logger log; + + public OneSignalBasicAuthorizationJWTRequestInterceptor( + @Value("${muttley.notification.onesignal.tokenHeader:Authorization}") final String tokenHeader, + @Value("${muttley.notification.onesignal.tokenValue:#null}") final String tokenValue) { + this.tokenHeader = tokenHeader; + this.tokenValue = tokenValue; + + if ("#null".equalsIgnoreCase(tokenValue)) { + this.log = LoggerFactory.getLogger(OneSignalBasicAuthorizationJWTRequestInterceptor.class); + this.log.error("\n\n NÃO FOI INFORMADO UM TOKEN VÁLIDO PARA O ONESIGNAL \n\n"); + } else { + this.log = null; + } + } + + @Override + public void apply(RequestTemplate template) { + template.header(this.tokenHeader, this.tokenValue); + if ("#null".equalsIgnoreCase(tokenValue)) { + this.log.error("\n\n NÃO FOI INFORMADO UM TOKEN VÁLIDO PARA O ONESIGNAL \n\n"); + } + } +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/impl/OneSignalNotificationServiceClient.java b/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/impl/OneSignalNotificationServiceClient.java new file mode 100644 index 00000000..d3ea5245 --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/impl/OneSignalNotificationServiceClient.java @@ -0,0 +1,15 @@ +package br.com.muttley.notification.onesignal.service.impl; + +import br.com.muttley.model.hermes.notification.onesignal.Notification; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +@FeignClient(url = "${muttley.notification.onesignal.domain:https://onesignal.com}", value = "onesignal", path = "/api/v1", configuration = {OneSignalBasicAuthorizationJWTRequestInterceptor.class}) +interface OneSignalNotificationServiceClient { + @RequestMapping(value = "/notifications", method = POST, produces = APPLICATION_JSON_VALUE) + public void sendNotification(@RequestBody Notification token); +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/impl/OneSignalNotificationServiceImpl.java b/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/impl/OneSignalNotificationServiceImpl.java new file mode 100644 index 00000000..041fa284 --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/onesignal/service/impl/OneSignalNotificationServiceImpl.java @@ -0,0 +1,43 @@ +package br.com.muttley.notification.onesignal.service.impl; + +import br.com.muttley.model.hermes.notification.onesignal.Notification; +import br.com.muttley.notification.onesignal.service.OneSignalNotificationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.validation.beanvalidation.SpringValidatorAdapter; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; +import java.util.Set; + +import static org.springframework.util.CollectionUtils.isEmpty; + +@Service +public class OneSignalNotificationServiceImpl implements OneSignalNotificationService { + private final Validator validator; + private final OneSignalNotificationServiceClient oneSignalNotificationServiceClient; + private final String APP_ID; + + @Autowired + public OneSignalNotificationServiceImpl(final Validator validator, final OneSignalNotificationServiceClient oneSignalNotificationServiceClient, @Value("${muttley.notification.onesignal.appId:#null}") final String APP_ID) { + this.validator = new SpringValidatorAdapter(validator); + this.oneSignalNotificationServiceClient = oneSignalNotificationServiceClient; + this.APP_ID = APP_ID; + } + + @Override + public void sendNotification(final Notification notification) { + notification.setAppId(APP_ID); + this.validate(notification); + this.oneSignalNotificationServiceClient.sendNotification(notification); + } + + private final void validate(final Object o) { + final Set> violations = validator.validate(o); + if (!isEmpty(violations)) { + throw new ConstraintViolationException("test", violations); + } + } +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/twilio/config/TwilioConfig.java b/muttley-notification/src/main/java/br/com/muttley/notification/twilio/config/TwilioConfig.java new file mode 100644 index 00000000..4e7f6d77 --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/twilio/config/TwilioConfig.java @@ -0,0 +1,83 @@ +package br.com.muttley.notification.twilio.config; + +import br.com.muttley.notification.twilio.service.impl.TwilioBasicAuthorizationRequestInterceptor; +import br.com.muttley.notification.twilio.service.impl.TwilioNotificationServiceClient; +import feign.Feign; +import feign.RequestTemplate; +import feign.Target; +import feign.codec.EncodeException; +import feign.codec.Encoder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.net.URLEncoder; +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira on 26/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Configuration +@ComponentScan(basePackages = "br.com.muttley.notification.twilio.service") +public class TwilioConfig { +/* + @Bean + @Primary + @Scope(BeanDefinition.SCOPE_PROTOTYPE) + feign.codec.Encoder feignFormEncoder(@Autowired final ObjectFactory messageConverters) { + return new FormEncoder(new SpringEncoder(messageConverters)); + } +*/ + + @Bean + public TwilioNotificationServiceClient createTwilioNotificationServiceClient(@Value("${muttley.notification.twilio.accountSid}") final String user, + @Value("${muttley.notification.twilio.accountToken}") final String password, @Value("${muttley.notification.twilio.url}") final String url) { + return Feign.builder() + .encoder(new FormEncoder()) + .requestInterceptor(new TwilioBasicAuthorizationRequestInterceptor(user, password)) + .target(new Target.HardCodedTarget<>(TwilioNotificationServiceClient.class, url)); + } + + private static class FormEncoder implements Encoder { + + @Override + public void encode(Object o, Type type, RequestTemplate rt) throws EncodeException { + if (!(o instanceof Map)) + throw new EncodeException("Can only encode Map data"); + + Map m = (Map) o; + + // XXX: quick n dirty! + StringBuilder sb = new StringBuilder(); + + for (Object k : m.keySet()) { + if (!(k instanceof String)) + throw new EncodeException("Can only encode String keys"); + + if (sb.length() > 0) + sb.append("&"); + + Object v = m.get(k); + if (!(v instanceof String)) + throw new EncodeException("Can only encode String values"); + + try { + sb.append(URLEncoder.encode((String) k, "UTF-8")) + .append("=") + .append(URLEncoder.encode((String) v, "UTF-8")); + } catch (UnsupportedEncodingException ex) { + throw new EncodeException("Invalid encoding", ex); + } + } + + rt.header("Content-Type", "application/x-www-form-urlencoded"); + rt.body(sb.toString()); + } + + } +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/TwilioNotificationService.java b/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/TwilioNotificationService.java new file mode 100644 index 00000000..72d75be4 --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/TwilioNotificationService.java @@ -0,0 +1,12 @@ +package br.com.muttley.notification.twilio.service; + +import br.com.muttley.model.hermes.notification.twilio.SMSPayload; + +/** + * @author Joel Rodrigues Moreira on 26/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface TwilioNotificationService { + void sendNotification(final SMSPayload payload); +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/impl/TwilioBasicAuthorizationRequestInterceptor.java b/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/impl/TwilioBasicAuthorizationRequestInterceptor.java new file mode 100644 index 00000000..787617f2 --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/impl/TwilioBasicAuthorizationRequestInterceptor.java @@ -0,0 +1,48 @@ +package br.com.muttley.notification.twilio.service.impl; + +import feign.RequestInterceptor; +import feign.RequestTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.codec.Base64; + +import java.nio.charset.Charset; + +/** + * @author Joel Rodrigues Moreira on 26/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class TwilioBasicAuthorizationRequestInterceptor implements RequestInterceptor { + private static final Charset CHARSET = Charset.forName("ISO-8859-1"); + private final String user; + private final String password; + private final String AUTHENTICATION; + private final Logger log; + + public TwilioBasicAuthorizationRequestInterceptor(@Value("${muttley.notification.twilio.accountSid:INVALID}") final String user, @Value("${muttley.notification.twilio.accountToken:INVALID}") final String password) { + this.user = user; + this.password = password; + this.AUTHENTICATION = base64Encode(user + ":" + password); + if ("INVALID".equalsIgnoreCase(user) || "INVALID".equalsIgnoreCase(password)) { + this.log = LoggerFactory.getLogger(TwilioBasicAuthorizationRequestInterceptor.class); + this.log.error("\n\n NÃO FOI INFORMADO USUÁRIO OU SENHA VÁLIDO PARA O TWILIO \n\n"); + } else { + this.log = null; + } + } + + private static String base64Encode(String payload) { + return "Basic " + new String(Base64.encode(payload.getBytes(CHARSET)), CHARSET); + } + + + @Override + public void apply(RequestTemplate template) { + template.header("Authorization", this.AUTHENTICATION); + if ("INVALID".equalsIgnoreCase(this.user) || "INVALID".equalsIgnoreCase(this.password)) { + this.log.error("\n\n NÃO FOI INFORMADO USUÁRIO OU SENHA VÁLIDO PARA O TWILIO \n\n"); + } + } +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/impl/TwilioNotificationServiceClient.java b/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/impl/TwilioNotificationServiceClient.java new file mode 100644 index 00000000..b7404c1f --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/impl/TwilioNotificationServiceClient.java @@ -0,0 +1,31 @@ +package br.com.muttley.notification.twilio.service.impl; + +import br.com.muttley.notification.twilio.config.TwilioConfig; +import feign.Headers; +import feign.Param; +import feign.RequestLine; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.util.Map; + +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +/** + * @author Joel Rodrigues Moreira on 26/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@FeignClient(url = "${muttley.notification.twilio.url:https://api.twilio.com}", + value = "twilio", + configuration = {TwilioConfig.class, TwilioBasicAuthorizationRequestInterceptor.class}) +public interface TwilioNotificationServiceClient { + + //@RequestMapping(method = POST, produces = APPLICATION_FORM_URLENCODED_VALUE) + @RequestLine("POST") + @Headers("Type: application/x-www-form-urlencoded") + void sendNotification(Map value); +} diff --git a/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/impl/TwilioNotificationServiceImpl.java b/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/impl/TwilioNotificationServiceImpl.java new file mode 100644 index 00000000..cb3646f0 --- /dev/null +++ b/muttley-notification/src/main/java/br/com/muttley/notification/twilio/service/impl/TwilioNotificationServiceImpl.java @@ -0,0 +1,43 @@ +package br.com.muttley.notification.twilio.service.impl; + +import br.com.muttley.model.hermes.notification.twilio.SMSPayload; +import br.com.muttley.notification.twilio.service.TwilioNotificationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira on 26/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class TwilioNotificationServiceImpl implements TwilioNotificationService { + private final String serviceSid; + + @Autowired + private TwilioNotificationServiceClient client; + + @Autowired + public TwilioNotificationServiceImpl(@Value("${muttley.notification.twilio.serviceId}") final String serviceSid) { + this.serviceSid = serviceSid; + } + + @Override + public void sendNotification(SMSPayload payload) { + payload.setServiceSid(this.serviceSid); + + final Map maps = new HashMap<>(); + maps.put("To", payload.getTo()); + maps.put("MessagingServiceSid", payload.getServiceSid()); + maps.put("Body", payload.getBody()); + + this.client.sendNotification(maps); + + + } +} diff --git a/muttley-notification/src/main/resources/META-INF/spring.factories b/muttley-notification/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..8ff9f4ac --- /dev/null +++ b/muttley-notification/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + br.com.muttley.notification.onesignal.config.OneSignalConfig,\ + br.com.muttley.notification.infobip.config.InfobipConfig diff --git a/muttley-redis/pom.xml b/muttley-redis/pom.xml index 5859b457..9213311c 100644 --- a/muttley-redis/pom.xml +++ b/muttley-redis/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar @@ -15,6 +15,13 @@ Demo project for Spring Boot + + + org.projectlombok + lombok + true + + org.springframework.boot spring-boot-starter-data-redis diff --git a/muttley-redis/src/main/java/br/com/muttley/redis/model/MuttleyRedisWrapper.java b/muttley-redis/src/main/java/br/com/muttley/redis/model/MuttleyRedisWrapper.java new file mode 100644 index 00000000..9a4d6279 --- /dev/null +++ b/muttley-redis/src/main/java/br/com/muttley/redis/model/MuttleyRedisWrapper.java @@ -0,0 +1,20 @@ +package br.com.muttley.redis.model; + +import lombok.Getter; + +/** + * @author Joel Rodrigues Moreira 17/06/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +public class MuttleyRedisWrapper { + private T content; + + public MuttleyRedisWrapper() { + } + + public MuttleyRedisWrapper(final T content) { + this.content = content; + } +} diff --git a/muttley-redis/src/main/java/br/com/muttley/redis/service/RedisService.java b/muttley-redis/src/main/java/br/com/muttley/redis/service/RedisService.java index 43cdff51..66712be0 100644 --- a/muttley-redis/src/main/java/br/com/muttley/redis/service/RedisService.java +++ b/muttley-redis/src/main/java/br/com/muttley/redis/service/RedisService.java @@ -1,6 +1,9 @@ package br.com.muttley.redis.service; import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Set; /** * @author Joel Rodrigues Moreira on 08/01/18. @@ -12,43 +15,101 @@ public interface RedisService { */ String getBasicKey(); + /** + * @return java.util.Set -> coleção de chaves salvas no banco + */ + Set getKey(); + + /** + * @param expression -> expressão basica a ser buscada + * @return java.util.Set -> coleção de chaves salvas no banco + */ + Set getKeys(final String expression); + /** * Salva um objeto qualquer com uma chave especifica * * @param key -> chave desejada * @param value -> valor a ser salvo */ - RedisService set(String key, T value); + RedisService set(final String key, final T value); /** * Salva um objeto qualquer de maneira temporaria * * @param key -> chave desejada * @param value -> valor a ser salvo - * @param time -> tempo em milisegundos para se expirar o registro + * @param date -> data futura que irá expierar o registro */ - RedisService set(String key, T value, long time); + RedisService set(final String key, final T value, final Date date); + + /** + * Salva um objeto qualquer de maneira temporaria + * + * @param key -> chave desejada + * @param value -> valor a ser salvo + * @param time -> tempo em segundos para se expirar o registro + */ + RedisService set(final String key, final T value, final long time); + + /** + * Set a expiração a uma chave existente + * + * @param key -> chave desejada + * @param date -> data futura que irá expierar o registro + */ + RedisService setExpire(final String key, final Date date); + + /** + * Set a expiração a uma chave existente + * + * @param key -> chave desejada + * @param time -> tempo em segundos para se expirar o registro + */ + RedisService setExpire(final String key, final long time); + + /** + * Renomeia uma chave de acesso existente + * + * @param currentKey -> chave atual + * @param newKey -> nova chave + */ + RedisService changeKey(final String currentKey, final String newKey); /** * Recupera um determinado valor qualquer do banco * * @param key -> chave desejada */ - T get(String key); + T get(final String key); /** * Remove um objeto * * @param key -> key do objeto a ser removido */ - RedisService delete(String key); + RedisService delete(final String key); + + /** + * Remove um objeto + * + * @param expression -> expressão basica das chaves a serem removidas + */ + RedisService deleteByExpression(final String expression); /** * lista todos os itens cujo a chave tenha o prefixo {@link #getBasicKey()} * * @return {@link Collection} */ - Collection list(); + Collection list(); + + /** + * Recupera valores do banco de acordo com a expressão regular + * + * @param expression -> chave desejada + */ + List getByExpression(final String expression); /** * Limpa todos os itens @@ -60,4 +121,9 @@ public interface RedisService { */ boolean hasKey(final String key); + /** + * Verifica se existe uma determinada chave no banco via expressão regular + */ + boolean hasKeyByExpression(final String expression); + } diff --git a/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateDeserializer.java b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateDeserializer.java new file mode 100644 index 00000000..2499c1c9 --- /dev/null +++ b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateDeserializer.java @@ -0,0 +1,35 @@ +package br.com.muttley.redis.service.impl; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.regex.Pattern; + +/** + * @author Joel Rodrigues Moreira on 03/09/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class LocalDateDeserializer extends JsonDeserializer { + public static final DateTimeFormatter DEFAULT_ISO_ZONED_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + public static final DateTimeFormatter DEFAULT_ISO_LOCAL_DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final Pattern DATE_PATTERN = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}$"); + + @Override + public LocalDate deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + if (node.isNull()) { + return null; + } + return LocalDate.parse(node.asText(), DEFAULT_ISO_LOCAL_DATE); + + } +} diff --git a/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateSerializer.java b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateSerializer.java new file mode 100644 index 00000000..e9f97ee8 --- /dev/null +++ b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateSerializer.java @@ -0,0 +1,29 @@ +package br.com.muttley.redis.service.impl; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalDate; + +import static br.com.muttley.redis.service.impl.LocalDateDeserializer.DEFAULT_ISO_LOCAL_DATE; + +/** + * @author Joel Rodrigues Moreira on 03/09/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class LocalDateSerializer extends JsonSerializer { + + @Override + public void serialize(final LocalDate date, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException { + if (date == null) { + gen.writeNull(); + } + + gen.writeString(date.format(DEFAULT_ISO_LOCAL_DATE)); + } +} + diff --git a/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateTimeDeserializer.java b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateTimeDeserializer.java new file mode 100644 index 00000000..0e38301b --- /dev/null +++ b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateTimeDeserializer.java @@ -0,0 +1,53 @@ +package br.com.muttley.redis.service.impl; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * @author Joel Rodrigues Moreira on 03/09/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class LocalDateTimeDeserializer extends JsonDeserializer { + public static final DateTimeFormatter dateTimeFormatterDefault = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + private static final DateTimeFormatter dateTimeFormatterOp1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"); + private static final DateTimeFormatter dateTimeFormatterOp2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"); + private static final DateTimeFormatter dateTimeFormatterOp3 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + @Override + public LocalDateTime deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final ObjectCodec oc = parser.getCodec(); + final JsonNode node = oc.readTree(parser); + final String value = node.asText(); + try { + switch (value.length()) { + case 28: + return ZonedDateTime.parse(value, dateTimeFormatterOp3).toLocalDateTime(); + case 21: + return LocalDateTime.parse(value, dateTimeFormatterOp2); + case 22: + return LocalDateTime.parse(value, dateTimeFormatterOp1); + case 23: + return LocalDateTime.parse(value, dateTimeFormatterOp1); + default: + return this.throwsExcpetion(parser); + } + } catch (RuntimeException e) { + return this.throwsExcpetion(parser); + + } + } + + private LocalDateTime throwsExcpetion(final JsonParser parser) throws IOException { + throw new RuntimeException(parser.getCurrentName() + ": Informe uma data válida "); + } +} diff --git a/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateTimeSerializer.java b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateTimeSerializer.java new file mode 100644 index 00000000..1020f3a0 --- /dev/null +++ b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/LocalDateTimeSerializer.java @@ -0,0 +1,22 @@ +package br.com.muttley.redis.service.impl; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalDateTime; + +/** + * @author Joel Rodrigues Moreira on 03/09/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class LocalDateTimeSerializer extends JsonSerializer { + + @Override + public void serialize(LocalDateTime value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException, JsonProcessingException { + jsonGenerator.writeString(value != null ? value.format(LocalDateTimeDeserializer.dateTimeFormatterDefault) : null); + } +} diff --git a/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/ObjectIdSerializer.java b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/ObjectIdSerializer.java index d6e9334c..f0754223 100644 --- a/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/ObjectIdSerializer.java +++ b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/ObjectIdSerializer.java @@ -18,4 +18,10 @@ public class ObjectIdSerializer extends JsonSerializer { public void serialize(ObjectId value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException, JsonProcessingException { jsonGenerator.writeString(value == null ? null : value.toString()); } +/* + @Override + public void serializeWithType(final ObjectId value, final JsonGenerator gen, final SerializerProvider serializers, final TypeSerializer typeSer) throws IOException { + System.out.println("#chamou ObjectIdSerializer"); + super.serializeWithType(value, gen, serializers, typeSer); + }*/ } diff --git a/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/RedisServiceImpl.java b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/RedisServiceImpl.java index f9221c9e..ebafe3f6 100644 --- a/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/RedisServiceImpl.java +++ b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/RedisServiceImpl.java @@ -1,8 +1,11 @@ package br.com.muttley.redis.service.impl; +import br.com.muttley.redis.model.MuttleyRedisWrapper; import br.com.muttley.redis.service.RedisService; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import org.bson.types.ObjectId; @@ -10,15 +13,22 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; +import org.springframework.util.CollectionUtils; import javax.annotation.PostConstruct; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.Collection; -import java.util.concurrent.TimeUnit; +import java.util.Date; +import java.util.List; +import java.util.Set; import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; -import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY; import static com.fasterxml.jackson.annotation.PropertyAccessor.FIELD; -import static com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping.NON_FINAL; +import static java.util.concurrent.TimeUnit.SECONDS; /** * @author Joel Rodrigues Moreira on 08/01/18. @@ -46,6 +56,16 @@ public String getBasicKey() { return this.basicKey; } + @Override + public Set getKey() { + return this.redisTemplate.keys(this.getBasicKey() + "*"); + } + + @Override + public Set getKeys(final String expression) { + return this.redisTemplate.keys(this.getBasicKey() + "*" + expression); + } + @Override public RedisService set(final String key, final T value) { //redisTemplate.opsForHash().put(getBasicKey(), key, value); @@ -53,10 +73,45 @@ public RedisService set(final String key, final T value) { return this; } + @Override + public RedisService set(final String key, final T value, final Date date) { + //caculando o tempo para expiração + final long time = Duration.between(Instant.now(), date.toInstant()).getSeconds(); + //se o tempo for menor que 1, logo não precisamos salvar nada pois é sinal que já foi expirado + if (time >= 1l) { + this.set(key, value, time); + } + return this; + } + @Override public RedisService set(final String key, final T value, final long time) { - final String keyValue = createKey(key); - this.redisTemplate.opsForValue().set(createKey(key), value, time, TimeUnit.MILLISECONDS); + this.redisTemplate.opsForValue().set(createKey(key), value, time, SECONDS); + return this; + } + + @Override + public RedisService setExpire(String key, Date date) { + //caculando o tempo para expiração + final long time = Duration.between(Instant.now(), date.toInstant()).getSeconds(); + //se o tempo for menor que 1, logo não precisamos salvar nada pois é sinal que já foi expirado + if (time >= 1l) { + this.setExpire(key, time); + } + return this; + } + + @Override + public RedisService setExpire(String key, long time) { + this.redisTemplate.opsForValue().getOperations().expire(createKey(key), time, SECONDS); + return this; + } + + @Override + public RedisService changeKey(final String currentKey, final String newKey) { + if (!currentKey.equals(newKey) && this.hasKey(currentKey)) { + this.redisTemplate.rename(createKey(currentKey), createKey(newKey)); + } return this; } @@ -72,13 +127,24 @@ public RedisService delete(final String key) { } @Override - public Collection list() { - return (Collection) this.redisTemplate.opsForValue().multiGet(this.redisTemplate.keys(getBasicKey())); + public RedisService deleteByExpression(final String expression) { + this.redisTemplate.delete(this.getKeys(expression)); + return this; + } + + @Override + public Collection list() { + return this.redisTemplate.opsForValue().multiGet(this.redisTemplate.keys(getBasicKey() + "*")); + } + + @Override + public List getByExpression(final String expression) { + return this.redisTemplate.opsForValue().multiGet(this.redisTemplate.keys(getBasicKey() + "*" + expression)); } @Override public RedisService clearAll() { - this.redisTemplate.delete(this.redisTemplate.keys(getBasicKey())); + this.redisTemplate.delete(this.redisTemplate.keys(getBasicKey() + "*")); return this; } @@ -87,6 +153,11 @@ public boolean hasKey(final String key) { return this.redisTemplate.hasKey(createKey(key)); } + @Override + public boolean hasKeyByExpression(final String expression) { + return !CollectionUtils.isEmpty(this.redisTemplate.keys(this.getBasicKey() + "*" + expression)); + } + private String createKey(final String key) { return this.getBasicKey() + ":" + key; } @@ -97,7 +168,7 @@ class JsonRedisSerializer implements RedisSerializer { @Override public byte[] serialize(final Object value) throws SerializationException { try { - return getObjectMapper().writeValueAsBytes(value); + return getObjectMapper().writeValueAsBytes(new MuttleyRedisWrapper<>(value)); } catch (final JsonProcessingException e) { throw new SerializationException(e.getMessage(), e); } @@ -109,7 +180,7 @@ public Object deserialize(final byte[] bytes) throws SerializationException { return null; } try { - return getObjectMapper().readValue(bytes, Object.class); + return getObjectMapper().readValue(bytes, MuttleyRedisWrapper.class).getContent(); } catch (final Exception e) { throw new SerializationException(e.getMessage(), e); } @@ -117,12 +188,29 @@ public Object deserialize(final byte[] bytes) throws SerializationException { private static ObjectMapper getObjectMapper() { return new ObjectMapper() - .enableDefaultTyping(NON_FINAL, PROPERTY) + .enableDefaultTyping() + .enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT, JsonTypeInfo.As.PROPERTY) .registerModule( new SimpleModule("ObjectId", new Version(1, 0, 0, null, null, null) ).addSerializer(ObjectId.class, new ObjectIdSerializer()) + ).registerModule( + new SimpleModule("ZonedDateTime", + new Version(1, 0, 0, null, null, null) + ).addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer()) + .addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializer()) + ).registerModule( + new SimpleModule("LocalDateTime", + new Version(1, 0, 0, null, null, null) + ).addSerializer(LocalDateTime.class, new LocalDateTimeSerializer()) + .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer()) + ).registerModule( + new SimpleModule("LocaDate", + new Version(1, 0, 0, null, null, null) + ).addSerializer(LocalDate.class, new LocalDateSerializer()) + .addDeserializer(LocalDate.class, new LocalDateDeserializer()) ) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .setVisibility(FIELD, ANY); } } diff --git a/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/ZonedDateTimeDeserializer.java b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/ZonedDateTimeDeserializer.java new file mode 100644 index 00000000..bcc68b23 --- /dev/null +++ b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/ZonedDateTimeDeserializer.java @@ -0,0 +1,50 @@ +package br.com.muttley.redis.service.impl; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Value; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Date; + +/** + * @author Joel Rodrigues Moreira on 12/03/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ZonedDateTimeDeserializer extends JsonDeserializer { + + private final String pattern; + + public ZonedDateTimeDeserializer(@Value("${br.com.muttley.jackson.date-pattern:yyyy-MM-dd'T'HH:mm:ss.SSSZ}") final String pattern) { + this.pattern = pattern; + } + + public ZonedDateTimeDeserializer() { + this("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + } + + @Override + public ZonedDateTime deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + final JsonNode node = parser.getCodec().readTree(parser); + if (node.isNull()) { + return null; + } + return ZonedDateTime.ofInstant(getDateFrom(node.get("date")).toInstant(), ZoneOffset.of(node.get("offset").asText())); + } + + private Date getDateFrom(final JsonNode node) { + try { + return new SimpleDateFormat(this.pattern).parse(node.asText()); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } +} diff --git a/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/ZonedDateTimeSerializer.java b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/ZonedDateTimeSerializer.java new file mode 100644 index 00000000..823556df --- /dev/null +++ b/muttley-redis/src/main/java/br/com/muttley/redis/service/impl/ZonedDateTimeSerializer.java @@ -0,0 +1,107 @@ +package br.com.muttley.redis.service.impl; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.springframework.beans.factory.annotation.Value; +import sun.util.calendar.ZoneInfo; + +import java.io.IOException; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +/** + * @author Joel Rodrigues Moreira on 12/03/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ZonedDateTimeSerializer extends JsonSerializer { + private static final String TIMEZONE_REGEX = "(([+-]|)([01]?[0-9]|2[0-3]):[0-5][0-9])|(([+-]|)([01]?[0-9]|2[0-3])([0-5][0-9]))"; + private static final Pattern TIMEZONE_PATTERN = Pattern.compile(TIMEZONE_REGEX); + private final String pattern; + + + public ZonedDateTimeSerializer(@Value("${br.com.muttley.jackson.date-pattern:yyyy-MM-dd'T'HH:mm:ss.SSSZ}") final String pattern) { + this.pattern = pattern; + } + + public ZonedDateTimeSerializer() { + this("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + } + + @Override + public void serialize(ZonedDateTime dateTime, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException, JsonProcessingException { + if (dateTime != null) { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("date", dateTime.format(DateTimeFormatter.ofPattern(pattern))); + jsonGenerator.writeStringField("offset", getTimezoneFromId(dateTime.getOffset().toString())); + jsonGenerator.writeEndObject(); + } else { + jsonGenerator.writeNull(); + } + } + + private static boolean isValidTimeZone(final String timezone) { + return timezone != null ? TIMEZONE_PATTERN.matcher(timezone).matches() : false; + } + + private static String getTimezoneFromId(String zoneId) { + if ("z".equalsIgnoreCase(zoneId)) { + return "+00:00"; + } + //verificando se zona informada é válida + if (isValidTimeZone(zoneId)) { + //checando se esta formatada com dois pontos + if (!zoneId.contains("+") && !zoneId.contains("-")) { + zoneId = "+" + zoneId; + } + //adicionando os dois pontos caso não tenha + if (!zoneId.contains(":")) { + zoneId = zoneId.substring(0, zoneId.length() - 2) + ":" + zoneId.substring(zoneId.length() - 2, zoneId.length()); + } + //se for válida só retorna a mesma + + return zoneId; + } + + //Se chegou até aqui quer dizer que não mandou no formato de hora + //logo devemos recuperar pelo timezone de fato + final TimeZone timeZone = ZoneInfo.getTimeZone(zoneId); + + //se não recuperou um timezone válido, + //podemos parar o processo + if (timeZone == null) { + return null; + } + + final long hours = TimeUnit.MILLISECONDS.toHours(timeZone.getRawOffset()); + long minutes = TimeUnit.MILLISECONDS.toMinutes(timeZone.getRawOffset()) - TimeUnit.HOURS.toMinutes(hours); + // avoid -4:-30 issue + minutes = Math.abs(minutes); + + if (hours == 0 && minutes == 0) { + return "+00:00"; + } + + final NumberFormat numberFormat = new DecimalFormat("00"); + final String hoursFormated = numberFormat.format(hours); + + if (hours > 0) { + return "+" + hoursFormated + ":" + String.format("%02d", minutes); + } else if (hours == 0 && minutes > 0) { + return "+" + hoursFormated + ":" + String.format("%02d", minutes); + } else if (hours < 0) { + return hoursFormated + ":" + String.format("%02d", minutes); + } else if (hours == 0 && minutes < 0) { + return hoursFormated + ":" + String.format("%02d", minutes); + } + return null; + + } +} diff --git a/muttley-report/.gitignore b/muttley-report/.gitignore new file mode 100644 index 00000000..2af7cefb --- /dev/null +++ b/muttley-report/.gitignore @@ -0,0 +1,24 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ \ No newline at end of file diff --git a/muttley-report/.mvn/wrapper/maven-wrapper.jar b/muttley-report/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..9cc84ea9 Binary files /dev/null and b/muttley-report/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-report/.mvn/wrapper/maven-wrapper.properties b/muttley-report/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..9dda3b65 --- /dev/null +++ b/muttley-report/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip diff --git a/muttley-report/mvnw b/muttley-report/mvnw new file mode 100755 index 00000000..5bf251c0 --- /dev/null +++ b/muttley-report/mvnw @@ -0,0 +1,225 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-report/mvnw.cmd b/muttley-report/mvnw.cmd new file mode 100644 index 00000000..019bd74d --- /dev/null +++ b/muttley-report/mvnw.cmd @@ -0,0 +1,143 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/muttley-report/pom.xml b/muttley-report/pom.xml new file mode 100644 index 00000000..c60b8183 --- /dev/null +++ b/muttley-report/pom.xml @@ -0,0 +1,71 @@ + + + + br.com.muttley + muttley-cloud + ${revision} + + 4.0.0 + jar + + + muttley-report + + muttley-rest + Demo project for Spring Boot + + + + + br.com.muttley + muttley-model + provided + + + + br.com.muttley + muttley-mongo + provided + + + + br.com.muttley + muttley-headers + provided + + + + br.com.muttley + muttley-exception + provided + + + + net.sf.jasperreports + jasperreports + + + + net.sf.jasperreports + jasperreports-fonts + + + + com.google.zxing + core + + + + com.google.zxing + javase + + + + org.projectlombok + lombok + true + + + + diff --git a/muttley-report/src/main/java/br/com/muttley/report/AbstractMuttleyReport.java b/muttley-report/src/main/java/br/com/muttley/report/AbstractMuttleyReport.java new file mode 100644 index 00000000..9881464f --- /dev/null +++ b/muttley-report/src/main/java/br/com/muttley/report/AbstractMuttleyReport.java @@ -0,0 +1,78 @@ +package br.com.muttley.report; + +import br.com.muttley.exception.throwables.MuttleyException; +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.engine.JasperReport; +import net.sf.jasperreports.engine.fill.JRSwapFileVirtualizer; +import net.sf.jasperreports.engine.util.JRSwapFile; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; + +import static java.lang.System.getProperty; +import static net.sf.jasperreports.engine.JRParameter.REPORT_VIRTUALIZER; +import static net.sf.jasperreports.engine.JasperExportManager.exportReportToPdfStream; +import static net.sf.jasperreports.engine.JasperFillManager.fillReport; +import static net.sf.jasperreports.engine.util.JRLoader.loadObject; + +/** + * @author Joel Rodrigues Moreira 10/11/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AbstractMuttleyReport implements MuttleyReport { + protected final AbstractMuttleyReportBuilder builder; + + protected AbstractMuttleyReport(final AbstractMuttleyReportBuilder builder) { + this.builder = builder; + } + + @Override + public InputStream getSourceReport() { + return this.getClass().getResourceAsStream(this.builder.getFileReport()); + } + + @Override + public JasperReport loadReport() { + try { + return (JasperReport) loadObject(getSourceReport()); + } catch (final JRException ex) { + throw new MuttleyException(ex); + } + } + + @Override + public Map getParams() { + return builder.getParams(); + } + + @Override + public void print(final OutputStream outputStream) { + //criando o virtualizador de cache para impressão + final JRSwapFileVirtualizer virtualizer = new JRSwapFileVirtualizer( + 1, + new JRSwapFile(getProperty("java.io.tmpdir"), 1, 100), + true + ); + try { + //adicionando o virtualizador + final Map map = this.getParams(); + map.put(REPORT_VIRTUALIZER, virtualizer); + + exportReportToPdfStream( + fillReport( + this.loadReport(), + map, + this.getDataSource() + ), + outputStream + ); + } catch (final JRException e) { + throw new MuttleyException(e); + } finally { + virtualizer.cleanup(); + } + } + +} diff --git a/muttley-report/src/main/java/br/com/muttley/report/AbstractMuttleyReportBuilder.java b/muttley-report/src/main/java/br/com/muttley/report/AbstractMuttleyReportBuilder.java new file mode 100644 index 00000000..3042edbd --- /dev/null +++ b/muttley-report/src/main/java/br/com/muttley/report/AbstractMuttleyReportBuilder.java @@ -0,0 +1,107 @@ +package br.com.muttley.report; + +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.headers.components.MuttleyCurrentTimezone; +import br.com.muttley.headers.components.MuttleyCurrentVersion; +import br.com.muttley.model.security.User; +import lombok.Getter; +import org.springframework.data.mongodb.core.MongoTemplate; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira 10/11/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AbstractMuttleyReportBuilder implements MuttleyReportBuilder { + + public static final String CURRENT_USER = "CURRENT_USER"; + public static final String CURRENT_VERSION = "CURRENT_VERSION"; + public static final String CURRENT_TIMEZONE = "CURRENT_TIMEZONE"; + public static final String MUTTLEY_CLOUD_FILES = "MUTTLEY_CLOUD_FILES"; + + @Getter + protected Map params = new HashMap<>(); + @Getter + protected MongoTemplate template; + @Getter + protected User currentUser; + @Getter + protected MuttleyCurrentVersion currentVersion; + @Getter + protected MuttleyCurrentTimezone currentTimezone; + @Getter + protected String currentCloudFileDirectory; + protected final T INSTANCE = (T) this; + + protected AbstractMuttleyReportBuilder() { + } + + protected AbstractMuttleyReportBuilder(final MuttleyReportBuilder builder) { + this.setTemplate(builder.getTemplate()); + this.setCurrentUser(builder.getCurrentUser()); + this.setCurrentTimezone(builder.getCurrentTimezone()); + this.setCurrentVersion(builder.getCurrentVersion()); + this.setCurrentCloudFileDirectory(builder.getCurrentCloudFileDirectory()); + } + + public T addParam(final String key, final Object value) { + this.params.put(key, value); + return this.INSTANCE; + } + + @Override + public T addSubReport(MuttleyReportBuilder report) { + this.getParams().putAll(report.getParamsForSubReport()); + return this.INSTANCE; + } + + @Override + public Map getParamsForSubReport() { + throw new MuttleyException("CONFIGURE OS PARAMENTROS NECESSÁRIOS"); + } + + public T removeParam(final String key) { + this.params.remove(key); + return this.INSTANCE; + } + + public T setTemplate(final MongoTemplate template) { + this.template = template; + return this.INSTANCE; + } + + + public T setCurrentUser(final User user) { + this.currentUser = user; + if (user != null) { + this.addParam(CURRENT_USER, user.getName()); + } + return this.INSTANCE; + } + + public T setCurrentVersion(final MuttleyCurrentVersion version) { + this.currentVersion = version; + this.addParam(CURRENT_VERSION, version.getCurrenteFromServer()); + return this.INSTANCE; + } + + public T setCurrentTimezone(final MuttleyCurrentTimezone timezone) { + this.currentTimezone = timezone; + this.addParam(CURRENT_TIMEZONE, timezone.getCurrentTimezoneFromRequestOrServer()); + return this.INSTANCE; + } + + public T setCurrentCloudFileDirectory(final String path) { + this.currentCloudFileDirectory = path; + this.addParam(MUTTLEY_CLOUD_FILES, this.currentCloudFileDirectory); + return this.INSTANCE; + } + + @Override + public String getFileForSubReport() { + return null; + } +} diff --git a/muttley-report/src/main/java/br/com/muttley/report/JRMuttleyMongoCursorDataSource.java b/muttley-report/src/main/java/br/com/muttley/report/JRMuttleyMongoCursorDataSource.java new file mode 100644 index 00000000..4d8c0364 --- /dev/null +++ b/muttley-report/src/main/java/br/com/muttley/report/JRMuttleyMongoCursorDataSource.java @@ -0,0 +1,125 @@ +package br.com.muttley.report; + +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.report.strategy.MuttleyReportAggregationStrategy; +import com.mongodb.AggregationOptions; +import com.mongodb.BasicDBObject; +import com.mongodb.Cursor; +import com.mongodb.DBObject; +import net.sf.jasperreports.engine.JRDataSource; +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.engine.JRField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static br.com.muttley.utils.MapUtils.getValueByNavigation; + +/** + * @author Joel Rodrigues Moreira on 03/09/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class JRMuttleyMongoCursorDataSource implements JRDataSource { + protected final MongoTemplate mongoTemplate; + + protected final MuttleyReportAggregationStrategy aggregationStrategy; + + protected final String COLLECTION_NAME; + protected Cursor cursor; + protected BasicDBObject currentValue; + protected long currentSkip = 0l; + protected final Long currentLimit; + protected boolean throwsExceptionsIsEmpty = true; + private final Logger LOGGER = LoggerFactory.getLogger(JRMuttleyMongoCursorDataSource.class); + private boolean printQueries = false; + private long totalConsumed = 0l; + + + public JRMuttleyMongoCursorDataSource(final MongoTemplate mongoTemplate, final MuttleyReportAggregationStrategy aggregationStrategy, final String collection) { + this(mongoTemplate, aggregationStrategy, 100000l, collection); + } + + public JRMuttleyMongoCursorDataSource(final MongoTemplate mongoTemplate, final MuttleyReportAggregationStrategy aggregationStrategy, final long limit, final String collection) { + this.currentLimit = limit; + this.currentSkip = limit * -1; + this.mongoTemplate = mongoTemplate; + this.aggregationStrategy = aggregationStrategy; + this.COLLECTION_NAME = collection; + } + + public JRMuttleyMongoCursorDataSource throwsExceptionsIsEmpty(final boolean throwsExceptionsIsEmpty) { + this.throwsExceptionsIsEmpty = throwsExceptionsIsEmpty; + return this; + } + + public JRMuttleyMongoCursorDataSource setPrintQueries(boolean printQueries) { + this.printQueries = printQueries; + return this; + } + + protected boolean fetchQuery() { + if (cursor != null) { + cursor.close(); + cursor = null; + } + + //CONTROLE DE PAGINAÇãO + if (totalConsumed == this.currentSkip + this.currentLimit) { + this.currentSkip += this.currentLimit; + final List aggregation = this.createAggregationReport(this.currentSkip, this.currentLimit); + if (this.printQueries) { + LOGGER.info("Executing aggregation: {}", aggregation.stream().map(it -> ((BasicDBObject) it).toJson()).collect(Collectors.joining(","))); + } + this.cursor = this.mongoTemplate.getCollection(this.COLLECTION_NAME) + .aggregate( + this.createAggregationReport(this.currentSkip, this.currentLimit), + AggregationOptions.builder() + .batchSize(100) + .maxTime(60, TimeUnit.MINUTES) + .outputMode(AggregationOptions.OutputMode.CURSOR) + .allowDiskUse(true) + .build() + ); + } + return cursor != null && cursor.hasNext(); + } + + @Override + public boolean next() throws JRException { + final boolean result; + if (this.cursor == null) { + //se é null quer dizer que estamos na primeira pagina + result = this.fetchQuery(); + if (!cursor.hasNext() && this.throwsExceptionsIsEmpty) { + //matando o cursor + this.cursor.close(); + throw new MuttleyNoContentException(null, null, "Nenhum registro encontrado para o relatório!"); + } + } else { + result = this.cursor.hasNext() ? true : this.fetchQuery(); + } + if (result) { + this.currentValue = (BasicDBObject) this.cursor.next(); + this.totalConsumed++; + } + return result; + } + + @Override + public Object getFieldValue(JRField jrField) throws JRException { + return getValueByNavigation(jrField.getName(), this.currentValue); + } + + protected List createAggregationReport(final long skip, final long limit) { + return this.aggregationStrategy.getAggregation(skip, limit) + .stream() + .map(it -> it.toDBObject(Aggregation.DEFAULT_CONTEXT)) + .collect(Collectors.toList()); + } +} diff --git a/muttley-report/src/main/java/br/com/muttley/report/JRMuttleyMongoDataSource.java b/muttley-report/src/main/java/br/com/muttley/report/JRMuttleyMongoDataSource.java new file mode 100644 index 00000000..826bca18 --- /dev/null +++ b/muttley-report/src/main/java/br/com/muttley/report/JRMuttleyMongoDataSource.java @@ -0,0 +1,157 @@ +package br.com.muttley.report; + +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.report.strategy.MuttleyReportAggregationStrategy; +import net.sf.jasperreports.engine.JRDataSource; +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.engine.JRField; +import org.bson.Document; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.util.CollectionUtils; + +import java.util.Iterator; + +import static br.com.muttley.utils.MapUtils.getValueByNavigation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; + +/** + * @author Joel Rodrigues Moreira on 27/02/20. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class JRMuttleyMongoDataSource implements JRDataSource { + private long currentPageSize = 0; + protected final MongoTemplate mongoTemplate; + + protected final MuttleyReportAggregationStrategy aggregationStrategy; + + protected final Class COLLECTION; + protected final String COLLECTION_NAME; + protected Iterator currentResult; + protected Document currentValue; + protected long currentSkip = 0l; + protected final Long currentLimit; + protected boolean throwsExceptionsIsEmpty = true; + + public JRMuttleyMongoDataSource(final MongoTemplate mongoTemplate, final MuttleyReportAggregationStrategy aggregationStrategy, Class collection) { + this(mongoTemplate, aggregationStrategy, 100l, collection); + } + + public JRMuttleyMongoDataSource(final MongoTemplate mongoTemplate, final MuttleyReportAggregationStrategy aggregationStrategy, final String collection) { + this(mongoTemplate, aggregationStrategy, 100l, collection); + } + + public JRMuttleyMongoDataSource(final MongoTemplate mongoTemplate, final MuttleyReportAggregationStrategy aggregationStrategy, final long limit, Class collection) { + this.currentLimit = limit; + this.mongoTemplate = mongoTemplate; + this.aggregationStrategy = aggregationStrategy; + this.COLLECTION = collection; + this.COLLECTION_NAME = null; + } + + public JRMuttleyMongoDataSource(final MongoTemplate mongoTemplate, final MuttleyReportAggregationStrategy aggregationStrategy, final long limit, final String collection) { + this.currentLimit = limit; + this.mongoTemplate = mongoTemplate; + this.aggregationStrategy = aggregationStrategy; + this.COLLECTION = null; + this.COLLECTION_NAME = collection; + } + + @Override + public boolean next() throws JRException { + final boolean result; + if (this.currentResult == null) { + //se é null quer dizer que estamos na primeira pagina + result = this.fetchQuery(); + if (this.currentPageSize == 0 && this.throwsExceptionsIsEmpty) { + throw new MuttleyNoContentException(this.COLLECTION, null, "Nenhum registro encontrado para o relatório!"); + } + } else { + result = this.currentResult.hasNext() ? true : this.fetchQuery(); + } + if (result) { + this.currentValue = this.currentResult.next(); + } + return result; + } + + @Override + public Object getFieldValue(final JRField jrField) throws JRException { + //se o campo buscado tiver algum ponto + //devemos navegar em níveis para recuperar o valor + /*if (jrField.getName().contains(".")) { + //pegando a cascata de nivél a ser percorrida + final String[] fields = jrField.getName().split("\\."); + //objeto de auxilio para os níveis + LinkedHashMap linkedHashMap = null; + //navegando + for (int i = 0; i < fields.length; i++) { + //se estivermos no ultimo nívels + if (i == fields.length - 1) { + //basta apenas retornar o valor + return linkedHashMap.get(fields[i]); + } else { + //recuperando o nível atual + linkedHashMap = (LinkedHashMap) this.currentValue.get(fields[i]); + } + //se o nível atual for null apenas retornamos + if (linkedHashMap == null) { + return null; + } + } + }*/ + //retornando o valor de maneira simples + //return this.currentValue.get(jrField.getName()); + return getValueByNavigation(jrField.getName(), this.currentValue); + } + + public JRMuttleyMongoDataSource throwsExceptionsIsEmpty(final boolean throwsExceptionsIsEmpty) { + this.throwsExceptionsIsEmpty = throwsExceptionsIsEmpty; + return this; + } + + protected boolean fetchQuery() { + //Se a ultima consulta resultou em uma quantidade menor + //do que o limit máximo trabalhado + //logo podemos inferir que já percorremos todos os dados + //do banco + if (this.currentPageSize > 0 && this.currentPageSize < this.currentLimit) { + return false; + } + + final AggregationResults results; + + if (this.COLLECTION != null) { + results = this.mongoTemplate.aggregate( + this.createAggregationReport(this.currentSkip, this.currentLimit), + this.COLLECTION, + Document.class + ); + } else { + results = this.mongoTemplate.aggregate( + this.createAggregationReport(this.currentSkip, this.currentLimit), + this.COLLECTION_NAME, + Document.class + ); + } + + this.currentSkip += this.currentLimit; + + if (results == null || CollectionUtils.isEmpty(results.getMappedResults())) { + this.currentPageSize = 0; + return false; + } + + this.currentPageSize = results.getMappedResults().size(); + + this.currentResult = results.getMappedResults().iterator(); + + return true; + } + + protected Aggregation createAggregationReport(final long skip, final long limit) { + return newAggregation(this.aggregationStrategy.getAggregation(skip, limit)); + } +} diff --git a/muttley-report/src/main/java/br/com/muttley/report/MuttleyReport.java b/muttley-report/src/main/java/br/com/muttley/report/MuttleyReport.java new file mode 100644 index 00000000..03ad5240 --- /dev/null +++ b/muttley-report/src/main/java/br/com/muttley/report/MuttleyReport.java @@ -0,0 +1,26 @@ +package br.com.muttley.report; + +import net.sf.jasperreports.engine.JRDataSource; +import net.sf.jasperreports.engine.JasperReport; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira 10/11/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyReport { + + InputStream getSourceReport(); + + JasperReport loadReport(); + + Map getParams(); + + void print(final OutputStream outputStream); + + JRDataSource getDataSource(); +} diff --git a/muttley-report/src/main/java/br/com/muttley/report/MuttleyReportBuilder.java b/muttley-report/src/main/java/br/com/muttley/report/MuttleyReportBuilder.java new file mode 100644 index 00000000..d91722c9 --- /dev/null +++ b/muttley-report/src/main/java/br/com/muttley/report/MuttleyReportBuilder.java @@ -0,0 +1,40 @@ +package br.com.muttley.report; + +import br.com.muttley.headers.components.MuttleyCurrentTimezone; +import br.com.muttley.headers.components.MuttleyCurrentVersion; +import br.com.muttley.model.security.User; +import org.springframework.data.mongodb.core.MongoTemplate; + +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira 10/11/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyReportBuilder { + + MongoTemplate getTemplate(); + + User getCurrentUser(); + + MuttleyCurrentVersion getCurrentVersion(); + + MuttleyCurrentTimezone getCurrentTimezone(); + + String getCurrentCloudFileDirectory(); + + T addParam(final String key, final Object value); + + T addSubReport(final MuttleyReportBuilder report); + + Map getParams(); + + MuttleyReport build(); + + Map getParamsForSubReport(); + + String getFileReport(); + + String getFileForSubReport(); +} diff --git a/muttley-report/src/main/java/br/com/muttley/report/MuttleyReportFileXml.java b/muttley-report/src/main/java/br/com/muttley/report/MuttleyReportFileXml.java new file mode 100644 index 00000000..a23fb562 --- /dev/null +++ b/muttley-report/src/main/java/br/com/muttley/report/MuttleyReportFileXml.java @@ -0,0 +1,51 @@ +package br.com.muttley.report; + +import java.io.File; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * @author Joel Rodrigues Moreira on 28/02/20. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class MuttleyReportFileXml { + private final String resource; + protected File createdTmpFile; + private final MuttleyReportFileXml[] subReports; + + public MuttleyReportFileXml(final String resource) { + this(resource, (String) null); + } + + public MuttleyReportFileXml(final String resource, String... subReports) { + this.resource = resource; + if (subReports != null) { + this.subReports = Stream.of(subReports).map(it -> new MuttleyReportFileXml(it)).toArray(MuttleyReportFileXml[]::new); + } else { + this.subReports = null; + } + } + + public MuttleyReportFileXml(final String resource, MuttleyReportFileXml... subReports) { + this.resource = resource; + this.subReports = subReports; + } + + public String getResource() { + return resource; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof MuttleyReportFileXml)) return false; + final MuttleyReportFileXml that = (MuttleyReportFileXml) o; + return Objects.equals(getResource(), that.getResource()); + } + + @Override + public int hashCode() { + return Objects.hash(getResource()); + } +} diff --git a/muttley-report/src/main/java/br/com/muttley/report/strategy/AdvancedMuttleyReportAggregationStrategy.java b/muttley-report/src/main/java/br/com/muttley/report/strategy/AdvancedMuttleyReportAggregationStrategy.java new file mode 100644 index 00000000..f7d4ea78 --- /dev/null +++ b/muttley-report/src/main/java/br/com/muttley/report/strategy/AdvancedMuttleyReportAggregationStrategy.java @@ -0,0 +1,33 @@ +package br.com.muttley.report.strategy; + +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; + +import java.util.LinkedList; +import java.util.List; + +import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.skip; + +/** + * @author Joel Rodrigues Moreira on 13/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class AdvancedMuttleyReportAggregationStrategy implements MuttleyReportAggregationStrategy { + private final List before; + private final List after; + + public AdvancedMuttleyReportAggregationStrategy(List before, List after) { + this.before = before; + this.after = after; + } + + @Override + public List getAggregation(long skip, long limit) { + final List operations = new LinkedList<>(this.before); + operations.add(skip(skip)); + operations.add(limit(limit)); + operations.addAll(this.after); + return operations; + } +} diff --git a/muttley-report/src/main/java/br/com/muttley/report/strategy/MuttleyReportAggregationStrategy.java b/muttley-report/src/main/java/br/com/muttley/report/strategy/MuttleyReportAggregationStrategy.java new file mode 100644 index 00000000..404a76e9 --- /dev/null +++ b/muttley-report/src/main/java/br/com/muttley/report/strategy/MuttleyReportAggregationStrategy.java @@ -0,0 +1,15 @@ +package br.com.muttley.report.strategy; + +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 13/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyReportAggregationStrategy { + + List getAggregation(final long skip, final long limit); +} diff --git a/muttley-report/src/main/java/br/com/muttley/report/strategy/MuttleyReportPaginateAggregationStrategy.java b/muttley-report/src/main/java/br/com/muttley/report/strategy/MuttleyReportPaginateAggregationStrategy.java new file mode 100644 index 00000000..00f7d420 --- /dev/null +++ b/muttley-report/src/main/java/br/com/muttley/report/strategy/MuttleyReportPaginateAggregationStrategy.java @@ -0,0 +1,15 @@ +package br.com.muttley.report.strategy; + +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 03/09/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface MuttleyReportPaginateAggregationStrategy extends MuttleyReportAggregationStrategy { + @Override + List getAggregation(final long skip, final long limit); +} diff --git a/muttley-report/src/main/java/br/com/muttley/report/strategy/SimpleMuttleyReportAggregationStrategy.java b/muttley-report/src/main/java/br/com/muttley/report/strategy/SimpleMuttleyReportAggregationStrategy.java new file mode 100644 index 00000000..2916a5c6 --- /dev/null +++ b/muttley-report/src/main/java/br/com/muttley/report/strategy/SimpleMuttleyReportAggregationStrategy.java @@ -0,0 +1,31 @@ +package br.com.muttley.report.strategy; + +import br.com.muttley.report.strategy.MuttleyReportAggregationStrategy; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; + +import java.util.LinkedList; +import java.util.List; + +import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.skip; + +/** + * @author Joel Rodrigues Moreira on 13/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class SimpleMuttleyReportAggregationStrategy implements MuttleyReportAggregationStrategy { + private final List operations; + + public SimpleMuttleyReportAggregationStrategy(List operations) { + this.operations = operations; + } + + @Override + public List getAggregation(long skip, long limit) { + List operations = new LinkedList<>(this.operations); + operations.add(skip(skip)); + operations.add(limit(limit)); + return operations; + } +} diff --git a/muttley-rest/pom.xml b/muttley-rest/pom.xml index 39595fa7..bd198827 100644 --- a/muttley-rest/pom.xml +++ b/muttley-rest/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar @@ -16,6 +16,12 @@ Demo project for Spring Boot + + + br.com.muttley + muttley-headers + + br.com.muttley muttley-model @@ -49,6 +55,7 @@ org.springframework.boot spring-boot-starter + provided diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/AbstractModelSyncRestController.java b/muttley-rest/src/main/java/br/com/muttley/rest/AbstractModelSyncRestController.java new file mode 100644 index 00000000..776cc57f --- /dev/null +++ b/muttley-rest/src/main/java/br/com/muttley/rest/AbstractModelSyncRestController.java @@ -0,0 +1,139 @@ +package br.com.muttley.rest; + +import br.com.muttley.domain.service.ModelSyncService; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.model.Document; +import br.com.muttley.model.ModelSync; +import br.com.muttley.model.jackson.DefaultDateFormatConfig; +import br.com.muttley.rest.hateoas.event.ModelSyncResourceCreatedEvent; +import br.com.muttley.security.infra.service.AuthService; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletResponse; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Arrays.asList; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; +import static org.springframework.web.servlet.HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; + +/** + * @author Joel Rodrigues Moreira on 06/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AbstractModelSyncRestController extends AbstractRestController implements RestResource, ModelSyncRestController { + protected final ModelSyncService service; + + public AbstractModelSyncRestController(final ModelSyncService service, final AuthService userService, final ApplicationEventPublisher eventPublisher) { + super(service, userService, eventPublisher); + this.service = service; + } + + @Override + public void publishCreateResourceEvent(final ApplicationEventPublisher eventPublisher, final HttpServletResponse response, final Document model) { + eventPublisher.publishEvent(new ModelSyncResourceCreatedEvent((ModelSync) model, response)); + } + + @RequestMapping(value = "/sync", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity updateBySync(@RequestParam(value = "sync", required = false) String sync, @RequestBody T model) { + model.setSync(sync); + return ResponseEntity.ok(this.service.updateBySync(this.userService.getCurrentUser(), model)); + } + + @RequestMapping(value = "/sync", method = RequestMethod.DELETE, consumes = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}, produces = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(OK) + public ResponseEntity delteBySync(@RequestParam(value = "sync", required = false) String sync) { + service.deleteBySync(this.userService.getCurrentUser(), sync); + return ResponseEntity.ok().build(); + } + + @RequestMapping(value = "/sync", method = GET, produces = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findBySync(@RequestParam(value = "sync", required = false) final String sync, final HttpServletResponse response) { + final T value = (T) this.service.findBySync(this.userService.getCurrentUser(), sync); + this.publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(value); + } + + @Override + @RequestMapping(value = "/reference/sync", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findReferenceBySync(@RequestParam(value = "sync", required = false) final String sync, final HttpServletResponse response) { + final T value = (T) this.service.findReferenceBySync(this.userService.getCurrentUser(), sync); + this.publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(value); + } + + @RequestMapping(value = "/syncOrId", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(OK) + @Override + public ResponseEntity findBySyncOrId(@RequestParam(value = "syncOrId", required = false) final String syncOrId, final HttpServletResponse response) { + final T value = (T) this.service.findByIdOrSync(this.userService.getCurrentUser(), syncOrId); + this.publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(value); + } + + @RequestMapping(value = "/syncs", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + @Override + public ResponseEntity findBySyncs(@RequestParam(required = false, value = "syncs") final String[] syncs, final HttpServletResponse response) { + final Set values = service.getIdsOfSyncs(this.userService.getCurrentUser(), new HashSet<>(asList(syncs))); + + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + + return ResponseEntity.ok(values); + } + + @RequestMapping(value = "/synchronization", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity synchronization(@RequestBody final List values) { + service.synchronize(this.userService.getCurrentUser(), values); + return ResponseEntity.ok().build(); + } + + @RequestMapping(value = "/lastModify", method = GET, produces = TEXT_PLAIN_VALUE) + @ResponseStatus(OK) + public ResponseEntity getLastModify(@Autowired final DefaultDateFormatConfig dateFormat) { + final Date result = this.service.getLastModify(this.userService.getCurrentUser()); + return ResponseEntity.ok(result == null ? null : dateFormat.format(result)); + } + + @RequestMapping(value = "/sync/id", method = GET, produces = TEXT_PLAIN_VALUE) + @ResponseStatus(OK) + public ResponseEntity getIdOfSync(@RequestParam(value = "sync", required = false) final String sync) { + final String objectId = this.service.getIdOfSync(this.userService.getCurrentUser(), sync); + return ResponseEntity.ok(objectId); + } + + protected String getPathVariable(final String key) { + final Map pathVariavles = (Map) (((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()).getAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE); + return pathVariavles.get(key); + } + + protected String isValidOrNotFoud(final String id, final Class clazz) { + if (!ObjectId.isValid(id)) { + throw new MuttleyNotFoundException(clazz, "id", "Registro não encontrado"); + } + return id; + } +} diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/AbstractModelSyncRestControllerDeprecated.java b/muttley-rest/src/main/java/br/com/muttley/rest/AbstractModelSyncRestControllerDeprecated.java new file mode 100644 index 00000000..bd8a8972 --- /dev/null +++ b/muttley-rest/src/main/java/br/com/muttley/rest/AbstractModelSyncRestControllerDeprecated.java @@ -0,0 +1,141 @@ +package br.com.muttley.rest; + +import br.com.muttley.domain.service.ModelSyncService; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.model.Document; +import br.com.muttley.model.ModelSync; +import br.com.muttley.model.jackson.DefaultDateFormatConfig; +import br.com.muttley.rest.hateoas.event.ModelSyncResourceCreatedEvent; +import br.com.muttley.security.infra.service.AuthService; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletResponse; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Arrays.asList; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; +import static org.springframework.web.servlet.HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; + +/** + * @author Joel Rodrigues Moreira on 06/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Deprecated +public abstract class AbstractModelSyncRestControllerDeprecated extends AbstractRestController implements RestResource, ModelSyncRestController { + protected final ModelSyncService service; + + public AbstractModelSyncRestControllerDeprecated(final ModelSyncService service, final AuthService userService, final ApplicationEventPublisher eventPublisher) { + super(service, userService, eventPublisher); + this.service = service; + } + + @Override + public void publishCreateResourceEvent(final ApplicationEventPublisher eventPublisher, final HttpServletResponse response, final Document model) { + eventPublisher.publishEvent(new ModelSyncResourceCreatedEvent((ModelSync) model, response)); + } + + @RequestMapping(value = "/sync/{sync}", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity updateBySync(@PathVariable("sync") String sync, @RequestBody T model) { + model.setSync(sync); + return ResponseEntity.ok(this.service.updateBySync(this.userService.getCurrentUser(), model)); + } + + @RequestMapping(value = "/sync/{sync}", method = RequestMethod.DELETE, consumes = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}, produces = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(OK) + public ResponseEntity delteBySync(@PathVariable("sync") String sync) { + service.deleteBySync(this.userService.getCurrentUser(), sync); + return ResponseEntity.ok().build(); + } + + @RequestMapping(value = "/sync/{sync}", method = GET, produces = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findBySync(@PathVariable("sync") final String sync, final HttpServletResponse response) { + final T value = (T) this.service.findBySync(this.userService.getCurrentUser(), sync); + this.publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(value); + } + + @Override + @RequestMapping(value = "/reference/sync/{sync}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findReferenceBySync(@PathVariable("sync") final String sync, final HttpServletResponse response) { + final T value = (T) this.service.findReferenceBySync(this.userService.getCurrentUser(), sync); + this.publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(value); + } + + @RequestMapping(value = "/syncOrId/{syncOrId}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(OK) + @Override + public ResponseEntity findBySyncOrId(@PathVariable("syncOrId") final String syncOrId, final HttpServletResponse response) { + final T value = (T) this.service.findByIdOrSync(this.userService.getCurrentUser(), syncOrId); + this.publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(value); + } + + @RequestMapping(value = "/syncs", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + @Override + public ResponseEntity findBySyncs(@RequestParam(required = false, value = "syncs") final String[] syncs, final HttpServletResponse response) { + final Set values = service.getIdsOfSyncs(this.userService.getCurrentUser(), new HashSet<>(asList(syncs))); + + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + + return ResponseEntity.ok(values); + } + + @RequestMapping(value = "/synchronization", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity synchronization(@RequestBody final List values) { + service.synchronize(this.userService.getCurrentUser(), values); + return ResponseEntity.ok().build(); + } + + @RequestMapping(value = "/lastModify", method = GET, produces = TEXT_PLAIN_VALUE) + @ResponseStatus(OK) + public ResponseEntity getLastModify(@Autowired final DefaultDateFormatConfig dateFormat) { + final Date result = this.service.getLastModify(this.userService.getCurrentUser()); + return ResponseEntity.ok(result == null ? null : dateFormat.format(result)); + } + + @RequestMapping(value = "/sync/{sync}/id", method = GET, produces = TEXT_PLAIN_VALUE) + @ResponseStatus(OK) + public ResponseEntity getIdOfSync(@PathVariable("sync") final String sync) { + final String objectId = this.service.getIdOfSync(this.userService.getCurrentUser(), sync); + return ResponseEntity.ok(objectId); + } + + protected String getPathVariable(final String key) { + final Map pathVariavles = (Map) (((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()).getAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE); + return pathVariavles.get(key); + } + + protected String isValidOrNotFoud(final String id, final Class clazz) { + if (!ObjectId.isValid(id)) { + throw new MuttleyNotFoundException(clazz, "id", "Registro não encontrado"); + } + return id; + } +} diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/AbstractProxyRestController.java b/muttley-rest/src/main/java/br/com/muttley/rest/AbstractProxyRestController.java index f4af31be..f67c6db2 100644 --- a/muttley-rest/src/main/java/br/com/muttley/rest/AbstractProxyRestController.java +++ b/muttley-rest/src/main/java/br/com/muttley/rest/AbstractProxyRestController.java @@ -1,7 +1,6 @@ package br.com.muttley.rest; import br.com.muttley.model.Document; -import br.com.muttley.model.Historic; import br.com.muttley.rest.hateoas.resource.PageableResource; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpStatus; @@ -10,11 +9,14 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; import javax.servlet.http.HttpServletResponse; import java.util.Map; +import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.DELETE; import static org.springframework.web.bind.annotation.RequestMethod.GET; @@ -74,19 +76,22 @@ public ResponseEntity findById(@PathVariable("id") final String id, final HttpSe } @Override - @RequestMapping(value = "/first", method = GET, produces = APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity first(final HttpServletResponse response) { - final T value = client.first(); + @RequestMapping(value = "/reference/{id}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findReferenceById(@PathVariable("id") String id, HttpServletResponse response) { + final T value = client.findReferenceById(id); + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(value); } @Override - @RequestMapping(value = "/{id}/historic", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity loadHistoric(@PathVariable("id") final String id, final HttpServletResponse response) { - final Historic historic = client.loadHistoric(id); + @RequestMapping(value = "/first", method = GET, produces = APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity first(final HttpServletResponse response) { + final T value = client.first(); publishSingleResourceRetrievedEvent(this.eventPublisher, response); - return ResponseEntity.ok(historic); + return ResponseEntity.ok(value); } @Override @@ -98,7 +103,7 @@ public ResponseEntity list(final HttpServletResponse response, @Override @RequestMapping(value = "/count", method = GET, produces = TEXT_PLAIN_VALUE) - public ResponseEntity count(@RequestParam final Map allRequestParams) { + public ResponseEntity count(@RequestParam final Map allRequestParams) { return ResponseEntity.ok(client.count(allRequestParams)); } } diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/AbstractRestController.java b/muttley-rest/src/main/java/br/com/muttley/rest/AbstractRestController.java index 30ab368e..ec035be1 100644 --- a/muttley-rest/src/main/java/br/com/muttley/rest/AbstractRestController.java +++ b/muttley-rest/src/main/java/br/com/muttley/rest/AbstractRestController.java @@ -1,31 +1,30 @@ package br.com.muttley.rest; import br.com.muttley.domain.service.Service; -import br.com.muttley.exception.throwables.security.MuttleySecurityCredentialException; import br.com.muttley.model.Document; -import br.com.muttley.model.Historic; -import br.com.muttley.model.security.enumeration.Authorities; import br.com.muttley.rest.hateoas.resource.PageableResource; import br.com.muttley.security.infra.service.AuthService; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import javax.servlet.http.HttpServletResponse; import java.util.Map; +import java.util.Set; -import static java.util.Objects.isNull; +import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; /** * @author Joel Rodrigues Moreira on 30/01/18. @@ -44,42 +43,38 @@ public AbstractRestController(final Service service, final AuthService userServi @Override @RequestMapping(method = POST, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) - @ResponseStatus(HttpStatus.CREATED) + @ResponseStatus(CREATED) public ResponseEntity save(@RequestBody final T value, final HttpServletResponse response, @RequestParam(required = false, value = "returnEntity", defaultValue = "") final String returnEntity) { - this.checkRoleCreate(); final T record = service.save(this.userService.getCurrentUser(), value); publishCreateResourceEvent(this.eventPublisher, response, record); if (returnEntity != null && returnEntity.equals("true")) { - return ResponseEntity.status(HttpStatus.CREATED).body(record); + return ResponseEntity.status(CREATED).body(record); } - return ResponseEntity.status(HttpStatus.CREATED).build(); + return ResponseEntity.status(CREATED).build(); } @Override - @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @RequestMapping(value = "/{id}", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(OK) public ResponseEntity update(@PathVariable("id") final String id, @RequestBody final T model) { - checkRoleUpdate(); model.setId(id); return ResponseEntity.ok(service.update(this.userService.getCurrentUser(), model)); } @Override - @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @RequestMapping(value = "/{id}", method = DELETE, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(OK) public ResponseEntity deleteById(@PathVariable("id") final String id) { - checkRoleDelete(); service.deleteById(this.userService.getCurrentUser(), id); return ResponseEntity.ok().build(); } @Override - @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @RequestMapping(value = "/{id}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(OK) public ResponseEntity findById(@PathVariable("id") final String id, final HttpServletResponse response) { - checkRoleRead(); final T value = service.findById(this.userService.getCurrentUser(), id); publishSingleResourceRetrievedEvent(this.eventPublisher, response); @@ -88,11 +83,10 @@ public ResponseEntity findById(@PathVariable("id") final String id, final HttpSe } @Override - @RequestMapping(value = "/first", method = RequestMethod.GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @RequestMapping(value = "/reference/{id}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(OK) - public ResponseEntity first(final HttpServletResponse response) { - checkRoleRead(); - final T value = service.findFirst(this.userService.getCurrentUser()); + public ResponseEntity findReferenceById(@PathVariable("id") String id, HttpServletResponse response) { + final T value = service.findReferenceById(this.userService.getCurrentUser(), id); publishSingleResourceRetrievedEvent(this.eventPublisher, response); @@ -100,80 +94,37 @@ public ResponseEntity first(final HttpServletResponse response) { } @Override - @RequestMapping(value = "/{id}/historic", method = RequestMethod.GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @RequestMapping(value = "/ids", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(OK) - public ResponseEntity loadHistoric(@PathVariable("id") final String id, final HttpServletResponse response) { - checkRoleRead(); - final Historic historic = service.loadHistoric(this.userService.getCurrentUser(), id); + public ResponseEntity findByIds(@RequestParam(required = false, value = "ids") String[] ids, HttpServletResponse response) { + final Set value = service.findByIds(this.userService.getCurrentUser(), ids); publishSingleResourceRetrievedEvent(this.eventPublisher, response); - return ResponseEntity.ok(historic); - } - - @Override - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity list(final HttpServletResponse response, @RequestParam final Map allRequestParams) { - checkRoleRead(); - return ResponseEntity.ok(toPageableResource(eventPublisher, response, this.service, this.userService.getCurrentUser(), allRequestParams)); + return ResponseEntity.ok(value); } @Override - @RequestMapping(value = "/count", method = RequestMethod.GET, produces = {MediaType.TEXT_PLAIN_VALUE}) + @RequestMapping(value = "/first", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(OK) - public final ResponseEntity count(final Map allRequestParams) { - checkRoleRead(); - return ResponseEntity.ok(String.valueOf(service.count(this.userService.getCurrentUser(), allRequestParams))); - } - - protected String[] getCreateRoles() { - return null; - } - - protected String[] getReadRoles() { - return null; - } - - protected String[] getUpdateRoles() { - return null; - } - - protected String[] getDeleteRoles() { - return null; - } - - protected final void checkRoleCreate() { - if (!isNull(getCreateRoles())) - this.checkCredentials(getCreateRoles()); - - } - - protected final void checkRoleRead() { - if (!isNull(getReadRoles())) - this.checkCredentials(getReadRoles()); - } + public ResponseEntity first(final HttpServletResponse response) { + final T value = service.findFirst(this.userService.getCurrentUser()); - protected final void checkRoleUpdate() { - if (!isNull(getUpdateRoles())) - this.checkCredentials(this.getUpdateRoles()); - } + publishSingleResourceRetrievedEvent(this.eventPublisher, response); - protected final void checkRoleDelete() { - if (!isNull(getDeleteRoles())) - this.checkCredentials(this.getDeleteRoles()); + return ResponseEntity.ok(value); } - protected final void checkCredentials(final String... roles) { - if (!this.userService.getCurrentUser().inAnyRole(roles)) { - throw new MuttleySecurityCredentialException("Você não tem permissão para acessar este recurso ") - .addDetails("isNecessary", roles); - } + @Override + @RequestMapping(method = GET) + public ResponseEntity list(final HttpServletResponse response, @RequestParam final Map allRequestParams) { + return ResponseEntity.ok(toPageableResource(eventPublisher, response, this.service, this.userService.getCurrentUser(), allRequestParams)); } - protected final void checkCredentials(final Authorities... roles) { - if (!this.userService.getCurrentUser().inAnyRole(roles)) { - throw new MuttleySecurityCredentialException("Você não tem permissão para acessar este recurso ") - .addDetails("isNecessary", roles); - } + @Override + @RequestMapping(value = "/count", method = GET, produces = {MediaType.TEXT_PLAIN_VALUE}) + @ResponseStatus(OK) + public ResponseEntity count(@RequestParam final Map allRequestParams) { + return ResponseEntity.ok(String.valueOf(service.count(this.userService.getCurrentUser(), allRequestParams))); } } diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestController.java b/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestController.java new file mode 100644 index 00000000..fbfefdb8 --- /dev/null +++ b/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestController.java @@ -0,0 +1,61 @@ +package br.com.muttley.rest; + +import br.com.muttley.model.ModelSync; +import br.com.muttley.model.jackson.DefaultDateFormatConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; + +/** + * @author Joel Rodrigues Moreira on 06/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface ModelSyncRestController extends RestResource, RestController { + @RequestMapping(value = "/sync", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity updateBySync(@RequestParam(value = "sync", required = false) String sync, @RequestBody T model); + + @RequestMapping(value = "/sync", method = RequestMethod.DELETE, consumes = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}, produces = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(OK) + public ResponseEntity delteBySync(@RequestParam(value = "sync", required = false) String sync); + + @RequestMapping(value = "/sync", method = GET, produces = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findBySync(@RequestParam(value = "sync", required = false) final String sync, final HttpServletResponse response); + + @RequestMapping(value = "/reference/sync", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findReferenceBySync(@RequestParam(value = "sync", required = false) final String sync, final HttpServletResponse response); + + @RequestMapping(value = "/syncOrId", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findBySyncOrId(@RequestParam(value = "syncOrId", required = false) final String syncOrId, final HttpServletResponse response); + + @RequestMapping(value = "/syncs", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findBySyncs(@RequestParam(required = false, value = "syncs") String[] syncs, HttpServletResponse response); + + @RequestMapping(value = "/synchronization", method = RequestMethod.PUT, produces = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity synchronization(@RequestBody final List values); + + @RequestMapping(value = "/lastModify", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getLastModify(@Autowired final DefaultDateFormatConfig dateFormat); +} diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestControllerClient.java b/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestControllerClient.java new file mode 100644 index 00000000..e62c6851 --- /dev/null +++ b/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestControllerClient.java @@ -0,0 +1,91 @@ +package br.com.muttley.rest; + +import br.com.muttley.model.SyncObjectId; +import br.com.muttley.security.infra.resource.PageableResource; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; + +/** + * @author Joel Rodrigues Moreira on 11/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface ModelSyncRestControllerClient { + + @RequestMapping(method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + T save(@RequestBody T value, @RequestParam(required = false, value = "returnEntity", defaultValue = "") String returnEntity); + + @RequestMapping(value = "/{id}", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + T update(@PathVariable("id") String id, @RequestBody T model); + + @RequestMapping(value = "/sync", method = RequestMethod.PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + T updateBySync(@RequestParam(value = "sync", required = false) String sync, @RequestBody T model); + + @RequestMapping(value = "/synchronization", method = RequestMethod.PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + void synchronization(@RequestBody final List values); + + @RequestMapping(value = "/synchronization", method = RequestMethod.PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + void synchronization(@RequestBody final List values, @RequestHeader(value = "X-Api-Token", defaultValue = "") final String xAPIToken); + + @RequestMapping(value = "/{id}", method = DELETE) + void deleteById(@PathVariable("id") String id); + + @RequestMapping(value = "/sync", method = RequestMethod.DELETE, consumes = APPLICATION_JSON_UTF8_VALUE) + void delteBySync(@RequestParam(value = "sync", required = false) String sync); + + @RequestMapping(value = "/{id}", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + T findById(@PathVariable("id") String id); + + @RequestMapping(value = "/reference/{id}", method = GET, produces = APPLICATION_JSON_UTF8_VALUE) + T findReferenceById(@PathVariable("id") String id); + + @RequestMapping(value = "/sync", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + T findBySync(@RequestParam(value = "sync", required = false) final String sync); + + @RequestMapping(value = "/reference/sync", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + T findReferenceBySync(@RequestParam(value = "sync", required = false) final String sync); + + @RequestMapping(value = "/syncOrId", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + T findBySyncOrId(@RequestParam(value = "syncOrId", required = false) final String syncOrId); + + @RequestMapping(value = "/syncs", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + public Set findBySyncs(@RequestParam(required = false, value = "syncs") String[] syncs); + + @RequestMapping(value = "/first", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + T first(); + + @RequestMapping(method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + PageableResource list(@RequestParam Map allRequestParams); + + @RequestMapping(method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + PageableResource list(); + + @RequestMapping(value = "/count", method = GET, consumes = TEXT_PLAIN_VALUE) + Long count(@RequestParam Map allRequestParams); + + @RequestMapping(value = "/count", method = GET, consumes = TEXT_PLAIN_VALUE) + Long count(); + + @RequestMapping(value = "/lastModify", method = RequestMethod.GET, consumes = TEXT_PLAIN_VALUE) + Date getLastModify(); + + @RequestMapping(value = "/sync/id", method = RequestMethod.GET, consumes = TEXT_PLAIN_VALUE) + String getIdOfsync(@RequestParam(value = "sync", required = false) final String sync); +} + diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestControllerClientDeprecated.java b/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestControllerClientDeprecated.java new file mode 100644 index 00000000..f45e1f86 --- /dev/null +++ b/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestControllerClientDeprecated.java @@ -0,0 +1,88 @@ +package br.com.muttley.rest; + +import br.com.muttley.model.SyncObjectId; +import br.com.muttley.security.infra.resource.PageableResource; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; + +/** + * @author Joel Rodrigues Moreira on 11/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Deprecated +public interface ModelSyncRestControllerClientDeprecated { + + @RequestMapping(method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + T save(@RequestBody T value, @RequestParam(required = false, value = "returnEntity", defaultValue = "") String returnEntity); + + @RequestMapping(value = "/{id}", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + T update(@PathVariable("id") String id, @RequestBody T model); + + @RequestMapping(value = "/sync/{sync}", method = RequestMethod.PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + T updateBySync(@PathVariable("sync") String sync, @RequestBody T model); + + @RequestMapping(value = "/synchronization", method = RequestMethod.PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + void synchronization(@RequestBody final List values); + + @RequestMapping(value = "/{id}", method = DELETE) + void deleteById(@PathVariable("id") String id); + + @RequestMapping(value = "/sync/{sync}", method = RequestMethod.DELETE, consumes = APPLICATION_JSON_UTF8_VALUE) + void delteBySync(@PathVariable("sync") String sync); + + @RequestMapping(value = "/{id}", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + T findById(@PathVariable("id") String id); + + @RequestMapping(value = "/reference/{id}", method = GET, produces = APPLICATION_JSON_UTF8_VALUE) + T findReferenceById(@PathVariable("id") String id); + + @RequestMapping(value = "/sync/{sync}", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + T findBySync(@PathVariable("sync") final String sync); + + @RequestMapping(value = "/reference/sync/{sync}", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + T findReferenceBySync(@PathVariable("sync") final String sync); + + @RequestMapping(value = "/syncOrId/{syncOrId}", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + T findBySyncOrId(@PathVariable("syncOrId") final String syncOrId); + + @RequestMapping(value = "/syncs", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + public Set findBySyncs(@RequestParam(required = false, value = "syncs") String[] syncs); + + @RequestMapping(value = "/first", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + T first(); + + @RequestMapping(method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + PageableResource list(@RequestParam Map allRequestParams); + + @RequestMapping(method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + PageableResource list(); + + @RequestMapping(value = "/count", method = GET, consumes = TEXT_PLAIN_VALUE) + Long count(@RequestParam Map allRequestParams); + + @RequestMapping(value = "/count", method = GET, consumes = TEXT_PLAIN_VALUE) + Long count(); + + @RequestMapping(value = "/lastModify", method = RequestMethod.GET, consumes = TEXT_PLAIN_VALUE) + Date getLastModify(); + + @RequestMapping(value = "/sync/{sync}/id", method = RequestMethod.GET, consumes = TEXT_PLAIN_VALUE) + String getIdOfsync(@PathVariable("sync") final String sync); +} + diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestControllerDeprecated.java b/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestControllerDeprecated.java new file mode 100644 index 00000000..4cbc587e --- /dev/null +++ b/muttley-rest/src/main/java/br/com/muttley/rest/ModelSyncRestControllerDeprecated.java @@ -0,0 +1,62 @@ +package br.com.muttley.rest; + +import br.com.muttley.model.ModelSync; +import br.com.muttley.model.jackson.DefaultDateFormatConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +/** + * @author Joel Rodrigues Moreira on 06/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Deprecated +public interface ModelSyncRestControllerDeprecated extends RestResource, RestController { + @RequestMapping(value = "/sync/{sync}", method = RequestMethod.PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity updateBySync(@PathVariable("sync") String sync, @RequestBody T model); + + @RequestMapping(value = "/sync/{sync}", method = RequestMethod.DELETE, consumes = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}, produces = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity delteBySync(@PathVariable("sync") String sync); + + @RequestMapping(value = "/sync/{sync}", method = RequestMethod.GET, consumes = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}, produces = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findBySync(@PathVariable("sync") final String sync, final HttpServletResponse response); + + @RequestMapping(value = "/reference/sync/{sync}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + ResponseEntity findReferenceBySync(@PathVariable("sync") final String sync, final HttpServletResponse response); + + @RequestMapping(value = "/syncOrId/{syncOrId}", method = RequestMethod.GET, consumes = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}, produces = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findBySyncOrId(@PathVariable("syncOrId") final String syncOrId, final HttpServletResponse response); + + @RequestMapping(value = "/syncs", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findBySyncs(@RequestParam(required = false, value = "syncs") String[] syncs, HttpServletResponse response); + + @RequestMapping(value = "/synchronization", method = RequestMethod.PUT, produces = {APPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity synchronization(@RequestBody final List values); + + @RequestMapping(value = "/lastModify", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getLastModify(@Autowired final DefaultDateFormatConfig dateFormat); +} diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/RestController.java b/muttley-rest/src/main/java/br/com/muttley/rest/RestController.java index 5463f364..b23f6adf 100644 --- a/muttley-rest/src/main/java/br/com/muttley/rest/RestController.java +++ b/muttley-rest/src/main/java/br/com/muttley/rest/RestController.java @@ -2,7 +2,6 @@ import br.com.muttley.rest.hateoas.resource.PageableResource; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -14,8 +13,12 @@ import javax.servlet.http.HttpServletResponse; import java.util.Map; +import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; /** * @author Joel Rodrigues Moreira on 23/02/18. @@ -23,34 +26,38 @@ * @project muttley-cloud */ public interface RestController { - @RequestMapping(method = RequestMethod.POST, consumes = {APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) + @RequestMapping(method = RequestMethod.POST, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.CREATED) ResponseEntity save(@RequestBody T value, HttpServletResponse response, @RequestParam(required = false, value = "returnEntity", defaultValue = "") String returnEntity); - @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - @ResponseStatus(HttpStatus.OK) + @RequestMapping(value = "/{id}", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) ResponseEntity update(@PathVariable("id") String id, @RequestBody T model); - @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = {APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - @ResponseStatus(HttpStatus.OK) + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) ResponseEntity deleteById(@PathVariable("id") String id); - @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = {APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - @ResponseStatus(HttpStatus.OK) + @RequestMapping(value = "/{id}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) ResponseEntity findById(@PathVariable("id") String id, HttpServletResponse response); - @RequestMapping(value = "/first", method = RequestMethod.GET, consumes = {APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - @ResponseStatus(HttpStatus.OK) - ResponseEntity first(HttpServletResponse response); + @RequestMapping(value = "/reference/{id}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + ResponseEntity findReferenceById(@PathVariable("id") String id, HttpServletResponse response); + + @RequestMapping(value = "/ids", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + ResponseEntity findByIds(@RequestParam(required = false, value = "ids") String[] ids, HttpServletResponse response); - @RequestMapping(value = "/{id}/historic", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) - @ResponseStatus(HttpStatus.OK) - ResponseEntity loadHistoric(@PathVariable("id") String id, HttpServletResponse response); + @RequestMapping(value = "/first", method = GET, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + ResponseEntity first(HttpServletResponse response); - @RequestMapping(method = RequestMethod.GET) + @RequestMapping(method = GET) ResponseEntity list(HttpServletResponse response, @RequestParam Map allRequestParams); - @RequestMapping(value = "/count", method = RequestMethod.GET, consumes = TEXT_PLAIN_VALUE) - @ResponseStatus(HttpStatus.OK) - ResponseEntity count(Map allRequestParams); + @RequestMapping(value = "/count", method = GET, consumes = TEXT_PLAIN_VALUE) + @ResponseStatus(OK) + ResponseEntity count(Map allRequestParams); } diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/RestControllerClient.java b/muttley-rest/src/main/java/br/com/muttley/rest/RestControllerClient.java index caeb4bad..065fffe8 100644 --- a/muttley-rest/src/main/java/br/com/muttley/rest/RestControllerClient.java +++ b/muttley-rest/src/main/java/br/com/muttley/rest/RestControllerClient.java @@ -1,6 +1,5 @@ package br.com.muttley.rest; -import br.com.muttley.model.Historic; import br.com.muttley.rest.hateoas.resource.PageableResource; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -9,6 +8,7 @@ import java.io.Serializable; import java.util.Map; +import java.util.Set; import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; @@ -35,12 +35,15 @@ public interface RestControllerClient { @RequestMapping(value = "/{id}", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) T findById(@PathVariable("id") String id); + @RequestMapping(value = "/reference/{id}", method = GET, produces = APPLICATION_JSON_UTF8_VALUE) + T findReferenceById(@PathVariable("id") String id); + + @RequestMapping(value = "/ids", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + Set findByIds(@RequestParam(required = false, value = "ids") String[] ids); + @RequestMapping(value = "/first", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) T first(); - @RequestMapping(value = "/{id}/historic", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) - Historic loadHistoric(@PathVariable("id") String id); - @RequestMapping(method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) PageableResource list(@RequestParam Map allRequestParams); @@ -48,7 +51,7 @@ public interface RestControllerClient { PageableResource list(); @RequestMapping(value = "/count", method = GET, consumes = TEXT_PLAIN_VALUE) - Long count(@RequestParam Map allRequestParams); + Long count(@RequestParam Map allRequestParams); @RequestMapping(value = "/count", method = GET, consumes = TEXT_PLAIN_VALUE) Long count(); diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/RestResource.java b/muttley-rest/src/main/java/br/com/muttley/rest/RestResource.java index 63edbff4..781dd7b2 100644 --- a/muttley-rest/src/main/java/br/com/muttley/rest/RestResource.java +++ b/muttley-rest/src/main/java/br/com/muttley/rest/RestResource.java @@ -5,7 +5,7 @@ import br.com.muttley.exception.throwables.MuttleyPageableRequestException; import br.com.muttley.model.Document; import br.com.muttley.model.security.User; -import br.com.muttley.mongo.service.infra.Operators; +import br.com.muttley.mongo.service.infra.Operator; import br.com.muttley.rest.hateoas.event.PaginatedResultsRetrievedEvent; import br.com.muttley.rest.hateoas.event.ResourceCreatedEvent; import br.com.muttley.rest.hateoas.event.SingleResourceRetrievedEvent; @@ -17,7 +17,7 @@ import org.springframework.web.util.UriComponentsBuilder; import javax.servlet.http.HttpServletResponse; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -35,7 +35,7 @@ public interface RestResource { * @param response -> objeto response * @param model -> objeto criado */ - default void publishCreateResourceEvent(final ApplicationEventPublisher eventPublisher, final HttpServletResponse response, final T model) { + default void publishCreateResourceEvent(final ApplicationEventPublisher eventPublisher, final HttpServletResponse response, final Document model) { eventPublisher.publishEvent(this.newResourceCreatedEvent(model, response)); } @@ -45,7 +45,7 @@ default void publishCreateResourceEvent(final ApplicationEventPublisher eventPub * @param model -> objeto criado * @param response -> objeto response */ - default ApplicationEvent newResourceCreatedEvent(final T model, final HttpServletResponse response) { + default ApplicationEvent newResourceCreatedEvent(final Document model, final HttpServletResponse response) { return new ResourceCreatedEvent(model, response); } @@ -91,15 +91,16 @@ default void publishPaginatedResultsRetrievedEvent(final ApplicationEventPublish */ default PageableResource toPageableResource(final ApplicationEventPublisher eventPublisher, final HttpServletResponse response, final Service service, final User user, final Map params) { //validando os parametros passados - final Map allRequestParams = validPageable(params); - final Long SKIP = Long.valueOf(allRequestParams.get(Operators.SKIP.toString()).toString()); - final Long LIMIT = Long.valueOf(allRequestParams.get(Operators.LIMIT.toString()).toString()); + final Map allRequestParams = validPageable(params); + final Long SKIP = Long.valueOf(allRequestParams.get(Operator.SKIP.toString()).toString()); + final Long LIMIT = Long.valueOf(allRequestParams.get(Operator.LIMIT.toString()).toString()); final long total = service.count(user, createQueryParamForCount(allRequestParams)); if (total == 0) { throw new MuttleyNoContentException(null, null, "registros não encontrados!"); } + final long totalAll = service.count(user, null); final List records = service .findAll(user, allRequestParams); @@ -110,6 +111,32 @@ default PageableResource toPageableResource(final ApplicationEventPublisher even ServletUriComponentsBuilder.fromCurrentRequest(), LIMIT, SKIP, + recordSize, + total, + totalAll); + + publishPaginatedResultsRetrievedEvent( + eventPublisher, + response, + ServletUriComponentsBuilder.fromCurrentRequest(), + metadataPageable + ); + + + return new PageableResource(records, metadataPageable); + } + + default PageableResource toPageableResource(final ApplicationEventPublisher eventPublisher, final HttpServletResponse response, final List records, final Long total, final Long skip, final Long limit) { + if (total == 0) { + throw new MuttleyNoContentException(null, null, "registros não encontrados!"); + } + + final Long recordSize = Long.valueOf(records.size()); + + final MetadataPageable metadataPageable = new MetadataPageable( + ServletUriComponentsBuilder.fromCurrentRequest(), + limit, + skip, recordSize, total); publishPaginatedResultsRetrievedEvent( @@ -119,6 +146,30 @@ default PageableResource toPageableResource(final ApplicationEventPublisher even metadataPageable ); + return new PageableResource(records, metadataPageable); + } + + default PageableResource toPageableResource(final ApplicationEventPublisher eventPublisher, final HttpServletResponse response, final List records, final Long total, final Long totalAll, final Long skip, final Long limit) { + if (total == 0) { + throw new MuttleyNoContentException(null, null, "registros não encontrados!"); + } + + final Long recordSize = Long.valueOf(records.size()); + + final MetadataPageable metadataPageable = new MetadataPageable( + ServletUriComponentsBuilder.fromCurrentRequest(), + limit, + skip, + recordSize, + total, + totalAll); + + publishPaginatedResultsRetrievedEvent( + eventPublisher, + response, + ServletUriComponentsBuilder.fromCurrentRequest(), + metadataPageable + ); return new PageableResource(records, metadataPageable); } @@ -148,12 +199,12 @@ default PageableResource toPageableResource(final ApplicationEventPublisher even * * @param allRequestParams -> parametros da requisição */ - default Map createQueryParamForCount(final Map allRequestParams) { + default Map createQueryParamForCount(final Map allRequestParams) { return allRequestParams .entrySet() - .stream() + .parallelStream() .filter(key -> - !key.getKey().equals(Operators.LIMIT.toString()) && !key.getKey().equals(Operators.SKIP.toString()) + !key.getKey().equals(Operator.LIMIT.toString()) && !key.getKey().equals(Operator.SKIP.toString()) ) .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue())); } @@ -163,40 +214,44 @@ default Map createQueryParamForCount(final Map a * * @param allRequestParams -> parametro da requisição */ - default Map validPageable(final Map allRequestParams) { + default Map validPageable(final Map allRequestParams) { final MuttleyPageableRequestException ex = new MuttleyPageableRequestException(); - - if (allRequestParams.containsKey(Operators.LIMIT.toString())) { + Map map = allRequestParams == null ? new LinkedHashMap<>() : new LinkedHashMap<>(allRequestParams); + if (map.containsKey(Operator.LIMIT.toString())) { Integer limit = null; try { - limit = Integer.valueOf(allRequestParams.get(Operators.LIMIT.toString())); - if (limit > 100) { - ex.addDetails(Operators.LIMIT.toString(), "o limite informado foi (" + limit + ") mas o maxímo é(100)"); + limit = Integer.valueOf(map.get(Operator.LIMIT.toString())); + if (limit > this.getMaxrecords()) { + ex.addDetails(Operator.LIMIT.toString(), "o limite informado foi (" + limit + ") mas o maxímo é(" + this.getMaxrecords() + ")"); } } catch (NumberFormatException nex) { - ex.addDetails(Operators.LIMIT.toString(), "deve conter um numero com o tamanho maximo de 100"); + ex.addDetails(Operator.LIMIT.toString(), "deve conter um numero com o tamanho maximo de " + this.getMaxrecords()); } } else { - allRequestParams.put(Operators.LIMIT.toString(), "100"); + map.put(Operator.LIMIT.toString(), String.valueOf(this.getMaxrecords())); } - if (allRequestParams.containsKey(Operators.SKIP.toString())) { + if (map.containsKey(Operator.SKIP.toString())) { Integer page = null; try { - page = Integer.valueOf(allRequestParams.get(Operators.SKIP.toString())); + page = Integer.valueOf(map.get(Operator.SKIP.toString())); if (page < 0) { - ex.addDetails(Operators.SKIP.toString(), "a pagina informada foi (" + page + ") mas a deve ter o tamanho minimo de (0)"); + ex.addDetails(Operator.SKIP.toString(), "a pagina informada foi (" + page + ") mas a deve ter o tamanho minimo de (0)"); } } catch (final NumberFormatException nex) { - ex.addDetails(Operators.SKIP.toString(), "deve conter um numero com o tamanho minimo de 0"); + ex.addDetails(Operator.SKIP.toString(), "deve conter um numero com o tamanho minimo de 0"); } } else { - allRequestParams.put(Operators.SKIP.toString(), "0"); + map.put(Operator.SKIP.toString(), "0"); } if (ex.containsDetais()) { throw ex; } - return new HashMap<>(allRequestParams); + return map; + } + + default int getMaxrecords() { + return 100; } } diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/client/AbstracProxyRestControllerClient.java b/muttley-rest/src/main/java/br/com/muttley/rest/client/AbstracProxyRestControllerClient.java new file mode 100644 index 00000000..0edf7229 --- /dev/null +++ b/muttley-rest/src/main/java/br/com/muttley/rest/client/AbstracProxyRestControllerClient.java @@ -0,0 +1,146 @@ +package br.com.muttley.rest.client; + +import br.com.muttley.localcache.services.LocalModelService; +import br.com.muttley.model.SyncObjectId; +import br.com.muttley.rest.ModelSyncRestControllerClient; +import br.com.muttley.security.infra.resource.PageableResource; +import br.com.muttley.security.infra.service.AuthService; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 31/08/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class AbstracProxyRestControllerClient implements ProxyRestControllerClient { + protected final AuthService authService; + protected final Class clazz; + protected final ModelSyncRestControllerClient client; + protected final LocalModelService localModelService; + + public AbstracProxyRestControllerClient(final AuthService authService, final Class clazz, final ModelSyncRestControllerClient client, final LocalModelService localModelService) { + this.authService = authService; + this.clazz = clazz; + this.client = client; + this.localModelService = localModelService; + } + + @Override + public T save(final T value, final String returnEntity) { + return this.client.save(value, returnEntity); + } + + @Override + public T update(final String id, final T model) { + return this.client.update(id, model); + } + + @Override + public T updateBySync(final String sync, final T model) { + return this.client.updateBySync(sync, model); + } + + @Override + public void synchronization(final List values) { + this.client.synchronization(values); + } + + @Override + public void deleteById(final String id) { + this.client.deleteById(id); + } + + @Override + public void delteBySync(final String sync) { + this.client.delteBySync(sync); + } + + @Override + public T findById(final String id) { + final T value = (T) this.localModelService.loadModel(this.authService.getCurrentUser(), this.clazz, id); + if (value == null) { + return this.client.findById(id); + } + return value; + } + + @Override + public T findReferenceById(String id) { + final T value = (T) this.localModelService.loadReference(this.authService.getCurrentUser(), this.clazz, id); + if (value == null) { + return this.client.findReferenceById(id); + } + return value; + } + + @Override + public T findBySync(final String sync) { + final T value = (T) this.localModelService.loadModel(this.authService.getCurrentUser(), this.clazz, sync); + if (value == null) { + return this.client.findBySync(sync); + } + return value; + } + + @Override + public T findReferenceBySync(String sync) { + final T value = (T) this.localModelService.loadReference(this.authService.getCurrentUser(), this.clazz, sync); + if (value == null) { + return this.client.findReferenceBySync(sync); + } + return value; + } + + @Override + public T findBySyncOrId(final String syncOrId) { + final T value = (T) this.localModelService.loadModel(this.authService.getCurrentUser(), this.clazz, syncOrId); + if (value == null) { + return this.client.findBySyncOrId(syncOrId); + } + return value; + } + + @Override + public Set findBySyncs(final String[] syncs) { + return this.client.findBySyncs(syncs); + } + + @Override + public T first() { + return this.client.first(); + } + + @Override + public PageableResource list(final Map allRequestParams) { + return this.client.list(allRequestParams); + } + + @Override + public PageableResource list() { + return this.client.list(); + } + + @Override + public Long count(final Map allRequestParams) { + return this.client.count(allRequestParams); + } + + @Override + public Long count() { + return this.client.count(); + } + + @Override + public Date getLastModify() { + return this.client.getLastModify(); + } + + @Override + public String getIdOfsync(final String sync) { + return this.client.getIdOfsync(sync); + } +} diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/client/ProxyRestControllerClient.java b/muttley-rest/src/main/java/br/com/muttley/rest/client/ProxyRestControllerClient.java new file mode 100644 index 00000000..449d7e69 --- /dev/null +++ b/muttley-rest/src/main/java/br/com/muttley/rest/client/ProxyRestControllerClient.java @@ -0,0 +1,11 @@ +package br.com.muttley.rest.client; + +import br.com.muttley.rest.ModelSyncRestControllerClient; + +/** + * @author Joel Rodrigues Moreira on 02/09/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface ProxyRestControllerClient extends ModelSyncRestControllerClient { +} diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/feign/InternalSerializeInterceptor.java b/muttley-rest/src/main/java/br/com/muttley/rest/feign/InternalSerializeInterceptor.java new file mode 100644 index 00000000..23c2a1af --- /dev/null +++ b/muttley-rest/src/main/java/br/com/muttley/rest/feign/InternalSerializeInterceptor.java @@ -0,0 +1,18 @@ +package br.com.muttley.rest.feign; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +import static br.com.muttley.model.SerializeType.KEY_INTERNAL_FROM_HEADER; + +/** + * @author Joel Rodrigues Moreira on 11/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class InternalSerializeInterceptor implements RequestInterceptor { + @Override + public void apply(final RequestTemplate template) { + template.header(KEY_INTERNAL_FROM_HEADER, "true"); + } +} diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/hateoas/event/ModelSyncResourceCreatedEvent.java b/muttley-rest/src/main/java/br/com/muttley/rest/hateoas/event/ModelSyncResourceCreatedEvent.java new file mode 100644 index 00000000..c4958028 --- /dev/null +++ b/muttley-rest/src/main/java/br/com/muttley/rest/hateoas/event/ModelSyncResourceCreatedEvent.java @@ -0,0 +1,31 @@ +package br.com.muttley.rest.hateoas.event; + +import br.com.muttley.model.ModelSync; +import org.springframework.context.ApplicationEvent; + +import javax.servlet.http.HttpServletResponse; + +/** + * @author Joel Rodrigues Moreira on 06/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ModelSyncResourceCreatedEvent extends ApplicationEvent { + private final ModelSync model; + private final HttpServletResponse response; + + public ModelSyncResourceCreatedEvent(final ModelSync source, final HttpServletResponse response) { + super(source); + this.model = source; + this.response = response; + } + + @Override + public ModelSync getSource() { + return this.model; + } + + public HttpServletResponse getResponse() { + return response; + } +} diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/hateoas/resource/MetadataPageable.java b/muttley-rest/src/main/java/br/com/muttley/rest/hateoas/resource/MetadataPageable.java index fc9e9ef0..d63e3240 100644 --- a/muttley-rest/src/main/java/br/com/muttley/rest/hateoas/resource/MetadataPageable.java +++ b/muttley-rest/src/main/java/br/com/muttley/rest/hateoas/resource/MetadataPageable.java @@ -20,6 +20,7 @@ public class MetadataPageable { private Long totalPages; private Long totalRecords; private Long totalRecordsInPage; + private Long totalAll; private final List links; @JsonIgnore private Long limit; @@ -57,6 +58,11 @@ public MetadataPageable(final UriComponentsBuilder componentsBuilder, final Long addLastPage(components); } + public MetadataPageable(final UriComponentsBuilder componentsBuilder, final Long limit, final Long skip, final Long pageSize, final Long totalRecords, final Long totalAll) { + this(componentsBuilder, limit, skip, pageSize, totalRecords); + this.totalAll = totalAll; + } + public Long getPage() { return page; } @@ -73,6 +79,10 @@ public Long getTotalRecords() { return totalRecords; } + public Long getTotalAll() { + return totalAll; + } + public List getLinks() { return links; } @@ -98,7 +108,7 @@ public boolean containsNextPage() { @JsonIgnore public LinkResource getNextPage() { - return this.links.stream() + return this.links.parallelStream() .filter(l -> LinkUtil.REL_NEXT.equals(l.getRel())) .findFirst() .get(); @@ -125,7 +135,7 @@ public boolean containsPreviusPage() { @JsonIgnore public LinkResource getPreviusPage() { - return this.links.stream() + return this.links.parallelStream() .filter(l -> LinkUtil.REL_PREV.equals(l.getRel())) .findFirst() .get(); @@ -152,7 +162,7 @@ public boolean containsFirstPage() { @JsonIgnore public LinkResource getFirstPage() { - return this.links.stream() + return this.links.parallelStream() .filter(l -> LinkUtil.REL_FIRST.equals(l.getRel())) .findFirst() .get(); @@ -195,7 +205,7 @@ public boolean containsLastPage() { @JsonIgnore public LinkResource getLastPage() { - return this.links.stream() + return this.links.parallelStream() .filter(l -> LinkUtil.REL_LAST.equals(l.getRel())) .findFirst() .get(); diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/hateoas/resource/PageableResource.java b/muttley-rest/src/main/java/br/com/muttley/rest/hateoas/resource/PageableResource.java index 35d72c4c..a57e5165 100644 --- a/muttley-rest/src/main/java/br/com/muttley/rest/hateoas/resource/PageableResource.java +++ b/muttley-rest/src/main/java/br/com/muttley/rest/hateoas/resource/PageableResource.java @@ -1,6 +1,8 @@ package br.com.muttley.rest.hateoas.resource; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.util.CollectionUtils; import java.io.Serializable; @@ -10,17 +12,18 @@ * @author Joel Rodrigues Moreira on 30/01/18. * @project muttley-cloud */ -public final class PageableResource implements Serializable { +public final class PageableResource implements Serializable { - private final List records; + private final List records; private final MetadataPageable _metadata; - public PageableResource(final List records, final MetadataPageable _metadata) { + @JsonCreator + public PageableResource(@JsonProperty("records") final List records, @JsonProperty("_metadata") final MetadataPageable _metadata) { this.records = records; this._metadata = _metadata; } - public List getRecords() { + public List getRecords() { return records; } diff --git a/muttley-rest/src/main/java/br/com/muttley/rest/service/listenerEventsHateoas/ModelSyncResourceCreatedEventListener.java b/muttley-rest/src/main/java/br/com/muttley/rest/service/listenerEventsHateoas/ModelSyncResourceCreatedEventListener.java new file mode 100644 index 00000000..4ac08ab8 --- /dev/null +++ b/muttley-rest/src/main/java/br/com/muttley/rest/service/listenerEventsHateoas/ModelSyncResourceCreatedEventListener.java @@ -0,0 +1,44 @@ +package br.com.muttley.rest.service.listenerEventsHateoas; + +import br.com.muttley.headers.components.MuttleySerializeType; +import br.com.muttley.rest.hateoas.event.ModelSyncResourceCreatedEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.http.HttpHeaders; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +/** + * @author Joel Rodrigues Moreira on 06/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ModelSyncResourceCreatedEventListener implements ApplicationListener { + + @Autowired + private MuttleySerializeType serializerType; + + @Override + public void onApplicationEvent(ModelSyncResourceCreatedEvent event) { + if (event == null) { + throw new IllegalArgumentException("Event has null"); + } else if (serializerType.isObjectId()) { + event.getResponse() + .setHeader(HttpHeaders.LOCATION, ServletUriComponentsBuilder + .fromCurrentRequest() + .path("/{id}") + .buildAndExpand(event.getSource().getId()) + .toUri().toASCIIString()); + } else { + event.getResponse() + .setHeader( + HttpHeaders.LOCATION, + ServletUriComponentsBuilder + .fromCurrentRequest() + .path("/sync/{sync}") + .buildAndExpand(event.getSource().getSync()) + .toUri() + .toASCIIString() + ); + } + } +} diff --git a/muttley-security-server/.gitignore b/muttley-security-server/.gitignore index 2af7cefb..869abcf0 100644 --- a/muttley-security-server/.gitignore +++ b/muttley-security-server/.gitignore @@ -21,4 +21,6 @@ build/ nbbuild/ dist/ nbdist/ -.nb-gradle/ \ No newline at end of file +.nb-gradle/ + +.env diff --git a/muttley-security-server/pom.xml b/muttley-security-server/pom.xml index 1c325da7..16ed30d6 100644 --- a/muttley-security-server/pom.xml +++ b/muttley-security-server/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar @@ -16,16 +16,34 @@ + + org.projectlombok + lombok + true + + + br.com.muttley muttley-jackson + + org.springframework.boot + spring-boot-starter-mail + + + br.com.muttley muttley-mongo + + br.com.muttley + muttley-local-cache + + br.com.muttley muttley-model diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/MuttleySecurityServerApplication.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/MuttleySecurityServerApplication.java index 16f29ce6..c924ccca 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/MuttleySecurityServerApplication.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/MuttleySecurityServerApplication.java @@ -16,6 +16,7 @@ "br.com.muttley.security.server", //Configurações de segurança para o gateway //"br.com.muttley.security.zuul.gateway.service", + "br.com.muttley.security.server.repository", //Configurações de exceptions "br.com.muttley.exception.service", //Configurações de serialização diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/components/RSAPairKeyComponent.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/components/RSAPairKeyComponent.java new file mode 100644 index 00000000..82eac493 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/components/RSAPairKeyComponent.java @@ -0,0 +1,81 @@ +package br.com.muttley.security.server.components; + +import br.com.muttley.localcache.services.LocalRSAKeyPairService; +import br.com.muttley.localcache.services.impl.AbstractLocalRSAKeyPairService; +import br.com.muttley.redis.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.DependsOn; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; + +import static br.com.muttley.model.security.rsa.RSAUtil.*; + +/** + * @author Joel Rodrigues Moreira on 10/08/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@EnableConfigurationProperties(RSAPairKeyProperty.class) +@Component +@DependsOn("clearRedis") +public class RSAPairKeyComponent extends AbstractLocalRSAKeyPairService implements LocalRSAKeyPairService, ApplicationListener { + private final RSAPairKeyProperty properties; + + + @Autowired + public RSAPairKeyComponent(final RedisService service, final RSAPairKeyProperty properties) { + super(service); + this.properties = properties; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + final File privateKeyFile = new File(properties.getPrivateKeyFile()); + final File publicKeyFile = new File(properties.getPublicKeyFile()); + //verificando se precisa criar a chave + if (properties.isAutoCriateIfNotExists() && !privateKeyFile.exists()) { + final KeyPair keyPair = createKeyPair(4096, StringUtils.isEmpty(this.properties.getSeed()) ? generateRandomString(15000) : this.properties.getSeed()); + write(privateKeyFile, keyPair.getPrivate()); + write(publicKeyFile, keyPair.getPublic()); + } + //forçando o carregamento do par de chaves + this.getPrivateKey(); + this.getPublicKey(); + } + + @Override + public String encryptMessage(String message) { + return encrypt(getPrivateKey(), message); + } + + @Override + public String decryptMessage(String encryptedMessage) { + return decrypt(getPublicKey(), encryptedMessage); + } + + @Override + protected PrivateKey getPrivateKey() { + if (this.privateKey == null) { + this.privateKey = readPrivateKeyFromFile(this.properties.getPrivateKeyFile()); + this.setPrivateKey(privateKey); + } + return privateKey; + } + + @Override + protected PublicKey getPublicKey() { + if (this.publicKey == null) { + this.publicKey = readPublicKeyFromFile(this.properties.getPublicKeyFile()); + this.setPublicKey(this.publicKey); + } + return publicKey; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/components/RSAPairKeyProperty.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/components/RSAPairKeyProperty.java new file mode 100644 index 00000000..3e7f0d05 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/components/RSAPairKeyProperty.java @@ -0,0 +1,22 @@ +package br.com.muttley.security.server.components; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import static br.com.muttley.security.server.components.RSAPairKeyProperty.PREFIX; + +@ConfigurationProperties(prefix = PREFIX) +@Getter +@Setter +@Accessors(chain = true) +public class RSAPairKeyProperty { + protected static final String PREFIX = "muttley.rsa"; + + private String privateKeyFile = "rsa/id_rsa_sfa"; + private String publicKeyFile = "rsa/id_rsa_sfa.pub"; + private String seed; + private boolean autoCriateIfNotExists = true; + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/http/Httpformatters.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/http/Httpformatters.java new file mode 100644 index 00000000..c6b6a635 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/http/Httpformatters.java @@ -0,0 +1,46 @@ +package br.com.muttley.security.server.config.http; + +import br.com.muttley.model.security.KeyUserDataBinding; +import br.com.muttley.model.security.events.KeyUserDataBindingResolverEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.Formatter; + +import java.text.ParseException; +import java.util.Locale; + +/** + * @author Joel Rodrigues Moreira 12/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Configuration +public class Httpformatters { + + private final ApplicationEventPublisher publisher; + + @Autowired + public Httpformatters(final ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + @Bean + public Formatter localDateFormatter() { + return new Formatter() { + + @Override + public String print(final KeyUserDataBinding key, final Locale locale) { + return key.getKey(); + } + + @Override + public KeyUserDataBinding parse(final String text, final Locale locale) throws ParseException { + final KeyUserDataBindingResolverEvent event = new KeyUserDataBindingResolverEvent(text); + publisher.publishEvent(event); + return event.getResolvedValue(); + } + }; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/mail/MailConfig.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/mail/MailConfig.java new file mode 100644 index 00000000..808504a4 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/mail/MailConfig.java @@ -0,0 +1,35 @@ +package br.com.muttley.security.server.config.mail; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +public class MailConfig { + + @Value("${mail.password}") + private String emailPassword; + + + @Bean + public JavaMailSender javaMailSender() { + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost("smtp.maxxsoft.com.br"); + mailSender.setPort(587); + + mailSender.setUsername("agrifocus@maxxsoft.com.br"); + mailSender.setPassword(emailPassword); + + Properties props = mailSender.getJavaMailProperties(); + props.put("mail.transport.protocol", "smtp"); + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.starttls.enable", "true"); + props.put("mail.debug", "true"); + + return mailSender; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/metadata/ConfigRequestMetaDataAgrifocus.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/metadata/ConfigRequestMetaDataAgrifocus.java new file mode 100644 index 00000000..33624d21 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/metadata/ConfigRequestMetaDataAgrifocus.java @@ -0,0 +1,55 @@ +package br.com.muttley.security.server.config.metadata; + +import br.com.muttley.headers.components.MuttleyCurrentTimezone; +import br.com.muttley.headers.components.MuttleyCurrentVersion; +import br.com.muttley.headers.components.MuttleyUserAgent; +import br.com.muttley.headers.components.MuttleyUserAgentName; +import br.com.muttley.headers.components.impl.MuttleyCurrentTimezoneImpl; +import br.com.muttley.headers.components.impl.MuttleyCurrentVersionImpl; +import br.com.muttley.headers.components.impl.MuttleyUserAgentImpl; +import br.com.muttley.headers.components.impl.MuttleyUserAgentNameImpl; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.info.BuildProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +import javax.servlet.http.HttpServletRequest; + +import static org.springframework.context.annotation.ScopedProxyMode.TARGET_CLASS; +import static org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST; + +/** + * @author Joel Rodrigues Moreira on 08/06/18. + * e-mail: joel.databox@gmail.com + * @project agrifocus-cloud + */ +@Configuration +public class ConfigRequestMetaDataAgrifocus { + + @Bean(name = "userAgent") + @Scope(value = SCOPE_REQUEST, proxyMode = TARGET_CLASS) + public MuttleyUserAgent getUserAgent(@Autowired final ObjectProvider requestProvider) { + return new MuttleyUserAgentImpl(requestProvider); + } + + @Bean(name = "currentTimezone") + @Scope(value = SCOPE_REQUEST, proxyMode = TARGET_CLASS) + public MuttleyCurrentTimezone getCurrentTimezone(@Autowired final ObjectProvider requestProvider) { + return new MuttleyCurrentTimezoneImpl(requestProvider); + } + + @Bean(name = "currentVersion") + @Scope(value = SCOPE_REQUEST, proxyMode = TARGET_CLASS) + @Autowired + public MuttleyCurrentVersion getCurrentVersion(final ObjectProvider requestProvider, final BuildProperties buildProperties) { + return new MuttleyCurrentVersionImpl(requestProvider, buildProperties); + } + + @Bean(name = "userAgentName") + @Scope(value = SCOPE_REQUEST, proxyMode = TARGET_CLASS) + public MuttleyUserAgentName getUserAgentName(@Autowired final ObjectProvider requestProvider) { + return new MuttleyUserAgentNameImpl(requestProvider); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/model/DocumentNameConfig.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/model/DocumentNameConfig.java index a10f84a6..1361586e 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/model/DocumentNameConfig.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/model/DocumentNameConfig.java @@ -1,6 +1,7 @@ package br.com.muttley.security.server.config.model; +import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @@ -10,44 +11,67 @@ * @project muttley-cloud */ @Configuration(value = "documentNameConfig") +@Getter public class DocumentNameConfig { + private final String nameCollectionAdminOwner; private final String nameCollectionOwner; private final String nameCollectionUser; + private final String nameCollectionPassword; + private final String nameCollectionAdminUserBase; + private final String nameCollectionUserBase; private final String nameCollectionAccessPlan; private final String nameCollectionUserPreferences; + private final String nameCollectionUserTokensNotification; + private final String nameCollectionAdminPassaport; + private final String nameCollectionPassaport; private final String nameCollectionWorkTeam; + private final String nameCollectionUserDataBinding; + private final String nameViewCollectionUser; + private final String nameViewCollectionPassaport; + + private final String nameCollectionXAPIToken; + private final String nameViewCollectionPassaportRolesUser; + + private final String nameViewCollectionWorkTeam; public DocumentNameConfig( + @Value("${br.com.muttley.security.server.owner-document:muttley-admin-owners}") final String nameCollectionAdminOwner, @Value("${br.com.muttley.security.server.owner-document:muttley-owners}") final String nameCollectionOwner, @Value("${br.com.muttley.security.server.user-document:muttley-users}") final String nameCollectionUser, + @Value("${br.com.muttley.security.server.user-password-document:muttley-users-password}") final String nameCollectionPassword, + @Value("${br.com.muttley.security.server.user-base-document:muttley-admin-users-base}") final String nameCollectionAdminUserBase, + @Value("${br.com.muttley.security.server.user-base-document:muttley-users-base}") final String nameCollectionUserBase, @Value("${br.com.muttley.security.server.access-plan-document:muttley-access-plans}") final String nameCollectionAccessPlan, @Value("${br.com.muttley.security.server.user-preference-document:muttley-users-preferences}") final String nameCollectionUserPreferences, - @Value("${br.com.muttley.security.server.work-team-document:muttley-work-teams}") final String nameCollectionWorkTeam) { + @Value("${br.com.muttley.security.server.user-tokens-notification-document:muttley-users-tokens-notification}") final String nameCollectionUserTokensNotification, + @Value("${br.com.muttley.security.server.admin-passaport-document:muttley-admin-passaports}") final String nameCollectionAdminPassaport, + @Value("${br.com.muttley.security.server.passaport-document:muttley-passaports}") final String nameCollectionPassaport, + @Value("${br.com.muttley.security.server.work-team-document:muttley-work-teams}") final String nameCollectionWorkTeam, + @Value("${br.com.muttley.security.server.user-data-binding:muttley-users-databinding}") final String nameCollectionUserDataBinding, + @Value("${br.com.muttley.security.server.user-document-view:view-muttley-users}") final String nameViewCollectionUser, + @Value("${br.com.muttley.security.server.passaport-document-view:view-muttley-passaports}") final String nameViewCollectionPassaport, + @Value("${br.com.muttley.security.server.x-api-token-document:muttley-x-api-token}") final String nameCollectionXAPIToken, + @Value("${br.com.muttley.security.server.passaport-role-document-view:view-muttley-passaports-roles-user}") final String nameViewCollectionPassaportRolesUser, + @Value("${br.com.muttley.security.server.work-team-document-view:view-muttley-work-teams}") final String nameViewCollectionWorkTeam + ) { + this.nameCollectionAdminOwner = nameCollectionAdminOwner; this.nameCollectionOwner = nameCollectionOwner; this.nameCollectionUser = nameCollectionUser; + this.nameCollectionPassword = nameCollectionPassword; + this.nameCollectionAdminUserBase = nameCollectionAdminUserBase; + this.nameCollectionUserBase = nameCollectionUserBase; this.nameCollectionAccessPlan = nameCollectionAccessPlan; this.nameCollectionUserPreferences = nameCollectionUserPreferences; + this.nameCollectionUserTokensNotification = nameCollectionUserTokensNotification; + this.nameCollectionAdminPassaport = nameCollectionAdminPassaport; + this.nameCollectionPassaport = nameCollectionPassaport; this.nameCollectionWorkTeam = nameCollectionWorkTeam; - } - - public String getNameCollectionOwner() { - return nameCollectionOwner; - } - - public String getNameCollectionUser() { - return nameCollectionUser; - } - - public String getNameCollectionAccessPlan() { - return nameCollectionAccessPlan; - } - - public String getNameCollectionUserPreferences() { - return nameCollectionUserPreferences; - } - - public String getNameCollectionWorkTeam() { - return nameCollectionWorkTeam; + this.nameCollectionUserDataBinding = nameCollectionUserDataBinding; + this.nameViewCollectionUser = nameViewCollectionUser; + this.nameViewCollectionPassaport = nameViewCollectionPassaport; + this.nameCollectionXAPIToken = nameCollectionXAPIToken; + this.nameViewCollectionPassaportRolesUser = nameViewCollectionPassaportRolesUser; + this.nameViewCollectionWorkTeam = nameViewCollectionWorkTeam; } } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/mongo/MongoConfig.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/mongo/MongoConfig.java index 260b118e..05840f69 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/mongo/MongoConfig.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/mongo/MongoConfig.java @@ -1,8 +1,12 @@ package br.com.muttley.security.server.config.mongo; +import br.com.muttley.model.security.converters.KeyUserDataBindingToStringConverter; +import br.com.muttley.model.security.converters.StringToKeyUserDataBindingConverter; import br.com.muttley.mongo.service.repository.impl.DocumentMongoRepositoryImpl; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; /** @@ -15,12 +19,23 @@ @Configuration @EnableMongoRepositories(basePackages = {"br.com.muttley.security.server.repository"}, repositoryBaseClass = DocumentMongoRepositoryImpl.class) public class MongoConfig extends br.com.muttley.mongo.service.MongoConfig { + private final ApplicationEventPublisher publisher; public MongoConfig(@Value("${spring.data.mongodb.database}") final String dataBaseName, @Value("${spring.data.mongodb.host}") final String hostDataBase, @Value("${spring.data.mongodb.port}") final String portDataBase, @Value("${spring.data.mongodb.username}") final String userName, - @Value("${spring.data.mongodb.password}") final String password) { + @Value("${spring.data.mongodb.password}") final String password, + final ApplicationEventPublisher publisher) { super(dataBaseName, hostDataBase, portDataBase, userName, password); + this.publisher = publisher; + } + + @Override + protected Converter[] getConverters() { + return new Converter[]{ + new StringToKeyUserDataBindingConverter(publisher), + new KeyUserDataBindingToStringConverter() + }; } } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/postinit/ClearRedis.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/postinit/ClearRedis.java new file mode 100644 index 00000000..bc3398c0 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/postinit/ClearRedis.java @@ -0,0 +1,35 @@ +package br.com.muttley.security.server.config.postinit; + +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.server.service.SecretService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira 24/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class ClearRedis implements ApplicationListener { + final RedisService service; + final SecretService secretService; + + @Autowired + public ClearRedis(RedisService service, final SecretService secretService) { + this.service = service; + this.secretService = secretService; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + this.service.clearAll(); + //forçando gerar os secretes + this.secretService.getHS512SecretBytes(); + } +} + diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/ViewConfig.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/ViewConfig.java new file mode 100644 index 00000000..75bced5b --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/ViewConfig.java @@ -0,0 +1,85 @@ +package br.com.muttley.security.server.config.view; + +import br.com.muttley.model.View; +import br.com.muttley.mongo.service.config.source.ViewSource; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import br.com.muttley.security.server.config.view.source.ViewMuttleyUsers; +import br.com.muttley.security.server.config.view.source.ViewMuttleyWorkTeams; +import com.mongodb.MongoClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Component; + +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * @author Joel Rodrigues Moreira on 29/04/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class ViewConfig implements ApplicationListener { + @Value("${spring.data.mongodb.database}") + private String dbName; + private final MongoTemplate template; + private final MongoClient client; + private final DocumentNameConfig documentNameConfig; + + @Autowired + public ViewConfig(final MongoTemplate template, final MongoClient client, final DocumentNameConfig documentNameConfig) { + this.template = template; + this.client = client; + this.documentNameConfig = documentNameConfig; + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + try { + createView(new ViewMuttleyWorkTeams(this.documentNameConfig)); + createView(new ViewMuttleyUsers(this.documentNameConfig)); + //createView(new ViewMuttleyWorkTeamRolesUser(this.documentNameConfig)); + } catch (final Exception ex) { + ex.printStackTrace(); + throw ex; + } + } + + private void createView(final ViewSource source) { + final Query query = new Query(); + query.addCriteria(where("name").is(source.getViewName())); + //verificando se a view existe + final View view = this.template.findOne(new Query(where("name").is(source.getViewName())), View.class); + + if (view == null) { + //a view não existe, logo devemos criar a mesma + this.client + .getDatabase(this.dbName) + .createView(source.getViewName(), source.getViewOn(), source.getPipeline()); + //salvando informações da view criada + this.template.save(new View(source.getViewName(), source.getVersion(), source.getDescription())); + } else { + //se a view já existe devemos verificar a versão da mesma + //se a versão for diferente devemos dropar essa view + if (!view.getVersion().equals(source.getVersion())) { + this.client + .getDatabase(this.dbName) + .getCollection(source.getViewName()) + .drop(); + + //adicionando novamente a view + this.client + .getDatabase(this.dbName) + .createView(source.getViewName(), source.getViewOn(), source.getPipeline()); + + //atualizando info da view + //this.template.save(view.updateInfo(source)); + this.template.save(view.setDescription(source.getDescription()) + .setVersion(source.getVersion())); + } + } + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyPassaport.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyPassaport.java new file mode 100644 index 00000000..9ef07858 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyPassaport.java @@ -0,0 +1,182 @@ +package br.com.muttley.security.server.config.view.source; + +import br.com.muttley.mongo.service.config.source.ViewSource; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonElement; +import org.bson.BsonString; +import org.bson.conversions.Bson; + +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * @author Joel Rodrigues Moreira on 26/04/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ViewMuttleyPassaport implements ViewSource { + private final String VERSION = "2.0.10"; + private final String NAME;//= "view_muttley_work_teams"; + private final String SOURCE;//= "muttley-work-teams"; + private final String DESCRIPTION = "A view foi criada para facilitar a listagem de usuário por owners"; + + public ViewMuttleyPassaport(final DocumentNameConfig documentNameConfig) { + this.NAME = documentNameConfig.getNameViewCollectionPassaport(); + this.SOURCE = documentNameConfig.getNameCollectionPassaport(); + } + + @Override + public String getVersion() { + return VERSION; + } + + @Override + public String getViewName() { + return NAME; + } + + @Override + public String getViewOn() { + return SOURCE; + } + + @Override + public List getPipeline() { + /** + db.getCollection("muttley-work-teams").aggregate([ + //pegando os dados necessários + {$project:{ _id:1, _class:1, owner:1, userMaster:1, members:1}}, + //transformando o array + {$unwind:'$members'}, + //transformando o array + {$project:{ _id:1, _class:1, owner:1, records : {$objectToArray:"$$ROOT"}}}, + {$unwind:'$records'}, + //filtrando apenas os registros necessários + {$match: {$or:[{'records.k':'members'},{'records.k':'userMaster'}]}}, + //pegando apenas os registros necessários + {$project:{_id:1, _class:1, owner: 1, user: '$records.v'}}, + //agrupando registros + {$group: { + _id: { + _id: "$_id", + _class: '$_class', + owner: "$owner", + user: "$user", + } + }}, + //transormando a visualização dos mesmos + {$project:{_id:'$_id._id', _class:'$_id._class', owner:'$_id.owner', user:'$_id.user', userId: {$objectToArray:"$_id.user"}}}, + //transformando o array + {$unwind:"$userId"}, + //filtrando apenas o necessário + {$match:{"userId.k":"$id"}}, + //transormando a visualização dos mesmos + {$project:{_id:1, _class:1, owner:1, user:1, userId: '$userId.v'}} + ]) + */ + return asList( + //pegando os dados necessários + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("_id", _TRUE), + new BsonElement("_class", _TRUE), + new BsonElement("owner", _TRUE), + new BsonElement("userMaster", _TRUE), + new BsonElement("members", _TRUE) + ) + ) + ), + //transformando o array + new BsonDocument("$unwind", new BsonString("$members")), + //transformando o array + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("_id", _TRUE), + new BsonElement("_class", _TRUE), + new BsonElement("owner", _TRUE), + new BsonElement("userMaster", _TRUE), + new BsonElement("members", _TRUE), + new BsonElement("records", + new BsonDocument("$objectToArray", new BsonString("$$ROOT")) + ) + ) + ) + ), + new BsonDocument("$unwind", new BsonString("$records")), + //filtrando apenas os registros necessários + new BsonDocument("$match", + new BsonDocument("$or", + new BsonArray( + asList( + new BsonDocument("records.k", new BsonString("members")), + new BsonDocument("records.k", new BsonString("userMaster")) + ) + ) + ) + ), + //pegando apenas os registros necessários + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("_id", _TRUE), + new BsonElement("_class", _TRUE), + new BsonElement("owner", _TRUE), + new BsonElement("user", new BsonString("$records.v")) + ) + ) + ), + //agrupando registros + new BsonDocument("$group", + new BsonDocument("_id", + new BsonDocument( + asList( + new BsonElement("_id", new BsonString("$_id")), + new BsonElement("_class", new BsonString("$_class")), + new BsonElement("owner", new BsonString("$owner")), + new BsonElement("user", new BsonString("$user")) + ) + ) + ) + ), + //transormando a visualização dos mesmos + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("_id", new BsonString("$_id._id")), + new BsonElement("_class", new BsonString("$_id._class")), + new BsonElement("owner", new BsonString("$_id.owner")), + new BsonElement("user", new BsonString("$_id.user")), + new BsonElement("userId", new BsonDocument("$objectToArray", new BsonString("$_id.user"))) + + ) + ) + ), + //transformando o array + new BsonDocument("$unwind", new BsonString("$userId")), + //filtrando apenas o necessário + new BsonDocument("$match", new BsonDocument("userId.k", new BsonString("$id"))), + //transormando a visualização dos mesmos + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("_id", _TRUE), + new BsonElement("_class", _TRUE), + new BsonElement("owner", _TRUE), + new BsonElement("userId", new BsonString("$userId.v")), + new BsonElement("user", _TRUE) + ) + ) + ) + ); + } + + @Override + public String getDescription() { + return DESCRIPTION; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyPassaportRolesUser.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyPassaportRolesUser.java new file mode 100644 index 00000000..99eacaee --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyPassaportRolesUser.java @@ -0,0 +1,149 @@ +package br.com.muttley.security.server.config.view.source; + +import br.com.muttley.mongo.service.config.source.ViewSource; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonElement; +import org.bson.BsonInt32; +import org.bson.BsonString; +import org.bson.conversions.Bson; + +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * @author Joel Rodrigues Moreira on 22/05/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ViewMuttleyPassaportRolesUser implements ViewSource { + private final String VERSION = "1.0.0"; + private final String NAME = "view_muttley_passaports_roles_user"; + private final String SOURCE = "muttley-passaports"; + private final String DESCRIPTION = "A view foi criada para facilitar a listagem de usuário por owners juntamente com sua respectivas autorizações"; + + @Override + public String getVersion() { + return this.VERSION; + } + + @Override + public String getViewName() { + return this.NAME; + } + + @Override + public String getViewOn() { + return this.SOURCE; + } + + @Override + public List getPipeline() { + /** + db.getCollection("muttley-work-teams").aggregate([ + //pegando os dados necessários + {$project:{ _id:1, owner:1, userMaster:1, members:1, roles:1}}, + {$unwind:'$members'}, + {$unwind:'$roles'}, + {$project:{ _id:1, owner:1, users:["$userMaster", "$members"], roles:1}}, + {$unwind:'$users'}, + {$group:{ + _id:{owner:"$owner", user:"$users" }, + roles:{$addToSet:"$roles"} + }}, + {$project:{ _id: {$arrayElemAt:[{$objectToArray:"$_id.user"},1]}, owner:"$_id.owner", user:"$_id.user", roles:1}}, + {$project:{_id:"$_id.v", userId:"$_id.v", owner:1, user:1, roles:1 }}, + ]) + */ + + return asList( + //pegando os dados necessários + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("_id", _TRUE), + new BsonElement("owner", _TRUE), + new BsonElement("userMaster", _TRUE), + new BsonElement("members", _TRUE), + new BsonElement("roles", _TRUE) + ) + ) + ), + //transformando o array + new BsonDocument("$unwind", new BsonString("$members")), + new BsonDocument("$unwind", new BsonString("$roles")), + //transformando o array + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("_id", _TRUE), + new BsonElement("owner", _TRUE), + new BsonElement("users", + new BsonArray( + asList( + new BsonString("$userMaster"), + new BsonString("$members") + ) + ) + ), + new BsonElement("roles", _TRUE) + ) + ) + ), + new BsonDocument("$unwind", new BsonString("$users")), + //agrupando registros + new BsonDocument("$group", + new BsonDocument( + asList( + new BsonElement("_id", + new BsonDocument( + asList( + new BsonElement("owner", new BsonString("$owner")), + new BsonElement("user", new BsonString("$users")) + ) + ) + ), + new BsonElement("roles", new BsonDocument("$addToSet", new BsonString("$roles"))) + ) + ) + ), + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("_id", + new BsonDocument("$arrayElemAt", + new BsonArray( + asList( + new BsonDocument("$objectToArray", new BsonString("$_id.user")), + new BsonInt32(1) + ) + ) + ) + ), + new BsonElement("owner", new BsonString("$_id.owner")), + new BsonElement("user", new BsonString("$_id.user")), + new BsonElement("roles", _TRUE) + + ) + ) + ), + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("_id", new BsonString("$_id.v")), + new BsonElement("userId", new BsonString("$_id.v")), + new BsonElement("owner", _TRUE), + new BsonElement("user", _TRUE), + new BsonElement("roles", _TRUE) + ) + ) + ) + ); + } + + @Override + public String getDescription() { + return this.DESCRIPTION; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyUsers.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyUsers.java new file mode 100644 index 00000000..63ebd57c --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyUsers.java @@ -0,0 +1,139 @@ +package br.com.muttley.security.server.config.view.source; + +import br.com.muttley.mongo.service.config.source.ViewSource; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import org.bson.BsonArray; +import org.bson.BsonBoolean; +import org.bson.BsonDocument; +import org.bson.BsonElement; +import org.bson.BsonInt32; +import org.bson.BsonString; +import org.bson.conversions.Bson; + +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * @author Joel Rodrigues Moreira on 29/04/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ViewMuttleyUsers implements ViewSource { + private final String VERSION = "1.0.8"; + private final String NAME;//= "view_muttley_users"; + private final String SOURCE;//= "muttley-users-base"; + private final String DESCRIPTION = "A view foi criada para facilitar a listagem de usuários e seus owners já linkados"; + + public ViewMuttleyUsers(final DocumentNameConfig documentNameConfig) { + this.NAME = documentNameConfig.getNameViewCollectionUser(); + this.SOURCE = documentNameConfig.getNameCollectionUserBase(); + } + + @Override + public String getVersion() { + return VERSION; + } + + @Override + public String getViewName() { + return NAME; + } + + @Override + public String getViewOn() { + return SOURCE; + } + + + @Override + public List getPipeline() { + /** + db.getCollection("muttley-users-base").aggregate([ + {$unwind:"$users"}, + //preparando para lookup + {$project:{owner:1, user:{$objectToArray:"$users.user"}, status:"$users.status"}}, + {$project:{owner:1, user:{$arrayElemAt:["$user.v",1]}, status:1}}, + //lookup comusers + {$lookup:{ + from:"muttley-users", + localField:"user", + foreignField:"_id", + as:"user" + }}, + {$unwind:"$user"}, + //removendo o usuário do odin + {$match:{"user.odinUser":false}}, + //exibindo apenas os dados necessários + {$project:{_id:"$user._id", _class:"$user._class", name:"$user.name", userName:"$user.userName", email:"$user.email", nickUsers:"$user.nickUsers", owner:1, description:"$user.description", status:1}}, + ]) + */ + return asList( + new BsonDocument("$unwind", new BsonString("$users")), + //preparando para lookup + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("owner", _TRUE), + new BsonElement("user", new BsonDocument("$objectToArray", new BsonString("$users.user"))), + new BsonElement("status", new BsonString("$users.status")) + ) + ) + ), + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("owner", _TRUE), + new BsonElement("user", new BsonDocument("$arrayElemAt", new BsonArray(asList(new BsonString("$user.v"), new BsonInt32(1))))), + new BsonElement("status", _TRUE) + ) + ) + ), + //lookup comusers + new BsonDocument("$lookup", + new BsonDocument( + asList( + new BsonElement("from", new BsonString("muttley-users")), + new BsonElement("localField", new BsonString("user")), + new BsonElement("foreignField", new BsonString("_id")), + new BsonElement("as", new BsonString("user")) + ) + ) + ), + new BsonDocument("$unwind", new BsonString("$user")), + //removendo o usuário do odin + new BsonDocument("$match", new BsonDocument("user.odinUser", new BsonBoolean(false))), + //exibindo apenas os dados necessários + new BsonDocument("$project", + new BsonDocument( + asList( + new BsonElement("_id", new BsonString("$user._id")), + new BsonElement("_class", new BsonString("$user._class")), + new BsonElement("name", new BsonString("$user.name")), + new BsonElement("userName", new BsonString("$user.userName")), + new BsonElement("email", new BsonString("$user.email")), + new BsonElement("nickUsers", new BsonString("$user.nickUsers")), + new BsonElement("owner", _TRUE), + new BsonElement("description", new BsonString("$user.description")), + new BsonElement("fone", new BsonString("$user.fone")), + new BsonElement("status", _TRUE), + new BsonElement("_view_information", + new BsonDocument( + asList( + new BsonElement("version", new BsonString(VERSION)), + new BsonElement("name", new BsonString(NAME)), + new BsonElement("source", new BsonString(SOURCE)) + ) + ) + ) + ) + ) + ) + ); + + } + + public String getDescription() { + return DESCRIPTION; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyWorkTeams.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyWorkTeams.java new file mode 100644 index 00000000..e84d1dd5 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/config/view/source/ViewMuttleyWorkTeams.java @@ -0,0 +1,60 @@ +package br.com.muttley.security.server.config.view.source; + +import br.com.muttley.mongo.service.config.source.ViewSource; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.conversions.Bson; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 25/07/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ViewMuttleyWorkTeams implements ViewSource { + private final String VERSION = "1.0.0"; + private final String NAME; + private final String SOURCE; + private final String DESCRIPTION = "A view foi criada para facilitar o processo de listagem de workteams por userMaster. O operador $graphLookup não aceita trabalhar com array, logo, se faz necessário dar um $unwind para facilitar o processo"; + + public ViewMuttleyWorkTeams(final DocumentNameConfig documentNameConfig) { + this.NAME = documentNameConfig.getNameViewCollectionWorkTeam(); + this.SOURCE = documentNameConfig.getNameCollectionWorkTeam(); + } + + + @Override + public String getVersion() { + return VERSION; + } + + @Override + public String getViewName() { + return NAME; + } + + @Override + public String getViewOn() { + return SOURCE; + } + + @Override + public List getPipeline() { + /** + * db.getCollection("muttley-work-teams").aggregate([ + * {$unwind:"$usersMaster"} + * ]) + */ + return Arrays.asList( + new BsonDocument("$unwind", new BsonString("$usersMaster")) + ); + } + + @Override + public String getDescription() { + return DESCRIPTION; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/AbstractRestController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/AbstractRestController.java index de35d7cb..9f646207 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/AbstractRestController.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/AbstractRestController.java @@ -3,10 +3,9 @@ import br.com.muttley.domain.service.Service; import br.com.muttley.exception.throwables.security.MuttleySecurityCredentialException; import br.com.muttley.model.Document; -import br.com.muttley.model.Historic; +import br.com.muttley.model.security.Authority; import br.com.muttley.model.security.JwtToken; import br.com.muttley.model.security.User; -import br.com.muttley.model.security.enumeration.Authorities; import br.com.muttley.rest.RestResource; import br.com.muttley.rest.hateoas.resource.PageableResource; import br.com.muttley.security.server.service.UserService; @@ -24,8 +23,13 @@ import javax.servlet.http.HttpServletResponse; import java.util.Map; +import java.util.Set; import static java.util.Objects.isNull; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; /** * @author Joel Rodrigues Moreira on 18/04/18. @@ -91,24 +95,25 @@ public ResponseEntity findById(@PathVariable("id") final String id, final HttpSe return ResponseEntity.ok(value); } - @RequestMapping(value = "/first", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - @ResponseStatus(HttpStatus.OK) - public ResponseEntity first(final HttpServletResponse response, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + @RequestMapping(value = "/ids", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findByIds(@RequestParam(required = false, value = "ids") String[] ids, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, HttpServletResponse response) { final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); - checkRoleRead(user); - final T value = service.findFirst(user); + final Set value = service.findByIds(user, ids); + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(value); } - @RequestMapping(value = "/{id}/historic", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) + @RequestMapping(value = "/first", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.OK) - public ResponseEntity loadHistoric(@PathVariable("id") final String id, final HttpServletResponse response, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + public ResponseEntity first(final HttpServletResponse response, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); checkRoleRead(user); - final Historic historic = service.loadHistoric(user, id); + final T value = service.findFirst(user); publishSingleResourceRetrievedEvent(this.eventPublisher, response); - return ResponseEntity.ok(historic); + return ResponseEntity.ok(value); } @RequestMapping(method = RequestMethod.GET) @@ -121,7 +126,7 @@ public ResponseEntity list(final HttpServletResponse response, @RequestMapping(value = "/count", method = RequestMethod.GET, produces = {MediaType.TEXT_PLAIN_VALUE}) @ResponseStatus(HttpStatus.OK) - public ResponseEntity count(@RequestParam final Map allRequestParams, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + public ResponseEntity count(@RequestParam final Map allRequestParams, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); checkRoleRead(user); return ResponseEntity.ok(String.valueOf(service.count(user, allRequestParams))); @@ -171,7 +176,7 @@ protected final void checkCredentials(final User user, final String... roles) { } } - protected final void checkCredentials(final User user, final Authorities... roles) { + protected final void checkCredentials(final User user, final Authority... roles) { if (!user.inAnyRole(roles)) { throw new MuttleySecurityCredentialException("Você não tem permissão para acessar este recurso ") .addDetails("isNecessary", roles); diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/AccessPlanController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/AccessPlanController.java index f4d62019..ad10e03b 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/AccessPlanController.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/AccessPlanController.java @@ -1,8 +1,7 @@ package br.com.muttley.security.server.controller; -import br.com.muttley.model.Historic; import br.com.muttley.model.security.AccessPlan; -import br.com.muttley.model.security.enumeration.Authorities; +import br.com.muttley.model.security.Authority; import br.com.muttley.rest.hateoas.resource.PageableResource; import br.com.muttley.security.server.service.AccessPlanService; import br.com.muttley.security.server.service.UserService; @@ -24,6 +23,7 @@ import java.util.Map; import static java.util.Objects.isNull; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; /** * @author Joel Rodrigues Moreira on 23/02/18. @@ -31,15 +31,17 @@ * @project muttley-cloud */ @RestController -@RequestMapping(value = "/api/v1/access-plan", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) +@RequestMapping(value = "/api/v1/access-plan", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) public class AccessPlanController extends AbstractRestController { + final AccessPlanService service; @Autowired public AccessPlanController(final AccessPlanService service, final UserService userService, final ApplicationEventPublisher eventPublisher) { super(service, userService, eventPublisher); + this.service = service; } - @RequestMapping(method = RequestMethod.POST, consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) + @RequestMapping(method = RequestMethod.POST, consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.CREATED) public ResponseEntity save( @RequestBody final AccessPlan value, @@ -57,7 +59,7 @@ public ResponseEntity save( return ResponseEntity.status(HttpStatus.CREATED).build(); } - @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) + @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.OK) public ResponseEntity update(@PathVariable("id") final String id, @RequestBody final AccessPlan model, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { @@ -65,7 +67,7 @@ public ResponseEntity update(@PathVariable("id") final String id, @RequestBody f return ResponseEntity.ok(service.update(null, model)); } - @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.OK) public ResponseEntity deleteById(@PathVariable("id") final String id, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { @@ -74,7 +76,7 @@ public ResponseEntity deleteById(@PathVariable("id") final String id, return ResponseEntity.ok().build(); } - @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) + @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.OK) public ResponseEntity findById(@PathVariable("id") final String id, final HttpServletResponse response, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { @@ -86,7 +88,7 @@ public ResponseEntity findById(@PathVariable("id") final String id, final HttpSe return ResponseEntity.ok(value); } - @RequestMapping(value = "/first", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) + @RequestMapping(value = "/first", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.OK) public ResponseEntity first(final HttpServletResponse response, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { @@ -98,17 +100,6 @@ public ResponseEntity first(final HttpServletResponse response, return ResponseEntity.ok(value); } - @RequestMapping(value = "/{id}/historic", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - @ResponseStatus(HttpStatus.OK) - public ResponseEntity loadHistoric(@PathVariable("id") final String id, final HttpServletResponse response, - @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { - final Historic historic = service.loadHistoric(null, id); - - publishSingleResourceRetrievedEvent(this.eventPublisher, response); - - return ResponseEntity.ok(historic); - } - @RequestMapping(method = RequestMethod.GET) public ResponseEntity list(final HttpServletResponse response, @RequestParam final Map allRequestParams, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { @@ -119,11 +110,17 @@ public ResponseEntity list(final HttpServletResponse response, @RequestMapping(value = "/count", method = RequestMethod.GET, produces = {MediaType.TEXT_PLAIN_VALUE}) @ResponseStatus(HttpStatus.OK) - public final ResponseEntity count(@RequestParam final Map allRequestParams, + public final ResponseEntity count(@RequestParam final Map allRequestParams, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { return ResponseEntity.ok(String.valueOf(service.count(null, allRequestParams))); } + @RequestMapping(value = "/find-by-owner", method = RequestMethod.GET) + @ResponseStatus(HttpStatus.OK) + public final ResponseEntity findByOwner(@RequestParam("owner") final String owner, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + return ResponseEntity.ok(this.service.findByOwner(owner)); + } + protected String[] getCreateRoles() { return null; } @@ -168,7 +165,7 @@ protected final void checkCredentials(final String... roles) { }*/ } - protected final void checkCredentials(final Authorities... roles) { + protected final void checkCredentials(final Authority... roles) { /*if (!user.inAnyRole(roles)) { throw new MuttleySecurityCredentialException("Você não tem permissão para acessar este recurso ") .addDetails("isNecessary", roles); diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/OwnerController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/OwnerController.java index b54616c8..926eca0f 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/OwnerController.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/OwnerController.java @@ -1,6 +1,6 @@ package br.com.muttley.security.server.controller; -import br.com.muttley.model.Historic; +import br.com.muttley.model.security.JwtToken; import br.com.muttley.model.security.Owner; import br.com.muttley.rest.hateoas.resource.PageableResource; import br.com.muttley.security.server.service.OwnerService; @@ -33,10 +33,12 @@ @RestController @RequestMapping(value = "/api/v1/owners", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) public class OwnerController extends AbstractRestController { + private final OwnerService service; @Autowired public OwnerController(final OwnerService service, final UserService userService, final ApplicationEventPublisher eventPublisher) { super(service, userService, eventPublisher); + this.service = service; } @RequestMapping(method = RequestMethod.POST, consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) @@ -81,14 +83,6 @@ public ResponseEntity first(final HttpServletResponse response, @RequestHeader(v return ResponseEntity.ok(value); } - @RequestMapping(value = "/{id}/historic", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - @ResponseStatus(HttpStatus.OK) - public ResponseEntity loadHistoric(@PathVariable("id") final String id, final HttpServletResponse response, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { - final Historic historic = service.loadHistoric(null, id); - publishSingleResourceRetrievedEvent(this.eventPublisher, response); - return ResponseEntity.ok(historic); - } - @RequestMapping(method = RequestMethod.GET) public ResponseEntity list(final HttpServletResponse response, @RequestParam final Map allRequestParams, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { return ResponseEntity.ok(toPageableResource(eventPublisher, response, this.service, null, allRequestParams)); @@ -96,7 +90,19 @@ public ResponseEntity list(final HttpServletResponse response, @RequestMapping(value = "/count", method = RequestMethod.GET, produces = {MediaType.TEXT_PLAIN_VALUE}) @ResponseStatus(HttpStatus.OK) - public final ResponseEntity count(@RequestParam final Map allRequestParams, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + public final ResponseEntity count(@RequestParam final Map allRequestParams, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { return ResponseEntity.ok(String.valueOf(service.count(null, allRequestParams))); } + + @RequestMapping(value = "/by-user", method = RequestMethod.GET) + @ResponseStatus(HttpStatus.OK) + public final ResponseEntity loadOwnersOfUser(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + return ResponseEntity.ok(this.service.loadOwnersOfUser(this.userService.getUserFromToken(new JwtToken(tokenHeader)))); + } + + @RequestMapping(value = "/by-user-and-id/{id}", method = RequestMethod.GET) + @ResponseStatus(HttpStatus.OK) + public final ResponseEntity findByUserAndId(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @PathVariable("id") final String id) { + return ResponseEntity.ok(this.service.findByUserAndId(this.userService.getUserFromToken(new JwtToken(tokenHeader)), id)); + } } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/PassaportController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/PassaportController.java new file mode 100644 index 00000000..2f0ebbe8 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/PassaportController.java @@ -0,0 +1,71 @@ +package br.com.muttley.security.server.controller; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.Passaport; +import br.com.muttley.security.server.service.PassaportService; +import br.com.muttley.security.server.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +/** + * @author Joel Rodrigues Moreira on 23/02/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@RestController +@RequestMapping(value = "/api/v1/passaports", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) +public class PassaportController extends AbstractRestController { + private PassaportService service; + + @Autowired + public PassaportController(final PassaportService service, final UserService userService, final ApplicationEventPublisher eventPublisher) { + super(service, userService, eventPublisher); + this.service = service; + } + + @RequestMapping(value = "/roles/current-roles", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity loadCurrentRoles(final HttpServletResponse response, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + return ResponseEntity.ok(service.loadCurrentRoles(userService.getUserFromToken(new JwtToken(tokenHeader)))); + } + + @RequestMapping(value = "/avaliable-roles", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity loadAvaliableRoles(final HttpServletResponse response, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + return ResponseEntity.ok(service.loadAvaliableRoles(userService.getUserFromToken(new JwtToken(tokenHeader)))); + } + + @RequestMapping(value = "/find-by-name", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findByName(@RequestParam(name = "name", defaultValue = "") final String name, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + return ResponseEntity.ok(this.service.findByName(userService.getUserFromToken(new JwtToken(tokenHeader)), name)); + } + + @RequestMapping(value = "/find-by-user", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findByUser(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + return ResponseEntity.ok(service.findByUser(userService.getUserFromToken(new JwtToken(tokenHeader)))); + } + + @RequestMapping(value = "/create-passaport-for", method = POST, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity createPassaportFor(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @RequestParam(required = false, value = "ownerId", defaultValue = "") final String ownerId, @RequestBody final Passaport passaport) { + return ResponseEntity.ok(service.createPassaportFor(userService.getUserFromToken(new JwtToken(tokenHeader)), ownerId, passaport)); + } + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserBaseController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserBaseController.java new file mode 100644 index 00000000..114795a0 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserBaseController.java @@ -0,0 +1,92 @@ +package br.com.muttley.security.server.controller; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBase; +import br.com.muttley.model.security.UserBaseItem; +import br.com.muttley.security.server.service.UserBaseService; +import br.com.muttley.security.server.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Set; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +/** + * @author Joel Rodrigues Moreira on 09/12/2020. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@RestController +@RequestMapping(value = "/api/v1/users-base", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) +public class UserBaseController extends AbstractRestController { + + private final UserBaseService service; + + @Autowired + public UserBaseController(final UserBaseService service, final UserService userService, final ApplicationEventPublisher eventPublisher) { + super(service, userService, eventPublisher); + this.service = service; + } + + @RequestMapping(value = "/userNamesIsAvaliable", method = RequestMethod.GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity userNameIsAvaliable(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @RequestParam(value = "userNameFor", required = false) final String userNameFor, @RequestParam(value = "userNames") final Set userNames) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + return ResponseEntity.ok(StringUtils.isEmpty(userNameFor) ? this.service.userNameIsAvaliable(user, userNames) : this.service.userNameIsAvaliableForUserName(user, userNameFor, userNames)); + } + + @RequestMapping(value = "/userByEmailOrUserName", method = RequestMethod.GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity userNameIsAvaliable(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @RequestParam(value = "emailOrUsername") final String emailOrUsername) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + return ResponseEntity.ok(this.service.findUserByEmailOrUserNameOrNickUser(user, emailOrUsername)); + } + + @RequestMapping(value = "/create-new-user-and-add", method = RequestMethod.POST, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity createNewUserAndAdd(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @RequestBody final UserBaseItem item) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + this.service.createNewUserAndAdd(user, item); + return ResponseEntity.ok().build(); + } + + @RequestMapping(value = "/merge-user-item-if-exists", method = RequestMethod.POST, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity mergeUserItemIfExists(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @RequestBody final UserBaseItem item) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + this.service.mergeUserItemIfExists(user, item); + return ResponseEntity.ok().build(); + } + + @RequestMapping(value = "/add-if-not-exists", method = RequestMethod.POST, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity addIfNotExists(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @RequestBody final UserBaseItem item) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + this.service.addUserItemIfNotExists(user, item); + return ResponseEntity.ok().build(); + } + + @RequestMapping(value = "/remove-user/{userName}", method = RequestMethod.DELETE, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity removeUser(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @PathVariable(value = "userName") final String userName) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + this.service.removeByUserName(user, userName); + return ResponseEntity.ok().build(); + } + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserController.java index c7e0fbd7..827598e9 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserController.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserController.java @@ -1,26 +1,21 @@ package br.com.muttley.security.server.controller; import br.com.muttley.exception.throwables.MuttleyBadRequestException; -import br.com.muttley.exception.throwables.MuttleyMethodNotAllowedException; -import br.com.muttley.model.security.JwtToken; -import br.com.muttley.model.security.Passwd; -import br.com.muttley.model.security.User; -import br.com.muttley.model.security.UserPayLoad; +import br.com.muttley.exception.throwables.security.MuttleySecondaryEmailException; +import br.com.muttley.exception.throwables.security.MuttleySecurityUserNotFoundException; +import br.com.muttley.model.security.*; +import br.com.muttley.model.userManager.IncludeSecundaryEmail; +import br.com.muttley.security.server.repository.UserRepository; +import br.com.muttley.security.server.service.JwtTokenUtilService; +import br.com.muttley.security.server.service.PasswordService; import br.com.muttley.security.server.service.UserService; -import br.com.muttley.security.server.service.impl.JwtTokenUtilService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletResponse; +import java.util.Set; import static com.google.common.base.Strings.isNullOrEmpty; import static org.springframework.http.HttpStatus.CREATED; @@ -28,8 +23,11 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.DELETE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; import static org.springframework.web.bind.annotation.RequestMethod.PUT; +import static org.springframework.web.bind.annotation.RequestMethod.PATCH; + /** * @author Joel Rodrigues Moreira on 17/04/18. @@ -37,78 +35,136 @@ * @project muttley-cloud */ @org.springframework.web.bind.annotation.RestController -@RequestMapping(value = "/api/v1/users", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) +@RequestMapping(value = "/api/v1/users", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) public class UserController { - private UserService service; + private final UserService service; + private final PasswordService passwordService; + + private final UserRepository userRepository; + private final JwtTokenUtilService tokenUtil; @Autowired - public UserController(final UserService service) { + public UserController(final UserService service, final PasswordService passwordService, UserRepository userRepository, final JwtTokenUtilService tokenUtil) { this.service = service; + this.passwordService = passwordService; + this.userRepository = userRepository; + this.tokenUtil = tokenUtil; } @RequestMapping(method = POST, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(CREATED) public ResponseEntity save(@RequestBody final UserPayLoad value, final HttpServletResponse response, @RequestParam(required = false, value = "returnEntity", defaultValue = "") final String returnEntity) { - final User record = service.save(new User(value)); + final User record = service.save(value); + + if (record == null) { + return ResponseEntity.status(HttpStatus.ACCEPTED).build(); + } if (returnEntity != null && returnEntity.equals("true")) { return ResponseEntity.status(HttpStatus.CREATED).body(record.setPreferences(null).toJson()); } return ResponseEntity.status(HttpStatus.CREATED).build(); } - @RequestMapping(value = "/{email}", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @RequestMapping(value = "/{userName}", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(OK) - public ResponseEntity update(@PathVariable("email") final String email, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String token, @RequestBody final User user, final JwtTokenUtilService tokenUtil) { + public ResponseEntity update(@PathVariable("userName") final String userName, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String token, @RequestBody final User user) { if (isNullOrEmpty(token)) { throw new MuttleyBadRequestException(null, null, "informe um token válido"); } - final String emailFromToken = tokenUtil.getUsernameFromToken(token); + final String usernameFromToken = tokenUtil.getUsernameFromToken(token); - if (isNullOrEmpty(emailFromToken)) { + if (isNullOrEmpty(usernameFromToken)) { throw new MuttleyBadRequestException(null, null, "informe um token válido"); } - if (!emailFromToken.equals(email)) { - throw new MuttleyBadRequestException(null, null, "O token informado não contem o email " + email); + if (!usernameFromToken.equals(userName)) { + throw new MuttleyBadRequestException(null, null, "O token informado não contem o userName " + userName); } - user.setId(service.findByEmail(email).getId()); + user.setId(service.findByUserName(userName).getId()); //é necessário válidar a regra de négocio no processo de crud de usuário - throw new MuttleyMethodNotAllowedException(null, null, "Verifique a regra de negócios"); - //return ResponseEntity.ok(service.update(user)); + //throw new MuttleyMethodNotAllowedException(null, null, "Verifique a regra de negócios"); + + + return ResponseEntity.ok(service.update(user, new JwtToken(token))); } - @RequestMapping(value = "/passwd", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + + @RequestMapping(value = "/emailPrimary", method = PATCH, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity addPrimaryEmail(@RequestBody final IncludeSecundaryEmail request) { + if (request.getEmailSecundary().isEmpty()) { + throw new MuttleySecondaryEmailException(IncludeSecundaryEmail.class, "email", "O campo 'emailPrimary' está vazio Por favor, verifique os dados enviados." + ); + } + return ResponseEntity.ok(this.service.addOrUpdateSecundaryEmail(request)); + } + + + @RequestMapping(value = "/password", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(OK) - public ResponseEntity updatePasswd(@RequestBody final Passwd passwd) { - service.updatePasswd(passwd); + public ResponseEntity updatePasswd(@RequestBody final PasswdPayload passwdPayload) { + passwordService.update(passwdPayload); return ResponseEntity.ok().build(); } + /** - * Faz a deleção por email ao invez de ID + * Faz a deleção por userName ao invez de ID */ @RequestMapping(method = DELETE, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) @ResponseStatus(OK) - public ResponseEntity deleteByEmail(@RequestParam("email") final String email) { - service.removeByEmail(email); + public ResponseEntity deleteByUserName(@RequestParam("userName") final String userName) { + service.removeByUserName(userName); return ResponseEntity.ok().build(); } /** - * Faz a pesquisa pelo email ao invez do ID + * Faz a pesquisa pelo userName ao invez do ID */ - @RequestMapping(method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - @ResponseStatus(HttpStatus.OK) - public ResponseEntity findByEmail(@RequestParam("email") final String email, final HttpServletResponse response) { - return ResponseEntity.ok(service.findByEmail(email).toJson()); + @RequestMapping(method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findByUserName(@RequestParam("userName") final String userName, final HttpServletResponse response) { + return ResponseEntity.ok(service.findByUserName(userName).toJson()); } - @RequestMapping(value = "/user-from-token", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - @ResponseStatus(HttpStatus.OK) + @RequestMapping(value = "/{id}/password", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity loadPasswordById(@RequestParam("id") final String id, final HttpServletResponse response) { + return ResponseEntity.ok(passwordService.findByUserId(id)); + } + + @RequestMapping(value = "/email-or-username-or-nickUsers", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity findUserByEmailOrUserNameOrNickUsers(@RequestParam(value = "email", required = false) final String email, @RequestParam(value = "userName", required = false) final String userName, @RequestParam(value = "nickUsers", required = false) final Set nickUsers) { + return ResponseEntity.ok(service.findUserByEmailOrUserNameOrNickUsers(email, userName, nickUsers).toJson()); + } + + @RequestMapping(value = "/exist-email-or-username-or-nickUsers", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity existUserByEmailOrUserNameOrNickUsers(@RequestParam(value = "email", required = false) final String email, @RequestParam(value = "userName", required = false) final String userName, @RequestParam(value = "nickUsers", required = false) final Set nickUsers) { + return ResponseEntity.ok(service.existUserByEmailOrUserNameOrNickUsers(email, userName, nickUsers)); + } + + @RequestMapping(value = "/userNamesIsAvaliable", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity userNameIsAvaliable(@RequestParam(value = "userNames") final Set userNames) { + return ResponseEntity.ok(service.userNameIsAvaliable(userNames)); + } + + @RequestMapping(value = "/user-from-token", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) public ResponseEntity getUserFromToken(@RequestBody final JwtToken token) { return ResponseEntity.ok(this.service.getUserFromToken(token).toJson()); } + @RequestMapping(value = "/update-profile-pic", method = PATCH, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(OK) + public ResponseEntity updateProfilePic(@RequestBody final User user) { + + this.service.updateProfilePic(user); + return ResponseEntity.ok().build(); + } + } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserDataBindingController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserDataBindingController.java new file mode 100644 index 00000000..51e7b50c --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserDataBindingController.java @@ -0,0 +1,176 @@ +package br.com.muttley.security.server.controller; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.rest.RestResource; +import br.com.muttley.security.server.service.UserDataBindingService; +import br.com.muttley.security.server.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; + +/** + * @author Joel Rodrigues Moreira 12/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@RestController +@RequestMapping(value = "/api/v1/users-databinding", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) +public class UserDataBindingController implements RestResource { + + private final UserDataBindingService service; + private final UserService userService; + private final ApplicationEventPublisher eventPublisher; + + @Autowired + public UserDataBindingController(final UserDataBindingService service, final UserService userService, final ApplicationEventPublisher eventPublisher) { + this.service = service; + this.userService = userService; + this.eventPublisher = eventPublisher; + } + + @RequestMapping(method = POST, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity save( + @RequestBody final UserDataBinding value, + final HttpServletResponse response, + @RequestParam(required = false, value = "returnEntity", defaultValue = "") final String returnEntity, + @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + final UserDataBinding record = service.save(user, value); + + publishCreateResourceEvent(this.eventPublisher, response, record); + + if (returnEntity != null && returnEntity.equals("true")) { + return ResponseEntity.status(HttpStatus.CREATED).body(record); + } + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @RequestMapping(value = "/{id}", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity update( + @PathVariable("id") final String id, + @RequestBody final UserDataBinding model, + @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + //checkRoleUpdate(user); + model.setId(id); + return ResponseEntity.ok(service.update(user, model)); + } + + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity list(final HttpServletResponse response, @RequestParam final Map allRequestParams, + @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + //checkRoleRead(user); + return ResponseEntity.ok(this.service.listByUserName(user, user.getUserName())); + } + + @RequestMapping(value = "/by-username/{userName}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity listByUserName(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @PathVariable("userName") final String userName) { + return ResponseEntity.ok(this.service.listByUserName(userService.getUserFromToken(new JwtToken(tokenHeader)), userName)); + } + + @RequestMapping(value = "/by-username/{userName}", method = POST, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity saveByUserName( + final HttpServletResponse response, + @RequestParam(required = false, value = "returnEntity", defaultValue = "") final String returnEntity, + @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, + @PathVariable("userName") final String userName, + @RequestBody final UserDataBinding model) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + final UserDataBinding record = service.saveByUserName(user, userName, model); + + publishCreateResourceEvent(this.eventPublisher, response, record); + + if (returnEntity != null && returnEntity.equals("true")) { + return ResponseEntity.status(HttpStatus.CREATED).body(record); + } + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @RequestMapping(value = "/by-username/{userName}", method = PUT, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity updateByUserName( + @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, + @PathVariable("userName") final String userName, + @RequestBody final UserDataBinding model) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + return ResponseEntity.ok(service.updateByUserName(user, userName, model)); + } + + @RequestMapping(value = "/merge/{userName}", method = PUT, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity merger( + @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, + @RequestBody final UserDataBinding model, final HttpServletRequest request) { + final String uri = request.getRequestURI(); + //gato para pegar o parametro devido do usuário + //em alguns casos o path variable não estava funcionando corretamente + final String userName = uri.substring(uri.indexOf("/merge/")).substring(7); + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + service.merge(user, userName, model); + return ResponseEntity.ok().build(); + + } + + @RequestMapping(value = "/key/{key}", method = RequestMethod.GET) + public ResponseEntity getKey(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @PathVariable("key") final String key) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + //checkRoleRead(user); + return ResponseEntity.ok(this.service.getKey(user, key)); + } + + @RequestMapping(value = "/by-username/{userName}/key/{key}", method = RequestMethod.GET) + public ResponseEntity getKeyByUserName(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @PathVariable("userName") final String userName, @PathVariable("key") final String key) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + //checkRoleRead(user); + return ResponseEntity.ok(this.service.getKeyByUserName(user, userName, key)); + } + + @RequestMapping(value = "/contains/key/{key}", method = RequestMethod.GET) + public ResponseEntity contains(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @PathVariable("key") final String key) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + //checkRoleRead(user); + return ResponseEntity.ok(this.service.contains(user, key)); + } + + @RequestMapping(value = "/by-username/{userName}/contains/key/{key}", method = RequestMethod.GET) + public ResponseEntity containsByUserName(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @PathVariable("userName") final String userName, @PathVariable("key") final String key) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + return ResponseEntity.ok(this.service.containsByUserNameAndKey(user, userName, key)); + } + + @RequestMapping(value = "/constains-by-key-and-value-and-user-name-not-eq", method = RequestMethod.GET) + public ResponseEntity containsByKeyAndValueAndUserNameNotEq(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @RequestParam(value = "userName", required = false, defaultValue = "") final String userName, @RequestParam("key") final String key, @RequestParam("value") final String value) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + return ResponseEntity.ok(this.service.containsByKeyAndValueAndUserNameNotEq(user, userName, key, value)); + } + + @RequestMapping(value = "/user-by", method = RequestMethod.GET) + public ResponseEntity getUserBy(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @RequestParam(required = false, value = "key", defaultValue = "") final String key, @RequestParam(required = false, value = "value", defaultValue = "") final String value) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + return ResponseEntity.ok(this.service.getUserBy(user, key, value)); + } + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserPreferenceController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserPreferenceController.java index 369730ab..166d4c47 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserPreferenceController.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserPreferenceController.java @@ -1,23 +1,25 @@ package br.com.muttley.security.server.controller; -import br.com.muttley.exception.throwables.MuttleyBadRequestException; import br.com.muttley.exception.throwables.MuttleyNotFoundException; -import br.com.muttley.model.security.User; import br.com.muttley.model.security.preference.Preference; import br.com.muttley.model.security.preference.UserPreferences; -import br.com.muttley.security.server.repository.UserPreferencesRepository; +import br.com.muttley.security.server.service.AuthService; +import br.com.muttley.security.server.service.UserPreferencesService; import br.com.muttley.security.server.service.UserService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; /** * @author Joel Rodrigues Moreira on 24/04/18. @@ -28,42 +30,72 @@ @RequestMapping(value = "/api/v1/user-preferences", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) public class UserPreferenceController { - private final UserPreferencesRepository repository; + private final UserPreferencesService preferencesService; + private final AuthService authService; private final UserService userService; @Autowired - public UserPreferenceController(final UserPreferencesRepository repository, - final UserService userService) { - this.repository = repository; + public UserPreferenceController(final UserPreferencesService preferencesService, final AuthService authService, final UserService userService) { + this.preferencesService = preferencesService; + this.authService = authService; this.userService = userService; } - @RequestMapping(value = "/{idUser}", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - public ResponseEntity getPreferences(@PathVariable("idUser") String idUser) { - final UserPreferences preferences = this.repository.findByUser(new User().setId(idUser)); + @RequestMapping(method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity getUserPreferences() { + final UserPreferences preferences = this.preferencesService.getUserPreferences(this.authService.getCurrentUser()); if (preferences == null) { throw new MuttleyNotFoundException(UserPreferences.class, "user", "Nenhuma preferencia encontrada"); } return ResponseEntity.ok(preferences); } - @RequestMapping(value = "/{idUser}/preferences", method = RequestMethod.POST, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - public ResponseEntity setPreference(@PathVariable("idUser") final String idUser, @RequestBody final Preference preference) { - final UserPreferences userPreferences = (UserPreferences) getPreferences(idUser).getBody(); - if (!preference.isValid()) { - throw new MuttleyBadRequestException(Preference.class, "key", "valor inválido"); - } - userPreferences.set(preference); - this.repository.save(userPreferences); + @RequestMapping(value = "/preferences", method = PUT, consumes = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity setPreference(@RequestBody final Preference preference) { + this.preferencesService.setPreference(this.authService.getCurrentUser(), preference); + return ResponseEntity.ok().build(); + + } + + @RequestMapping(value = "/preferences/{key}", method = DELETE, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity removePreference(@PathVariable("key") final String key) { + this.preferencesService.removePreference(this.authService.getCurrentUser(), key); return ResponseEntity.ok().build(); + } + + @RequestMapping(value = "/preferences/contains", method = GET, produces = TEXT_PLAIN_VALUE) + public ResponseEntity containsPreferences(@RequestParam(name = "key", required = false) final String keyPreference) { + return ResponseEntity.ok("" + this.preferencesService.containsPreference(this.authService.getCurrentUser(), keyPreference)); + } + @RequestMapping(value = "/preferences", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity getCurrentPreferences() { + return ResponseEntity.ok(this.preferencesService.getUserPreferences(this.authService.getCurrentUser()).getPreferences()); } - @RequestMapping(value = "/{idUser}/preferences/{key}", method = RequestMethod.DELETE, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) - public ResponseEntity removePreference(@PathVariable("idUser") final String idUser, @PathVariable("key") final String key) { - final UserPreferences userPreferences = (UserPreferences) getPreferences(idUser).getBody(); - userPreferences.remove(key); - this.repository.save(userPreferences); + @RequestMapping(value = "/preferences/{key}", method = PUT, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity setPreferences(@PathVariable("key") final String key, @RequestParam(value = "value", required = false) final String value) { + this.preferencesService.setPreference(this.authService.getCurrentUser(), key, value); return ResponseEntity.ok().build(); } + + @RequestMapping(value = "/preferences/{key}", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity getPreference(@PathVariable("key") final String key) { + return ResponseEntity.ok(this.preferencesService.getPreference(this.authService.getCurrentUser(), key)); + } + + @RequestMapping(value = "/preferences/{key}/value", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity getPreferenceValue(@PathVariable("key") final String key) { + return ResponseEntity.ok(this.preferencesService.getPreferenceValue(this.authService.getCurrentUser(), key)); + } + + @RequestMapping(value = "/users-from-preference", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity getUsersFromPreference(@RequestParam(name = "key", required = false) final String key, @RequestParam(name = "value", required = false) final String value) { + return ResponseEntity.ok(this.userService.getUsersFromPreference(new Preference(key, value))); + } + + @RequestMapping(value = "/user-from-preference", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + public ResponseEntity getUserFromPreference(@RequestParam(name = "key", required = false) final String key, @RequestParam(name = "value", required = false) final String value) { + return ResponseEntity.ok(this.userService.getUserFromPreference(new Preference(key, value))); + } } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserViewController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserViewController.java new file mode 100644 index 00000000..725c68fa --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/UserViewController.java @@ -0,0 +1,116 @@ +package br.com.muttley.security.server.controller; + + +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserView; +import br.com.muttley.mongo.service.infra.Operator; +import br.com.muttley.rest.hateoas.resource.MetadataPageable; +import br.com.muttley.rest.hateoas.resource.PageableResource; +import br.com.muttley.security.server.service.UserService; +import br.com.muttley.security.server.service.UserViewService; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +/** + * @author Joel Rodrigues Moreira on 29/04/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@RestController +@RequestMapping(value = "/api/v1/users-view", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) +public class UserViewController extends AbstractRestController { + final UserViewService service; + + public UserViewController(final UserViewService service, final UserService userService, final ApplicationEventPublisher eventPublisher) { + super(service, userService, eventPublisher); + this.service = service; + } + + @Override + @RequestMapping(method = GET) + public ResponseEntity list(final HttpServletResponse response, @RequestParam final Map allRequestParams, + @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + //validando os parametros passados + final Map params = validPageable(allRequestParams); + final Long SKIP = allRequestParams.containsKey(Operator.SKIP.toString()) ? Long.valueOf(allRequestParams.get(Operator.SKIP.toString()).toString()) : 0l; + final Long LIMIT = allRequestParams.containsKey(Operator.LIMIT.toString()) ? Long.valueOf(allRequestParams.get(Operator.LIMIT.toString()).toString()) : 100l; + + final long total = service.count(allRequestParams.get("q"), allRequestParams.get("owner")); + + + if (total == 0) { + throw new MuttleyNoContentException(null, null, "registros não encontrados!"); + } + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + final Owner owner = user.getCurrentOwner(); + final long totalAll = service.count(null, owner != null ? owner.getId() : null); + final List records = service.list(allRequestParams.get("q"), owner != null ? owner.getId() : null); + + final Long recordSize = Long.valueOf(records.size()); + + final MetadataPageable metadataPageable = new MetadataPageable( + ServletUriComponentsBuilder.fromCurrentRequest(), + LIMIT, + SKIP, + recordSize, + total, + totalAll); + + publishPaginatedResultsRetrievedEvent( + eventPublisher, + response, + ServletUriComponentsBuilder.fromCurrentRequest(), + metadataPageable + ); + + + return ResponseEntity.ok(new PageableResource(records, metadataPageable)); + + + + + /*final String owner = allRequestParams.get("owner"); + System.out.println(this.service.list(allRequestParams.get("q"), owner)); + return ResponseEntity.ok().build();*/ + } + + @RequestMapping(value = "/userName", method = GET, produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findById(@RequestParam(value = "userName", required = false, defaultValue = "null") final String userName, final HttpServletResponse response, @RequestParam final Map allRequestParams, + @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + final Owner owner = user.getCurrentOwner(); + + final UserView value = service.findByUserName(userName, owner != null ? owner.getId() : null); + + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(value); + } + + @RequestMapping(value = "/count", method = GET, produces = {MediaType.TEXT_PLAIN_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity count(@RequestParam final Map allRequestParams, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + final Owner owner = user.getCurrentOwner(); + checkRoleRead(user); + return ResponseEntity.ok(String.valueOf(service.count(allRequestParams.get("q"), owner != null ? owner.getId() : null))); + } + + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/WorkTeamController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/WorkTeamController.java index 773b8b47..9169e0a6 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/WorkTeamController.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/WorkTeamController.java @@ -1,13 +1,23 @@ package br.com.muttley.security.server.controller; -import br.com.muttley.model.security.WorkTeam; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.workteam.WorkTeam; import br.com.muttley.security.server.service.UserService; import br.com.muttley.security.server.service.WorkTeamService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletResponse; + import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @@ -19,10 +29,19 @@ @RestController @RequestMapping(value = "/api/v1/work-teams", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) public class WorkTeamController extends AbstractRestController { + private final WorkTeamService service; @Autowired public WorkTeamController(final WorkTeamService service, final UserService userService, final ApplicationEventPublisher eventPublisher) { super(service, userService, eventPublisher); + this.service = service; } + @RequestMapping(value = "/domain", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + public ResponseEntity loadDomain(final HttpServletResponse response, @RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader) { + final User user = this.userService.getUserFromToken(new JwtToken(tokenHeader)); + publishSingleResourceRetrievedEvent(this.eventPublisher, response); + return ResponseEntity.ok(this.service.loadDomain(user)); + } } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/XAPITokenController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/XAPITokenController.java new file mode 100644 index 00000000..280350fa --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/XAPITokenController.java @@ -0,0 +1,49 @@ +package br.com.muttley.security.server.controller; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.security.server.service.UserService; +import br.com.muttley.security.server.service.XAPITokenService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +/** + * @author Joel Rodrigues Moreira on 09/08/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@RestController +@RequestMapping(value = "/api/v1/x-api-tokens", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) +public class XAPITokenController extends AbstractRestController { + private final XAPITokenService service; + + @Autowired + public XAPITokenController(XAPITokenService service, UserService userService, ApplicationEventPublisher eventPublisher) { + super(service, userService, eventPublisher); + this.service = service; + } + + @RequestMapping(value = "/generate-x-api-token", method = POST) + public ResponseEntity generateXAPIToken(@RequestHeader(value = "${muttley.security.jwt.controller.tokenHeader-jwt}", defaultValue = "") final String tokenHeader, @RequestBody final Map payload) { + return ResponseEntity.ok(this.service.generateXAPIToken(this.userService.getUserFromToken(new JwtToken(tokenHeader)), payload.get("description"))); + } + + @RequestMapping(value = "/token", method = GET) + public ResponseEntity getByToken(@RequestParam("token") final String token) { + return ResponseEntity.ok(this.service.loadUserByAPIToken(token)); + } + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/auth/AuthenticationRestController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/auth/AuthenticationRestController.java index 6979f494..23704961 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/auth/AuthenticationRestController.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/auth/AuthenticationRestController.java @@ -1,20 +1,21 @@ package br.com.muttley.security.server.controller.auth; import br.com.muttley.exception.throwables.security.MuttleySecurityBadRequestException; +import br.com.muttley.exception.throwables.security.MuttleySecurityUnauthorizedException; import br.com.muttley.exception.throwables.security.MuttleySecurityUserNameOrPasswordInvalidException; -import br.com.muttley.model.security.JwtToken; -import br.com.muttley.model.security.JwtUser; +import br.com.muttley.model.security.*; import br.com.muttley.model.security.events.UserLoggedEvent; -import br.com.muttley.security.server.service.impl.JwtTokenUtilService; +import br.com.muttley.security.server.service.JwtTokenUtilService; +import br.com.muttley.security.server.service.PasswordService; +import br.com.muttley.security.server.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.ResponseEntity; import org.springframework.mobile.device.Device; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -42,14 +43,16 @@ public class AuthenticationRestController { protected final ApplicationEventPublisher eventPublisher; protected final AuthenticationManager authenticationManager; protected final JwtTokenUtilService jwtTokenUtil; - protected final UserDetailsService userDetailsService; + protected final UserService userService; + protected final PasswordService passwordService; @Autowired - public AuthenticationRestController(final AuthenticationManager authenticationManager, final JwtTokenUtilService jwtTokenUtil, final UserDetailsService userDetailsService, final ApplicationEventPublisher eventPublisher) { + public AuthenticationRestController(final AuthenticationManager authenticationManager, final JwtTokenUtilService jwtTokenUtil, final UserService userService, final ApplicationEventPublisher eventPublisher, PasswordService passwordService) { this.authenticationManager = authenticationManager; this.jwtTokenUtil = jwtTokenUtil; - this.userDetailsService = userDetailsService; + this.userService = userService; this.eventPublisher = eventPublisher; + this.passwordService = passwordService; } @RequestMapping(value = "/login", method = POST) @@ -60,14 +63,29 @@ public ResponseEntity createAuthenticationToken(@RequestBody final Map refreshAndGetAuthenticationToken(@RequestBody JwtToken token) { String username = jwtTokenUtil.getUsernameFromToken(token.getToken()); - JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username); - if (jwtTokenUtil.canTokenBeRefreshed(token.getToken(), user.getLastPasswordResetDate())) { + User user = userService.findByUserName(username); + final Password password = this.passwordService.findByUserId(user.getId()); + if (jwtTokenUtil.canTokenBeRefreshed(token.getToken(), password.getLastDatePasswordChanges())) { String refreshedToken = jwtTokenUtil.refreshToken(token.getToken()); return ResponseEntity.ok(new JwtToken(refreshedToken)); } throw new MuttleySecurityBadRequestException(null, null, "Token invalido. Faça login novamente"); } + @RequestMapping(value = "/reset-password", method = POST) + public RecoveryPasswordResponse recoveryPassword(@RequestBody RecoveryPayload recovery) { + return this.userService.recoveryPassword(recovery); + } + private final void checkPayloadContainsUserNameAndPasswdOndy(final Map payload) { if (payload.isEmpty() || payload.size() < 2 || !payload.containsKey(USERNAME) || !payload.containsKey(PASSWORD)) { throw new MuttleySecurityBadRequestException(User.class, null, "Informe os campos de usuário e senha") diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/auth/AuthenticationTokenController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/auth/AuthenticationTokenController.java index 5e14357e..2f336476 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/auth/AuthenticationTokenController.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/auth/AuthenticationTokenController.java @@ -1,20 +1,29 @@ package br.com.muttley.security.server.controller.auth; import br.com.muttley.exception.throwables.security.MuttleySecurityUnauthorizedException; +import br.com.muttley.localcache.services.LocalDatabindingService; +import br.com.muttley.localcache.services.LocalUserPreferenceService; import br.com.muttley.model.security.JwtToken; import br.com.muttley.model.security.JwtUser; -import br.com.muttley.security.server.repository.UserPreferencesRepository; +import br.com.muttley.model.security.Password; +import br.com.muttley.model.security.User; +import br.com.muttley.security.server.events.CurrentOwnerResolverEvent; +import br.com.muttley.security.server.service.JwtTokenUtilService; +import br.com.muttley.security.server.service.PasswordService; import br.com.muttley.security.server.service.UserService; -import br.com.muttley.security.server.service.impl.JwtTokenUtilService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import static com.google.common.base.Strings.isNullOrEmpty; import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; /** * Aplica o filtro de autenticação necessario @@ -24,12 +33,25 @@ public class AuthenticationTokenController { private final JwtTokenUtilService tokenUtil; private final UserService userService; - private final UserPreferencesRepository preferencesRepository; + private final LocalUserPreferenceService preferencesService; + private final LocalDatabindingService dataBindingService; + private final PasswordService passwordService; + private final ApplicationEventPublisher eventPublisher; - public AuthenticationTokenController(final JwtTokenUtilService tokenUtil, final UserService userService, final UserPreferencesRepository preferencesRepository) { + @Autowired + public AuthenticationTokenController( + final JwtTokenUtilService tokenUtil, + final UserService userService, + final LocalUserPreferenceService preferencesService, + final LocalDatabindingService dataBindingService, + final PasswordService passwordService, + final ApplicationEventPublisher eventPublisher) { this.tokenUtil = tokenUtil; this.userService = userService; - this.preferencesRepository = preferencesRepository; + this.preferencesService = preferencesService; + this.dataBindingService = dataBindingService; + this.passwordService = passwordService; + this.eventPublisher = eventPublisher; } @RequestMapping(value = "/user-from-token", method = RequestMethod.POST) @@ -38,16 +60,24 @@ public ResponseEntity getUserFromToken(final @RequestBody JwtToken token) { final String userName = this.tokenUtil.getUsernameFromToken(token.getToken()); if (!isNullOrEmpty(userName)) { //buscando o usuário presente no token - final JwtUser jwtUser = (JwtUser) this.userService.loadUserByUsername(userName); + final User user = this.userService.findByUserName(userName); //buscando as preferencias de usuário - jwtUser.getOriginUser().setPreferences(this.preferencesRepository.findByUser(jwtUser.getOriginUser())); + + user.setPreferences(this.preferencesService.getUserPreferences(token, user)); + //disparando evento para resolver o owner corrent + final CurrentOwnerResolverEvent event = new CurrentOwnerResolverEvent(user); + this.eventPublisher.publishEvent(event); + //buscando os databindinqs do usuário + user.setDataBindings(this.dataBindingService.getUserDataBindings(token, user)); + final Password password = this.passwordService.findByUserId(user.getId()); //verificando a validade do token - if (tokenUtil.validateToken(token.getToken(), jwtUser)) { - return ResponseEntity.ok(jwtUser.toJson()); + if (tokenUtil.validateTokenWithUser(token.getToken(), user, password)) { + return ResponseEntity.ok(JwtUser.Builder.newInstance().set(user).setPassword(password).build().toJson()); } } } throw new MuttleySecurityUnauthorizedException(); } + } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/auth/ForgotPasswordController.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/auth/ForgotPasswordController.java new file mode 100644 index 00000000..5e35c0c2 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/controller/auth/ForgotPasswordController.java @@ -0,0 +1,54 @@ +package br.com.muttley.security.server.controller.auth; + +import br.com.muttley.exception.throwables.security.MuttleySecurityUserNotFoundException; +import br.com.muttley.model.recoveryPassword.ResetPasswordRequest; +import br.com.muttley.model.recoveryPassword.UserForgotPasswordRequest; +import br.com.muttley.security.server.service.ForgotPasswordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +@RestController +@RequestMapping(value = "/api/v1/recovery", produces = {APPLICATION_JSON_UTF8_VALUE, APPLICATION_JSON_VALUE}) +public class ForgotPasswordController { + + + protected final ForgotPasswordService forgotPasswordService; + + @Autowired + public ForgotPasswordController(final ForgotPasswordService forgotPasswordService) { + this.forgotPasswordService = forgotPasswordService; + } + + + @RequestMapping(value = "/forgot-password", method = POST) + public ResponseEntity forgotPassword(@RequestBody UserForgotPasswordRequest request) { + + if (request.getEmail().isEmpty()) { + throw new MuttleySecurityUserNotFoundException(UserForgotPasswordRequest.class, "email", "O campo de email não pode estar vazio. Por favor, preencha e tente novamente."); + } + + return ResponseEntity.ok(this.forgotPasswordService.forgotPassword(request.getEmail())); + + } + + @RequestMapping(value = "/reset-password", method = POST) + public ResponseEntity resetPassword(@RequestBody ResetPasswordRequest request) { + if (request.getNewPassword().isEmpty()) { + throw new MuttleySecurityUserNotFoundException(ResetPasswordRequest.class, "novaSenha", "O campo de nova senha não pode estar vazio. Por favor, preencha e tente novamente."); + } + + if (request.getToken().isEmpty()) { + throw new MuttleySecurityUserNotFoundException(ResetPasswordRequest.class, "token", "O campo de token não pode estar vazio. Por favor, preencha e tente novamente."); + } + return ResponseEntity.ok(this.forgotPasswordService.resetPassword(request)); + } + + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/events/AccessPlanDefaultEvent.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/AccessPlanDefaultEvent.java new file mode 100644 index 00000000..a1b2289b --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/AccessPlanDefaultEvent.java @@ -0,0 +1,26 @@ +package br.com.muttley.security.server.events; + +import br.com.muttley.model.security.AccessPlan; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira on 21/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class AccessPlanDefaultEvent extends ApplicationEvent { + + @Getter + @Setter + @Accessors(chain = true) + private AccessPlan resolved; + + public AccessPlanDefaultEvent(final String source) { + super(source); + } + + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CheckUserHasBeenIncludedAnyGroupEvent.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CheckUserHasBeenIncludedAnyGroupEvent.java new file mode 100644 index 00000000..1a32e55c --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CheckUserHasBeenIncludedAnyGroupEvent.java @@ -0,0 +1,32 @@ +package br.com.muttley.security.server.events; + +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira 23/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class CheckUserHasBeenIncludedAnyGroupEvent extends ApplicationEvent { + private boolean userHasBeenIncludedAnyGroup = false; + private final String userName; + + public CheckUserHasBeenIncludedAnyGroupEvent(final String userName) { + super(userName); + this.userName = userName; + } + + @Override + public String getSource() { + return this.userName; + } + + public boolean isUserHasBeenIncludedAnyGroup() { + return userHasBeenIncludedAnyGroup; + } + + public CheckUserHasBeenIncludedAnyGroupEvent setUserHasBeenIncludedAnyGroup(final boolean userHasBeenIncludedAnyGroup) { + this.userHasBeenIncludedAnyGroup = userHasBeenIncludedAnyGroup; + return this; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/events/ConfigFirstOwnerPreferenceEvent.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/ConfigFirstOwnerPreferenceEvent.java new file mode 100644 index 00000000..855aa2f8 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/ConfigFirstOwnerPreferenceEvent.java @@ -0,0 +1,23 @@ +package br.com.muttley.security.server.events; + +import br.com.muttley.model.security.User; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira on 07/01/20. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class ConfigFirstOwnerPreferenceEvent extends ApplicationEvent { + private final User owner; + + public ConfigFirstOwnerPreferenceEvent(final User owner) { + super(owner); + this.owner = owner; + } + + @Override + public User getSource() { + return this.owner; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentAdminRolesResolverEvent.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentAdminRolesResolverEvent.java new file mode 100644 index 00000000..a64ebd36 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentAdminRolesResolverEvent.java @@ -0,0 +1,53 @@ +package br.com.muttley.security.server.events; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.context.ApplicationEvent; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 30/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + *

+ * Evento disparado para recuperação de roles de um determinado usuário + */ +public class CurrentAdminRolesResolverEvent extends ApplicationEvent { + private final CurrentAdminRolesResolverEventItem source; + + @Getter + @Setter + @Accessors(chain = true) + private boolean resolved = false; + + @Getter + @Setter + @Accessors(chain = true) + private Set roles; + + public CurrentAdminRolesResolverEvent(final CurrentAdminRolesResolverEventItem source) { + super(source); + this.source = source; + } + + @Override + public CurrentAdminRolesResolverEventItem getSource() { + return this.source; + } + + @Getter + public static class CurrentAdminRolesResolverEventItem { + private final JwtToken token; + private final User user; + + public CurrentAdminRolesResolverEventItem(final JwtToken token, final User user) { + this.token = token; + this.user = user; + } + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentDatabindingResolverEvent.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentDatabindingResolverEvent.java new file mode 100644 index 00000000..87591e3b --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentDatabindingResolverEvent.java @@ -0,0 +1,53 @@ +package br.com.muttley.security.server.events; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.context.ApplicationEvent; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira 30/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + *

+ * Evento utilizado para carregamento de informações de databinding do banco de dados + */ +public class CurrentDatabindingResolverEvent extends ApplicationEvent { + private final CurrentDatabindingEventItem source; + + @Getter + @Setter + @Accessors(chain = true) + private boolean resolved = false; + + @Getter + @Setter + @Accessors(chain = true) + private List dataBindings; + + public CurrentDatabindingResolverEvent(final CurrentDatabindingEventItem source) { + super(source); + this.source = source; + } + + @Override + public CurrentDatabindingEventItem getSource() { + return this.source; + } + + @Getter + public static class CurrentDatabindingEventItem { + private final T token; + private final User user; + + public CurrentDatabindingEventItem(final T token, final User user) { + this.token = token; + this.user = user; + } + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentOwnerResolverEvent.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentOwnerResolverEvent.java new file mode 100644 index 00000000..2c2c7ef3 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentOwnerResolverEvent.java @@ -0,0 +1,42 @@ +package br.com.muttley.security.server.events; + +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira 20/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class CurrentOwnerResolverEvent extends ApplicationEvent { + private final User user; + private boolean resolved = false; + + public CurrentOwnerResolverEvent(final User user) { + super(user); + this.user = user; + } + + @Override + public User getSource() { + return this.user; + } + + public CurrentOwnerResolverEvent setOwnerResolved(final Owner owner) { + this.user.setCurrentOwner(owner); + this.resolved = owner != null; + return this; + } + + public CurrentOwnerResolverEvent setOwnerResolved(final OwnerData owner) { + this.user.setCurrentOwner(owner); + this.resolved = owner != null; + return this; + } + + public boolean isResolved() { + return resolved; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentPreferencesResolverEvent.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentPreferencesResolverEvent.java new file mode 100644 index 00000000..cb1fd6c3 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentPreferencesResolverEvent.java @@ -0,0 +1,51 @@ +package br.com.muttley.security.server.events; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.preference.UserPreferences; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira 29/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + *

+ * Evento disparado para recuperação de preferencias de um determinado usuário + */ +public class CurrentPreferencesResolverEvent extends ApplicationEvent { + + private final CurrentPreferencesResolverEventItem item; + @Getter + @Setter + @Accessors(chain = true) + private boolean resolved = false; + + @Getter + @Setter + @Accessors(chain = true) + private UserPreferences userPreferences; + + public CurrentPreferencesResolverEvent(final CurrentPreferencesResolverEventItem item) { + super(item); + this.item = item; + } + + @Override + public CurrentPreferencesResolverEventItem getSource() { + return this.item; + } + + @Getter + public static class CurrentPreferencesResolverEventItem { + private final JwtToken token; + private final User user; + + public CurrentPreferencesResolverEventItem(final JwtToken token, final User user) { + this.token = token; + this.user = user; + } + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentRolesResolverEvent.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentRolesResolverEvent.java new file mode 100644 index 00000000..32fe0ad2 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/CurrentRolesResolverEvent.java @@ -0,0 +1,52 @@ +package br.com.muttley.security.server.events; + +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.context.ApplicationEvent; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 30/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + *

+ * Evento disparado para recuperação de roles de um determinado usuário + */ +public class CurrentRolesResolverEvent extends ApplicationEvent { + private final CurrentRolesResolverEventItem source; + + @Getter + @Setter + @Accessors(chain = true) + private boolean resolved = false; + + @Getter + @Setter + @Accessors(chain = true) + private Set roles; + + public CurrentRolesResolverEvent(final CurrentRolesResolverEventItem source) { + super(source); + this.source = source; + } + + @Override + public CurrentRolesResolverEventItem getSource() { + return this.source; + } + + @Getter + public static class CurrentRolesResolverEventItem { + private final T token; + private final User user; + + public CurrentRolesResolverEventItem(final T token, final User user) { + this.token = token; + this.user = user; + } + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/events/NewUserHasBeenAddedInBaseEvent.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/NewUserHasBeenAddedInBaseEvent.java new file mode 100644 index 00000000..4161353b --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/NewUserHasBeenAddedInBaseEvent.java @@ -0,0 +1,41 @@ +package br.com.muttley.security.server.events; + +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserData; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira on 11/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class NewUserHasBeenAddedInBaseEvent extends ApplicationEvent { + private final NewUserHasBeenAddedInBaseItemEvent source; + + /** + * Create a new ApplicationEvent. + * + * @param source the object on which the event initially occurred (never {@code null}) + */ + public NewUserHasBeenAddedInBaseEvent(final NewUserHasBeenAddedInBaseItemEvent source) { + super(source); + this.source = source; + } + + @Override + public NewUserHasBeenAddedInBaseItemEvent getSource() { + return this.source; + } + + @Getter + public static class NewUserHasBeenAddedInBaseItemEvent { + private final User currentUser; + private final UserData newUserAdded; + + public NewUserHasBeenAddedInBaseItemEvent(final User currentUser, final UserData newUserAdded) { + this.currentUser = currentUser; + this.newUserAdded = newUserAdded; + } + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/events/NoSecurityOwnerCreateEvent.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/NoSecurityOwnerCreateEvent.java new file mode 100644 index 00000000..d0f4e26f --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/events/NoSecurityOwnerCreateEvent.java @@ -0,0 +1,23 @@ +package br.com.muttley.security.server.events; + +import br.com.muttley.model.security.Owner; +import org.springframework.context.ApplicationEvent; + +/** + * @author Joel Rodrigues Moreira on 16/05/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class NoSecurityOwnerCreateEvent extends ApplicationEvent { + private final Owner owner; + + public NoSecurityOwnerCreateEvent(final Owner owner) { + super(owner); + this.owner = owner; + } + + @Override + public Owner getSource() { + return this.owner; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CheckUserHasBeenIncludedAnyGroupListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CheckUserHasBeenIncludedAnyGroupListener.java new file mode 100644 index 00000000..7b1b6704 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CheckUserHasBeenIncludedAnyGroupListener.java @@ -0,0 +1,27 @@ +package br.com.muttley.security.server.listeners; + +import br.com.muttley.security.server.events.CheckUserHasBeenIncludedAnyGroupEvent; +import br.com.muttley.security.server.service.UserBaseService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira 23/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class CheckUserHasBeenIncludedAnyGroupListener implements ApplicationListener { + final UserBaseService service; + + @Autowired + public CheckUserHasBeenIncludedAnyGroupListener(final UserBaseService service) { + this.service = service; + } + + @Override + public void onApplicationEvent(final CheckUserHasBeenIncludedAnyGroupEvent event) { + event.setUserHasBeenIncludedAnyGroup(this.service.hasBeenIncludedAnyGroup(event.getSource())); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/ConfigFirstOwnerPreferenceEventListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/ConfigFirstOwnerPreferenceEventListener.java new file mode 100644 index 00000000..d403965b --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/ConfigFirstOwnerPreferenceEventListener.java @@ -0,0 +1,50 @@ +package br.com.muttley.security.server.listeners; + +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.security.server.events.ConfigFirstOwnerPreferenceEvent; +import br.com.muttley.security.server.service.PassaportService; +import br.com.muttley.security.server.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import static br.com.muttley.model.security.preference.UserPreferences.OWNER_PREFERENCE; + +/** + * @author Joel Rodrigues Moreira on 16/05/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + *

+ *

+ * Talvez o usuário da requisição não tenha um owner configurado por padrão ou esteja com as preferencias vazias, + * levando isso em consideração esse serviço é disparado para se configurar opções básicas necessárias + */ +@Component +public class ConfigFirstOwnerPreferenceEventListener implements ApplicationListener { + private final UserService service; + private final PassaportService passaportService; + + @Autowired + public ConfigFirstOwnerPreferenceEventListener(final UserService service, final PassaportService passaportService) { + this.service = service; + this.passaportService = passaportService; + } + + @Override + public void onApplicationEvent(final ConfigFirstOwnerPreferenceEvent configFirst) { + final User user = configFirst.getSource(); + + final UserPreferences preference = this.service.loadPreference(user); + if (!preference.contains(OWNER_PREFERENCE)) { + //setando o owner do primeiro workteam que encontrar + final Passaport passaport = this.passaportService.findByUser(user).get(0); + preference.set(OWNER_PREFERENCE, passaport.getOwner()); + //salvando as alterções das preferencias + this.service.save(user, preference); + user.setPreferences(preference); + user.setCurrentOwner(passaport.getOwner()); + } + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentDatabindingResolverEventListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentDatabindingResolverEventListener.java new file mode 100644 index 00000000..0ef90b90 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentDatabindingResolverEventListener.java @@ -0,0 +1,28 @@ +package br.com.muttley.security.server.listeners; + +import br.com.muttley.security.server.events.CurrentDatabindingResolverEvent; +import br.com.muttley.security.server.service.UserDataBindingService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira 30/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class CurrentDatabindingResolverEventListener implements ApplicationListener { + private final UserDataBindingService service; + + @Autowired + public CurrentDatabindingResolverEventListener(final UserDataBindingService service) { + this.service = service; + } + + @Override + public void onApplicationEvent(final CurrentDatabindingResolverEvent event) { + event.setDataBindings(this.service.listBy(event.getSource().getUser())) + .setResolved(true); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentOwnerResolverEventListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentOwnerResolverEventListener.java new file mode 100644 index 00000000..d5be1248 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentOwnerResolverEventListener.java @@ -0,0 +1,156 @@ +package br.com.muttley.security.server.listeners; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.exception.throwables.security.MuttleySecurityUnauthorizedException; +import br.com.muttley.headers.components.MuttleyRequestHeader; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.preference.Preference; +import br.com.muttley.security.server.events.CurrentOwnerResolverEvent; +import br.com.muttley.security.server.service.AdminOwnerService; +import br.com.muttley.security.server.service.OwnerService; +import br.com.muttley.security.server.service.UserDataBindingService; +import br.com.muttley.security.server.service.UserPreferencesService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; + +import java.util.List; + +import static br.com.muttley.model.security.preference.UserPreferences.OWNER_PREFERENCE; + +/** + * @author Joel Rodrigues Moreira 20/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class CurrentOwnerResolverEventListener implements ApplicationListener { + + private final UserPreferencesService preferencesService; + private final UserDataBindingService dataBindingService; + private final OwnerService ownerService; + private final AdminOwnerService adminOwnerService; + @Autowired + private MuttleyRequestHeader requestHeader; + + @Autowired + public CurrentOwnerResolverEventListener(final UserPreferencesService preferencesService, final UserDataBindingService dataBindingService, final OwnerService ownerService, final AdminOwnerService adminOwnerService) { + this.preferencesService = preferencesService; + this.dataBindingService = dataBindingService; + this.ownerService = ownerService; + this.adminOwnerService = adminOwnerService; + } + + @Override + public void onApplicationEvent(final CurrentOwnerResolverEvent event) { + final String ownerId; + + //verificando se as preferencias foram carregadas + if (!event.getSource().preferencesIsEmpty()) { + //verificando se tem uma preferencia relacionada ao owner + if (event.getSource().getPreferences().contains(OWNER_PREFERENCE)) { + //se chegou até aqui é sinal que tem onwer na preferencia. Logo basta carregar o mesmo + event.setOwnerResolved(this.resolveOwnerById(event.getSource(), event.getSource().getPreferences().get(OWNER_PREFERENCE).getValue().toString())); + //verificando se existe a preferencia no banco de dados + } else if (preferencesService.containsPreference(event.getSource(), OWNER_PREFERENCE)) { + event.setOwnerResolved(this.resolveOwnerById(event.getSource(), preferencesService.getPreference(event.getSource(), OWNER_PREFERENCE).getValue().toString())); + } else { + this.resolverFirstOwner(event); + } + } else { + this.resolverFirstOwner(event); + } + + /*//verificando se as preferencias foram carregadas + if (!event.getSource().preferencesIsEmpty() || !event.getSource().getPreferences().contains(OWNER_PREFERENCE)) { + //verificando se existe uma preferencia de owner para ser carregada + if (preferencesService.containsPreference(event.getSource(), OWNER_PREFERENCE)) { + ownerId = preferencesService.getPreference(event.getSource(), OWNER_PREFERENCE).getValue().toString(); + } else { + try { + //se chegou até aqui é sinal que não tem nenhum owner para o usuário corrente + //logo vamo ver se conseguimos encontrar um owner + + final List owners; + //é uma requisição do serviço de admin? + if (this.requestHeader.isRequestFromAdminServer()) { + owners = adminOwnerService.loadOwnersOfUser(event.getSource()); + } else { + owners = ownerService.loadOwnersOfUser(event.getSource()); + } + //pegando o primeiro owner e salvando o mesmo na preferencia + final Preference preference = new Preference(OWNER_PREFERENCE, owners.get(0).getId()); + //salvando nas preferencias do usuario + this.preferencesService.setPreference(event.getSource(), preference); + //setando a preferencia criada + event.getSource().getPreferences().set(preference); + event.setOwnerResolved(owners.get(0)); + } catch (MuttleyNoContentException exception) { + throw new MuttleySecurityUnauthorizedException("O usuário não está presente em nenhuma base. Entre em contato com o administrador"); + //se chegou aqui quer dizer que é uma requisição do odin + } + return; + //ownerId = preference.getValue().toString(); + } + } else { + //se chegou até aqui é sinal que tem onwer na preferencia. Logo basta carregar o mesmo + ownerId = event.getSource().getPreferences().get(OWNER_PREFERENCE).getValue().toString(); + } + //carregando o owner + try { + event.setOwnerResolved(this.ownerService.findByUserAndId(event.getSource(), ownerId)); + } catch (final MuttleyBadRequestException ex) { + //se chegou aqui é sinal que o usuário não está em nenhuma base de usuário + //logo podemos remover + preferencesService.removePreference(event.getSource(), OWNER_PREFERENCE); + }*/ + } + + private void resolverFirstOwner(final CurrentOwnerResolverEvent event) { + try { + //se chegou até aqui é sinal que não tem nenhum owner para o usuário corrente + //logo vamo ver se conseguimos encontrar um owner + + final List owners; + //é uma requisição do serviço de admin? + if (this.requestHeader.isRequestFromAdminServer()) { + owners = adminOwnerService.loadOwnersOfUser(event.getSource()); + } else { + owners = ownerService.loadOwnersOfUser(event.getSource()); + } + //pegando o primeiro owner e salvando o mesmo na preferencia + final Preference preference = new Preference(OWNER_PREFERENCE, owners.get(0).getId()); + //salvando nas preferencias do usuario + this.preferencesService.setPreference(event.getSource(), preference); + //setando a preferencia criada + event.getSource().getPreferences().set(preference); + event.setOwnerResolved(owners.get(0)); + } catch (MuttleyNoContentException exception) { + throw new MuttleySecurityUnauthorizedException("O usuário não está presente em nenhuma base. Entre em contato com o administrador"); + //se chegou aqui quer dizer que é uma requisição do odin + } + } + + private Owner resolveOwnerById(final User user, final String ownerId) { + try { + if (!ObjectUtils.isEmpty(ownerId)) { + //é uma requisição do serviço de admin? + if (this.requestHeader.isRequestFromAdminServer()) { + return adminOwnerService.findByUserAndId(user, ownerId); + } else { + return this.ownerService.findByUserAndId(user, ownerId); + } + + } + } catch (MuttleyBadRequestException ex) { + //se chegou aqui é sinal que o usuário não está em nenhuma base de usuário + //logo podemos remover + preferencesService.removePreference(user, OWNER_PREFERENCE); + } + throw new MuttleySecurityUnauthorizedException("O usuário não está presente em nenhuma base. Entre em contato com o administrador"); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentPreferencesResolverEventListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentPreferencesResolverEventListener.java new file mode 100644 index 00000000..5b1c2d71 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentPreferencesResolverEventListener.java @@ -0,0 +1,29 @@ +package br.com.muttley.security.server.listeners; + +import br.com.muttley.security.server.events.CurrentPreferencesResolverEvent; +import br.com.muttley.security.server.service.UserPreferencesService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira 30/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class CurrentPreferencesResolverEventListener implements ApplicationListener { + + private final UserPreferencesService service; + + @Autowired + public CurrentPreferencesResolverEventListener(final UserPreferencesService service) { + this.service = service; + } + + @Override + public void onApplicationEvent(final CurrentPreferencesResolverEvent event) { + event.setUserPreferences(this.service.getUserPreferences(event.getSource().getUser())) + .setResolved(true); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentRolesResolverEventListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentRolesResolverEventListener.java new file mode 100644 index 00000000..f395f669 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/CurrentRolesResolverEventListener.java @@ -0,0 +1,38 @@ +package br.com.muttley.security.server.listeners; + +import br.com.muttley.headers.components.MuttleyRequestHeader; +import br.com.muttley.security.server.events.CurrentRolesResolverEvent; +import br.com.muttley.security.server.service.AdminPassaportService; +import br.com.muttley.security.server.service.PassaportService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira 30/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class CurrentRolesResolverEventListener implements ApplicationListener { + private final PassaportService service; + private final AdminPassaportService adminService; + @Autowired + private MuttleyRequestHeader requestHeader; + + @Autowired + public CurrentRolesResolverEventListener(final PassaportService service, final AdminPassaportService adminService) { + this.service = service; + this.adminService = adminService; + } + + @Override + public void onApplicationEvent(final CurrentRolesResolverEvent event) { + if (this.requestHeader.isRequestFromAdminServer()) { + event.setRoles(this.adminService.loadCurrentRoles(event.getSource().getUser())); + } else { + event.setRoles(this.service.loadCurrentRoles(event.getSource().getUser())) + .setResolved(true); + } + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/NoSecurityOwnerCreateEventListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/NoSecurityOwnerCreateEventListener.java new file mode 100644 index 00000000..3100a1a7 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/NoSecurityOwnerCreateEventListener.java @@ -0,0 +1,105 @@ +package br.com.muttley.security.server.listeners; + +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBase; +import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.security.server.events.NoSecurityOwnerCreateEvent; +import br.com.muttley.security.server.service.AuthService; +import br.com.muttley.security.server.service.NoSecurityUserBaseService; +import br.com.muttley.security.server.service.PassaportService; +import br.com.muttley.security.server.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import java.util.stream.Collectors; + +import static br.com.muttley.model.security.Role.ROLE_OWNER; +import static br.com.muttley.model.security.Role.ROLE_PASSAPORT_CREATE; +import static br.com.muttley.model.security.Role.ROLE_ROOT; +import static br.com.muttley.model.security.Role.ROLE_USER_DATA_BINDING_CREATE; +import static br.com.muttley.model.security.preference.UserPreferences.OWNER_PREFERENCE; + +/** + * @author Joel Rodrigues Moreira on 16/05/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + *

+ * Ao se criar um Owner no sistema, devemos garantir que seja criado um Grupo de trabalho + * padrão para esse mesmo usuário + */ +@Component +public class NoSecurityOwnerCreateEventListener implements ApplicationListener { + + private final PassaportService service; + private final UserService userService; + private final NoSecurityUserBaseService userBaseService; + private final AuthService authService; + + @Autowired + public NoSecurityOwnerCreateEventListener(final PassaportService service, final UserService userService, final NoSecurityUserBaseService userBaseService, final AuthService authService) { + this.service = service; + this.userService = userService; + this.userBaseService = userBaseService; + this.authService = authService; + } + + @Override + public void onApplicationEvent(final NoSecurityOwnerCreateEvent ownerCreateEvent) { + final User userMaster = ownerCreateEvent.getSource().getUserMaster(); + Passaport passaport = new Passaport() + .setName("Master") + .setDescription("Esse é o grupo principal") + .setOwner(ownerCreateEvent.getSource()) + .setUserMaster(userMaster) + .addMember(userMaster) + .addRole(ROLE_OWNER); + userMaster.setCurrentOwner(passaport.getOwner()); + + passaport = this.service.save(userMaster, passaport); + + Passaport passaportGestores = new Passaport() + .setName("Gestores") + .setDescription("Grupo de gestores") + .setOwner(ownerCreateEvent.getSource()) + .setUserMaster(userMaster) + .addMember(userMaster) + .addRoles(Role + .getValues() + .parallelStream() + .filter(it -> { + final String roleName = it.getRoleName(); + + return !roleName.equals(ROLE_OWNER.getRoleName()) + && !roleName.equals(ROLE_ROOT.getRoleName()) + && !roleName.contains(ROLE_PASSAPORT_CREATE.getSimpleName()) + && !roleName.contains(ROLE_USER_DATA_BINDING_CREATE.getSimpleName()) + && !roleName.endsWith("CREATE") + && !roleName.endsWith("UPDATE") + && !roleName.endsWith("DELETE"); + }) + .collect(Collectors.toSet()) + ); + + passaportGestores = this.service.save(userMaster, passaportGestores); + /*Já que acabamos de criar um Owner, devemos verificar se o usuário master já tem algumas preferencias básicas + * tudo isso para evitar erros + */ + final UserPreferences preference = this.userService.loadPreference(userMaster); + if (!preference.contains(OWNER_PREFERENCE)) { + preference.set(OWNER_PREFERENCE, passaport.getOwner()); + //salvando as alterções das preferencias + this.userService.save(userMaster, preference); + } + + // Adicinando a base de usuário para esse novo owner cadastradao + final UserBase userBase = new UserBase(); + //final User currentUser = this.userService.getUserFromToken(this.authService.getCurrentToken()); + userBase.setOwner(ownerCreateEvent.getSource()) + .addUser(userMaster, userMaster); + + this.userBaseService.save(userMaster, ownerCreateEvent.getSource(), userBase); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/OwnerCreateEventListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/OwnerCreateEventListener.java index a615e22b..1b5152d8 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/OwnerCreateEventListener.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/OwnerCreateEventListener.java @@ -1,16 +1,23 @@ package br.com.muttley.security.server.listeners; +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.Role; import br.com.muttley.model.security.User; -import br.com.muttley.model.security.WorkTeam; +import br.com.muttley.model.security.UserBase; import br.com.muttley.model.security.preference.UserPreferences; import br.com.muttley.security.server.events.OwnerCreateEvent; +import br.com.muttley.security.server.service.AuthService; +import br.com.muttley.security.server.service.PassaportService; +import br.com.muttley.security.server.service.UserBaseService; import br.com.muttley.security.server.service.UserService; -import br.com.muttley.security.server.service.WorkTeamService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; -import static br.com.muttley.model.security.preference.UserPreferences.WORK_TEAM_PREFERENCE; +import java.util.stream.Collectors; + +import static br.com.muttley.model.security.Role.ROLE_OWNER; +import static br.com.muttley.model.security.preference.UserPreferences.OWNER_PREFERENCE; /** * @author Joel Rodrigues Moreira on 16/05/18. @@ -23,36 +30,49 @@ @Component public class OwnerCreateEventListener implements ApplicationListener { - private final WorkTeamService service; + private final PassaportService service; private final UserService userService; + private final UserBaseService userBaseService; + private final AuthService authService; @Autowired - public OwnerCreateEventListener(final WorkTeamService service, final UserService userService) { + public OwnerCreateEventListener(final PassaportService service, final UserService userService, final UserBaseService userBaseService, final AuthService authService) { this.service = service; this.userService = userService; + this.userBaseService = userBaseService; + this.authService = authService; } @Override public void onApplicationEvent(final OwnerCreateEvent ownerCreateEvent) { final User userMaster = ownerCreateEvent.getSource().getUserMaster(); - final WorkTeam workTeam = this.service.save( - userMaster, - new WorkTeam() - .setName("Master") - .setDescription("Esse é o grupo principal") - .setOwner(ownerCreateEvent.getSource()) - .setUserMaster(userMaster) - .addMember(userMaster) - ); + Passaport passaport = new Passaport() + .setName("Master") + .setDescription("Esse é o grupo principal") + .setOwner(ownerCreateEvent.getSource()) + .setUserMaster(userMaster) + .addMember(userMaster) + .addRole(ROLE_OWNER); + userMaster.setCurrentOwner(passaport.getOwner()); + + passaport = this.service.save(userMaster, passaport); /*Já que acabamos de criar um Owner, devemos verificar se o usuário master já tem algumas preferencias básicas * tudo isso para evitar erros */ final UserPreferences preference = this.userService.loadPreference(userMaster); - if (!preference.contains(WORK_TEAM_PREFERENCE)) { - preference.set(WORK_TEAM_PREFERENCE, workTeam); + if (!preference.contains(OWNER_PREFERENCE)) { + preference.set(OWNER_PREFERENCE, passaport.getOwner()); //salvando as alterções das preferencias this.userService.save(userMaster, preference); } + + // Adicinando a base de usuário para esse novo owner cadastradao + final UserBase userBase = new UserBase(); + final User currentUser = this.userService.getUserFromToken(this.authService.getCurrentToken()); + userBase.setOwner(ownerCreateEvent.getSource()) + .addUser(userMaster, userMaster); + + this.userBaseService.save(currentUser, ownerCreateEvent.getSource(), userBase); } } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/PassaportResolverListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/PassaportResolverListener.java new file mode 100644 index 00000000..ed704547 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/PassaportResolverListener.java @@ -0,0 +1,31 @@ +package br.com.muttley.security.server.listeners; + +import br.com.muttley.domain.service.listener.AbstractModelResolverEventListener; +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.events.PassaportResolverEvent; +import br.com.muttley.security.server.service.AuthService; +import br.com.muttley.security.server.service.PassaportService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira on 07/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class PassaportResolverListener extends AbstractModelResolverEventListener { + private final AuthService authService; + private final PassaportService passaportService; + + @Autowired + public PassaportResolverListener(AuthService authService, PassaportService passaportService) { + this.authService = authService; + this.passaportService = passaportService; + } + + @Override + protected Passaport loadValueById(String id) { + return passaportService.findById(authService.getCurrentJwtUser().getOriginUser(), id); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/UserCreatedEventListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/UserCreatedEventListener.java new file mode 100644 index 00000000..a4999142 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/UserCreatedEventListener.java @@ -0,0 +1,44 @@ +package br.com.muttley.security.server.listeners; + +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.events.UserCreatedEvent; +import br.com.muttley.security.server.events.AccessPlanDefaultEvent; +import br.com.muttley.security.server.service.NoSecurityOwnerService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira on 21/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class UserCreatedEventListener implements ApplicationListener { + private final boolean autoCreateOwner; + private final ApplicationEventPublisher applicationEventPublisher; + private final NoSecurityOwnerService ownerService; + + public UserCreatedEventListener(@Value("${muttley.security-server.owner.autoCreateBefore:false}") boolean autoCreateOwner, final ApplicationEventPublisher applicationEventPublisher, NoSecurityOwnerService ownerService) { + this.autoCreateOwner = autoCreateOwner; + this.applicationEventPublisher = applicationEventPublisher; + this.ownerService = ownerService; + } + + @Override + public void onApplicationEvent(UserCreatedEvent userCreatedEvent) { + if (this.autoCreateOwner) { + if (!userCreatedEvent.getUser().isOdinUser()) { + final AccessPlanDefaultEvent event = new AccessPlanDefaultEvent(""); + this.applicationEventPublisher.publishEvent(event); + final Owner owner = new Owner() + .setUserMaster(userCreatedEvent.getUser()) + .setAccessPlan(event.getResolved()) + .setName("Meus dados(" + userCreatedEvent.getUser().getName() + ")"); + this.ownerService.save(owner); + } + } + + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/UserEventResolverListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/UserEventResolverListener.java index cc5f8401..4ea38692 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/UserEventResolverListener.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/UserEventResolverListener.java @@ -22,6 +22,6 @@ public UserEventResolverListener(final UserService service) { @Override public void onApplicationEvent(final UserResolverEvent userEventResolver) { - userEventResolver.setValueResolved(this.service.findByEmail(userEventResolver.getEmail())); + userEventResolver.setValueResolved(this.service.findByUserName(userEventResolver.getUserName())); } } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/UserResolverFromJWTEventListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/UserResolverFromJWTEventListener.java new file mode 100644 index 00000000..9d5afc77 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/UserResolverFromJWTEventListener.java @@ -0,0 +1,27 @@ +package br.com.muttley.security.server.listeners; + +import br.com.muttley.model.security.events.UserResolverFromJWTEvent; +import br.com.muttley.security.server.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira 12/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class UserResolverFromJWTEventListener implements ApplicationListener { + private final UserService service; + + @Autowired + public UserResolverFromJWTEventListener(final UserService service) { + this.service = service; + } + + @Override + public void onApplicationEvent(final UserResolverFromJWTEvent userResolverFromJWTEvent) { + userResolverFromJWTEvent.setUserResolved(this.service.getUserFromToken(userResolverFromJWTEvent.getSource())); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/ValidateOwnerEventListener.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/ValidateOwnerEventListener.java new file mode 100644 index 00000000..aa0c4073 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/listeners/ValidateOwnerEventListener.java @@ -0,0 +1,19 @@ +package br.com.muttley.security.server.listeners; + +import br.com.muttley.model.security.events.ValidateOwnerEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira on 08/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class ValidateOwnerEventListener implements ApplicationListener { + + @Override + public void onApplicationEvent(ValidateOwnerEvent event) { + event.getSource().setOwner(event.getCurrenteUserFromRequest().getCurrentOwner()); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/AccessPlanRepository.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/AccessPlanRepository.java index 2c31f31c..e31a1afa 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/AccessPlanRepository.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/AccessPlanRepository.java @@ -12,4 +12,6 @@ @Repository public interface AccessPlanRepository extends DocumentMongoRepository { AccessPlan findByDescription(final String name); + + AccessPlan findByName(final String name); } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/AdminOwnerRepository.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/AdminOwnerRepository.java new file mode 100644 index 00000000..8d83804d --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/AdminOwnerRepository.java @@ -0,0 +1,21 @@ +package br.com.muttley.security.server.repository; + +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.security.User; +import br.com.muttley.mongo.service.repository.DocumentMongoRepository; +import org.springframework.stereotype.Repository; + +/** + * @author Joel Rodrigues Moreira on 22/02/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + *

+ * Repositório específico para owner de clientes + */ +@Repository +public interface AdminOwnerRepository extends DocumentMongoRepository { + + AdminOwner findByName(final String nome); + + boolean existsByUserMaster(final User userMaster); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/AdminPassaportRepository.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/AdminPassaportRepository.java new file mode 100644 index 00000000..70aceb77 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/AdminPassaportRepository.java @@ -0,0 +1,30 @@ +package br.com.muttley.security.server.repository; + +import br.com.muttley.model.admin.AdminPassaport; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.User; +import br.com.muttley.mongo.service.repository.DocumentMongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 01/03/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Repository +public interface AdminPassaportRepository extends DocumentMongoRepository { + @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionAdminOwner()}, '$id' : ?#{[0].getId()}}, 'name': '?1' }") + AdminPassaport findByName(final Owner owner, final String name); + + @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionAdminOwner()}, '$id' : ?#{[0].getId()}}, 'userMaster': {'$ref' : ?#{@documentNameConfig.getNameCollectionUser()}, '$id' : ?#{[1].getId()}}}") + List findByUserMaster(final Owner owner, final User user); + + @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionAdminOwner()}, '$id' : ?#{[0].getId()}}}") + List findAll(final Owner owner); + + @Query(value = "{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionAdminOwner()}, '$id' : ?#{[0].getId()}}}", count = true) + Long count(final Owner owner); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/OwnerRepository.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/OwnerRepository.java index 543d310a..a6eb6a47 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/OwnerRepository.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/OwnerRepository.java @@ -1,6 +1,7 @@ package br.com.muttley.security.server.repository; import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.User; import br.com.muttley.mongo.service.repository.DocumentMongoRepository; import org.springframework.stereotype.Repository; @@ -15,4 +16,6 @@ public interface OwnerRepository extends DocumentMongoRepository { Owner findByName(final String nome); + + boolean existsByUserMaster(final User userMaster); } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/PassaportRepository.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/PassaportRepository.java new file mode 100644 index 00000000..79c124e0 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/PassaportRepository.java @@ -0,0 +1,30 @@ +package br.com.muttley.security.server.repository; + +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.User; +import br.com.muttley.mongo.service.repository.DocumentMongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 01/03/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Repository +public interface PassaportRepository extends DocumentMongoRepository { + @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionOwner()}, '$id' : ?#{[0].getId()}}, 'name': '?1' }") + Passaport findByName(final Owner owner, final String name); + + @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionOwner()}, '$id' : ?#{[0].getId()}}, 'userMaster': {'$ref' : ?#{@documentNameConfig.getNameCollectionUser()}, '$id' : ?#{[1].getId()}}}") + List findByUserMaster(final Owner owner, final User user); + + @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionOwner()}, '$id' : ?#{[0].getId()}}}") + List findAll(final Owner owner); + + @Query(value = "{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionOwner()}, '$id' : ?#{[0].getId()}}}", count = true) + Long count(final Owner owner); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/PasswordRepository.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/PasswordRepository.java new file mode 100644 index 00000000..d3d2dd21 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/PasswordRepository.java @@ -0,0 +1,18 @@ +package br.com.muttley.security.server.repository; + +import br.com.muttley.model.security.Password; +import br.com.muttley.model.security.User; +import br.com.muttley.mongo.service.repository.DocumentMongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +/** + * @author Joel Rodrigues Moreira 12/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Repository +public interface PasswordRepository extends DocumentMongoRepository { + @Query("{'user': {'$ref' : ?#{@documentNameConfig.getNameCollectionUser()}, '$id' : ?#{[0].getObjectId()}} }") + Password findByUser(final User user); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/UserDataBindingRepository.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/UserDataBindingRepository.java new file mode 100644 index 00000000..4a4630a8 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/UserDataBindingRepository.java @@ -0,0 +1,25 @@ +package br.com.muttley.security.server.repository; + +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.mongo.service.repository.DocumentMongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 01/03/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Repository +public interface UserDataBindingRepository extends DocumentMongoRepository { + + @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionOwner()}, '$id' : ?#{[0].getId()}}, 'user': {'$ref' : ?#{@documentNameConfig.getNameCollectionUser()}, '$id' : ?#{[1].getId()}}}") + List findByUser(final Owner owner, final User user); + + @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionOwner()}, '$id' : ?#{[0].getId()}}, 'user': {'$ref' : ?#{@documentNameConfig.getNameCollectionUser()}, '$id' : ?#{[1].getId()}}, 'key': '?2'}") + UserDataBinding findByUserAndKey(final Owner owner, final User user, final String key); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/UserRepository.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/UserRepository.java index a3f90c05..9f665fb4 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/UserRepository.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/UserRepository.java @@ -4,12 +4,26 @@ import br.com.muttley.mongo.service.repository.DocumentMongoRepository; import org.springframework.stereotype.Repository; +import java.util.Set; + /** * @author Joel Rodrigues Moreira on 12/01/18. * @project spring-cloud */ @Repository public interface UserRepository extends DocumentMongoRepository { - User findByEmail(String email); + + User findByResetToken(String resetToken); + + User findByUserName(final String userName); + + User findByEmail(final String email); + + User findByNickUsers(Set nickUsers); + + User findByEmailSecundario(String email); + + User findByEmailOrEmailSecundario(String email, String emailSecundario); + } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/WorkTeamRepository.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/WorkTeamRepository.java index 08f25448..4f2e2662 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/WorkTeamRepository.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/WorkTeamRepository.java @@ -2,7 +2,7 @@ import br.com.muttley.model.security.Owner; import br.com.muttley.model.security.User; -import br.com.muttley.model.security.WorkTeam; +import br.com.muttley.model.workteam.WorkTeam; import br.com.muttley.mongo.service.repository.DocumentMongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; @@ -10,7 +10,7 @@ import java.util.List; /** - * @author Joel Rodrigues Moreira on 01/03/18. + * @author Joel Rodrigues Moreira on 02/03/2022. * e-mail: joel.databox@gmail.com * @project muttley-cloud */ @@ -21,4 +21,10 @@ public interface WorkTeamRepository extends DocumentMongoRepository { @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionOwner()}, '$id' : ?#{[0].getId()}}, 'userMaster': {'$ref' : ?#{@documentNameConfig.getNameCollectionUser()}, '$id' : ?#{[1].getId()}}}") List findByUserMaster(final Owner owner, final User user); + + @Query("{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionOwner()}, '$id' : ?#{[0].getId()}}}") + List findAll(final Owner owner); + + @Query(value = "{'owner': {'$ref' : ?#{@documentNameConfig.getNameCollectionOwner()}, '$id' : ?#{[0].getId()}}}", count = true) + Long count(final Owner owner); } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/XAPITokenRepository.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/XAPITokenRepository.java new file mode 100644 index 00000000..c2f5d2d1 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/repository/XAPITokenRepository.java @@ -0,0 +1,16 @@ +package br.com.muttley.security.server.repository; + +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.mongo.service.repository.DocumentMongoRepository; +import org.springframework.stereotype.Repository; + +/** + * @author Joel Rodrigues Moreira on 09/08/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Repository +public interface XAPITokenRepository extends DocumentMongoRepository { + + XAPIToken findByToken(final String token); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AccessPlanService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AccessPlanService.java index 30596d1c..6fdbaa02 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AccessPlanService.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AccessPlanService.java @@ -1,6 +1,7 @@ package br.com.muttley.security.server.service; import br.com.muttley.model.security.AccessPlan; +import org.springframework.security.access.prepost.PreAuthorize; /** * @author Joel Rodrigues Moreira on 26/02/18. @@ -8,5 +9,31 @@ * @project muttley-cloud */ public interface AccessPlanService extends SecurityService { + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString()" + + " ) " + + "or " + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).toPatternRole('read', this.getBasicRoles())" + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).toPatternRole('read', 'MOBILE_' + this.getBasicRoles()) " + + " ) " + + " ):false " + + " )" + + "): " + + " true" + ) AccessPlan findByDescription(String descricao); + + AccessPlan findByName(final String name); + + AccessPlan findByOwner(final String idOwner); } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AdminOwnerService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AdminOwnerService.java new file mode 100644 index 00000000..8ad64937 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AdminOwnerService.java @@ -0,0 +1,29 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.domain.service.Service; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface AdminOwnerService extends Service { + AdminOwner findByName(final String name); + + /** + * Retorna todos os owner estão lincado a um determinado usuário usando como base + * a colection de UserBase + */ + List loadOwnersOfUser(final User user); + + /** + * Busca um owner pelo id que tenha vinculo com o usuário informado + */ + Owner findByUserAndId(final User user, final String id); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AdminPassaportService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AdminPassaportService.java new file mode 100644 index 00000000..3def2f86 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AdminPassaportService.java @@ -0,0 +1,40 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.domain.service.Service; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.admin.AdminPassaport; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.rolesconfig.AvaliableRoles; + +import java.util.List; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 26/02/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface AdminPassaportService extends Service { + AdminPassaport findByName(final User user, final String name); + + List findByUserMaster(final AdminOwner owner, final User user); + + List findByUser(final User user); + + Set loadCurrentRoles(final User user); + + AvaliableRoles loadAvaliableRoles(final User user); + + void removeUserFromAllPassaport(final AdminOwner owner, final User user); + + /** + * Método para + */ + AdminPassaport createPassaportFor(final User user, final String ownerId, final AdminPassaport passaport); + + /** + * Realiza as configurações + */ + void configPassaports(final User user); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AdminUserBaseService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AdminUserBaseService.java new file mode 100644 index 00000000..e0f8a06e --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AdminUserBaseService.java @@ -0,0 +1,43 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.model.admin.AdminUserBase; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBaseItem; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.UserView; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 26/11/2020. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface AdminUserBaseService extends SecurityService { + AdminUserBase save(final User user, final OwnerData owner, final AdminUserBase userBase); + + boolean userNameIsAvaliableForUserName(final User user, final String userName, final Set userNames); + + boolean userNameIsAvaliable(final User user, final Set userNames); + + UserView findUserByEmailOrUserNameOrNickUser(final User user, final String emailOrUserName); + + void addUserItemIfNotExists(final User user, final User userForAdd); + + void addUserItemIfNotExists(final User user, final UserBaseItem userForAdd); + + void createNewUserAndAdd(final User user, final UserBaseItem item); + + void mergeUserItemIfExists(User user, final UserBaseItem item); + + void removeByUserName(User user, String userName); + + boolean hasBeenIncludedAnyGroup(final User user, UserData userForCheck); + + boolean hasBeenIncludedAnyGroup(UserData userForCheck); + + boolean hasBeenIncludedAnyGroup(final User user, final String userNameForCheck); + + boolean hasBeenIncludedAnyGroup(final String userNameForCheck); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AuthService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AuthService.java new file mode 100644 index 00000000..375fe551 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/AuthService.java @@ -0,0 +1,34 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.JwtUser; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.preference.Preference; +import br.com.muttley.model.security.preference.UserPreferences; +import org.springframework.security.core.Authentication; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 04/12/2020. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface AuthService { + Authentication getCurrentAuthentication(); + + JwtUser getCurrentJwtUser(); + + JwtToken getCurrentToken(); + + User getCurrentUser(); + + UserPreferences getUserPreferences(); + + Preference getPreference(final String key); + + Set getDataBindings(); + + UserDataBinding getDataBinding(final String key); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/ForgotPasswordService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/ForgotPasswordService.java new file mode 100644 index 00000000..154da2ca --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/ForgotPasswordService.java @@ -0,0 +1,16 @@ +package br.com.muttley.security.server.service; + + +import br.com.muttley.model.recoveryPassword.ResetPasswordRequest; + +/** + * @author Carolina Cedro on 06/10/2024. + * e-mail: ana.carolina@maxxsoft.com + * @project muttley-cloud + */ +public interface ForgotPasswordService { + + T forgotPassword(final String email); + + T resetPassword(final ResetPasswordRequest request); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/JwtTokenUtilService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/JwtTokenUtilService.java new file mode 100644 index 00000000..5fbf0399 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/JwtTokenUtilService.java @@ -0,0 +1,46 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.model.security.Password; +import br.com.muttley.model.security.User; +import org.springframework.mobile.device.Device; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Date; +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira on 19/02/20. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface JwtTokenUtilService { + String getUsernameFromToken(String token); + + Date getCreatedDateFromToken(String token); + + Date getExpirationDateFromToken(String token); + + String getAudienceFromToken(String token); + + String generateToken(final UserDetails userDetails, final Device device); + + String generateToken(final UserDetails userDetails, final Device device, Map details); + + String generateToken(User user, Device device); + + String generateToken(User user, Device device, Map details); + + boolean canTokenBeRefreshed(String token, Date lastPasswordReset); + + String refreshToken(String token); + + boolean validateTokenWithUser(String token, UserDetails userDetails); + + boolean validateTokenWithUser(String token, User user, final Password password); + + /** + * Verifica se o token foi assinado pelo servidor e se o mesmo ainda não esta expirado + */ + boolean isValidToken(final String token); + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/NoSecurityOwnerService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/NoSecurityOwnerService.java new file mode 100644 index 00000000..d6f50b65 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/NoSecurityOwnerService.java @@ -0,0 +1,17 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.model.security.Owner; + +/** + * Essa classe deve ser utilizada apenas para configuração automatica do sistema, + * A mesma não contem validação de negócio nem mesmo de segurança + * + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface NoSecurityOwnerService { + Owner findByName(final String nome); + + Owner save(final Owner owner); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/NoSecurityUserBaseService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/NoSecurityUserBaseService.java new file mode 100644 index 00000000..0279fc16 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/NoSecurityUserBaseService.java @@ -0,0 +1,22 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBase; +import br.com.muttley.model.security.UserBaseItem; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.UserView; +import br.com.muttley.model.security.merge.MergedUserBaseResponse; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 26/11/2020. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface NoSecurityUserBaseService extends SecurityService { + UserBase save(final User user, final OwnerData owner, final UserBase userBase); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/OwnerService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/OwnerService.java index 8e050055..70cd2733 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/OwnerService.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/OwnerService.java @@ -2,6 +2,10 @@ import br.com.muttley.domain.service.Service; import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; + +import java.util.List; /** * @author Joel Rodrigues Moreira on 26/02/18. @@ -10,4 +14,17 @@ */ public interface OwnerService extends Service { Owner findByName(final String name); + + /** + * Retorna todos os owner estão lincado a um determinado usuário usando como base + * a colection de UserBase + */ + List loadOwnersOfUser(final User user); + + /** + * Busca um owner pelo id que tenha vinculo com o usuário informado + */ + Owner findByUserAndId(final User user, final String id); + + Owner loadCurrentOwner(final User user); } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/PassaportService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/PassaportService.java new file mode 100644 index 00000000..fa3d6e81 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/PassaportService.java @@ -0,0 +1,47 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.domain.service.Service; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.rolesconfig.AvaliableRoles; + +import java.util.List; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 26/02/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface PassaportService extends Service { + Passaport findByName(final User user, final String name); + + List findByUserMaster(final Owner owner, final User user); + + List findByUser(final User user); + + Set loadCurrentRoles(final User user); + + AvaliableRoles loadAvaliableRoles(final User user); + + void removeUserFromAllPassaport(final Owner owner, final User user); + + void addUserForPassaportIfNotExists(final User user, final Passaport passaport, final UserData userForAdd); + + boolean userIsPresentInPassaport(final User user, final Passaport passaport, final UserData userForCheck); + + boolean userIsPresentInPassaport(final User user, final String idPassaport, final UserData userForCheck); + + /** + * Método para + */ + Passaport createPassaportFor(final User user, final String ownerId, final Passaport passaport); + + /** + * Realiza as configurações + */ + void configPassaports(final User user); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/PasswordService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/PasswordService.java new file mode 100644 index 00000000..08f88ed6 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/PasswordService.java @@ -0,0 +1,25 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.model.security.PasswdPayload; +import br.com.muttley.model.security.Password; +import br.com.muttley.model.security.User; + +/** + * @author Joel Rodrigues Moreira 12/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ + +public interface PasswordService { + Password findByUserId(final String userId); + + void save(final User user, final T password); + + void createPasswordFor(final User user, final String password); + + void createPasswordFor(final User user, final PasswdPayload password); + + void update(final PasswdPayload password); + + void resetePasswordFor(final User user, final String password); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/SecretService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/SecretService.java new file mode 100644 index 00000000..857b2731 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/SecretService.java @@ -0,0 +1,18 @@ +package br.com.muttley.security.server.service; + +import io.jsonwebtoken.SigningKeyResolver; + +/** + * @author Joel Rodrigues Moreira on 19/02/20. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface SecretService { + SigningKeyResolver getSigningKeyResolver(); + + byte[] getHS256SecretBytes(); + + byte[] getHS384SecretBytes(); + + byte[] getHS512SecretBytes(); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserBaseService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserBaseService.java new file mode 100644 index 00000000..8823e89d --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserBaseService.java @@ -0,0 +1,52 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBase; +import br.com.muttley.model.security.UserBaseItem; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.UserView; +import br.com.muttley.model.security.merge.MergedUserBaseResponse; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 26/11/2020. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface UserBaseService extends SecurityService { + UserBase save(final User user, final OwnerData owner, final UserBase userBase); + + boolean userNameIsAvaliableForUserName(final User user, final String userName, final Set userNames); + + boolean userNameIsAvaliable(final User user, final Set userNames); + + UserView findUserByEmailOrUserNameOrNickUser(final User user, final String emailOrUserName); + + void addUserItemIfNotExists(final User user, final User userForAdd); + + void addUserItemIfNotExists(final User user, final UserBaseItem userForAdd); + + void createNewUserAndAdd(final User user, final UserBaseItem item); + + void mergeUserItemIfExists(User user, final UserBaseItem item); + + void mergeUserItem(final User user, final UserBaseItem item); + + MergedUserBaseResponse mergeUserItens(final User user, final List itens); + + void removeByUserName(User user, String userName); + + boolean hasBeenIncludedAnyGroup(final User user, UserData userForCheck); + + boolean hasBeenIncludedAnyGroup(UserData userForCheck); + + boolean hasBeenIncludedAnyGroup(final User user, final String userNameForCheck); + + boolean hasBeenIncludedAnyGroup(final String userNameForCheck); + + boolean allHasBeenIncludedGroup(final User user, final Collection users); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserDataBindingService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserDataBindingService.java new file mode 100644 index 00000000..89fc930f --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserDataBindingService.java @@ -0,0 +1,257 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.model.security.KeyUserDataBinding; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.UserDataBinding; +import org.springframework.security.access.prepost.PreAuthorize; + +import java.util.List; +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 12/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface UserDataBindingService { + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_CREATE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_CREATE.toString() " + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + UserDataBinding save(final User user, final UserDataBinding dataBinding); + + @PreAuthorize( + "this.isCheckRole()? " + + "(" + + " hasAnyRole(" + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_UPDATE.toString() " + + " )" + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_UPDATE.toString() " + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + UserDataBinding update(final User user, final UserDataBinding dataBinding); + + /** + * Lista os itens levando em consideração não o usuário da requisição, + * mas sim o userName informado + * + * @param user -> usuário da requisição corrente + * @param userName -> nome de usuário desejado + */ + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString()" + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + List listByUserName(final User user, final String userName); + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_READ.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_READ.toString() " + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + List listBy(final User user); + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString()" + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + UserDataBinding saveByUserName(final User user, final String userName, final UserDataBinding value); + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString()" + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + UserDataBinding updateByUserName(final User user, final String userName, final UserDataBinding dataBinding); + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString()" + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + void merge(final User user, final String userName, final UserDataBinding dataBinding); + + @PreAuthorize( + "this.isCheckRole()? " + + "( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_OWNER.toString(), " + + " T(br.com.muttley.model.security.Role).ROLE_ROOT.toString() " + + " ) " + + "or " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString() " + + " ) " + + "or (" + + " @userAgent.isMobile()? " + + " ( " + + " hasAnyRole( " + + " T(br.com.muttley.model.security.Role).ROLE_MOBILE_USER_DATA_BINDING_OTHERS_USERS_MERGE.toString()" + + " ) " + + " ):false " + + " )" + + "): " + + " true " + ) + void merge(final User user, final String userName, final Set dataBindings); + + UserDataBinding getKey(final User user, final KeyUserDataBinding key); + + UserDataBinding getKey(final User user, final String key); + + UserDataBinding getKeyByUserName(final User user, final String userName, final KeyUserDataBinding key); + + UserDataBinding getKeyByUserName(final User user, final String userName, final String key); + + boolean contains(final User user, final KeyUserDataBinding key); + + boolean contains(final User user, final String key); + + boolean containsByUserNameAndKey(final User user, final String userName, final KeyUserDataBinding key); + + boolean containsByUserNameAndKey(final User user, final String userName, final String key); + + /** + * Verifica se uma determina chave e valor já esta reservado para algum usuário + */ + boolean containsByKeyAndValue(final User user, final KeyUserDataBinding key, final String value); + + /** + * Verifica se uma determina chave e valor já esta reservado para algum usuário + */ + boolean containsByKeyAndValue(final User user, final String key, final String value); + + /** + * Verifica se uma determina chave e valor já esta reservado para algum usuário diferente do username informado + */ + boolean containsByKeyAndValueAndUserNameNotEq(final User user, final String userName, final KeyUserDataBinding key, final String value); + + /** + * Verifica se uma determina chave e valor já esta reservado para algum usuário diferente do username informado + */ + boolean containsByKeyAndValueAndUserNameNotEq(final User user, final String userName, final String key, final String value); + + UserData getUserBy(final User user, final KeyUserDataBinding key, final String value); + + UserData getUserBy(final User user, final String key, final String value); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserPreferencesService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserPreferencesService.java new file mode 100644 index 00000000..a11434e6 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserPreferencesService.java @@ -0,0 +1,34 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.model.Document; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.preference.Preference; +import br.com.muttley.model.security.preference.UserPreferences; + +/** + * @author Joel Rodrigues Moreira 08/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public interface UserPreferencesService { + + UserPreferences createPreferencesFor(final User user); + + void save(final User user, final UserPreferences preferences); + + void setPreference(final User user, final Preference preference); + + void setPreference(final User user, final String key, final String value); + + void setPreference(final User user, final String key, final Document value); + + Preference getPreference(final User user, final String key); + + String getPreferenceValue(final User user, final String key); + + void removePreference(final User user, final String key); + + UserPreferences getUserPreferences(final User user); + + boolean containsPreference(final User user, final String key); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserRolesView.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserRolesView.java new file mode 100644 index 00000000..10a6fdf0 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserRolesView.java @@ -0,0 +1,15 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 23/05/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface UserRolesView { + Set findByUser(User user); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserService.java index 400d7b5e..ebba298f 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserService.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserService.java @@ -1,18 +1,21 @@ package br.com.muttley.security.server.service; -import br.com.muttley.model.security.JwtToken; -import br.com.muttley.model.security.Passwd; -import br.com.muttley.model.security.User; +import br.com.muttley.model.security.*; +import br.com.muttley.model.security.preference.Preference; import br.com.muttley.model.security.preference.UserPreferences; -import org.springframework.security.core.userdetails.UserDetailsService; +import br.com.muttley.model.userManager.IncludeSecundaryEmail; import java.util.Collection; +import java.util.List; +import java.util.Set; /** * @author Joel Rodrigues Moreira on 12/01/18. * @project spring-cloud */ -public interface UserService extends UserDetailsService { +public interface UserService { + User save(final UserPayLoad value); + User save(final User user); /** @@ -22,13 +25,30 @@ public interface UserService extends UserDetailsService { boolean remove(final User user); - boolean removeByEmail(final String email); + boolean removeByUserName(final String userName); + + User update(final User user, final JwtToken token); + + User update(final User user, final User userForUpdate); + + User updateProfilePic(final User user); + + + /*User updatePasswd(final PasswdPayload user);*/ + + User findByUserName(final String userName); + + User findUserByEmailOrUserNameOrNickUsers(final String email, final String userName, final Set nickUsers); + + User findUserByEmailOrUserNameOrNickUser(final String emailOrUserName); - User update(final User user); + boolean existUserByEmailOrUserNameOrNickUsers(final String email, final String userName, final Set nickUsers); - User updatePasswd(final Passwd user); + boolean userNameIsAvaliableForUserName(final String userName, final Set userNames); - User findByEmail(final String email); + boolean userNameIsAvaliable(final Set userNames); + + boolean userNameIsAvaliable(final String userName); User findById(final String id); @@ -36,13 +56,21 @@ public interface UserService extends UserDetailsService { User getUserFromToken(final JwtToken token); - /*Authentication getCurrentAuthentication(); + User getUserFromToken(final XAPIToken token); + + UserPreferences loadPreference(final User user); + + /** + * Retorna um usuários baseado em uma preferencia + */ + List getUsersFromPreference(final Preference preference); - JwtUser getCurrentJwtUser(); + User getUserFromPreference(final Preference preference); - JwtToken getCurrentToken(); + boolean constainsPreference(final User user, final String keyPreference); - User getCurrentUser();*/ + RecoveryPasswordResponse recoveryPassword(RecoveryPayload recovery); + + User addOrUpdateSecundaryEmail(IncludeSecundaryEmail request); - UserPreferences loadPreference(final User user); } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserViewService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserViewService.java new file mode 100644 index 00000000..d6c0d824 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/UserViewService.java @@ -0,0 +1,22 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.domain.service.Service; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserView; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira on 29/04/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface UserViewService extends Service { + + UserView findByUserName(final String userName, final String idOwner); + + List list(final String criterio, final String idOwner); + + + long count(final String criterio, final String idOwner); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/WorkTeamService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/WorkTeamService.java index d341c724..a6d70340 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/WorkTeamService.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/WorkTeamService.java @@ -1,19 +1,19 @@ package br.com.muttley.security.server.service; import br.com.muttley.domain.service.Service; -import br.com.muttley.model.security.Owner; import br.com.muttley.model.security.User; -import br.com.muttley.model.security.WorkTeam; +import br.com.muttley.model.workteam.WorkTeam; +import br.com.muttley.model.workteam.WorkTeamDomain; import java.util.List; /** - * @author Joel Rodrigues Moreira on 26/02/18. + * @author Joel Rodrigues Moreira on 03/03/2022. * e-mail: joel.databox@gmail.com * @project muttley-cloud */ public interface WorkTeamService extends Service { - WorkTeam findByName(final Owner owner, final String name); + WorkTeamDomain loadDomain(final User user); - List findByUserMaster(final Owner owner, final User user); + List findByUser(final User user); } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/XAPITokenService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/XAPITokenService.java new file mode 100644 index 00000000..55572f6b --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/XAPITokenService.java @@ -0,0 +1,16 @@ +package br.com.muttley.security.server.service; + +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.security.User; + +/** + * @author Joel Rodrigues Moreira on 08/08/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public interface XAPITokenService extends SecurityService { + + XAPIToken loadUserByAPIToken(final String token); + + XAPIToken generateXAPIToken(final User user, final String description); +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AccessPlanServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AccessPlanServiceImpl.java index 6fddb57b..84588d6b 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AccessPlanServiceImpl.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AccessPlanServiceImpl.java @@ -4,11 +4,26 @@ import br.com.muttley.exception.throwables.MuttleyNotFoundException; import br.com.muttley.model.security.AccessPlan; import br.com.muttley.model.security.User; +import br.com.muttley.security.server.config.model.DocumentNameConfig; import br.com.muttley.security.server.repository.AccessPlanRepository; import br.com.muttley.security.server.service.AccessPlanService; +import com.mongodb.BasicDBObject; +import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.stereotype.Service; +import java.util.LinkedList; +import java.util.List; + +import static br.com.muttley.model.security.Role.ROLE_ACCESS_PLAN_CREATE; +import static java.util.Arrays.asList; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; + /** * @author Joel Rodrigues Moreira on 26/02/18. * e-mail: joel.databox@gmail.com @@ -17,20 +32,75 @@ @Service public class AccessPlanServiceImpl extends SecurityServiceImpl implements AccessPlanService { final AccessPlanRepository repository; + final DocumentNameConfig documentNameConfig; + private static final String[] basicRoles = new String[]{ROLE_ACCESS_PLAN_CREATE.getSimpleName()}; @Autowired - public AccessPlanServiceImpl(final AccessPlanRepository repository) { - super(repository, AccessPlan.class); + public AccessPlanServiceImpl(final AccessPlanRepository repository, final MongoTemplate template, DocumentNameConfig documentNameConfig) { + super(repository, template, AccessPlan.class); this.repository = repository; + this.documentNameConfig = documentNameConfig; + } + + @Override + public String[] getBasicRoles() { + return basicRoles; } @Override public AccessPlan findByDescription(final String descricao) { - final AccessPlan AccessPlan = this.repository.findByDescription(descricao); - if (AccessPlan == null) { + final AccessPlan accessPlan = this.repository.findByDescription(descricao); + if (accessPlan == null) { throw new MuttleyNotFoundException(AccessPlan.class, "descricao", "Registro não encontrado"); } - return AccessPlan; + return accessPlan; + } + + @Override + public AccessPlan findByName(String name) { + final AccessPlan accessPlan = this.repository.findByName(name); + if (accessPlan == null) { + throw new MuttleyNotFoundException(AccessPlan.class, "name", "Registro não encontrado"); + } + return accessPlan; + } + + @Override + public AccessPlan findByOwner(String idOwner) { + /** + * db.getCollection("muttley-owners").aggregate([ + * {$match:{"_id":ObjectId("632c4a6636f59f5538c3c8b6")}}, + * {$lookup:{ + * from:"muttley-access-plans", + * localField:"accessPlan.$id", + * foreignField:"_id", + * as:"accessPlan" + * }}, + * {$replaceRoot:{newRoot:{$arrayElemAt:["$accessPlan", 0]}}} + * ]) + */ + final List operationList = new LinkedList<>(); + operationList.add(match(Criteria.where("_id").is(new ObjectId(idOwner)))); + operationList.add(context -> new BasicDBObject("$lookup", + new BasicDBObject("from", this.documentNameConfig.getNameCollectionAccessPlan()) + .append("localField", "accessPlan.$id") + .append("foreignField", "_id") + .append("as", "accessPlan") + )); + operationList.add(context -> new BasicDBObject("$replaceRoot", + new BasicDBObject("newRoot", + new BasicDBObject("$arrayElemAt", asList("$accessPlan", 0)) + ) + )); + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation(operationList), + this.documentNameConfig.getNameCollectionOwner(), + AccessPlan.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + throw new MuttleyNotFoundException(AccessPlan.class, "", "Registro não encontrado"); + } + return results.getUniqueMappedResult(); } @Override diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AdminOwnerServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AdminOwnerServiceImpl.java new file mode 100644 index 00000000..9aed7420 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AdminOwnerServiceImpl.java @@ -0,0 +1,183 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.OwnerDataImpl; +import br.com.muttley.model.security.User; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import br.com.muttley.security.server.events.OwnerCreateEvent; +import br.com.muttley.security.server.repository.AdminOwnerRepository; +import br.com.muttley.security.server.service.AdminOwnerService; +import com.mongodb.BasicDBObject; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +import static java.util.Arrays.asList; +import static java.util.Objects.isNull; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.lookup; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.replaceRoot; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * @author Joel Rodrigues Moreira on 26/02/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + * Service do owner do odin + */ +@Service +public class AdminOwnerServiceImpl extends SecurityServiceImpl implements AdminOwnerService { + private final AdminOwnerRepository repository; + private final ApplicationEventPublisher eventPublisher; + private final DocumentNameConfig documentNameConfig; + private static final String[] basicRoles = new String[]{"owner"}; + + @Autowired + public AdminOwnerServiceImpl(final AdminOwnerRepository repository, final MongoTemplate mongoTemplate, final ApplicationEventPublisher eventPublisher, final DocumentNameConfig documentNameConfig) { + super(repository, mongoTemplate, AdminOwner.class); + this.repository = repository; + this.eventPublisher = eventPublisher; + this.documentNameConfig = documentNameConfig; + } + + @Override + public String[] getBasicRoles() { + return basicRoles; + } + + @Override + public void checkPrecondictionSave(final User user, final AdminOwner value) { + if (this.repository.existsByUserMaster(value.getUserMaster())) { + throw new MuttleyBadRequestException(Owner.class, "userMaster", "Já existe um owner cadastrado com esse usuário master"); + } + } + + @Override + public AdminOwner save(final User user, final AdminOwner value) { + if (value.getUserMaster() == null || value.getUserMaster().getId() == null) { + throw new MuttleyBadRequestException(AdminOwner.class, "userMaster", "Informe um usuário válido"); + } + if (value.getAccessPlan() == null || value.getAccessPlan().getId() == null) { + throw new MuttleyBadRequestException(AdminOwner.class, "accessPlan", "Informe um plano de acesso"); + } + final AdminOwner salvedOwner = super.save(user, value); + this.eventPublisher.publishEvent(new OwnerCreateEvent(salvedOwner)); + return salvedOwner; + } + + @Override + public AdminOwner update(final User user, final AdminOwner value) { + if (value.getUserMaster() == null || value.getUserMaster().getId() == null) { + throw new MuttleyBadRequestException(AdminOwner.class, "userMaster", "Informe um usuário válido"); + } + return super.update(user, value); + } + + @Override + public void checkPrecondictionDelete(final User user, final String id) { + throw new MuttleyBadRequestException(AdminOwner.class, "id", "Não é possível deletear um owner"); + } + + @Override + public AdminOwner findByName(final String name) { + final AdminOwner clienteOwner = repository.findByName(name); + if (isNull(clienteOwner)) + throw new MuttleyNotFoundException(Owner.class, "name", "Registro não encontrado") + .addDetails("name", name); + return clienteOwner; + } + + @Override + public List loadOwnersOfUser(final User user) { + /** + * db.getCollection("muttley-users-base").aggregate([ + * {$match:{"users.user.$id":ObjectId("5feb26305c7fab2d6479b3ed")}}, + * {$unwind:"$users"}, + * {$match:{"users.user.$id":ObjectId("5feb26305c7fab2d6479b3ed")}}, + * {$project:{owner:{$objectToArray:"$owner"}}}, + * {$project:{owner:{$arrayElemAt:["$owner.v", 1]}}}, + * {$lookup:{ + * from: "muttley-owners", + * localField: "owner", + * foreignField:"_id", + * as: "owner" + * }}, + * {$unwind:"$owner"}, + * {$project:{_id:"$owner._id", name:"$owner.name", description:"$owner.description", userMaster:"$owner.userMaster"}} + * ]) + */ + final AggregationResults owners = this.mongoTemplate.aggregate( + newAggregation( + match(where("users.user.$id").is(new ObjectId(user.getId()))), + unwind("$users"), + match(where("users.user.$id").is(new ObjectId(user.getId()))), + project().and(context -> new BasicDBObject("$objectToArray", "$owner")).as("owner"), + project().and(context -> new BasicDBObject("$arrayElemAt", asList("$owner.v", 1))).as("owner"), + lookup(this.documentNameConfig.getNameCollectionAdminOwner(), "owner", "_id", "owner"), + unwind("$owner"), + project().and("$owner._id").as("_id") + .and("$owner.name").as("name") + .and("$owner.description").as("description") + .and("$owner.userMaster").as("userMaster") + ), + this.documentNameConfig.getNameCollectionAdminUserBase(), + OwnerDataImpl.class + ); + if (owners == null || CollectionUtils.isEmpty(owners.getMappedResults())) { + throw new MuttleyNoContentException(Owner.class, null, "Nenhum registro encontrado"); + } + return owners.getMappedResults(); + } + + @Override + public Owner findByUserAndId(final User user, final String id) { + /** + * db.getCollection("muttley-users-base").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"), "users.user.$id":ObjectId("5feb26305c7fab2d6479b3ed")}}, + * {$limit:1}, + * {$project:{owner:{$objectToArray:"$owner"}}}, + * {$project:{owner:{$arrayElemAt:["$owner.v", 1]}}}, + * {$lookup:{ + * from: "muttley-owners", + * localField: "owner", + * foreignField:"_id", + * as: "owner" + * }}, + * {$unwind:"$owner"}, + * {$replaceRoot:{newRoot:"$owner"}} + * ]) + */ + final AggregationResults owners = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(new ObjectId(id)).and("users.user.$id").is(new ObjectId(user.getId()))), + limit(1l), + project().and(context -> new BasicDBObject("$objectToArray", "$owner")).as("owner"), + project().and(context -> new BasicDBObject("$arrayElemAt", asList("$owner.v", 1))).as("owner"), + lookup(this.documentNameConfig.getNameCollectionAdminOwner(), "owner", "_id", "owner"), + unwind("$owner"), + replaceRoot("$owner") + ), + this.documentNameConfig.getNameCollectionAdminUserBase(), + Owner.class + ); + if (owners == null || owners.getUniqueMappedResult() == null) { + throw new MuttleyBadRequestException(Owner.class, null, "Nenhum registro encontrado"); + } + return owners.getUniqueMappedResult(); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AdminPassaportServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AdminPassaportServiceImpl.java new file mode 100644 index 00000000..42d75986 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AdminPassaportServiceImpl.java @@ -0,0 +1,347 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.localcache.services.LocalRolesService; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.admin.AdminPassaport; +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.events.ValidateOwnerInWorkGroupEvent; +import br.com.muttley.model.security.rolesconfig.AvaliableRoles; +import br.com.muttley.model.security.rolesconfig.event.AvaliableRolesEvent; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import br.com.muttley.security.server.repository.AdminPassaportRepository; +import br.com.muttley.security.server.service.AdminOwnerService; +import br.com.muttley.security.server.service.AdminPassaportService; +import br.com.muttley.security.server.service.UserRolesView; +import com.mongodb.BasicDBObject; +import com.mongodb.DBRef; +import org.bson.types.ObjectId; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static br.com.muttley.model.security.Role.ROLE_OWNER; +import static br.com.muttley.model.security.Role.ROLE_PASSAPORT_CREATE; +import static br.com.muttley.model.security.Role.ROLE_PASSAPORT_DELETE; +import static br.com.muttley.model.security.Role.ROLE_PASSAPORT_READ; +import static br.com.muttley.model.security.Role.ROLE_PASSAPORT_UPDATE; +import static br.com.muttley.model.security.rolesconfig.AvaliableRoles.newAvaliableRoles; +import static br.com.muttley.model.security.rolesconfig.AvaliableRoles.newViewRoleDefinition; +import static java.util.Arrays.asList; +import static java.util.Objects.isNull; +import static java.util.stream.Collectors.toSet; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * @author Joel Rodrigues Moreira 23/04/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class AdminPassaportServiceImpl extends SecurityServiceImpl implements AdminPassaportService { + private final AdminPassaportRepository repository; + private final UserRolesView userRolesView; + private static final String[] basicRoles = new String[]{"passaport"}; + private final DocumentNameConfig documentNameConfig; + private final ApplicationEventPublisher applicationEventPublisher; + private final AdminOwnerService ownerService; + private final LocalRolesService localRolesService; + + + public AdminPassaportServiceImpl( + final AdminPassaportRepository repository, + final UserRolesView userRolesView, + final MongoTemplate template, + final DocumentNameConfig documentNameConfig, + final ApplicationEventPublisher applicationEventPublisher, + final AdminOwnerService ownerService, + final LocalRolesService localRolesService) { + super(repository, template, AdminPassaport.class); + this.repository = repository; + this.userRolesView = userRolesView; + this.documentNameConfig = documentNameConfig; + this.applicationEventPublisher = applicationEventPublisher; + this.ownerService = ownerService; + this.localRolesService = localRolesService; + } + + + @Override + public String[] getBasicRoles() { + return basicRoles; + } + + @Override + public void beforeSave(final User user, final AdminPassaport passaport) { + //garantindo que não será alterado informações cruciais + if (!(user.getCurrentOwner() == null && passaport.getOwner() != null)) { + passaport.setOwner(user.getCurrentOwner()); + } + //adicionando as roles de dependencias + passaport.addRoles(this.loadAvaliableRoles(user).getDependenciesRolesFrom(passaport.getRoles())); + super.beforeSave(user, passaport); + } + + @Override + public void checkPrecondictionSave(final User user, final AdminPassaport passaport) { + + //verificando validando o owner + //o evento irá verificar se foi informado o owner corretamente + //pegando o owner da requisição atual, ou o owner já vindo no json caso seja uma requisição + //do servidor odin + this.applicationEventPublisher.publishEvent(new ValidateOwnerInWorkGroupEvent(user, passaport)); + + //só podemo aceitar salvar um grupo pro owner caso ainda não exista um + if (this.existPassaportForOwner(passaport) && passaport.containsRole(ROLE_OWNER)) { + if (!this.isEmpty(user)) { + throw new MuttleyBadRequestException(Passaport.class, "roles", "Não se pode existir mais de um grupo principal"); + } + } + final Map filter = new HashMap(2); + filter.put("owner.$id", user.getCurrentOwner() == null ? passaport.getOwner().getObjectId() : user.getCurrentOwner().getObjectId()); + filter.put("userMaster", passaport.getUserMaster() == null ? user : passaport.getUserMaster()); + filter.put("name", passaport.getName()); + if (this.repository.exists(filter)) { + throw new MuttleyBadRequestException(Passaport.class, "name", "Já existe um grupo de trabalho com este nome"); + } + + //validando usuário + passaport.setMembers( + passaport.getMembers() + .parallelStream() + .filter(it -> it.getId() != null && !"".equals(it.getUserName())) + .collect(toSet()) + ); + } + + @Override + public void afterSave(final User user, final AdminPassaport passaport) { + this.expire(user, passaport); + } + + @Override + public void beforeUpdate(final User user, final AdminPassaport passaport) { + //garantindo que não será alterado informações cruciais + passaport.setOwner(user.getCurrentOwner()); + passaport.addRoles(this.loadAvaliableRoles(user).getDependenciesRolesFrom(passaport.getRoles())); + super.beforeUpdate(user, passaport); + } + + @Override + public void checkPrecondictionUpdate(final User user, final AdminPassaport passaport) { + //não se pode alterar workteam que seja do owner + if (this.existPassaportForOwner(passaport) && passaport.containsRole(ROLE_OWNER)) { + throw new MuttleyBadRequestException(Passaport.class, "roles", "Não se pode editar o grupo principal"); + } + //verificando se o workteam é do owner ou não + final AdminPassaport other = this.findById(user, passaport.getId()); + if (other.containsRole(ROLE_OWNER)) { + throw new MuttleyBadRequestException(Passaport.class, "roles", "Não se pode editar o grupo principal"); + } + //validando usuário + passaport.setMembers( + passaport.getMembers() + .parallelStream() + .filter(it -> it.getId() != null && !"".equals(it.getUserName())) + .collect(toSet()) + ); + } + + @Override + public void afterUpdate(final User user, final AdminPassaport passaport) { + this.expire(user, passaport); + } + + @Override + public void beforeDelete(final User user, final AdminPassaport passaport) { + this.expire(user, passaport); + } + + @Override + public void checkPrecondictionDelete(final User user, final String id) { + final AdminPassaport passaport = this.findById(user, id); + if (passaport.containsRole(ROLE_OWNER)) { + throw new MuttleyBadRequestException(Passaport.class, "roles", "Não se pode excluir o grupo principal"); + } + this.expire(user, passaport); + super.checkPrecondictionDelete(user, id); + } + + /*@Override + public Long count(final User user, final Map allRequestParams) { + return this.repository.count(user.getCurrentOwner()); + }*/ + + @Override + public List findAll(final User user, final Map allRequestParams) { + return this.findByUser(user); + } + + @Override + public AdminPassaport findByName(final User user, final String name) { + final AdminPassaport cwt = repository.findByName(user.getCurrentOwner(), name); + if (isNull(cwt)) { + throw new MuttleyNotFoundException(Passaport.class, "name", "Registro não encontrado") + .addDetails("name", name); + } + return cwt; + } + + @Override + public List findByUserMaster(final AdminOwner owner, final User user) { + final List itens = repository.findByUserMaster(owner, user); + if (CollectionUtils.isEmpty(itens)) { + throw new MuttleyNoContentException(Passaport.class, "name", "Nenhum time de trabalho encontrado"); + } + return itens; + } + + @Override + public List findByUser(final User user) { + /** + *db.getCollection("muttley-work-teams").aggregate([ + * {$match:{$or:[{"userMaster.$id": ObjectId("5d49cca5a1d16f19595be983")}, {"members.$id":ObjectId("5d49cca5a1d16f19595be983")}]}}, + * ]) + */ + final AggregationResults passaportsResult = this.mongoTemplate.aggregate( + newAggregation( + match( + new Criteria().orOperator( + where("userMaster.$id").is(new ObjectId(user.getId())), + where("members.$id").is(new ObjectId(user.getId())) + ) + ) + ) + , AdminPassaport.class, AdminPassaport.class); + if (passaportsResult == null) { + throw new MuttleyNotFoundException(AdminPassaport.class, "members", "Nenhum passaport encontrado para o usuário informado"); + } + final List passaports = passaportsResult.getMappedResults(); + if (CollectionUtils.isEmpty(passaports)) { + throw new MuttleyNotFoundException(Passaport.class, "members", "Nenhum passaport encontrado para o usuário informado"); + } + return passaports; + } + + @Override + public Set loadCurrentRoles(final User user) { + return this.userRolesView.findByUser(user); + } + + @Override + public AvaliableRoles loadAvaliableRoles(final User user) { + final AvaliableRolesEvent event = new AvaliableRolesEvent(user, + newAvaliableRoles( + newViewRoleDefinition("Times de trabalho", "Ações relacionada a times de trabalho", ROLE_PASSAPORT_CREATE, ROLE_PASSAPORT_READ, ROLE_PASSAPORT_UPDATE, ROLE_PASSAPORT_DELETE) + ) + ); + + this.applicationEventPublisher.publishEvent(event); + + return event.getSource(); + } + + @Override + public void removeUserFromAllPassaport(final AdminOwner owner, final User user) { + this.mongoTemplate.updateMulti( + new Query( + where("owner.$id").is(owner.getObjectId()) + ), + new Update().pull("members", new BasicDBObject("$in", asList( + new DBRef(this.documentNameConfig.getNameCollectionUser(), user.getObjectId()) + ))), + Passaport.class + ); + } + + @Override + public AdminPassaport createPassaportFor(final User user, final String ownerId, final AdminPassaport passaport) { + final AdminOwner owner = this.ownerService.findById(user, ownerId); + passaport.setOwner(owner); + passaport.setUserMaster(owner.getUserMaster()); + //this.checkPrecondictionSave(owner.getUserMaster(), workTeam); + return passaport; + } + + @Override + public void configPassaports(final User user) { + //criando grupo principal + final AdminPassaport passaport = (AdminPassaport) new AdminPassaport() + .setName("Grupo principal") + .setDescription("Grupo principal do sistema criado específicamente para dar autorizações de uso do usuário principal do sistema (Owner)") + .setUserMaster(user) + .addRole(ROLE_OWNER); + + //verificando se não existe workTeam para o Owner + if (!existPassaportForOwner(passaport)) { + + + passaport.setUserMaster(user); + + //criando grupo + } + + + } + + /** + * Checando se existe grupo de trabalho para o Owner + */ + private boolean existPassaportForOwner(final AdminPassaport passaport) { + /** + * db.getCollection("muttley-passaports").aggregate([ + * {$match:{ + * owner: {'$ref' : 'muttley-owners', '$id' : ObjectId('5d07cece444c5b2ceb5e0942')}, + * userMaster:{'$ref' : 'muttley-users', '$id' : ObjectId('5d07cada444c5b2ceb5e0940')}, + * roles:{$elemMatch:{'roleName':'ROLE_OWNER'}} + * }}, + * { + * $count: "count" + * } + * ]) + */ + final AggregationResults result = this.mongoTemplate.aggregate( + newAggregation( + match( + //filtrando o owner + where("owner.$id").is(passaport.getOwner().getObjectId()) + //filtrando o usuário principal + .and("userMaster.$id").is(new ObjectId(passaport.getUserMaster().getId())) + //filtrando as roles + .and("roles").elemMatch( + new Criteria().is(ROLE_OWNER) + ) + ), + Aggregation.count().as("count") + ), AdminPassaport.class, UserViewServiceImpl.ResultCount.class + ); + if (result == null || result.getUniqueMappedResult() != null) { + return result.getUniqueMappedResult().getCount() > 0; + } + return false; + } + + private void expire(final User user, final AdminPassaport passaport) { + this.localRolesService.expireRoles(passaport.getUserMaster()); + passaport.getMembers().forEach(m -> { + this.localRolesService.expireRoles(m); + }); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AdminUserBaseServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AdminUserBaseServiceImpl.java new file mode 100644 index 00000000..45f5b059 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AdminUserBaseServiceImpl.java @@ -0,0 +1,374 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.model.admin.AdminOwner; +import br.com.muttley.model.admin.AdminUserBase; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBase; +import br.com.muttley.model.security.UserBaseItem; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.UserView; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import br.com.muttley.security.server.service.AdminPassaportService; +import br.com.muttley.security.server.service.AdminUserBaseService; +import br.com.muttley.security.server.service.UserDataBindingService; +import br.com.muttley.security.server.service.UserService; +import com.mongodb.BasicDBObject; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; + +import javax.validation.constraints.NotNull; +import java.util.Date; +import java.util.Set; + +import static br.com.muttley.model.security.Role.ROLE_USER_BASE_CREATE; +import static java.util.Arrays.asList; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.group; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.lookup; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Update.Position.FIRST; + +/** + * @author Joel Rodrigues Moreira on 26/11/2020. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class AdminUserBaseServiceImpl extends SecurityModelServiceImpl implements AdminUserBaseService { + private static final String[] basicRoles = new String[]{ROLE_USER_BASE_CREATE.getSimpleName()}; + private final UserService userService; + private final UserDataBindingService dataBindingService; + private final DocumentNameConfig documentNameConfig; + private final AdminPassaportService passaportService; + + @Autowired + public AdminUserBaseServiceImpl( + final MongoTemplate template, + final UserService userService, + final UserDataBindingService dataBindingService, + final DocumentNameConfig documentNameConfig, + final AdminPassaportService passaportService) { + super(template, AdminUserBase.class); + this.userService = userService; + this.dataBindingService = dataBindingService; + this.documentNameConfig = documentNameConfig; + this.passaportService = passaportService; + } + + @Override + public String[] getBasicRoles() { + return basicRoles; + } + + @Override + public void checkPrecondictionSave(final User user, final AdminUserBase value) { + if (this.count(user, null) == 1) { + throw new MuttleyBadRequestException(UserBase.class, null, "Já existe uma base de usuário cadastrada no sistema"); + } + } + + @Override + public void checkPrecondictionSave(final User user, final OwnerData owner, final AdminUserBase value) { + if (this.count(user, owner, null) == 1) { + throw new MuttleyBadRequestException(UserBase.class, null, "Já existe uma base de usuário cadastrada no sistema"); + } + } + + @Override + public void checkPrecondictionDelete(final User user, final String id) { + throw new MuttleyBadRequestException(Owner.class, "id", "Não é possível deletar a base de usuário"); + } + + @Override + public boolean userNameIsAvaliableForUserName(final User user, final String userName, final Set userNames) { + return this.userService.userNameIsAvaliableForUserName(userName, userNames); + } + + @Override + public boolean userNameIsAvaliable(final User user, final Set userNames) { + return this.userService.userNameIsAvaliable(userNames); + } + + @Override + public UserView findUserByEmailOrUserNameOrNickUser(final User user, final String emailOrUserName) { + return new UserView(this.userService.findUserByEmailOrUserNameOrNickUser(emailOrUserName)); + } + + @Override + public void addUserItemIfNotExists(final User user, final User userForAdd) { + this.addUserItemIfNotExists(user, new UserBaseItem(user, userForAdd, null, new Date(), true, null)); + } + + @Override + public void addUserItemIfNotExists(final User user, final UserBaseItem userForAdd) { + if (!this.hasBeenIncludedAnyGroup(user, userForAdd.getUser())) { + userForAdd.setAddedBy(user); + if (userForAdd.getDtCreate() == null) { + userForAdd.setDtCreate(new Date()); + } + if (userForAdd.getAddedBy() == null) { + userForAdd.setAddedBy(user); + } + this.validator.validate(userForAdd); + if (this.hasBeenIncludedAnyGroup(user, userForAdd.getUser())) { + throw new MuttleyBadRequestException(UserBase.class, "users", "Usuário já está presente na base"); + } + this.mongoTemplate.updateFirst( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + ), + new Update() + .push("users") + .atPosition(FIRST) + .each(userForAdd), + UserBase.class + ); + } else { + this.mongoTemplate.updateMulti( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("users.user.$id").is(new ObjectId(userForAdd.getUser().getId())) + ), + new Update().set("users.$.status", userForAdd.isStatus()), + UserBase.class + ); + } + } + + @Override + public void createNewUserAndAdd(final User user, final UserBaseItem item) { + final User userForSave = new User(item.getUserInfoForMerge()); + if (!item.dataBindingsIsEmpty()) { + item.getDataBindings().forEach(it -> { + if (it.getKey().isUnique()) { + if (this.dataBindingService.containsByKeyAndValueAndUserNameNotEq(user, userForSave.getUserName(), it.getKey(), it.getValue())) { + throw new MuttleyBadRequestException(UserDataBinding.class, "key", "Já existe um usuário que possui ligação com " + it.getKey().getDisplayKey() + " informado(a)"); + } + } + }); + } + final User salvedUser = userService.save(userForSave); + if (!item.dataBindingsIsEmpty()) { + this.dataBindingService.merge(user, salvedUser.getUserName(), item.getDataBindings()); + } + this.addUserItemIfNotExists(user, salvedUser); + } + + @Override + public void mergeUserItemIfExists(final User user, final UserBaseItem item) { + item.setUser(userService.update(user, new User(item.getUserInfoForMerge()))); + if (!item.dataBindingsIsEmpty()) { + this.dataBindingService.merge(user, item.getUser().getUserName(), item.getDataBindings()); + } + this.addUserItemIfNotExists(user, item); + } + + @Override + public void removeByUserName(final User user, final String userName) { + final User userLoaded = this.userService.findByUserName(userName); + this.mongoTemplate.updateFirst( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + ), + new Update() + .pull("users", new BasicDBObject("user.$id", new ObjectId(userLoaded.getId()))), + UserBase.class + ); + this.passaportService.removeUserFromAllPassaport((AdminOwner) user.getCurrentOwner(), userLoaded); + } + + @Override + public boolean hasBeenIncludedAnyGroup(final User user, final UserData userForCheck) { + return this.mongoTemplate.exists( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("users.user.$id").is(new ObjectId(userForCheck.getId())) + ), UserBase.class + ); + } + + @Override + public boolean hasBeenIncludedAnyGroup(final UserData userForCheck) { + return this.mongoTemplate.exists( + new Query( + /*where("owner.$id").is(user.getCurrentOwner().getObjectId())*/ + where("users.user.$id").is(new ObjectId(userForCheck.getId())) + ), UserBase.class + ); + } + + @Override + public boolean hasBeenIncludedAnyGroup(final User user, final String userNameForCheck) { + /** + * db.getCollection("muttley-users-base").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6")}}, + * {$unwind:"$users"}, + * {$project:{user:{$objectToArray:"$users.user"}}}, + * {$project:{user:{$arrayElemAt:["$user.v",1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as:"user" + * }}, + * {$unwind:"$user"}, + * {$match:{ + * $or:[ + * {"user.userName":"asdfasd234asdfasdf"}, + * {"user.email":"df7899@gmail.com"}, + * {"user.nickUsers":{$in:["asdfas"]}}, + * ] + * }}, + * {$group:{_id:"$user"}}, + * {$count:"result"} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId())), + unwind("$users"), + project().and(context -> new BasicDBObject("$objectToArray", "$users.user")).as("user"), + project().and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup( + this.documentNameConfig.getNameCollectionUser(), + "user", + "_id", + "user" + ), + unwind("$user"), + match(new Criteria().orOperator( + where("user.userName").is(userNameForCheck), + where("user.email").is(userNameForCheck), + where("user.nickUsers").in(asList(userNameForCheck)) + )), + group("$user"), + Aggregation.count().as("count") + ), + this.documentNameConfig.getNameCollectionUserBase(), + UserViewServiceImpl.ResultCount.class + ); + if (results == null || results.getUniqueMappedResult() == null || results.getUniqueMappedResult().getCount() == 0) { + return false; + } + if (results.getUniqueMappedResult().getCount() > 1) { + //não pode retornar mais de um usuário + throw new MuttleyException("Erro interno"); + } + + return true; + } + + @Override + public boolean hasBeenIncludedAnyGroup(final String userNameForCheck) { + /** + * db.getCollection("muttley-users-base").aggregate([ + * {$unwind:"$users"}, + * {$project:{user:{$objectToArray:"$users.user"}}}, + * {$project:{user:{$arrayElemAt:["$user.v",1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as:"user" + * }}, + * {$unwind:"$user"}, + * {$match:{ + * $or:[ + * {"user.userName":"asdfasd234asdfasdf"}, + * {"user.email":"df7899@gmail.com"}, + * {"user.nickUsers":{$in:["asdfas"]}}, + * ] + * }}, + * {$group:{_id:"$user"}}, + * {$count:"result"} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + unwind("$users"), + project().and(context -> new BasicDBObject("$objectToArray", "$users.user")).as("user"), + project().and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup( + this.documentNameConfig.getNameCollectionUser(), + "user", + "_id", + "user" + ), + unwind("$user"), + match(new Criteria().orOperator( + where("user.userName").is(userNameForCheck), + where("user.email").is(userNameForCheck), + where("user.nickUsers").in(asList(userNameForCheck)) + )), + group("$user"), + Aggregation.count().as("count") + ), + UserBase.class, + UserViewServiceImpl.ResultCount.class + ); + if (results == null || results.getUniqueMappedResult() == null || results.getUniqueMappedResult().getCount() == 0) { + return false; + } + if (results.getUniqueMappedResult().getCount() > 1) { + //não pode retornar mais de um usuário + throw new MuttleyException("Erro interno"); + } + + return true; + } + + /* */ + + /** + * Verifica se o usuário já existe na base + *//* + private boolean userHasBeenIncluded(final User user, final User userForCheck) { + return this.userHasBeenIncluded(user, userForCheck.getId()); + } + + private boolean userHasBeenIncluded(final User user, final String id) { + return this.mongoTemplate.exists( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("users.user.$id").is(new ObjectId(id)) + ), UserBase.class + ); + }*/ + + @Getter + @Setter + @Accessors(chain = true) + private static class UserItemForAdd { + @DBRef + @NotNull(message = "Informe o usuário que está efetuando essa operação") + private User addedBy; + + @DBRef + @NotNull(message = "Informe o usuário participante da base") + private User user; + + @NotNull + private Date dtCreate; + + private boolean status; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AuthServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AuthServiceImpl.java new file mode 100644 index 00000000..9bd7b1a8 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/AuthServiceImpl.java @@ -0,0 +1,106 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.JwtUser; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.preference.Preference; +import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.security.server.service.AuthService; +import br.com.muttley.security.server.service.UserDataBindingService; +import br.com.muttley.security.server.service.UserPreferencesService; +import br.com.muttley.security.server.service.UserService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 19/04/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class AuthServiceImpl implements AuthService { + + protected final String tokenHeader; + protected final UserService userService; + protected final UserPreferencesService preferencesService; + protected final UserDataBindingService dataBindingService; + + public AuthServiceImpl(@Value("${muttley.security.jwt.controller.tokenHeader-jwt:Authorization-jwt}") final String tokenHeader, final UserService userService, final UserPreferencesService preferencesService, final UserDataBindingService dataBindingService) { + this.tokenHeader = tokenHeader; + this.userService = userService; + this.preferencesService = preferencesService; + this.dataBindingService = dataBindingService; + } + + @Override + public Authentication getCurrentAuthentication() { + return SecurityContextHolder.getContext().getAuthentication(); + } + + @Override + public JwtUser getCurrentJwtUser() { + return (JwtUser) getCurrentAuthentication().getPrincipal(); + } + + @Override + public JwtToken getCurrentToken() { + return new JwtToken( + ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()) + .getRequest() + .getHeader(this.tokenHeader) + ); + } + + @Override + public User getCurrentUser() { + return this.userService.getUserFromToken(this.getCurrentToken()); + //return getCurrentJwtUser().getOriginUser(); + } + + @Override + public UserPreferences getUserPreferences() { + final User user = this.getCurrentUser(); + if (user.getPreferences() == null || user.getPreferences().isEmpty()) { + final UserPreferences preferences = this.preferencesService.getUserPreferences(user); + if (preferences != null) { + user.setPreferences(preferences); + } + } + return user.getPreferences(); + } + + @Override + public Preference getPreference(final String key) { + return this.preferencesService.getPreference(this.getCurrentUser(), key); + } + + @Override + public Set getDataBindings() { + final User user = this.getCurrentUser(); + if (user.getDataBindings() == null || user.dataBindingsIsEmpty()) { + try { + user.setDataBindings(this.dataBindingService.listBy(user)); + } catch (final MuttleyNoContentException ex) { + } + } + return null; + } + + @Override + public UserDataBinding getDataBinding(final String key) { + try { + return this.dataBindingService.getKey(this.getCurrentUser(), key); + } catch (final MuttleyNotFoundException ex) { + } + return null; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/ForgotPasswordServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/ForgotPasswordServiceImpl.java new file mode 100644 index 00000000..55a5c201 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/ForgotPasswordServiceImpl.java @@ -0,0 +1,120 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.exception.throwables.security.MuttleySecurityEmailNotFoundtException; +import br.com.muttley.exception.throwables.security.MuttleySecurityExpiredTokenException; +import br.com.muttley.exception.throwables.security.MuttleySecurityUserNotFoundException; +import br.com.muttley.model.recoveryPassword.ResetPasswordRequest; +import br.com.muttley.model.security.User; +import br.com.muttley.security.server.repository.UserRepository; +import br.com.muttley.security.server.service.ForgotPasswordService; +import br.com.muttley.security.server.service.PasswordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.ResponseEntity; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Service +public class ForgotPasswordServiceImpl implements ForgotPasswordService { + + private final JavaMailSender mailSender; + private final PasswordService passwordService; + + + private final UserRepository userRepository; + + + @Autowired + public ForgotPasswordServiceImpl(final JavaMailSender mailSender, PasswordService passwordService, final UserRepository userRepository) { + this.mailSender = mailSender; + this.passwordService = passwordService; + this.userRepository = userRepository; + } + + + @Override + public ResponseEntity forgotPassword(String email) { + try { + User user = userRepository.findByEmailOrEmailSecundario(email, email); + + if (user != null) { + String token = UUID.randomUUID().toString(); + user.setResetToken(token); + userRepository.save(user); + + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom("noreply@maxxsoft.com.br"); + helper.setSubject("Redefinição de Senha"); + helper.addTo(email); + + String emailTemplate = pathForTemplateEmail("recovery-password-template.html"); + emailTemplate = emailTemplate.replace("#{token}", token); + + helper.setText(emailTemplate, true); + mailSender.send(message); + + return ResponseEntity.ok("Email de recuperação de senha enviado com sucesso. Por favor, verifique sua caixa de entrada e a pasta de spam."); + } else { + throw new MuttleySecurityEmailNotFoundtException(User.class, "email", "Email não encontrado. Contate o suporte."); + } + + } catch (MessagingException | IOException e) { + throw new RuntimeException("Falha ao enviar o email de recuperação de senha. Tente novamente mais tarde.", e); + } + } + + + @Override + public ResponseEntity resetPassword(ResetPasswordRequest request) { + + User user = userRepository.findByResetToken(request.getToken()); + + + if (user == null) { + throw new MuttleySecurityUserNotFoundException(User.class, "token", "Token expirado. Solicite uma nova recuperação de senha."); + } + + + // Verifica se o token está expirado + if (user.getResetTokenExpiryDate().isBefore(LocalDateTime.now())) { + throw new MuttleySecurityExpiredTokenException(User.class, "token", "Token expirado. Solicite uma nova recuperação de senha."); + } + + passwordService.resetePasswordFor(user, request.getNewPassword()); + user.setResetToken(null); + user.setResetTokenExpiryDate(null); + userRepository.save(user); + + return ResponseEntity.ok("Senha redefinida com sucesso."); + } + + + public String pathForTemplateEmail(String template) throws IOException { + ClassPathResource resource = new ClassPathResource(template); + + StringBuilder contentBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + contentBuilder.append(line).append("\n"); + } + } + return contentBuilder.toString(); + } + + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/JwtTokenUtilService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/JwtTokenUtilServiceImpl.java similarity index 66% rename from muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/JwtTokenUtilService.java rename to muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/JwtTokenUtilServiceImpl.java index 7aa6a9af..8eba5a5a 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/JwtTokenUtilService.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/JwtTokenUtilServiceImpl.java @@ -1,6 +1,9 @@ package br.com.muttley.security.server.service.impl; import br.com.muttley.model.security.JwtUser; +import br.com.muttley.model.security.Password; +import br.com.muttley.model.security.User; +import br.com.muttley.security.server.service.SecretService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -9,9 +12,11 @@ import org.springframework.mobile.device.Device; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import java.io.Serializable; import java.time.Instant; +import java.time.temporal.ChronoField; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -22,26 +27,29 @@ * @project muttley-cloud */ @Service -public class JwtTokenUtilService implements Serializable { +public class JwtTokenUtilServiceImpl implements Serializable, br.com.muttley.security.server.service.JwtTokenUtilService { - static final String CLAIM_KEY_USERNAME = "sub"; - static final String CLAIM_KEY_AUDIENCE = "audience"; - static final String CLAIM_KEY_CREATED = "created"; + private static final String CLAIM_KEY_USERNAME = "sub"; + private static final String CLAIM_KEY_AUDIENCE = "audience"; + private static final String CLAIM_KEY_CREATED = "created"; + + private static final String CLAIM_KEY_DETAILS = "details"; private static final String AUDIENCE_UNKNOWN = "unknown"; private static final String AUDIENCE_WEB = "web"; private static final String AUDIENCE_MOBILE = "mobile"; private static final String AUDIENCE_TABLET = "tablet"; - @Value("${muttley.security.jwt.token.expiration}") + @Value("${muttley.security.jwt.token.expiration.seconds}") private long expiration; private final SecretService secretService; @Autowired - public JwtTokenUtilService(final SecretService secretService) { + public JwtTokenUtilServiceImpl(final SecretService secretService) { this.secretService = secretService; } + @Override public final String getUsernameFromToken(final String token) { try { final Claims claims = getClaimsFromToken(token); @@ -53,6 +61,7 @@ public final String getUsernameFromToken(final String token) { return null; } + @Override public final Date getCreatedDateFromToken(final String token) { Date created; try { @@ -64,6 +73,7 @@ public final Date getCreatedDateFromToken(final String token) { return created; } + @Override public final Date getExpirationDateFromToken(final String token) { Date expiration; try { @@ -75,6 +85,7 @@ public final Date getExpirationDateFromToken(final String token) { return expiration; } + @Override public final String getAudienceFromToken(final String token) { String audience; try { @@ -100,12 +111,15 @@ private final Claims getClaimsFromToken(final String token) { } private final Date generateExpirationDate() { - return Date.from(Instant.now().plusMillis(expiration)); + return Date.from(Instant.now().plusSeconds(expiration)); } private final boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); - return expiration.before(new Date()); + if (expiration != null) { + return expiration.before(new Date()); + } + return true; } private boolean isCreatedBeforeLastPasswordReset(final Date created, final Date lastPasswordReset) { @@ -129,11 +143,37 @@ private final boolean ignoreTokenExpiration(final String token) { return (AUDIENCE_TABLET.equals(audience) || AUDIENCE_MOBILE.equals(audience)); } + @Override public final String generateToken(final UserDetails userDetails, Device device) { + return generateToken(userDetails, device, null); + } + + @Override + public final String generateToken(final UserDetails userDetails, final Device device, Map details) { Map claims = new HashMap<>(); claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername()); claims.put(CLAIM_KEY_AUDIENCE, generateAudience(device)); + claims.put(CLAIM_KEY_CREATED, Instant.now().get(ChronoField.INSTANT_SECONDS)); + if (details != null) { + claims.put(CLAIM_KEY_DETAILS, details); + } + return generateToken(claims); + } + + @Override + public String generateToken(final User user, final Device device) { + return generateToken(user, device, null); + } + + @Override + public String generateToken(final User user, final Device device, final Map details) { + Map claims = new HashMap<>(); + claims.put(CLAIM_KEY_USERNAME, user.getUserName()); + claims.put(CLAIM_KEY_AUDIENCE, generateAudience(device)); claims.put(CLAIM_KEY_CREATED, new Date()); + if (details != null) { + claims.put(CLAIM_KEY_DETAILS, details); + } return generateToken(claims); } @@ -145,12 +185,14 @@ final String generateToken(final Map claims) { .compact(); } + @Override public final boolean canTokenBeRefreshed(final String token, final Date lastPasswordReset) { final Date created = getCreatedDateFromToken(token); return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset) && (!isTokenExpired(token) || ignoreTokenExpiration(token)); } + @Override public final String refreshToken(final String token) { String refreshedToken; try { @@ -163,7 +205,8 @@ public final String refreshToken(final String token) { return refreshedToken; } - public final boolean validateToken(final String token, final UserDetails userDetails) { + @Override + public final boolean validateTokenWithUser(final String token, final UserDetails userDetails) { final JwtUser user = (JwtUser) userDetails; final String username = getUsernameFromToken(token); final Date created = getCreatedDateFromToken(token); @@ -173,4 +216,20 @@ public final boolean validateToken(final String token, final UserDetails userDet && !isTokenExpired(token) && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())); } + + @Override + public boolean validateTokenWithUser(final String token, final User user, final Password password) { + final String username = getUsernameFromToken(token); + final Date created = getCreatedDateFromToken(token); + //final Date expiration = getExpirationDateFromToken(token); + return ( + username.equals(user.getUserName()) + && !isTokenExpired(token) + && !isCreatedBeforeLastPasswordReset(created, password.getMetadata().getHistoric().getDtChange())); + } + + @Override + public boolean isValidToken(final String token) { + return !StringUtils.isEmpty(getUsernameFromToken(token)) && !this.isTokenExpired(token); + } } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalDatabindingServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalDatabindingServiceImpl.java new file mode 100644 index 00000000..4ca5e704 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalDatabindingServiceImpl.java @@ -0,0 +1,65 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.localcache.services.impl.AbstractLocalDatabindingServiceImpl; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.server.events.CurrentDatabindingResolverEvent; +import br.com.muttley.security.server.events.CurrentDatabindingResolverEvent.CurrentDatabindingEventItem; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class LocalDatabindingServiceImpl extends AbstractLocalDatabindingServiceImpl { + private final ApplicationEventPublisher publisher; + + @Autowired + public LocalDatabindingServiceImpl(final RedisService redisService, final ApplicationEventPublisher publisher) { + super(redisService); + this.publisher = publisher; + } + + @Override + public List getUserDataBindings(final JwtToken jwtUser, final User user) { + final List dataBindings; + //verificando se já existe no cache + if (this.redisService.hasKey(this.getBasicKey(user))) { + //recuperando dos itens + dataBindings = this.getDatabinDataBindingsInCache(jwtUser, user); + } else { + final CurrentDatabindingResolverEvent event = new CurrentDatabindingResolverEvent(new CurrentDatabindingEventItem(jwtUser, user)); + this.publisher.publishEvent(event); + dataBindings = event.getDataBindings(); + //salvando no cache + this.saveDatabindingsInCache(jwtUser, user, dataBindings); + } + return dataBindings; + } + + @Override + public List getUserDataBindings(final XAPIToken token, final User user) { + final List dataBindings; + //verificando se já existe no cache + if (this.redisService.hasKey(this.getBasicKey(user))) { + //recuperando dos itens + dataBindings = this.getDatabinDataBindingsInCache(token, user); + } else { + final CurrentDatabindingResolverEvent event = new CurrentDatabindingResolverEvent(new CurrentDatabindingEventItem(token, user)); + this.publisher.publishEvent(event); + dataBindings = event.getDataBindings(); + //salvando no cache + this.saveDatabindingsInCache(token, user, dataBindings); + } + return dataBindings; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalRolesServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalRolesServiceImpl.java new file mode 100644 index 00000000..51b1ba87 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalRolesServiceImpl.java @@ -0,0 +1,64 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.localcache.services.impl.AbstractLocalRolesServiceImpl; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.server.events.CurrentRolesResolverEvent; +import br.com.muttley.security.server.events.CurrentRolesResolverEvent.CurrentRolesResolverEventItem; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class LocalRolesServiceImpl extends AbstractLocalRolesServiceImpl { + private final ApplicationEventPublisher publisher; + + @Autowired + public LocalRolesServiceImpl(final RedisService redisService, final ApplicationEventPublisher publisher) { + super(redisService); + this.publisher = publisher; + } + + @Override + public Set loadCurrentRoles(final JwtToken token, final User user) { + final Set roles; + //verificando se já existe roles para esse usuário + if (this.redisService.hasKey(this.getBasicKey(user))) { + roles = this.loadRolesInCache(user); + } else { + final CurrentRolesResolverEvent event = new CurrentRolesResolverEvent(new CurrentRolesResolverEventItem(token, user)); + publisher.publishEvent(event); + //se chegou até aqui precisaremos buscar as roles do server + roles = event.getRoles(); + //salvando as roles no cache + this.saveRolesInCache(token, user, roles); + } + return roles; + } + + public Set loadCurrentRoles(final XAPIToken token, final User user) { + final Set roles; + //verificando se já existe roles para esse usuário + if (this.redisService.hasKey(this.getBasicKey(user))) { + roles = this.loadRolesInCache(user); + } else { + final CurrentRolesResolverEvent event = new CurrentRolesResolverEvent(new CurrentRolesResolverEventItem(token, user)); + publisher.publishEvent(event); + //se chegou até aqui precisaremos buscar as roles do server + roles = event.getRoles(); + //salvando as roles no cache + this.saveRolesInCache(token, user, roles); + } + return roles; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalUserPrefenceServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalUserPrefenceServiceImpl.java new file mode 100644 index 00000000..18e5d5cd --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalUserPrefenceServiceImpl.java @@ -0,0 +1,56 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.exception.throwables.security.MuttleySecurityBadRequestException; +import br.com.muttley.localcache.services.impl.AbstractLocalUserPrefenceServiceImpl; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.events.DeserializeUserPreferencesEvent; +import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.server.events.CurrentPreferencesResolverEvent; +import br.com.muttley.security.server.events.CurrentPreferencesResolverEvent.CurrentPreferencesResolverEventItem; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class LocalUserPrefenceServiceImpl extends AbstractLocalUserPrefenceServiceImpl { + @Autowired + public LocalUserPrefenceServiceImpl(final RedisService redisService, final ApplicationEventPublisher publisher) { + super(redisService, publisher); + } + + @Override + public UserPreferences getUserPreferences(final JwtToken jwtUser, final User user) { + if (jwtUser.getUsername().equals(user.getUserName())) { + final UserPreferences preferences; + //verificando se já existe a chave + if (this.redisService.hasKey(this.getBasicKey(user))) { + //recuperando as preferencias do usuário do cache + preferences = this.getPreferenceInCache(jwtUser, user); + } else { + //se chegou aqui é sinal que ainda não exite um preferencia carregada em cache, logo devemo buscar + //recuperando as preferencias do servidor de segurança + final CurrentPreferencesResolverEvent eventCurrentPref = new CurrentPreferencesResolverEvent(new CurrentPreferencesResolverEventItem(jwtUser, user)); + this.publisher.publishEvent(eventCurrentPref); + preferences = eventCurrentPref.getUserPreferences(); + //salvando no cache para evitar loops desnecessários + this.savePreferenceInCache(jwtUser, user, preferences); + if (preferences != null) { + //por comodidade vamo disparar o evento para resolução dos itens das preferencias + final DeserializeUserPreferencesEvent event = new DeserializeUserPreferencesEvent(new DeserializeUserPreferencesEvent.UserPreferencesResolverEventItem(user, preferences)); + this.publisher.publishEvent(event); + //salvando no cache novamente para guardar as modificações + this.savePreferenceInCache(jwtUser, user, preferences); + } + } + return preferences; + } + throw new MuttleySecurityBadRequestException(UserPreferences.class, "user", "O usuário informado é diferente do presente no token"); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalWorkTeamServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalWorkTeamServiceImpl.java new file mode 100644 index 00000000..d48c3bc0 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/LocalWorkTeamServiceImpl.java @@ -0,0 +1,53 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.localcache.services.impl.AbstractLocalWorkTemaServiceImpl; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.workteam.WorkTeamDomain; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.server.service.WorkTeamService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import static br.com.muttley.localcache.services.LocalWorkTeamService.getBasicKey; + +/** + * @author Joel Rodrigues Moreira on 21/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class LocalWorkTeamServiceImpl extends AbstractLocalWorkTemaServiceImpl { + private final WorkTeamService service; + + @Autowired + protected LocalWorkTeamServiceImpl(RedisService redisService, WorkTeamService service) { + super(redisService); + this.service = service; + } + + @Override + public WorkTeamDomain getWorkTeamDomain(JwtToken token, User user) { + final WorkTeamDomain workTeamDomain; + if (this.redisService.hasKey(getBasicKey(user.getCurrentOwner(), user))) { + workTeamDomain = (WorkTeamDomain) redisService.get(getBasicKey(user.getCurrentOwner(), user)); + } else { + workTeamDomain = service.loadDomain(user); + this.save(token, user, workTeamDomain); + } + return workTeamDomain; + } + + @Override + public WorkTeamDomain getWorkTeamDomain(XAPIToken token, User user) { + final WorkTeamDomain workTeamDomain; + if (this.redisService.hasKey(getBasicKey(user.getCurrentOwner(), user))) { + workTeamDomain = (WorkTeamDomain) redisService.get(getBasicKey(user.getCurrentOwner(), user)); + } else { + workTeamDomain = service.loadDomain(user); + this.save(token, user, workTeamDomain); + } + return workTeamDomain; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/NoSecurityOwnerServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/NoSecurityOwnerServiceImpl.java new file mode 100644 index 00000000..5fdc2214 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/NoSecurityOwnerServiceImpl.java @@ -0,0 +1,71 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.domain.service.Validator; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.headers.services.MetadataService; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.Passaport; +import br.com.muttley.security.server.events.NoSecurityOwnerCreateEvent; +import br.com.muttley.security.server.events.OwnerCreateEvent; +import br.com.muttley.security.server.repository.OwnerRepository; +import br.com.muttley.security.server.service.NoSecurityOwnerService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.stereotype.Service; + +/** + * @author Joel Rodrigues Moreira on 21/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class NoSecurityOwnerServiceImpl implements NoSecurityOwnerService { + protected final OwnerRepository repository; + @Autowired + protected MetadataService metadataService; + private final MongoTemplate template; + + @Autowired + protected Validator validator; + private final ApplicationEventPublisher publisher; + + @Autowired + public NoSecurityOwnerServiceImpl(final MongoTemplate template, final OwnerRepository repository, final ApplicationEventPublisher publisher) { + this.template = template; + this.repository = repository; + this.publisher = publisher; + } + + @Override + public Owner findByName(final String name) { + //validando contexto de execução + //this.validateContext(); + final Owner owner = this.repository.findByName(name); + if (owner == null) { + throw new MuttleyNotFoundException(Owner.class, "name", "Registro não encontrado"); + } + return owner; + } + + @Override + public Owner save(final Owner owner) { + //validando contexto de execução + //this.validateContext(); + //verificando se realmente está criando um novo registro + if (owner.getId() != null) { + throw new MuttleyBadRequestException(Passaport.class, "id", "Não é possível criar um registro com um id existente"); + } + //validando dados + this.validator.validate(owner); + /*//verificando precondições + this.checkPrecondictionSave(user, value); + this.beforeSave(user, value);*/ + //garantindo que o metadata ta preenchido + this.metadataService.generateNewMetadataFor(owner.getUserMaster(), owner); + final Owner salvedOwner = this.repository.save(owner); + this.publisher.publishEvent(new NoSecurityOwnerCreateEvent(salvedOwner)); + return salvedOwner; + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/NoSecurityUserBaseServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/NoSecurityUserBaseServiceImpl.java new file mode 100644 index 00000000..704fffc0 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/NoSecurityUserBaseServiceImpl.java @@ -0,0 +1,125 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.domain.service.Validator; +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBase; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import br.com.muttley.security.server.service.NoSecurityUserBaseService; +import br.com.muttley.security.server.service.PassaportService; +import br.com.muttley.security.server.service.UserDataBindingService; +import br.com.muttley.security.server.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.stereotype.Service; + +import static br.com.muttley.model.security.Role.ROLE_USER_BASE_CREATE; +import static java.util.Arrays.asList; +import static org.springframework.data.domain.Sort.Direction.DESC; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.sort; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * @author Joel Rodrigues Moreira on 26/11/2020. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class NoSecurityUserBaseServiceImpl extends SecurityModelServiceImpl implements NoSecurityUserBaseService { + private static final String[] basicRoles = new String[]{ROLE_USER_BASE_CREATE.getSimpleName()}; + private final String ODIN_USER; + private final UserService userService; + private final UserDataBindingService dataBindingService; + private final DocumentNameConfig documentNameConfig; + private final PassaportService passaportService; + private final Validator validator; + private final ApplicationEventPublisher publisher; + + @Autowired + public NoSecurityUserBaseServiceImpl( + final MongoTemplate template, + @Value("${muttley.security.odin.user}") final String odinUser, + final UserService userService, + final UserDataBindingService dataBindingService, + final DocumentNameConfig documentNameConfig, + final PassaportService passaportService, + final Validator validator, + final ApplicationEventPublisher publisher) { + super(template, UserBase.class); + this.ODIN_USER = odinUser; + this.userService = userService; + this.dataBindingService = dataBindingService; + this.documentNameConfig = documentNameConfig; + this.passaportService = passaportService; + this.validator = validator; + this.publisher = publisher; + } + + @Override + public String[] getBasicRoles() { + return basicRoles; + } + + public UserBase save(final User user, final OwnerData owner, final UserBase value) { + //somente usuario do serviço do odin podem fazer requisição para aqui + //verificando se realmente está criando um novo registro + checkIdForSave(value); + //setando o dono do registro + value.setOwner((Owner) owner); + //garantindo que o metadata ta preenchido + this.metadataService.generateNewMetadataFor(user, value); + //processa regra de negocio antes de qualquer validação + this.beforeSave(user, value); + //verificando precondições + this.checkPrecondictionSave(user, owner, value); + //validando dados do objeto + this.validator.validate(value); + final UserBase salvedValue = saveByTemplate((Owner) owner, value); + //realizando regras de enegocio depois do objeto ter sido salvo + this.afterSave(user, salvedValue); + //valor salvo + return salvedValue; + } + + private UserBase saveByTemplate(final Owner owner, final UserBase value) { + //validateOwner(owner); + value.setOwner(owner); + //salvando o registro + this.mongoTemplate.save(value); + //pegando o registro salvo + final AggregationResults results; + if (!value.contaisObjectId()) { + results = mongoTemplate.aggregate( + newAggregation( + asList( + sort(DESC, "id"), + limit(1) + ) + ), this.clazz, + this.clazz + ); + } else { + results = mongoTemplate.aggregate( + newAggregation( + asList( + match(where("owner.$id").is(owner.getObjectId()).and("id").is(value.getObjectId())), + limit(1) + ) + ), this.clazz, + this.clazz + ); + } + if (results == null || results.getUniqueMappedResult() == null) { + throw new MuttleyException(); + } + return results.getUniqueMappedResult(); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/OwnerServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/OwnerServiceImpl.java index 8e310f80..d6e2c2de 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/OwnerServiceImpl.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/OwnerServiceImpl.java @@ -1,17 +1,38 @@ package br.com.muttley.security.server.service.impl; import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyNoContentException; import br.com.muttley.exception.throwables.MuttleyNotFoundException; import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.OwnerDataImpl; import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBase; +import br.com.muttley.security.server.config.model.DocumentNameConfig; import br.com.muttley.security.server.events.OwnerCreateEvent; import br.com.muttley.security.server.repository.OwnerRepository; import br.com.muttley.security.server.service.OwnerService; +import com.mongodb.BasicDBObject; +import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import java.util.List; + +import static java.util.Arrays.asList; import static java.util.Objects.isNull; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.lookup; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.replaceRoot; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.query.Criteria.where; /** * @author Joel Rodrigues Moreira on 26/02/18. @@ -23,12 +44,27 @@ public class OwnerServiceImpl extends SecurityServiceImpl implements OwnerService { private final OwnerRepository repository; private final ApplicationEventPublisher eventPublisher; + private final DocumentNameConfig documentNameConfig; + private static final String[] basicRoles = new String[]{"owner"}; @Autowired - public OwnerServiceImpl(final OwnerRepository repository, final ApplicationEventPublisher eventPublisher) { - super(repository, Owner.class); + public OwnerServiceImpl(final OwnerRepository repository, final MongoTemplate mongoTemplate, final ApplicationEventPublisher eventPublisher, final DocumentNameConfig documentNameConfig) { + super(repository, mongoTemplate, Owner.class); this.repository = repository; this.eventPublisher = eventPublisher; + this.documentNameConfig = documentNameConfig; + } + + @Override + public String[] getBasicRoles() { + return basicRoles; + } + + @Override + public void checkPrecondictionSave(final User user, final Owner value) { + if (this.repository.existsByUserMaster(value.getUserMaster())) { + throw new MuttleyBadRequestException(Owner.class, "userMaster", "Já existe um owner cadastrado com esse usuário master"); + } } @Override @@ -52,6 +88,11 @@ public Owner update(final User user, final Owner value) { return super.update(user, value); } + @Override + public void checkPrecondictionDelete(final User user, final String id) { + throw new MuttleyBadRequestException(Owner.class, "id", "Não é possível deletear um owner"); + } + @Override public Owner findByName(final String name) { final Owner clienteOwner = repository.findByName(name); @@ -60,4 +101,88 @@ public Owner findByName(final String name) { .addDetails("name", name); return clienteOwner; } + + @Override + public List loadOwnersOfUser(final User user) { + /** + * db.getCollection("muttley-users-base").aggregate([ + * {$match:{"users.user.$id":ObjectId("5feb26305c7fab2d6479b3ed")}}, + * {$unwind:"$users"}, + * {$match:{"users.user.$id":ObjectId("5feb26305c7fab2d6479b3ed")}}, + * {$project:{owner:{$objectToArray:"$owner"}}}, + * {$project:{owner:{$arrayElemAt:["$owner.v", 1]}}}, + * {$lookup:{ + * from: "muttley-owners", + * localField: "owner", + * foreignField:"_id", + * as: "owner" + * }}, + * {$unwind:"$owner"}, + * {$project:{_id:"$owner._id", name:"$owner.name", description:"$owner.description", userMaster:"$owner.userMaster"}} + * ]) + */ + final AggregationResults owners = this.mongoTemplate.aggregate( + newAggregation( + match(where("users.user.$id").is(new ObjectId(user.getId()))), + unwind("$users"), + match(where("users.user.$id").is(new ObjectId(user.getId()))), + project().and(context -> new BasicDBObject("$objectToArray", "$owner")).as("owner"), + project().and(context -> new BasicDBObject("$arrayElemAt", asList("$owner.v", 1))).as("owner"), + lookup(this.documentNameConfig.getNameCollectionOwner(), "owner", "_id", "owner"), + unwind("$owner"), + project().and("$owner._id").as("_id") + .and("$owner.name").as("name") + .and("$owner.description").as("description") + .and("$owner.userMaster").as("userMaster") + ), + UserBase.class, + OwnerDataImpl.class + ); + if (owners == null || CollectionUtils.isEmpty(owners.getMappedResults())) { + throw new MuttleyNoContentException(Owner.class, null, "Nenhum registro encontrado"); + } + return owners.getMappedResults(); + } + + @Override + public Owner findByUserAndId(final User user, final String id) { + /** + * db.getCollection("muttley-users-base").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"), "users.user.$id":ObjectId("5feb26305c7fab2d6479b3ed")}}, + * {$limit:1}, + * {$project:{owner:{$objectToArray:"$owner"}}}, + * {$project:{owner:{$arrayElemAt:["$owner.v", 1]}}}, + * {$lookup:{ + * from: "muttley-owners", + * localField: "owner", + * foreignField:"_id", + * as: "owner" + * }}, + * {$unwind:"$owner"}, + * {$replaceRoot:{newRoot:"$owner"}} + * ]) + */ + final AggregationResults owners = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(new ObjectId(id)).and("users.user.$id").is(new ObjectId(user.getId()))), + limit(1l), + project().and(context -> new BasicDBObject("$objectToArray", "$owner")).as("owner"), + project().and(context -> new BasicDBObject("$arrayElemAt", asList("$owner.v", 1))).as("owner"), + lookup(this.documentNameConfig.getNameCollectionOwner(), "owner", "_id", "owner"), + unwind("$owner"), + replaceRoot("$owner") + ), + UserBase.class, + Owner.class + ); + if (owners == null || owners.getUniqueMappedResult() == null) { + throw new MuttleyBadRequestException(Owner.class, null, "Nenhum registro encontrado"); + } + return owners.getUniqueMappedResult(); + } + + @Override + public Owner loadCurrentOwner(User user) { + return this.findById(user, user.getCurrentOwner().getId()); + } } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/PassaportServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/PassaportServiceImpl.java new file mode 100644 index 00000000..72e87888 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/PassaportServiceImpl.java @@ -0,0 +1,379 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.localcache.services.LocalRolesService; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.events.ValidateOwnerInWorkGroupEvent; +import br.com.muttley.model.security.rolesconfig.AvaliableRoles; +import br.com.muttley.model.security.rolesconfig.event.AvaliableRolesEvent; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import br.com.muttley.security.server.repository.PassaportRepository; +import br.com.muttley.security.server.service.OwnerService; +import br.com.muttley.security.server.service.PassaportService; +import br.com.muttley.security.server.service.UserRolesView; +import com.mongodb.BasicDBObject; +import com.mongodb.DBRef; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static br.com.muttley.model.security.Role.ROLE_OWNER; +import static br.com.muttley.model.security.Role.ROLE_WORK_TEAM_CREATE; +import static br.com.muttley.model.security.Role.ROLE_WORK_TEAM_DELETE; +import static br.com.muttley.model.security.Role.ROLE_WORK_TEAM_READ; +import static br.com.muttley.model.security.Role.ROLE_WORK_TEAM_UPDATE; +import static br.com.muttley.model.security.rolesconfig.AvaliableRoles.newAvaliableRoles; +import static br.com.muttley.model.security.rolesconfig.AvaliableRoles.newViewRoleDefinition; +import static br.com.muttley.model.security.rolesconfig.FontSet.MDI; +import static br.com.muttley.model.security.rolesconfig.ViewRoleDefinition.newRoleDefinition; +import static java.util.Arrays.asList; +import static java.util.Objects.isNull; +import static java.util.stream.Collectors.toSet; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Update.Position.FIRST; + +/** + * @author Joel Rodrigues Moreira on 26/02/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + * Service do owner do odin + */ +@Service +public class PassaportServiceImpl extends SecurityServiceImpl implements PassaportService { + private final PassaportRepository repository; + private final UserRolesView userRolesView; + private static final String[] basicRoles = new String[]{"passaport"}; + private final DocumentNameConfig documentNameConfig; + private final ApplicationEventPublisher applicationEventPublisher; + private final OwnerService ownerService; + private final LocalRolesService localRolesService; + + @Autowired + public PassaportServiceImpl(final PassaportRepository repository, final UserRolesView userRolesView, final MongoTemplate template, final DocumentNameConfig documentNameConfig, final ApplicationEventPublisher applicationEventPublisher, final OwnerService ownerService, final LocalRolesService localRolesService) { + super(repository, template, Passaport.class); + this.repository = repository; + this.userRolesView = userRolesView; + this.documentNameConfig = documentNameConfig; + this.applicationEventPublisher = applicationEventPublisher; + this.ownerService = ownerService; + this.localRolesService = localRolesService; + } + + @Override + public String[] getBasicRoles() { + return basicRoles; + } + + @Override + public void beforeSave(final User user, final Passaport passaport) { + //garantindo que não será alterado informações cruciais + if (!(user.getCurrentOwner() == null && passaport.getOwner() != null)) { + passaport.setOwner(user.getCurrentOwner()); + } + //adicionando as roles de dependencias + passaport.addRoles(this.loadAvaliableRoles(user).getDependenciesRolesFrom(passaport.getRoles())); + super.beforeSave(user, passaport); + } + + @Override + public void checkPrecondictionSave(final User user, final Passaport passaport) { + + //verificando validando o owner + //o evento irá verificar se foi informado o owner corretamente + //pegando o owner da requisição atual, ou o owner já vindo no json caso seja uma requisição + //do servidor odin + this.applicationEventPublisher.publishEvent(new ValidateOwnerInWorkGroupEvent(user, passaport)); + + //só podemo aceitar salvar um grupo pro owner caso ainda não exista um + if (this.existPassaportForOwner(passaport) && passaport.containsRole(ROLE_OWNER)) { + if (!this.isEmpty(user)) { + throw new MuttleyBadRequestException(Passaport.class, "roles", "Não se pode existir mais de um grupo principal"); + } + } + final Map filter = new HashMap(2); + filter.put("owner.$id", user.getCurrentOwner() == null ? passaport.getOwner().getObjectId() : user.getCurrentOwner().getObjectId()); + filter.put("userMaster", passaport.getUserMaster() == null ? user : passaport.getUserMaster()); + filter.put("name", passaport.getName()); + if (this.repository.exists(filter)) { + throw new MuttleyBadRequestException(Passaport.class, "name", "Já existe um grupo de trabalho com este nome"); + } + + //validando usuário + passaport.setMembers( + passaport.getMembers() + .parallelStream() + .filter(it -> it.getId() != null && !"".equals(it.getUserName())) + .collect(toSet()) + ); + } + + @Override + public void afterSave(final User user, final Passaport passaport) { + this.expire(user, passaport); + } + + @Override + public void beforeUpdate(final User user, final Passaport passaport) { + //garantindo que não será alterado informações cruciais + passaport.setOwner(user.getCurrentOwner()); + passaport.addRoles(this.loadAvaliableRoles(user).getDependenciesRolesFrom(passaport.getRoles())); + super.beforeUpdate(user, passaport); + } + + @Override + public void checkPrecondictionUpdate(final User user, final Passaport passaport) { + //não se pode alterar workteam que seja do owner + if (this.existPassaportForOwner(passaport) && passaport.containsRole(ROLE_OWNER)) { + throw new MuttleyBadRequestException(Passaport.class, "roles", "Não se pode editar o grupo principal"); + } + //verificando se o workteam é do owner ou não + final Passaport other = this.findById(user, passaport.getId()); + if (other.containsRole(ROLE_OWNER)) { + throw new MuttleyBadRequestException(Passaport.class, "roles", "Não se pode editar o grupo principal"); + } + //validando usuário + passaport.setMembers( + passaport.getMembers() + .parallelStream() + .filter(it -> it.getId() != null && !"".equals(it.getUserName())) + .collect(toSet()) + ); + } + + @Override + public void afterUpdate(final User user, final Passaport passaport) { + this.expire(user, passaport); + } + + @Override + public void beforeDelete(final User user, final Passaport passaport) { + this.expire(user, passaport); + } + + @Override + public void checkPrecondictionDelete(final User user, final String id) { + final Passaport passaport = this.findById(user, id); + if (passaport.containsRole(ROLE_OWNER)) { + throw new MuttleyBadRequestException(Passaport.class, "roles", "Não se pode excluir o grupo principal"); + } + this.expire(user, passaport); + super.checkPrecondictionDelete(user, id); + } + + /*@Override + public Long count(final User user, final Map allRequestParams) { + return this.repository.count(user.getCurrentOwner()); + }*/ + + @Override + public List findAll(final User user, final Map allRequestParams) { + return this.findByUser(user); + } + + @Override + public Passaport findByName(final User user, final String name) { + final Passaport cwt = repository.findByName(user.getCurrentOwner(), name); + if (isNull(cwt)) { + throw new MuttleyNotFoundException(Passaport.class, "name", "Registro não encontrado") + .addDetails("name", name); + } + return cwt; + } + + @Override + public List findByUserMaster(final Owner owner, final User user) { + final List itens = repository.findByUserMaster(owner, user); + if (CollectionUtils.isEmpty(itens)) { + throw new MuttleyNoContentException(Passaport.class, "name", "Nenhum time de trabalho encontrado"); + } + return itens; + } + + @Override + public List findByUser(final User user) { + /** + *db.getCollection("muttley-work-teams").aggregate([ + * {$match:{$or:[{"userMaster.$id": ObjectId("5d49cca5a1d16f19595be983")}, {"members.$id":ObjectId("5d49cca5a1d16f19595be983")}]}}, + * ]) + */ + final AggregationResults passaportsResult = this.mongoTemplate.aggregate( + newAggregation( + match( + new Criteria().orOperator( + where("userMaster.$id").is(new ObjectId(user.getId())), + where("members.$id").is(new ObjectId(user.getId())) + ) + ) + ) + , Passaport.class, Passaport.class); + if (passaportsResult == null) { + throw new MuttleyNotFoundException(Passaport.class, "members", "Nenhum passaport encontrado para o usuário informado"); + } + final List passaports = passaportsResult.getMappedResults(); + if (CollectionUtils.isEmpty(passaports)) { + throw new MuttleyNotFoundException(Passaport.class, "members", "Nenhum passaport encontrado para o usuário informado"); + } + return passaports; + } + + @Override + public Set loadCurrentRoles(final User user) { + return this.userRolesView.findByUser(user); + } + + @Override + public AvaliableRoles loadAvaliableRoles(final User user) { + final AvaliableRolesEvent event = new AvaliableRolesEvent(user, + newAvaliableRoles( + newViewRoleDefinition("account-group-outline", MDI, "Times de trabalho", "Ações relacionada a times de trabalho", + newRoleDefinition(ROLE_WORK_TEAM_CREATE, "Inserir times de trabalho"), + newRoleDefinition(ROLE_WORK_TEAM_READ, "Visualizar times de trabalho"), + newRoleDefinition(ROLE_WORK_TEAM_UPDATE, "Atualizar times de trabalho"), + newRoleDefinition(ROLE_WORK_TEAM_DELETE, "Remover times de trabalho") + ) + ) + ); + + this.applicationEventPublisher.publishEvent(event); + + return event.getSource(); + } + + @Override + public void removeUserFromAllPassaport(Owner owner, User user) { + this.mongoTemplate.updateMulti( + new Query( + where("owner.$id").is(owner.getObjectId()) + ), + new Update().pull("members", new BasicDBObject("$in", asList( + new DBRef(this.documentNameConfig.getNameCollectionUser(), user.getObjectId()) + ))), + Passaport.class + ); + } + + @Override + public void addUserForPassaportIfNotExists(final User user, final Passaport passaport, final UserData userForAdd) { + if (!this.userIsPresentInPassaport(user, passaport, userForAdd)) { + this.mongoTemplate.updateMulti( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("id").is(passaport.getObjectId()) + ), + new Update().push("members").atPosition(FIRST).each(new DBRef(this.documentNameConfig.getNameCollectionUser(), new ObjectId(userForAdd.getId()))), + Passaport.class + ); + } + } + + @Override + public boolean userIsPresentInPassaport(final User user, final Passaport passaport, final UserData userForCheck) { + return this.userIsPresentInPassaport(user, passaport.getId(), userForCheck); + } + + @Override + public boolean userIsPresentInPassaport(final User user, final String idPassaport, final UserData userForCheck) { + return this.mongoTemplate.exists( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("id").is(new ObjectId(idPassaport)) + .and("members").in(new DBRef(this.documentNameConfig.getNameCollectionUser(), new ObjectId(userForCheck.getId()))) + ), + Passaport.class + ); + } + + @Override + public Passaport createPassaportFor(final User user, final String ownerId, final Passaport passaport) { + final Owner owner = this.ownerService.findById(user, ownerId); + passaport.setOwner(owner); + passaport.setUserMaster(owner.getUserMaster()); + return this.save(owner.getUserMaster().setCurrentOwner(owner), passaport); + } + + @Override + public void configPassaports(final User user) { + //criando grupo principal + final Passaport passaport = new Passaport() + .setName("Grupo principal") + .setDescription("Grupo principal do sistema criado específicamente para dar autorizações de uso do usuário principal do sistema (Owner)") + .setUserMaster(user) + .addRole(ROLE_OWNER); + + //verificando se não existe workTeam para o Owner + if (!existPassaportForOwner(passaport)) { + + + passaport.setUserMaster(user); + + //criando grupo + } + + + } + + /** + * Checando se existe grupo de trabalho para o Owner + */ + private boolean existPassaportForOwner(final Passaport passaport) { + /** + * db.getCollection("muttley-passaports").aggregate([ + * {$match:{ + * owner: {'$ref' : 'muttley-owners', '$id' : ObjectId('5d07cece444c5b2ceb5e0942')}, + * userMaster:{'$ref' : 'muttley-users', '$id' : ObjectId('5d07cada444c5b2ceb5e0940')}, + * roles:{$elemMatch:{'roleName':'ROLE_OWNER'}} + * }}, + * { + * $count: "count" + * } + * ]) + */ + final AggregationResults result = this.mongoTemplate.aggregate( + newAggregation( + match( + //filtrando o owner + where("owner.$id").is(passaport.getOwner().getObjectId()) + //filtrando o usuário principal + .and("userMaster.$id").is(new ObjectId(passaport.getUserMaster().getId())) + //filtrando as roles + .and("roles").elemMatch( + new Criteria().is(ROLE_OWNER) + ) + ), + Aggregation.count().as("count") + ), Passaport.class, UserViewServiceImpl.ResultCount.class + ); + if (result == null || result.getUniqueMappedResult() != null) { + return result.getUniqueMappedResult().getCount() > 0; + } + return false; + } + + private void expire(final User user, final Passaport passaport) { + this.localRolesService.expireRoles(passaport.getUserMaster()); + passaport.getMembers().forEach(m -> { + this.localRolesService.expireRoles(m); + }); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/PasswordServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/PasswordServiceImpl.java new file mode 100644 index 00000000..7399601b --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/PasswordServiceImpl.java @@ -0,0 +1,143 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.domain.service.Validator; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.headers.services.MetadataService; +import br.com.muttley.model.security.PasswdPayload; +import br.com.muttley.model.security.Password; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.events.UserResolverFromJWTEvent; +import br.com.muttley.security.server.repository.PasswordRepository; +import br.com.muttley.security.server.service.PasswordService; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Date; + +/** + * @author Joel Rodrigues Moreira 12/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class PasswordServiceImpl implements PasswordService { + private final PasswordRepository repository; + private final MetadataService metadataService; + private final Validator validator; + private final ApplicationEventPublisher publisher; + + @Autowired + public PasswordServiceImpl(final PasswordRepository repository, final MetadataService metadataService, final Validator validator, final ApplicationEventPublisher publisher) { + this.repository = repository; + this.metadataService = metadataService; + this.validator = validator; + this.publisher = publisher; + } + + @Override + public Password findByUserId(final String userId) { + return this.findByUser(new User().setId(userId)); + } + + private void checkPrecondictionSave(final User user, final T password) { + //verificando se já tem uma senha salva pra determinado usuário + if (this.repository.exists("user.$id", new ObjectId(password.getUser().getId()))) { + throw new MuttleyBadRequestException(Password.class, "user", "Esse usuário já possuiu uma senha cadastrada"); + } + } + + @Override + public void save(final User user, final T password) { + //verificando se realmente está criando um novo registro + checkIdForSave(password); + //garantindo que o metadata ta preenchido + this.metadataService.generateNewMetadataFor(user, password); + //processa regra de negocio antes de qualquer validação + //this.beforeSave(user, value); + //verificando precondições + this.checkPrecondictionSave(user, password); + if (password.getLastDatePasswordChanges() == null) { + password.setLastDatePasswordChanges(new Date()); + } + //validando dados do objeto + this.validator.validate(password); + + this.repository.save(password); + //realizando regras de enegocio depois do objeto ter sido salvo + //this.afterSave(user, salvedValue); + //valor salvo + } + + + + @Override + public void createPasswordFor(final User user, final String password) { + final Password newPassword = Password.Builder + .newInstance() + .setUser(user) + .setPassword(password) + .builder(); + //verificando se realmente está criando um novo registro + checkIdForSave((T) newPassword); + //garantindo que o metadata ta preenchido + this.metadataService.generateNewMetadataFor(user, newPassword); + //processa regra de negocio antes de qualquer validação + //this.beforeSave(user, value); + //verificando precondições + this.checkPrecondictionSave(user, (T) newPassword); + //validando dados do objeto + this.validator.validate(newPassword); + + this.repository.save(newPassword); + //realizando regras de enegocio depois do objeto ter sido salvo + //this.afterSave(user, salvedValue); + //valor salvo + } + + @Override + public void createPasswordFor(final User user, final PasswdPayload password) { + this.createPasswordFor(user, password.getNewPassword()); + } + + @Override + public void update(final PasswdPayload password) { + //recuperando o usuário necessário + final UserResolverFromJWTEvent eventResolver = new UserResolverFromJWTEvent(password.getToken()); + this.publisher.publishEvent(eventResolver); + final User user = eventResolver.getUserResolved(); + //recuperando a senha atual + final Password currentPassword = this.findByUser(user); + //realizando atualização + currentPassword.setPassword(password); + //salvando registros + repository.save(currentPassword); + } + + @Override + public void resetePasswordFor(final User user, String password) { + final Password passwordModel = this.findByUser(user); + passwordModel.setPassword(password); + repository.save(passwordModel); + } + + private void checkIdForSave(final T value) { + if (StringUtils.isEmpty(value.getId())) { + value.setId(null); + } + if (value.getId() != null) { + throw new MuttleyBadRequestException(value.getClass(), "id", "Não é possível criar um registro com um id existente"); + } + } + + private Password findByUser(final User user) { + final Password password = this.repository.findByUser(user); + if (password == null) { + throw new MuttleyBadRequestException(Password.class, "user", "Registro não encontrador"); + } + return password; + } + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecretService.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecretServiceImpl.java similarity index 52% rename from muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecretService.java rename to muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecretServiceImpl.java index e35c3d23..b3e773fd 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecretService.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecretServiceImpl.java @@ -1,15 +1,13 @@ package br.com.muttley.security.server.service.impl; +import br.com.muttley.redis.service.RedisService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.SigningKeyResolver; import io.jsonwebtoken.SigningKeyResolverAdapter; -import io.jsonwebtoken.impl.crypto.MacProvider; -import io.jsonwebtoken.lang.Assert; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import javax.annotation.PostConstruct; -import javax.crypto.SecretKey; import java.util.HashMap; import java.util.Map; @@ -17,19 +15,25 @@ import static io.jsonwebtoken.SignatureAlgorithm.HS384; import static io.jsonwebtoken.SignatureAlgorithm.HS512; import static io.jsonwebtoken.impl.TextCodec.BASE64; +import static io.jsonwebtoken.impl.crypto.MacProvider.generateKey; @Service -public class SecretService { +public class SecretServiceImpl implements br.com.muttley.security.server.service.SecretService { + private static final String BASIC_KEY = "BASIC_SERVER_KEY"; + private boolean hasBeenInitialized = false; private Map secrets; private final SigningKeyResolver signingKeyResolver; + private final RedisService redisService; - @PostConstruct + /*@PostConstruct public void setup() { refreshSecrets(); - } + }*/ - public SecretService() { + @Autowired + public SecretServiceImpl(final RedisService redisService) { + this.redisService = redisService; this.secrets = new HashMap(); this.signingKeyResolver = new SigningKeyResolverAdapter() { @Override @@ -39,14 +43,17 @@ public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { }; } + @Override public SigningKeyResolver getSigningKeyResolver() { return signingKeyResolver; } + /*@Override public Map getSecrets() { return secrets; } + @Override public void setSecrets(Map secrets) { Assert.notNull(secrets); Assert.hasText(secrets.get(HS256.getValue())); @@ -54,28 +61,42 @@ public void setSecrets(Map secrets) { Assert.hasText(secrets.get(HS512.getValue())); this.secrets = secrets; - } + }*/ + @Override public byte[] getHS256SecretBytes() { + this.refreshSecrets(); return BASE64.decode(secrets.get(HS256.getValue())); } + @Override public byte[] getHS384SecretBytes() { + this.refreshSecrets(); return BASE64.decode(secrets.get(HS384.getValue())); } + @Override public byte[] getHS512SecretBytes() { + this.refreshSecrets(); return BASE64.decode(secrets.get(HS512.getValue())); } - public final Map refreshSecrets() { - SecretKey key = MacProvider.generateKey(HS256); - secrets.put(HS256.getValue(), BASE64.encode(key.getEncoded())); - key = MacProvider.generateKey(HS384); - secrets.put(HS384.getValue(), BASE64.encode(key.getEncoded())); - key = MacProvider.generateKey(HS512); - secrets.put(HS512.getValue(), BASE64.encode(key.getEncoded())); - return secrets; + private final void refreshSecrets() { + if (!this.hasBeenInitialized) { + this.hasBeenInitialized = true; + //secrets.put(HS256.getValue(), BASE64.encode(generateKey(HS256).getEncoded())); + this.addSecret(HS256.getValue(), BASE64.encode(generateKey(HS256).getEncoded())); + //secrets.put(HS384.getValue(), BASE64.encode(generateKey(HS384).getEncoded())); + this.addSecret(HS384.getValue(), BASE64.encode(generateKey(HS384).getEncoded())); + //secrets.put(HS512.getValue(), BASE64.encode(generateKey(HS512).getEncoded())); + this.addSecret(HS512.getValue(), BASE64.encode(generateKey(HS512).getEncoded())); + //return secrets; + } + } + + private final void addSecret(final String key, final String value) { + this.secrets.put(key, value); + this.redisService.set(BASIC_KEY + ":" + key, value); } -} \ No newline at end of file +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecurityModelServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecurityModelServiceImpl.java new file mode 100644 index 00000000..76dcdc97 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecurityModelServiceImpl.java @@ -0,0 +1,555 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.domain.service.impl.ModelServiceImpl; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.exception.throwables.repository.MuttleyRepositoryIdIsNullException; +import br.com.muttley.exception.throwables.repository.MuttleyRepositoryInvalidIdException; +import br.com.muttley.exception.throwables.repository.MuttleyRepositoryOwnerNotInformedException; +import br.com.muttley.exception.throwables.security.MuttleySecurityCredentialException; +import br.com.muttley.model.Document; +import br.com.muttley.model.Historic; +import br.com.muttley.model.MetadataDocument; +import br.com.muttley.model.Model; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; +import br.com.muttley.mongo.service.infra.AggregationUtils; +import br.com.muttley.mongo.service.infra.metadata.EntityMetaData; +import br.com.muttley.security.server.service.SecurityService; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import static java.util.Arrays.asList; +import static java.util.Objects.isNull; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.Stream.of; +import static org.bson.types.ObjectId.isValid; +import static org.springframework.data.domain.Sort.Direction.DESC; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.sort; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * @author Joel Rodrigues Moreira on 22/02/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public abstract class SecurityModelServiceImpl extends ModelServiceImpl implements SecurityService { + //protected final DocumentMongoRepository repository; + private final EntityMetaData entityMetaData; + + @Value("${muttley.security.odin.user}") + private String odinUser; + + public SecurityModelServiceImpl(/*final DocumentMongoRepository repository,*/ final MongoTemplate mongoTemplate, final Class clazz) { + super(null, mongoTemplate, clazz); + // this.repository = repository; + this.entityMetaData = EntityMetaData.of(clazz); + } + + @Override + public T save(final User user, final T value) { + //verificando se realmente está criando um novo registro + checkIdForSave(value); + //setando o dono do registro + value.setOwner(user); + //garantindo que o metadata ta preenchido + this.metadataService.generateNewMetadataFor(user, value); + //processa regra de negocio antes de qualquer validação + this.beforeSave(user, value); + //verificando precondições + this.checkPrecondictionSave(user, value); + //validando dados do objeto + this.validator.validate(value); + final T salvedValue = this.saveByTemplate(user.getCurrentOwner(), value); + //realizando regras de enegocio depois do objeto ter sido salvo + this.afterSave(user, salvedValue); + //valor salvo + return salvedValue; + } + + @Override + public void checkPrecondictionSave(final User user, final T value) { + + } + + public void checkPrecondictionSave(final User user, final OwnerData owner, final T value) { + + } + + @Override + public Collection save(final User user, final Collection values) { + //verificando se realmente está criando um novo registro + checkIdForSave(values); + //garantindo que o metadata ta preenchido + this.metadataService.generateNewMetadataFor(user, values); + //processa regra de negocio antes de qualquer validação + this.beforeSave(user, values); + //verificando precondições + this.checkPrecondictionSave(user, values); + //validando dados do objeto + this.validator.validateCollection(values); + //final Collection otherValues = repository.save(user.getCurrentOwner(), values); + final Collection otherValues = this.saveByTemplate(user.getCurrentOwner(), values); + //realizando regras de enegocio depois do objeto ter sido salvo + this.afterSave(user, otherValues); + //valor salvo + return otherValues; + } + + //@PreAuthorize("hasAnyRole(T(br.com.muttley.model.security.Role).ROLE_ODIN_USER)") + public T save(final User user, final OwnerData owner, final T value) { + //somente usuario do serviço do odin podem fazer requisição para aqui + this.checkIsUserOdin(user); + //verificando se realmente está criando um novo registro + checkIdForSave(value); + //setando o dono do registro + value.setOwner(owner); + //garantindo que o metadata ta preenchido + this.metadataService.generateNewMetadataFor(user, value); + //processa regra de negocio antes de qualquer validação + this.beforeSave(user, value); + //verificando precondições + this.checkPrecondictionSave(user, owner, value); + //validando dados do objeto + this.validator.validate(value); + final T salvedValue = this.saveByTemplate(owner, value); + //realizando regras de enegocio depois do objeto ter sido salvo + this.afterSave(user, salvedValue); + //valor salvo + return salvedValue; + } + + @Override + public T update(final User user, final T value) { + //verificando se realmente está alterando um registro + if (value.getId() == null) { + throw new MuttleyBadRequestException(clazz, "id", "Não é possível alterar um registro sem informar um id válido"); + } + //verificando se o registro realmente existe + //if (!this.repository.exists(value)) { + if (!this.existsByTemplate(user.getCurrentOwner(), value)) { + throw new MuttleyNotFoundException(clazz, "id", "Registro não encontrado"); + } + value.setOwner(user); + //gerando metadata de alteração + this.metadataService.generateMetaDataUpdateFor(user, this.loadMetaDataByTemplate(user.getCurrentOwner(), value.getId()), value); + //processa regra de negocio antes de qualquer validação + this.beforeUpdate(user, value); + //verificando precondições + checkPrecondictionUpdate(user, value); + //validando dados + this.validator.validate(value); + final T salvedValue = this.saveByTemplate(user.getCurrentOwner(), value); + afterUpdate(user, salvedValue); + return salvedValue; + } + + @Override + public void checkPrecondictionUpdate(final User user, final T value) { + + } + + @Override + public void update(final User user, final Collection values) { + //verificando se realmente está alterando um registro + this.checkIdForUpdate(values); + //verificando se o registro realmente existe + + final Map> agroupedValues = values.stream() + .collect(groupingBy(it -> this.existsByTemplate(user.getCurrentOwner(), it.getId()))); + + final List valuesForSave = agroupedValues.get(Boolean.TRUE); + + if (!CollectionUtils.isEmpty(valuesForSave)) { + //gerando metadata de alteração + valuesForSave.forEach(it -> this.metadataService.generateMetaDataUpdateFor(user, this.loadMetaDataByTemplate(user.getCurrentOwner(), it.getId()), it)); + //processa regra de negocio antes de qualquer validação + beforeUpdate(user, valuesForSave); + //verificando precondições + checkPrecondictionUpdate(user, valuesForSave); + //validando dados + this.validator.validateCollection(valuesForSave); + final Collection otherValue = this.saveByTemplate(user.getCurrentOwner(), valuesForSave); + //realizando regras de enegocio depois do objeto ter sido alterado + afterUpdate(user, otherValue); + } + final List valuesNotSaved = agroupedValues.get(Boolean.FALSE); + if (!CollectionUtils.isEmpty(valuesNotSaved)) { + throw new MuttleyNotFoundException(clazz, "id", "Registros não encontrados") + .addDetails("ids", valuesNotSaved.parallelStream().map(Document::getId).collect(toList())); + } + } + + @Override + public T findById(final User user, final String id) { + if (isNull(id) || !ObjectId.isValid(id)) { + throw new MuttleyBadRequestException(clazz, "id", "informe um id válido"); + } + final List result = this.mongoTemplate.find(new Query(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("id").is(new ObjectId(id))), this.clazz); + //final T result = this.mongoTemplate.findById(new ObjectId(id), this.clazz); + if (CollectionUtils.isEmpty(result)) { + throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); + } + return result.get(0); + } + + @Override + public Set findByIds(final User user, final String[] ids) { + if (ObjectUtils.isEmpty(ids)) { + throw new MuttleyBadRequestException(clazz, "id", "informe pelo menos um id válido"); + } + if (ids.length > 50) { + throw new MuttleyBadRequestException(clazz, "ids", "Quantidade máxima excedida") + .addDetails("min", 1) + .addDetails("max", 50); + } + + //final Set records = this.repository.findMulti(user.getCurrentOwner(), ids); + final Set records = this.findMultByTemplate(user.getCurrentOwner(), ids); + if (records == null) { + return Collections.emptySet(); + } + return records; + } + + @Override + public T findFirst(final User user) { + final List result = this.mongoTemplate.find(new Query(where("owner.$id").is(user.getCurrentOwner().getObjectId())).limit(1), this.clazz); + if (isNull(result) || result.isEmpty()) { + throw new MuttleyNotFoundException(clazz, "user", "Nenhum registro encontrado"); + } + return result.get(0); + } + + @Override + public void deleteById(final User user, final String id) { + this.beforeDelete(user, id); + checkPrecondictionDelete(user, id); + //if (!repository.exists(user.getCurrentOwner(), id)) { + if (!this.existsByTemplate(user.getCurrentOwner(), id)) { + throw new MuttleyNotFoundException(clazz, "id", id + " este registro não foi encontrado"); + } + //this.repository.delete(user.getCurrentOwner(), id); + this.mongoTemplate.remove( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("_id").is(newObjectId(id)) + ), this.clazz + ); + this.afterDelete(user, id); + } + + @Override + public void delete(final User user, final T value) { + this.beforeDelete(user, value); + checkPrecondictionDelete(user, value.getId()); + //if (!repository.exists(user.getCurrentOwner(), value)) { + if (!this.existsByTemplate(user.getCurrentOwner(), value)) { + throw new MuttleyNotFoundException(clazz, "id", value.getId() + " este registro não foi encontrado"); + } + //this.repository.delete(user.getCurrentOwner(), value); + this.mongoTemplate.remove( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("_id").is(value.getObjectId()) + ), this.clazz + ); + this.afterDelete(user, value); + } + + @Override + public void checkPrecondictionDelete(final User user, final String id) { + + } + + @Override + public void beforeDelete(final User user, final T value) { + + } + + @Override + public void beforeDelete(final User user, final String id) { + + } + + @Override + public Long count(final User user, final Map allRequestParams) { + //return this.repository.count(user.getCurrentOwner(), allRequestParams); + return this.countByTemplate(user.getCurrentOwner(), allRequestParams); + } + + public Long count(final User user, final OwnerData owner, final Map allRequestParams) { + //return this.repository.count(user.getCurrentOwner(), allRequestParams); + //somente usuario do serviço do odin podem fazer requisição para aqui + this.checkIsUserOdin(user); + return this.countByTemplate(owner, allRequestParams); + } + + @Override + public List findAll(final User user, final Map allRequestParams) { + //final List results = this.repository.findAll(user.getCurrentOwner(), allRequestParams); + final List results = this.findAllByTemplate(user.getCurrentOwner(), allRequestParams); + if (CollectionUtils.isEmpty(results)) { + throw new MuttleyNoContentException(clazz, "user", "não foi encontrado nenhum registro"); + } + return results; + } + + @Override + protected AggregationResults createAggregateForLoadProperties(final User user, final Map condictions, final String... properties) { + condictions.put("owner.$id", user.getCurrentOwner().getObjectId()); + return super.createAggregateForLoadProperties(user, condictions, properties); + } + + /** + * Valida se ouve algum furo no processo de negociocio que venha a se alterar o dono do registro + */ + private final void checkOwner(final User user, final T value) { + final Model other = findById(user, value.getId()); + //não pode-se alterar o usuário + if (!other.getOwner().equals(user.getCurrentOwner())) { + throw new MuttleyBadRequestException(clazz, "user", "não é possível fazer a alteração do usuário dono do registro"); + } + } + + private T saveByTemplate(final OwnerData owner, final T value) { + validateOwner(owner); + value.setOwner(owner); + //salvando o registro + this.mongoTemplate.save(value); + //pegando o registro salvo + final AggregationResults results; + if (!value.contaisObjectId()) { + results = mongoTemplate.aggregate( + newAggregation( + asList( + sort(DESC, "id"), + limit(1) + ) + ), this.clazz, + this.clazz + ); + } else { + results = mongoTemplate.aggregate( + newAggregation( + asList( + match(where("owner.$id").is(owner.getObjectId()).and("id").is(value.getObjectId())), + limit(1) + ) + ), this.clazz, + this.clazz + ); + } + if (results == null || results.getUniqueMappedResult() == null) { + throw new MuttleyException(); + } + return results.getUniqueMappedResult(); + } + + /*private T updateByTemplate(final Owner owner, final T value) { + validateOwner(owner); + value.setOwner(owner); + //salvando o registro + this.mongoTemplate.save(value); + //pegando o registro salvo + final AggregationResults results = mongoTemplate.aggregate( + newAggregation( + asList( + sort(DESC, "_id"), + limit(1) + ) + ), this.clazz, + this.clazz + ); + if (results == null || results.getUniqueMappedResult() == null) { + throw new MuttleyException(); + } + return results.getUniqueMappedResult(); + }*/ + + private Collection saveByTemplate(final OwnerData owner, final Collection value) { + return value.stream() + .map(it -> this.saveByTemplate(owner, it)) + .collect(toList()); + } + + private final void validateOwner(final OwnerData owner) { + if (owner == null) { + throw new MuttleyRepositoryOwnerNotInformedException(this.clazz); + } + } + + private Historic loadHistoricByTemplate(final OwnerData owner, final String id) { + final AggregationResults result = this.mongoTemplate + .aggregate( + newAggregation( + match(where("owner.$id").is(owner.getObjectId()) + .and("_id").is(new ObjectId(id)) + ), project().and("$historic.createdBy").as("createdBy") + .and("$historic.dtCreate").as("dtCreate") + .and("$historic.dtChange").as("dtChange") + ), this.clazz, Historic.class); + + return result.getUniqueMappedResult() != null ? ((Historic) result.getUniqueMappedResult()) : null; + } + + + private MetadataDocument loadMetaDataByTemplate(final OwnerData owner, final String id) { + final AggregationResults result = this.mongoTemplate + .aggregate( + newAggregation( + match(where("owner.$id").is(owner.getObjectId()) + .and("_id").is(new ObjectId(id)) + ), project().and("$metadata.timeZones").as("timeZones") + .and("$metadata.versionDocument").as("versionDocument") + ), this.clazz, MetadataDocument.class); + + return result.getUniqueMappedResult() != null ? ((MetadataDocument) result.getUniqueMappedResult()) : null; + } + + private Set findMultByTemplate(final OwnerData owner, final String[] ids) { + //criando um array de ObjecIds + final ObjectId[] objectIds = of(ids) + .parallel() + .map(id -> { + try { + return newObjectId(id); + } catch (MuttleyRepositoryInvalidIdException ex) { + return null; + } + //pegando apenas ids válidos + }).filter(Objects::nonNull) + .collect(toSet()) + .stream() + .toArray(ObjectId[]::new); + + //filtrando os ids válidos + if (!ObjectUtils.isEmpty(objectIds)) { + final List records = this.mongoTemplate.find( + new Query( + where("owner.$id").is(owner.getObjectId()) + .and("id").in(objectIds) + ), this.clazz + ); + + if (CollectionUtils.isEmpty(records)) { + return null; + } + + return new HashSet<>(records); + } + + return null; + } + + private final List findAllByTemplate(final OwnerData owner, final Map queryParams) { + validateOwner(owner); + return this.mongoTemplate.aggregate( + newAggregation( + AggregationUtils.createAggregations(this.entityMetaData, getBasicPipelines(this.clazz), + addOwnerQueryParam(owner, queryParams) + ) + ), + this.clazz, this.clazz) + .getMappedResults(); + } + + private final long countByTemplate(final OwnerData owner, final Map queryParams) { + validateOwner(owner); + final AggregationResults result = this.mongoTemplate.aggregate( + newAggregation( + AggregationUtils.createAggregationsCount(this.entityMetaData, getBasicPipelines(this.clazz), + addOwnerQueryParam(owner, queryParams) + )), + this.clazz, ResultCount.class); + return result.getUniqueMappedResult() != null ? ((ResultCount) result.getUniqueMappedResult()).getCount() : 0; + } + + + /*private boolean existsByTemplate(final Owner owner, final T value) { + return this.mongoTemplate.exists(new Query(where("owner.$id").is(owner.getObjectId()).and("id").is(value.getObjectId())), this.clazz); + }*/ + + /*private boolean existsByTemplate(final Owner owner,final String id) { + return this.mongoTemplate.exists(new Query(where("owner.$id").is(owner.getObjectId()).and("id").is(new ObjectId(id))), this.clazz); + }*/ + + private boolean existsByTemplate(final OwnerData owner, final String id) { + return this.mongoTemplate.exists(new Query(where("owner.$id").is(owner.getObjectId()).and("_id").is(new ObjectId(id))), this.clazz); + } + + private boolean existsByTemplate(final OwnerData owner, final T value) { + return this.existsByTemplate(owner, value.getId()); + } + + /** + * Retorna uma lista de pipelines para agregação + */ + List getBasicPipelines(final Class clazz) { + return null; + } + + private final ObjectId newObjectId(final String id) { + this.validateId(id); + return new ObjectId(id); + } + + private final void validateId(final String id) { + if (id == null) { + throw new MuttleyRepositoryIdIsNullException(this.clazz); + } + if (!isValid(id)) { + throw new MuttleyRepositoryInvalidIdException(this.clazz); + } + } + + private final Map addOwnerQueryParam(final OwnerData owner, final Map queryParams) { + final Map query = new LinkedHashMap<>(1); + query.put("owner.$id.$is", owner.getObjectId().toString()); + if (queryParams != null) { + query.putAll(queryParams); + } + return query; + } + + protected final class ResultCount { + private Long count; + + public Long getCount() { + return count; + } + + public ResultCount setCount(final Long count) { + this.count = count; + return this; + } + } + + private void checkIsUserOdin(final User user) { + if (!this.odinUser.equals(user.getEmail())) { + throw new MuttleySecurityCredentialException("Você não tem permissão para acessar esse recurso"); + } + } + +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecurityServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecurityServiceImpl.java index 7f240ce8..2bdbc60c 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecurityServiceImpl.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/SecurityServiceImpl.java @@ -4,14 +4,15 @@ import br.com.muttley.model.Document; import br.com.muttley.mongo.service.repository.DocumentMongoRepository; import br.com.muttley.security.server.service.SecurityService; +import org.springframework.data.mongodb.core.MongoTemplate; /** * @author Joel Rodrigues Moreira on 22/02/18. * e-mail: joel.databox@gmail.com * @project muttley-cloud */ -public class SecurityServiceImpl extends ServiceImpl implements SecurityService { - public SecurityServiceImpl(final DocumentMongoRepository repository, final Class clazz) { - super(repository, clazz); +public abstract class SecurityServiceImpl extends ServiceImpl implements SecurityService { + public SecurityServiceImpl(final DocumentMongoRepository repository, final MongoTemplate mongoTemplate, final Class clazz) { + super(repository, mongoTemplate, clazz); } } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserBaseServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserBaseServiceImpl.java new file mode 100644 index 00000000..a65b5789 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserBaseServiceImpl.java @@ -0,0 +1,434 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.domain.service.Validator; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserBase; +import br.com.muttley.model.security.UserBaseItem; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.UserView; +import br.com.muttley.model.security.merge.MergedUserBaseItemResponse; +import br.com.muttley.model.security.merge.MergedUserBaseResponse; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import br.com.muttley.security.server.events.NewUserHasBeenAddedInBaseEvent; +import br.com.muttley.security.server.service.PassaportService; +import br.com.muttley.security.server.service.UserBaseService; +import br.com.muttley.security.server.service.UserDataBindingService; +import br.com.muttley.security.server.service.UserService; +import com.mongodb.BasicDBObject; +import org.bson.types.ObjectId; +import org.hibernate.validator.internal.engine.ConstraintViolationImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.validation.ConstraintViolationException; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import static br.com.muttley.model.security.Role.ROLE_USER_BASE_CREATE; +import static br.com.muttley.model.security.merge.Status.CONFLICT; +import static br.com.muttley.model.security.merge.Status.ERROR; +import static br.com.muttley.model.security.merge.Status.OK; +import static java.util.Arrays.asList; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.group; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.lookup; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Update.Position.FIRST; + +/** + * @author Joel Rodrigues Moreira on 26/11/2020. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class UserBaseServiceImpl extends SecurityModelServiceImpl implements UserBaseService { + private static final String[] basicRoles = new String[]{ROLE_USER_BASE_CREATE.getSimpleName()}; + private final String ODIN_USER; + private final UserService userService; + private final UserDataBindingService dataBindingService; + private final DocumentNameConfig documentNameConfig; + private final PassaportService passaportService; + private final Validator validator; + private final ApplicationEventPublisher publisher; + + @Autowired + public UserBaseServiceImpl( + final MongoTemplate template, + @Value("${muttley.security.odin.user}") final String odinUser, + final UserService userService, + final UserDataBindingService dataBindingService, + final DocumentNameConfig documentNameConfig, + final PassaportService passaportService, + final Validator validator, + final ApplicationEventPublisher publisher) { + super(template, UserBase.class); + this.ODIN_USER = odinUser; + this.userService = userService; + this.dataBindingService = dataBindingService; + this.documentNameConfig = documentNameConfig; + this.passaportService = passaportService; + this.validator = validator; + this.publisher = publisher; + } + + @Override + public String[] getBasicRoles() { + return basicRoles; + } + + @Override + public void checkPrecondictionSave(final User user, final UserBase value) { + if (this.count(user, null) == 1) { + throw new MuttleyBadRequestException(UserBase.class, null, "Já existe uma base de usuário cadastrada no sistema"); + } + } + + @Override + public void checkPrecondictionSave(final User user, final OwnerData owner, final UserBase value) { + if (this.count(user, owner, null) == 1) { + throw new MuttleyBadRequestException(UserBase.class, null, "Já existe uma base de usuário cadastrada no sistema"); + } + } + + @Override + public void checkPrecondictionDelete(final User user, final String id) { + throw new MuttleyBadRequestException(Owner.class, "id", "Não é possível deletar a base de usuário"); + } + + @Override + public boolean userNameIsAvaliableForUserName(final User user, final String userName, final Set userNames) { + return this.userService.userNameIsAvaliableForUserName(userName, userNames); + } + + @Override + public boolean userNameIsAvaliable(final User user, final Set userNames) { + return this.userService.userNameIsAvaliable(userNames); + } + + @Override + public UserView findUserByEmailOrUserNameOrNickUser(final User user, final String emailOrUserName) { + return new UserView(this.userService.findUserByEmailOrUserNameOrNickUser(emailOrUserName)); + } + + @Override + public void addUserItemIfNotExists(final User user, final User userForAdd) { + this.addUserItemIfNotExists(user, new UserBaseItem(user, userForAdd, null, new Date(), true, null)); + } + + @Override + public void addUserItemIfNotExists(final User user, final UserBaseItem userForAdd) { + if (!this.hasBeenIncludedAnyGroup(user, userForAdd.getUser())) { + userForAdd.setAddedBy(user); + if (userForAdd.getDtCreate() == null) { + userForAdd.setDtCreate(new Date()); + } + if (userForAdd.getAddedBy() == null) { + userForAdd.setAddedBy(user); + } + this.validator.validate(userForAdd); + if (this.hasBeenIncludedAnyGroup(user, userForAdd.getUser())) { + throw new MuttleyBadRequestException(UserBase.class, "users", "Usuário já está presente na base"); + } + this.mongoTemplate.updateFirst( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + ), + new Update() + .push("users") + .atPosition(FIRST) + .each(userForAdd), + UserBase.class + ); + } else { + this.mongoTemplate.updateMulti( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("users.user.$id").is(new ObjectId(userForAdd.getUser().getId())) + ), + new Update().set("users.$.status", userForAdd.isStatus()), + UserBase.class + ); + } + + this.publisher.publishEvent(new NewUserHasBeenAddedInBaseEvent(new NewUserHasBeenAddedInBaseEvent.NewUserHasBeenAddedInBaseItemEvent(user, userForAdd.getUser()))); + } + + @Override + public void createNewUserAndAdd(final User user, final UserBaseItem item) { + //final User userForSave = new User(item.getUserInfoForMerge()); + if (!item.dataBindingsIsEmpty()) { + item.getDataBindings().forEach(it -> { + if (it.getKey().isUnique()) { + if (this.dataBindingService.containsByKeyAndValueAndUserNameNotEq(user, item.getUserInfoForMerge().getUserName(), it.getKey(), it.getValue())) { + throw new MuttleyBadRequestException(UserDataBinding.class, "key", "Já existe um usuário que possui ligação com " + it.getKey().getDisplayKey() + " informado(a)"); + } + } + }); + } + final User salvedUser = userService.save(item.getUserInfoForMerge()); + if (!item.dataBindingsIsEmpty()) { + this.dataBindingService.merge(user, salvedUser.getUserName(), item.getDataBindings()); + } + this.addUserItemIfNotExists(user, salvedUser); + } + + @Override + public void mergeUserItemIfExists(final User user, final UserBaseItem item) { + item.setUser(userService.update(user, new User(item.getUserInfoForMerge()))); + if (!item.dataBindingsIsEmpty()) { + this.dataBindingService.merge(user, item.getUser().getUserName(), item.getDataBindings()); + } + this.addUserItemIfNotExists(user, item); + } + + @Override + public void mergeUserItem(User user, UserBaseItem item) { + if (this.userService.existUserByEmailOrUserNameOrNickUsers(item.getUserInfoForMerge().getEmail(), item.getUserInfoForMerge().getUserName(), item.getUserInfoForMerge().getNickUsers())) { + this.mergeUserItemIfExists(user, item); + } else { + this.createNewUserAndAdd(user, item); + } + + } + + @Override + public MergedUserBaseResponse mergeUserItens(final User user, final List itens) { + final MergedUserBaseResponse response = new MergedUserBaseResponse(); + itens.stream().map(UserBaseItem::getUserInfoForMerge).forEach(item -> { + final MergedUserBaseItemResponse it = new MergedUserBaseItemResponse(item.getEmail(), OK); + if (item == null || item.getEmail() == null) { + it.setStatus(ERROR); + it.addDetails("email", "Informe um email válidp"); + } + + response.add(it); + //validando item por si só + try { + this.validator.validate(item); + + //verificando se o username informado está disponível + if (!StringUtils.isEmpty(item.getUserName()) && !this.userService.userNameIsAvaliable(item.getUserName())) { + it.setStatus(CONFLICT); + it.addDetails("userName", "Esse userName não está disponível"); + } + //verificando o email + if (!StringUtils.isEmpty(item.getEmail()) && !this.userService.userNameIsAvaliable(item.getEmail())) { + it.setStatus(CONFLICT); + it.addDetails("email", "Esse email não está disponível"); + } + //verificando os nickUsers + if (item.getNickUsers() != null) { + item.getNickUsers().forEach(nick -> { + if (!StringUtils.isEmpty(nick) && !this.userService.userNameIsAvaliable(nick)) { + it.setStatus(CONFLICT); + it.addDetails("nickUsers[" + nick + "]", "Esse nickUser não está disponível"); + } + }); + } + } catch (final ConstraintViolationException ex) { + final ConstraintViolationImpl violation = ((ConstraintViolationImpl) ex.getConstraintViolations().toArray()[0]); + it.setStatus(ERROR) + .addDetails(violation.getPropertyPath().toString(), violation.getMessage()); + } + }); + response.getItens() + .stream() + .filter(it -> OK.equals(it.getStatus())) + .forEach(it -> { + this.mergeUserItem(user, + itens.stream().filter(item -> it.getEmail().equals(item.getUserInfoForMerge().getEmail())) + .findFirst() + .get() + ); + }); + + return response; + } + + @Override + public void removeByUserName(final User user, final String userName) { + final User userLoaded = this.userService.findByUserName(userName); + this.mongoTemplate.updateFirst( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + ), + new Update() + .pull("users", new BasicDBObject("user.$id", new ObjectId(userLoaded.getId()))), + UserBase.class + ); + this.passaportService.removeUserFromAllPassaport(user.getCurrentOwner(), userLoaded); + } + + @Override + public boolean hasBeenIncludedAnyGroup(final User user, final UserData userForCheck) { + return this.mongoTemplate.exists( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("users.user.$id").is(new ObjectId(userForCheck.getId())) + ), UserBase.class + ); + } + + @Override + public boolean hasBeenIncludedAnyGroup(final UserData userForCheck) { + return this.mongoTemplate.exists( + new Query( + /*where("owner.$id").is(user.getCurrentOwner().getObjectId())*/ + where("users.user.$id").is(new ObjectId(userForCheck.getId())) + ), UserBase.class + ); + } + + @Override + public boolean hasBeenIncludedAnyGroup(final User user, final String userNameForCheck) { + /** + * db.getCollection("muttley-users-base").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6")}}, + * {$unwind:"$users"}, + * {$project:{user:{$objectToArray:"$users.user"}}}, + * {$project:{user:{$arrayElemAt:["$user.v",1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as:"user" + * }}, + * {$unwind:"$user"}, + * {$match:{ + * $or:[ + * {"user.userName":"asdfasd234asdfasdf"}, + * {"user.email":"df7899@gmail.com"}, + * {"user.nickUsers":{$in:["asdfas"]}}, + * ] + * }}, + * {$group:{_id:"$user"}}, + * {$count:"result"} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId())), + unwind("$users"), + project().and(context -> new BasicDBObject("$objectToArray", "$users.user")).as("user"), + project().and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup( + this.documentNameConfig.getNameCollectionUser(), + "user", + "_id", + "user" + ), + unwind("$user"), + match(new Criteria().orOperator( + where("user.userName").is(userNameForCheck), + where("user.email").is(userNameForCheck), + where("user.nickUsers").in(asList(userNameForCheck)) + )), + group("$user"), + Aggregation.count().as("count") + ), + this.documentNameConfig.getNameCollectionUserBase(), + UserViewServiceImpl.ResultCount.class + ); + if (results == null || results.getUniqueMappedResult() == null || results.getUniqueMappedResult().getCount() == 0) { + return false; + } + if (results.getUniqueMappedResult().getCount() > 1) { + //não pode retornar mais de um usuário + throw new MuttleyException("Erro interno"); + } + + return true; + } + + @Override + public boolean hasBeenIncludedAnyGroup(final String userNameForCheck) { + /** + * db.getCollection("muttley-users-base").aggregate([ + * {$unwind:"$users"}, + * {$project:{user:{$objectToArray:"$users.user"}}}, + * {$project:{user:{$arrayElemAt:["$user.v",1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as:"user" + * }}, + * {$unwind:"$user"}, + * {$match:{ + * $or:[ + * {"user.userName":"asdfasd234asdfasdf"}, + * {"user.email":"df7899@gmail.com"}, + * {"user.nickUsers":{$in:["asdfas"]}}, + * ] + * }}, + * {$group:{_id:"$user"}}, + * {$count:"result"} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + unwind("$users"), + project().and(context -> new BasicDBObject("$objectToArray", "$users.user")).as("user"), + project().and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup( + this.documentNameConfig.getNameCollectionUser(), + "user", + "_id", + "user" + ), + unwind("$user"), + match(new Criteria().orOperator( + where("user.userName").is(userNameForCheck), + where("user.email").is(userNameForCheck), + where("user.nickUsers").in(asList(userNameForCheck)) + )), + group("$user"), + Aggregation.count().as("count") + ), + UserBase.class, + UserViewServiceImpl.ResultCount.class + ); + if (results == null || results.getUniqueMappedResult() == null || results.getUniqueMappedResult().getCount() == 0) { + return false; + } + if (results.getUniqueMappedResult().getCount() > 1) { + //não pode retornar mais de um usuário + throw new MuttleyException("Erro interno"); + } + + return true; + } + + @Override + public boolean allHasBeenIncludedGroup(User user, Collection users) { + return this.mongoTemplate.exists( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .andOperator( + users.parallelStream() + .map(it -> where("users.user.$id").is(it.getObjectId())) + .toArray(Criteria[]::new) + ) + ), UserBase.class + ); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserDataBindingServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserDataBindingServiceImpl.java new file mode 100644 index 00000000..6cdbf92f --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserDataBindingServiceImpl.java @@ -0,0 +1,628 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.domain.service.Validator; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyConflictException; +import br.com.muttley.headers.services.MetadataService; +import br.com.muttley.localcache.services.LocalDatabindingService; +import br.com.muttley.model.BasicAggregateResult; +import br.com.muttley.model.BasicAggregateResultCount; +import br.com.muttley.model.security.KeyUserDataBinding; +import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.events.UserResolverEvent; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import br.com.muttley.security.server.repository.UserDataBindingRepository; +import br.com.muttley.security.server.service.UserDataBindingService; +import com.mongodb.BasicDBObject; +import io.jsonwebtoken.lang.Collections; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import static java.util.Arrays.asList; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.count; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.lookup; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * @author Joel Rodrigues Moreira 12/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class UserDataBindingServiceImpl implements UserDataBindingService { + @Autowired + protected MetadataService metadataService; + private final MongoTemplate mongoTemplate; + private final UserDataBindingRepository repository; + private final DocumentNameConfig documentNameConfig; + private final Validator validator; + //private final UserService userService; + private final ApplicationEventPublisher eventPublisher; + @Value("${muttley.security.check-roles:false}") + private boolean checkRoles; + + private final LocalDatabindingService localDatabindingService; + + @Autowired + public UserDataBindingServiceImpl(final MongoTemplate mongoTemplate, final UserDataBindingRepository repository, final DocumentNameConfig documentNameConfig, final Validator validator, final ApplicationEventPublisher eventPublisher, final LocalDatabindingService localDatabindingService) { + this.mongoTemplate = mongoTemplate; + this.repository = repository; + this.documentNameConfig = documentNameConfig; + this.validator = validator; + this.eventPublisher = eventPublisher; + this.localDatabindingService = localDatabindingService; + } + + public boolean isCheckRole() { + return checkRoles; + } + + @Override + public UserDataBinding save(final User user, final UserDataBinding dataBinding) { + if (dataBinding.getUser() == null) { + dataBinding.setUser(user); + } + checkBasicInfos(user, dataBinding); + checkPrecondictionSave(user, dataBinding); + final UserDataBinding salved = repository.save(dataBinding); + this.localDatabindingService.expireUserDataBindings(user); + return salved; + } + + public void checkPrecondictionSave(final User user, final UserDataBinding dataBinding) { + if (!user.equals(dataBinding.getUser())) { + throw new MuttleyBadRequestException(UserDataBinding.class, "user", "O usuário informado é diferente do da requisição!"); + } + //verificando se já não existe um registro com as informações + this.checkIndex(user, dataBinding); + this.validator.validate(dataBinding); + } + + @Override + public UserDataBinding update(final User user, final UserDataBinding dataBinding) { + if (dataBinding.getUser() == null) { + dataBinding.setUser(user); + } + checkBasicInfos(user, dataBinding); + this.checkPrecondictionUpdate(user, dataBinding); + final UserDataBinding salved = repository.save(dataBinding); + this.localDatabindingService.expireUserDataBindings(user); + return salved; + } + + public void checkPrecondictionUpdate(final User user, final UserDataBinding dataBinding) { + if (!user.equals(dataBinding.getUser())) { + throw new MuttleyBadRequestException(UserDataBinding.class, "user", "O usuário informado é diferente do da requisição!"); + } + //verificando se já não existe um registro com as informações + this.checkIndex(user, dataBinding); + this.validator.validate(dataBinding); + } + + @Override + public List listByUserName(final User user, final String userName) { + if (user.getUserName().equals(userName)) { + return this.listBy(user); + } + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6")}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$objectToArray:"$user"}}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$arrayElemAt:["$user.v", 1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as: "user" + * }}, + * {$unwind:"$user"}, + * {$match:{"user.userName":"OwnerSiapi5e28b392637e580001e465d3"}} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId())), + project("key", "value", "metadata", "historic", "owner").and(context -> new BasicDBObject("$objectToArray", "$user")).as("user"), + project("key", "value", "metadata", "historic", "owner").and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup(documentNameConfig.getNameCollectionUser(), "user", "_id", "user"), + unwind("$user"), + match(where("user.userName").is(userName)) + ), + documentNameConfig.getNameCollectionUserDataBinding(), + UserDataBinding.class + ); + if (results == null || Collections.isEmpty(results.getMappedResults())) { + return new ArrayList<>(); + } + return results.getMappedResults(); + } + + @Override + public List listBy(final User user) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"),"user.$id":ObjectId("5e28b3e3637e580001e465d6")}}, + * ]) + */ + final Owner currentOwner = user.getCurrentOwner(); + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(currentOwner != null ? currentOwner.getObjectId() : null).and("user.$id").is(new ObjectId(user.getId()))) + ), + UserDataBinding.class, + UserDataBinding.class + ); + if (results == null || Collections.isEmpty(results.getMappedResults())) { + new ArrayList<>(); + } + return results.getMappedResults(); + } + + @Override + public UserDataBinding saveByUserName(final User user, final String userName, final UserDataBinding dataBinding) { + if (dataBinding.getUser() == null) { + dataBinding.setUser(this.findByUserName(userName)); + } + checkBasicInfos(user, dataBinding); + checkPrecondictionSaveByUserName(user, userName, dataBinding); + + final UserDataBinding salved = repository.save(dataBinding); + this.localDatabindingService.expireUserDataBindings(user); + return salved; + } + + public void checkPrecondictionSaveByUserName(final User user, final String userName, final UserDataBinding dataBinding) { + if (!userName.equals(dataBinding.getUser().getUserName())) { + throw new MuttleyBadRequestException(UserDataBinding.class, "user", "O usuário informado é diferente do da requisição!") + .addDetails("userName", userName) + .addDetails("userAgentName", dataBinding); + } + this.checkIndex(user, userName, dataBinding); + this.validator.validate(dataBinding); + } + + @Override + public UserDataBinding updateByUserName(final User user, final String userName, final UserDataBinding dataBinding) { + if (dataBinding.getUser() == null) { + dataBinding.setUser(this.findByUserName(userName)); + } + if (StringUtils.isEmpty(dataBinding.getId())) { + dataBinding.setId(this.loadIdFrom(user, dataBinding)); + } + checkBasicInfos(user, dataBinding); + checkPrecondictionUpdateByUserName(user, userName, dataBinding); + + final UserDataBinding salved = repository.save(dataBinding); + this.localDatabindingService.expireUserDataBindings(user); + return salved; + } + + public void checkPrecondictionUpdateByUserName(final User user, final String userName, final UserDataBinding dataBinding) { + if (!userName.equals(dataBinding.getUser().getUserName())) { + throw new MuttleyBadRequestException(UserDataBinding.class, "user", "O usuário informado é diferente do da requisição!"); + } + this.validator.validate(dataBinding); + //verificando se já não existe um registro com as informações + this.checkIndex(user, userName, dataBinding); + } + + @Override + public void merge(final User user, final String userName, final UserDataBinding dataBinding) { + if (dataBinding.getUser() == null) { + dataBinding.setUser(this.findByUserName(userName)); + } + if (exists(user, dataBinding)) { + + this.updateByUserName(user, userName, dataBinding); + } else { + + this.saveByUserName(user, userName, dataBinding); + } + } + + @Override + public void merge(final User user, final String userName, final Set dataBindings) { + User userCurrent = null; + for (UserDataBinding dataBinding : dataBindings) { + if (dataBinding.getUser() == null) { + if (userCurrent == null) { + if (user.getUserName().equals(userName)) { + userCurrent = user; + } else { + userCurrent = this.findByUserName(userName); + } + } + dataBinding.setUser(userCurrent); + } + if (exists(user, dataBinding)) { + this.updateByUserName(user, userName, dataBinding); + } else { + this.saveByUserName(user, userName, dataBinding); + } + } + } + + @Override + public UserDataBinding getKey(User user, KeyUserDataBinding key) { + return this.getKey(user, key.getKey()); + } + + @Override + public UserDataBinding getKey(final User user, final String key) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"),"user.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"asdfasd"}}, + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("user.$id").is(new ObjectId(user.getId())) + .and("key").is(key) + ) + ), + UserDataBinding.class, + UserDataBinding.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + return null; + } + return results.getUniqueMappedResult(); + } + + @Override + public UserDataBinding getKeyByUserName(User user, String userName, KeyUserDataBinding key) { + return this.getKeyByUserName(user, userName, key.getKey()); + } + + @Override + public UserDataBinding getKeyByUserName(final User user, final String userName, final String key) { + if (user.getUserName().equals(userName)) { + return this.getKey(user, key); + } + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"asdfasd"}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$objectToArray:"$user"}}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$arrayElemAt:["$user.v", 1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as: "user" + * }}, + * {$unwind:"$user"}, + * {$match:{"user.userName":"OwnerSiapi5e28b392637e580001e465d3", "key":"asdfasd"}} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("key").is(key)), + project("key", "value", "metadata", "historic", "owner").and(context -> new BasicDBObject("$objectToArray", "$user")).as("user"), + project("key", "value", "metadata", "historic", "owner").and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup(documentNameConfig.getNameCollectionUser(), "user", "_id", "user"), + unwind("$user"), + match(where("user.userName").is(userName).and("key").is(key)) + ), + documentNameConfig.getNameCollectionUserDataBinding(), + UserDataBinding.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + return null; + } + return results.getUniqueMappedResult(); + } + + @Override + public boolean contains(User user, KeyUserDataBinding key) { + return this.contains(user, key.getKey()); + } + + @Override + public boolean contains(final User user, final String key) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"),"user.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"asdfasd"}}, + * ]) + */ + return this.mongoTemplate.exists( + new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("user.$id").is(new ObjectId(user.getId())) + .and("key").is(key) + ), UserDataBinding.class); + } + + @Override + public boolean containsByUserNameAndKey(User user, String userName, KeyUserDataBinding key) { + return this.containsByUserNameAndKey(user, userName, key); + } + + @Override + public boolean containsByUserNameAndKey(final User user, final String userName, final String key) { + if (user.getUserName().equals(userName)) { + return this.contains(user, key); + } + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"asdfasd"}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$objectToArray:"$user"}}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$arrayElemAt:["$user.v", 1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as: "user" + * }}, + * {$unwind:"$user"}, + * {$match:{"user.userName":"OwnerSiapi5e28b392637e580001e465d3", "key":"asdfasd"}} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("key").is(key)), + project("key", "value", "metadata", "historic", "owner").and(context -> new BasicDBObject("$objectToArray", "$user")).as("user"), + project("key", "value", "metadata", "historic", "owner").and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup(documentNameConfig.getNameCollectionUser(), "user", "_id", "user"), + unwind("$user"), + match(where("user.userName").is(userName).and("key").is(key)), + count().as("result") + ), + documentNameConfig.getNameCollectionUserDataBinding(), + BasicAggregateResultCount.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + return false; + //throw new MuttleyNoContentException(UserDataBinding.class, "userName", "Nenhum registro encontrado para o usuário desejado"); + } + return results.getUniqueMappedResult().getResult() > 0; + } + + @Override + public boolean containsByKeyAndValue(User user, KeyUserDataBinding key, String value) { + return this.containsByKeyAndValue(user, key.getKey(), value); + } + + @Override + public boolean containsByKeyAndValue(final User user, final String key, final String value) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"UserColaborador", value:"5ff65e339208bc0007c0d6ba"}}, + * {} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("key").is(key).and("value").is(value)), + count().as("result") + ), + documentNameConfig.getNameCollectionUserDataBinding(), + BasicAggregateResultCount.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + return false; + //throw new MuttleyNoContentException(UserDataBinding.class, "userName", "Erro na consulta"); + } + return results.getUniqueMappedResult().getResult() > 0; + } + + @Override + public boolean containsByKeyAndValueAndUserNameNotEq(User user, String userName, KeyUserDataBinding key, String value) { + return this.containsByKeyAndValueAndUserNameNotEq(user, userName, key.getKey(), value); + } + + @Override + public boolean containsByKeyAndValueAndUserNameNotEq(final User user, final String userName, final String key, final String value) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6"), "key":"UserColaborador", value:"5ff65e339208bc0007c0d6ba"}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$objectToArray:"$user"}}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$arrayElemAt:["$user.v", 1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as: "user" + * }}, + * {$unwind:"$user"}, + * {$match:{"user.userName":{$ne:"0756143600010711000523BRUNA.Ab"}}} + * ]) + */ + final List operations = new LinkedList<>( + asList( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("key").is(key).and("value").is(value)) + ) + ); + /*if (!StringUtils.isEmpty(userName)) { + operations.addAll( + asList( + project("key", "value", "metadata", "historic", "owner").and(context -> new BasicDBObject("$objectToArray", "$user")).as("user"), + project("key", "value", "metadata", "historic", "owner").and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup(documentNameConfig.getNameCollectionUser(), "user", "_id", "user"), + unwind("$user") + //match(where("user.userName").ne(userName).and("key").is(key)) + ) + ); + }*/ + //operations.add(count().as("result")); + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + operations + ), + documentNameConfig.getNameCollectionUserDataBinding(), + UserDataBinding.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + //se não encontrou nada é sinal que o databinding está disponivel + return false; + } else { + return results.getMappedResults() + .parallelStream() + .filter(it -> !it.getUser().getUserName().equals(userName)) + .count() > 0; + } + } + + @Override + public UserData getUserBy(final User user, final KeyUserDataBinding key, final String value) { + return this.getUserBy(user, key.getKey(), value); + } + + @Override + public UserData getUserBy(final User user, final String key, final String value) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id": ObjectId("5e28b3e3637e580001e465d6"), "key":"UserColaborador", "value":"5e28bcf86f985c00017e7a28"}}, + * {$project:{user:1}} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).and("key").is(key).and("value").is(value)) + ), + UserDataBinding.class, + UserDataBinding.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + return null; + } + return results.getUniqueMappedResult().getUser(); + } + + private final void checkIndex(final User user, final UserDataBinding dataBinding) { + if (StringUtils.isEmpty(dataBinding.getId())) { + if (this.exists(user, dataBinding)) { + throw new MuttleyConflictException(UserDataBinding.class, "key", "Jás existe um registro com essas informações"); + } + } else { + if (this.mongoTemplate.exists( + new Query( + where("id").ne(dataBinding.getObjectId()) + .and("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("user.$id").is(new ObjectId(dataBinding.getUser().getId())) + .and("key").is(dataBinding.getKey()) + ), UserDataBinding.class)) { + throw new MuttleyConflictException(UserDataBinding.class, "key", "Jás existe um registro com essas informações"); + } + } + + if (dataBinding.getKey().isUnique()) { + //verificando se outro usário já tem esse databinding + if (this.containsByKeyAndValueAndUserNameNotEq(user, user.getUserName(), dataBinding.getKey().getKey(), dataBinding.getValue())) { + throw new MuttleyBadRequestException(UserDataBinding.class, "key", "Já existe um usuário que possui ligação com " + dataBinding.getKey().getDisplayKey() + " informado(a)"); + } + } + } + + private final void checkIndex(final User user, final String userName, final UserDataBinding dataBinding) { + if (user.getUserName().equals(userName)) { + this.checkIndex(user, dataBinding); + } else { + + //caso o registro não tenha id é sinal que estamos inserindo um novo + //com isso se faz necessário verificar se já não existe o mesmo + if (!dataBinding.contaisObjectId()) { + if (this.exists(user, userName, dataBinding.getKey().getKey())) { + throw new MuttleyConflictException(UserDataBinding.class, "key", "Jás existe um registro com essas informações"); + } + } + + if (dataBinding.getKey().isUnique()) { + //verificando se outro usário já tem esse databinding + if (this.containsByKeyAndValueAndUserNameNotEq(user, userName, dataBinding.getKey().getKey(), dataBinding.getValue())) { + throw new MuttleyBadRequestException(UserDataBinding.class, "key", "Já existe um usuário que possui ligação com " + dataBinding.getKey().getDisplayKey() + " informado(a)"); + } + } + } + } + + private boolean exists(final User user, UserDataBinding dataBinding) { + return this.repository.exists("owner.$id", user.getCurrentOwner().getObjectId(), "user.$id", new ObjectId(dataBinding.getUser().getId()), "key", dataBinding.getKey().getKey()); + } + + private boolean exists(final User user, final String userName, final String key) { + /** + * db.getCollection("muttley-users-databinding").aggregate([ + * {$match:{"owner.$id":ObjectId("5e28b3e3637e580001e465d6")}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$objectToArray:"$user"}}}, + * {$project:{_class:1, key:1, value:1, metadata:1, historic:1, owner:1, user:{$arrayElemAt:["$user.v", 1]}}}, + * {$lookup:{ + * from:"muttley-users", + * localField:"user", + * foreignField:"_id", + * as: "user" + * }}, + * {$unwind:"$user"}, + * {$match:{"user.userName":"OwnerSiapi5e28b392637e580001e465d3"}} + * ]) + */ + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId())), + project("key", "value").and(context -> new BasicDBObject("$objectToArray", "$user")).as("user"), + project("key", "value").and(context -> new BasicDBObject("$arrayElemAt", asList("$user.v", 1))).as("user"), + lookup(documentNameConfig.getNameCollectionUser(), "user", "_id", "user"), + unwind("$user"), + match(where("user.userName").is(userName)), + count().as("result") + ), + documentNameConfig.getNameCollectionUserDataBinding(), + BasicAggregateResultCount.class + ); + + return results != null && results.getUniqueMappedResult() != null && results.getUniqueMappedResult().getResult() > 0; + } + + private String loadIdFrom(final User user, final UserDataBinding dataBinding) { + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation( + match( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("user.$id").is(new ObjectId(dataBinding.getUser().getId())) + .and("key").is(dataBinding.getKey()) + ), + project().and("_id").as("result") + ), + documentNameConfig.getNameCollectionUserDataBinding(), + BasicAggregateResult.class + ); + return results.getUniqueMappedResult().getResult().toString(); + } + + private void checkBasicInfos(final User user, final UserDataBinding userDataBinding) { + userDataBinding.setOwner(user.getCurrentOwner()); + if (StringUtils.isEmpty(userDataBinding.getId())) { + this.metadataService.generateNewMetadataFor(user, userDataBinding); + } else { + this.metadataService.generateMetaDataUpdateFor(user, repository.loadMetadata(userDataBinding), userDataBinding); + } + } + + protected User findByUserName(final String userName) { + final UserResolverEvent event = new UserResolverEvent(userName); + this.eventPublisher.publishEvent(event); + return event.getUserResolver(); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserPreferencesImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserPreferencesImpl.java new file mode 100644 index 00000000..a7eb66e6 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserPreferencesImpl.java @@ -0,0 +1,158 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.localcache.services.LocalUserPreferenceService; +import br.com.muttley.model.Document; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.preference.Preference; +import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.security.server.repository.UserPreferencesRepository; +import br.com.muttley.security.server.service.UserPreferencesService; +import com.mongodb.BasicDBObject; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; + +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.replaceRoot; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * @author Joel Rodrigues Moreira 08/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class UserPreferencesImpl implements UserPreferencesService { + private final UserPreferencesRepository repository; + private final MongoTemplate template; + private final LocalUserPreferenceService localUserPreferenceService; + + @Autowired + public UserPreferencesImpl(final UserPreferencesRepository repository, final MongoTemplate template, final LocalUserPreferenceService localUserPreferenceService) { + this.repository = repository; + this.template = template; + this.localUserPreferenceService = localUserPreferenceService; + } + + @Override + public UserPreferences createPreferencesFor(final User user) { + //devemos garantir que cada usuário tenha no máximo um UserPreference + if (this.existsUserPreferencesFor(user)) { + throw new MuttleyBadRequestException(UserPreferences.class, "user", "Já existe preferencias cadastradas para esse usuário"); + } + final UserPreferences salved = this.repository.save(new UserPreferences().setUser(user)); + this.localUserPreferenceService.expireUserPreferences(user); + return salved; + } + + @Override + public void save(final User user, final UserPreferences preferences) { + //devemos garantir que cada usuário tenha no máximo um UserPreference + if (this.existsUserPreferencesFor(user)) { + throw new MuttleyBadRequestException(UserPreferences.class, "user", "Já existe preferencias cadastradas para esse usuário"); + } + if (preferences.getUser() == null) { + preferences.setUser(user); + } else if (!user.equals(preferences.getUser())) { + throw new MuttleyBadRequestException(UserPreferences.class, "user", "Somente o proprio usuário pode criar suas preferencias"); + } + + this.repository.save(preferences); + this.localUserPreferenceService.expireUserPreferences(user); + } + + @Override + public void setPreference(final User user, final Preference preference) { + if (!this.existsUserPreferencesFor(user)) { + //se não tiver UserPreferences, vamos criar um novo + this.save(user, new UserPreferences().setUser(user).set(preference)); + } else { + this.template.updateFirst( + new Query(where("user.$id").is(new ObjectId(user.getId()))), + new Update().pull("preferences", new BasicDBObject("key", preference.getKey())), + UserPreferences.class + ); + + this.template.updateFirst( + new Query(where("user.$id").is(new ObjectId(user.getId()))), + new Update().addToSet("preferences", preference), + UserPreferences.class + ); + this.localUserPreferenceService.expireUserPreferences(user); + } + } + + @Override + public void setPreference(final User user, final String key, final String value) { + this.setPreference(user, new Preference(key, value)); + } + + @Override + public void setPreference(final User user, final String key, final Document value) { + this.setPreference(user, new Preference(key, value.getId())); + } + + @Override + public Preference getPreference(final User user, final String key) { + /** + * db.getCollection("muttley-users-preferences").aggregate([ + * {$match:{"user.$id" : ObjectId("5e28b392637e580001e465d4"), "preferences.key":"OWNER_PREFERENCE"}}, + * {$project:{preferences:1}}, + * {$unwind:"$preferences"}, + * {$match:{"preferences.key":"OWNER_PREFERENCE"}} + * ]) + */ + final AggregationResults results = this.template.aggregate( + newAggregation( + match(where("user.$id").is(new ObjectId(user.getId())).and("preferences.key").is(key)), + project("preferences"), + unwind("$preferences"), + match(where("preferences.key").is(key)), + replaceRoot("preferences") + ), + UserPreferences.class, + Preference.class); + if (results != null && results.getUniqueMappedResult() != null) { + return results.getUniqueMappedResult(); + } + return null; + } + + @Override + public String getPreferenceValue(final User user, final String key) { + final Preference preference = this.getPreference(user, key); + return preference == null ? null : (String) preference.getValue(); + } + + @Override + public void removePreference(final User user, final String key) { + this.template.updateFirst( + new Query(where("user.$id").is(new ObjectId(user.getId()))), + new Update().pull("preferences", new BasicDBObject("key", key)), + UserPreferences.class + ); + this.localUserPreferenceService.expireUserPreferences(user); + } + + @Override + public UserPreferences getUserPreferences(final User user) { + return repository.findByUser(user); + } + + @Override + public boolean containsPreference(final User user, final String key) { + return repository.exists("user.$id", new ObjectId(user.getId()), "preferences.key", key); + } + + private boolean existsUserPreferencesFor(final User user) { + return repository.exists("user.$id", new ObjectId(user.getId())); + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserRolesViewImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserRolesViewImpl.java new file mode 100644 index 00000000..dda5e111 --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserRolesViewImpl.java @@ -0,0 +1,135 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.security.server.events.ConfigFirstOwnerPreferenceEvent; +import br.com.muttley.security.server.service.UserRolesView; +import com.mongodb.BasicDBObject; +import org.bson.BsonString; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.stereotype.Service; + +import java.util.Set; + +import static br.com.muttley.model.security.preference.UserPreferences.OWNER_PREFERENCE; +import static java.util.Arrays.asList; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.group; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * @author Joel Rodrigues Moreira on 23/05/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class UserRolesViewImpl implements UserRolesView { + private final MongoTemplate template; + private final ApplicationEventPublisher eventPublisher; + + @Autowired + public UserRolesViewImpl(final MongoTemplate mongoTemplate, final ApplicationEventPublisher eventPublisher) { + this.template = mongoTemplate; + this.eventPublisher = eventPublisher; + } + + @Override + public Set findByUser(final User user) { + if (!user.containsPreference(OWNER_PREFERENCE)) { + this.eventPublisher.publishEvent(new ConfigFirstOwnerPreferenceEvent(user)); + } + + //verificando se o usuário atual é o master + if (user.equals(user.getCurrentOwner().getUserMaster())) { + //se chegou aqui logo é o owner do sistem + return Role.getValues(); + } + + /*final UserRolesViewResul result = this.template.findOne(new Query( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("userId").is(new ObjectId(user.getId())) + ), UserRolesViewResul.class, "view_muttley_work_teams_roles_user"); + if (result == null || result.isEmpty()) { + throw new MuttleyNotFoundException(UserRolesViewResul.class, "userId", "Nenhuma role foi encontrada"); + } + return result.getRoles();*/ + /** + * db.getCollection("muttley-work-teams").aggregate([ + * {$match:{ + * "owner.$id":ObjectId("5e28b3e3637e580001e465d6"), + * $or:[ + * {"userMaster.$id":ObjectId("5e28b392637e580001e465d4")}, + * {"members.$id":ObjectId("5e28b392637e580001e465d4") } + * ]} + * }, + * {$group:{_id:"$owner", roles:{$addToSet:"$roles"}}}, + * {$project:{ + * roles:{ + * $reduce:{ + * input:"$roles", + * initialValue:[], + * in:{ + * $concatArrays:["$$value", "$$this"] + * } + * } + * } + * }}, + * {$unwind:"$roles"}, + * {$group:{_id:"", roles:{$addToSet:"$roles"}}} + * ]) + */ + final ObjectId idUser = new ObjectId(user.getId()); + final AggregationResults result = this.template.aggregate( + Aggregation.newAggregation( + match(where("owner.$id").is(user.getCurrentOwner().getObjectId()).orOperator( + where("userMaster.$id").is(idUser), + where("members.$id").is(idUser) + )), + group("$owner").addToSet("$roles").as("roles"), + project().and(context -> + new BasicDBObject("$reduce", + new BasicDBObject("input", "$roles") + .append("initialValue", asList()) + .append("in", + new BasicDBObject("$concatArrays", asList("$$value", "$$this")) + ) + ) + ).as("roles").and(context -> new BasicDBObject("aux", new BsonString("1"))).as("aux"), + unwind("$roles"), + group("$aux").addToSet("roles").as("roles") + ), + Passaport.class, + UserRolesResult.class + ); + if (result == null || result.getUniqueMappedResult() == null) { + throw new MuttleyNotFoundException(UserRolesResult.class, "userId", "Você está sem permissões de acesso. Para resolver isso contate o administrador do sistema"); + } + return result.getUniqueMappedResult().getRoles(); + } + + private class UserRolesResult { + Set roles; + + public Set getRoles() { + return roles; + } + + public UserRolesResult setRoles(final Set roles) { + this.roles = roles; + return this; + } + + public boolean isEmpty() { + return roles == null || roles.isEmpty(); + } + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserServiceImpl.java index 75e01d14..0cf6367a 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserServiceImpl.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserServiceImpl.java @@ -1,29 +1,47 @@ package br.com.muttley.security.server.service.impl; -import br.com.muttley.exception.throwables.security.MuttleySecurityBadRequestException; -import br.com.muttley.exception.throwables.security.MuttleySecurityConflictException; -import br.com.muttley.exception.throwables.security.MuttleySecurityNotFoundException; -import br.com.muttley.exception.throwables.security.MuttleySecurityUnauthorizedException; -import br.com.muttley.model.security.JwtToken; -import br.com.muttley.model.security.JwtUser; -import br.com.muttley.model.security.Passwd; -import br.com.muttley.model.security.User; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.exception.throwables.security.*; +import br.com.muttley.model.BasicAggregateResultCount; +import br.com.muttley.model.security.*; +import br.com.muttley.model.security.events.SendNewPasswordRecoveredEvent; +import br.com.muttley.model.security.events.UserCreatedEvent; +import br.com.muttley.model.security.events.ValidadeUserForeCreateEvent; +import br.com.muttley.model.security.events.ValidatePasswordRecoveryEvent; +import br.com.muttley.model.security.preference.Preference; import br.com.muttley.model.security.preference.UserPreferences; -import br.com.muttley.security.server.repository.UserPreferencesRepository; +import br.com.muttley.model.userManager.IncludeSecundaryEmail; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import br.com.muttley.security.server.events.CheckUserHasBeenIncludedAnyGroupEvent; +import br.com.muttley.security.server.events.CurrentOwnerResolverEvent; import br.com.muttley.security.server.repository.UserRepository; -import br.com.muttley.security.server.service.UserService; +import br.com.muttley.security.server.service.*; +import org.bson.types.ObjectId; +import org.hibernate.validator.internal.util.CollectionHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestBody; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; import static com.google.common.base.Strings.isNullOrEmpty; +import static java.util.stream.Collectors.toList; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; +import static org.springframework.data.mongodb.core.query.Criteria.where; /** * @author Joel Rodrigues Moreira on 12/01/18. @@ -32,33 +50,85 @@ @Service public class UserServiceImpl implements UserService { + @Value("${muttley.security-server.validateUserFoneNumber:false}") + protected boolean validateFoneNumber; + private final UserRepository repository; - private final UserPreferencesRepository preferencesRepository; + private final AuthService authService; + private final UserPreferencesService preferencesService; + private final UserDataBindingService dataBindingService; + private final PasswordService passwordService; private final JwtTokenUtilService tokenUtil; + + private final XAPITokenService xAPITokenService; private final String tokenHeader; + private final OwnerService ownerService; + private final MongoTemplate template; + private final DocumentNameConfig documentNameConfig; + private final ApplicationEventPublisher eventPublisher; @Autowired - public UserServiceImpl(final UserRepository repository, - final UserPreferencesRepository preferencesRepository, + public UserServiceImpl(final UserRepository repository, @Lazy AuthService authService, + final UserPreferencesService preferencesService, + final UserDataBindingService dataBindingService, + final PasswordService passwordService, @Value("${muttley.security.jwt.controller.tokenHeader}") final String tokenHeader, - final JwtTokenUtilService tokenUtil) { + final JwtTokenUtilService tokenUtil, + XAPITokenService xAPITokenService, final OwnerService ownerService, + final MongoTemplate template, + final DocumentNameConfig documentNameConfig, + final ApplicationEventPublisher eventPublisher) { this.repository = repository; - this.preferencesRepository = preferencesRepository; + this.authService = authService; + this.preferencesService = preferencesService; + this.dataBindingService = dataBindingService; + this.passwordService = passwordService; this.tokenHeader = tokenHeader; this.tokenUtil = tokenUtil; + this.xAPITokenService = xAPITokenService; + this.ownerService = ownerService; + this.template = template; + this.documentNameConfig = documentNameConfig; + this.eventPublisher = eventPublisher; + } + + @Override + public User save(final UserPayLoad value) { + //verificanso se já existe o usuário + this.checkIndexUser(new User(value)); + + if (this.validateFoneNumber && !value.isOdinUser()) { + final ValidadeUserForeCreateEvent validadeUserForeCreateEvent = new ValidadeUserForeCreateEvent(value); + this.eventPublisher.publishEvent(validadeUserForeCreateEvent); + if (value.seedHasBeeVerificate()) { + final User newUser = this.save(new User(value)); + this.passwordService.createPasswordFor(newUser, value.getPasswd()); + return newUser; + } else if (value.codeVerificationIsEmpty()) { + return null; + } + throw new MuttleyBadRequestException(null, null, "Código de verificação invalido"); + } else { + final User newUser = this.save(new User(value)); + this.passwordService.createPasswordFor(newUser, value.getPasswd()); + return newUser; + } + } @Override public User save(final User user) { final User salvedUser = merge(user); - salvedUser.setPreferences(this.preferencesRepository.save(new UserPreferences().setUser(salvedUser))); + salvedUser.setPreferences(this.preferencesService.createPreferencesFor(user)); + this.eventPublisher.publishEvent(new UserCreatedEvent(salvedUser)); return salvedUser; } @Override public void save(final User user, final UserPreferences preferences) { preferences.setUser(user); - this.preferencesRepository.save(preferences); + /*this.validatePreferences(preferences); + this.preferencesRepository.save(preferences);*/ } @Override @@ -69,23 +139,256 @@ public boolean remove(final User user) { } @Override - public boolean removeByEmail(final String email) { - return this.remove(findByEmail(email)); + public boolean removeByUserName(final String userName) { + return this.remove(findByUserName(userName)); + } + + @Override + public User update(final User user, final JwtToken token) { + + final User otherUser = this.getUserFromToken(token); + user.setId(otherUser.getId()) + .setEmail(otherUser.getEmail()) + .setUserName(otherUser.getUserName()) + .setNickUsers(otherUser.getNickUsers()) + //.setPasswd(otherUser) + //.setLastPasswordResetDate(otherUser.getLastPasswordResetDate()) + .setEnable(otherUser.isEnable()) + .setOdinUser(otherUser.isOdinUser()); + + + checkNameIsValid(user); + //validando infos do usuário + user.validateBasicInfoForLogin(); + return repository.save(user); + } + + @Override + public User update(final User user, final User userForUpdate) { + final User otherUser = this.findUserByEmailOrUserNameOrNickUsers(userForUpdate.getEmail(), userForUpdate.getUserName(), userForUpdate.getNickUsers()); + userForUpdate.setId(otherUser.getId()) + //.setEmail(otherUser.getEmail()) + .setUserName(otherUser.getUserName()) + //.setNickUsers(otherUser.getNickUsers()) + //.setPasswd(otherUser) + //.setLastPasswordResetDate(otherUser.getLastPasswordResetDate()) + //.setEnable(otherUser.isEnable()) + .setOdinUser(otherUser.isOdinUser()); + + checkNameIsValid(userForUpdate); + //validando infos do usuário + userForUpdate.validateBasicInfoForLogin(); + return this.repository.save(userForUpdate); + } + + + private void checkNameIsValid(final User user) { + if (user.getName() == null || user.getName().length() < 4) { + throw new MuttleySecurityBadRequestException(User.class, "nome", "O campo nome deve ter de 4 a 200 caracteres!"); + } + } + + /*@Override + public User updatePasswd(final PasswdPayload passwdPayload) { + final User user = getUserFromToken(passwdPayload.getToken()); + user.setPasswd(passwdPayload); + checkNameIsValid(user); + //validando infos do usuário + user.validateBasicInfoForLogin(); + return repository.save(user); + }*/ + + @Override + public User findByUserName(final String userName) { + final AggregationResults result = template.aggregate(newAggregation( + match( + new Criteria().orOperator( + where("userName").is(userName), + where("email").is(userName), + where("nickUsers").in(userName), + where("emailSecundario").is(userName) + ) + ) + ), User.class, User.class); + if (result == null) { + throw new UsernameNotFoundException("Usuário não encontrado"); + } + + final List users = result.getMappedResults(); + if (CollectionUtils.isEmpty(users)) { + throw new MuttleySecurityNotFoundException(User.class, "userName", userName + " este registro não foi encontrado"); + } + if (users.size() > 1) { + throw new MuttleyException("Erro interno no sistema"); + } + + final CheckUserHasBeenIncludedAnyGroupEvent event = new CheckUserHasBeenIncludedAnyGroupEvent(users.get(0).getUserName()); + this.eventPublisher.publishEvent(event); + /*if (!event.isUserHasBeenIncludedAnyGroup()) { + throw new MuttleySecurityUnauthorizedException("Usuário não autorizado para utilização"); + }*/ + + return users.get(0); + } + + @Override + public User findUserByEmailOrUserNameOrNickUsers(final String email, final String userName, final Set nickUsers) { + final Set nicks = createSetForNicks(email, userName, nickUsers); + final AggregationResults result = template.aggregate(newAggregation( + match( + new Criteria().orOperator( + where("userName").in(nicks), + where("email").in(nicks), + where("nickUsers").in(nicks), + where("emailSecundario").is(nicks) + ) + ) + ), User.class, User.class); + if (result == null) { + throw new UsernameNotFoundException("Usuário não encontrado"); + } + + final List users = result.getMappedResults(); + if (CollectionUtils.isEmpty(users)) { + throw new MuttleySecurityNotFoundException(User.class, null, userName + " este registro não foi encontrado"); + } + if (users.size() > 1) { + throw new MuttleyBadRequestException(User.class, null, "Mais de um usuário foi encontrado com os critérios informados"); + } + return users.get(0); + } + + @Override + public User findUserByEmailOrUserNameOrNickUser(final String emailOrUserName) { + return this.findUserByEmailOrUserNameOrNickUsers(emailOrUserName, null, null); } @Override - public User update(final User user) { - return merge(user); + public boolean existUserByEmailOrUserNameOrNickUsers(final String email, final String userName, final Set nickUsers) { + final Set nicks = createSetForNicks(email, userName, nickUsers); + final AggregationResults result = template.aggregate(newAggregation( + match( + new Criteria().orOperator( + where("userName").in(nicks), + where("email").in(nicks), + where("nickUsers").in(nicks), + where("emailSecundario").is(nicks) + ) + ), Aggregation.count().as("count") + ), User.class, UserViewServiceImpl.ResultCount.class); + if (result == null) { + return false; + } + + final UserViewServiceImpl.ResultCount resultCount = result.getUniqueMappedResult(); + if (resultCount == null) { + return false; + } + return resultCount.getCount() > 0; } @Override - public User updatePasswd(final Passwd passwd) { - final User user = getUserFromToken(passwd.getToken()); - user.setPasswd(passwd); - return save(user); + public boolean userNameIsAvaliableForUserName(final String userName, final Set userNames) { + if (StringUtils.isEmpty(userName)) { + return this.userNameIsAvaliable(userNames); + } else { + if (CollectionUtils.isEmpty(userNames)) { + throw new MuttleyBadRequestException(null, "userNames", "Informe algum valor válido para consulta"); + } + + final Set nicks = userNames + .parallelStream() + .filter(it -> !StringUtils.isEmpty(it)) + .collect(Collectors.toSet()); + for (final String nick : nicks) { + if (!(User.isValidEmail(nick) || User.isValidUserName(nick))) { + return false; + } + } + + final AggregationResults result = template.aggregate(newAggregation( + match( + new Criteria() + .and("userName").ne(userName) + .orOperator( + where("userName").in(nicks), + where("email").in(nicks), + where("nickUsers").in(nicks), + where("emailSecundario").is(nicks) + ) + ), Aggregation.count().as("count") + ), User.class, UserViewServiceImpl.ResultCount.class); + + if (result == null) { + return true; + } + + final UserViewServiceImpl.ResultCount resultCount = result.getUniqueMappedResult(); + if (resultCount == null) { + return true; + } + return resultCount.getCount() == 0; + } } @Override + public boolean userNameIsAvaliable(final Set userNames) { + if (CollectionUtils.isEmpty(userNames)) { + throw new MuttleyBadRequestException(null, "userNames", "Informe algum valor válido para consulta"); + } + + final Set nicks = userNames + .parallelStream() + .filter(it -> !StringUtils.isEmpty(it)) + .collect(Collectors.toSet()); + for (final String nick : nicks) { + if (!(User.isValidEmail(nick) || User.isValidUserName(nick))) { + return false; + } + } + + final AggregationResults result = template.aggregate(newAggregation( + match( + new Criteria().orOperator( + where("userName").in(nicks), + where("email").in(nicks), + where("nickUsers").in(nicks), + where("emailSecundario").is(nicks) + + ) + ), Aggregation.count().as("count") + ), User.class, UserViewServiceImpl.ResultCount.class); + + if (result == null) { + return true; + } + + final UserViewServiceImpl.ResultCount resultCount = result.getUniqueMappedResult(); + if (resultCount == null) { + return true; + } + return resultCount.getCount() == 0; + } + + @Override + public boolean userNameIsAvaliable(final String userName) { + return this.userNameIsAvaliable(CollectionHelper.asSet(userName)); + } + + private Set createSetForNicks(final String email, final String userName, final Set nickUsers) { + final Set nicks = new HashSet<>(); + if (!StringUtils.isEmpty(email)) { + nicks.add(email); + } + if (!StringUtils.isEmpty(userName)) { + nicks.add(userName); + } + if (!CollectionUtils.isEmpty(nickUsers)) { + nickUsers.parallelStream().filter(it -> !StringUtils.isEmpty(it)).forEach(it -> nicks.add(it)); + } + return nicks; + } + public User findByEmail(final String email) { final User user = repository.findByEmail(email); if (user == null) { @@ -111,78 +414,345 @@ public Collection findAll() { @Override public User getUserFromToken(final JwtToken token) { if (token != null && !token.isEmpty()) { + //pegando o nome de usuário do token final String userName = this.tokenUtil.getUsernameFromToken(token.getToken()); if (!isNullOrEmpty(userName)) { - return findByEmail(userName); + final User user = findByUserName(userName); + final UserPreferences preferences = this.preferencesService.getUserPreferences(user); + user.setPreferences(preferences); + + final CurrentOwnerResolverEvent event = new CurrentOwnerResolverEvent(user); + this.eventPublisher.publishEvent(event); + + //user.setCurrentOwner(this.ownerService.findByUserAndId(user, preferences.get(OWNER_PREFERENCE).getValue().toString())); + try { + user.setDataBindings(this.dataBindingService.listBy(user)); + } catch (MuttleyNoContentException ex) { + } + /*if (preferences.contains(OWNER_PREFERENCE)) { + user.setCurrentOwner(this.ownerService.findByUserAndId(user, preferences.get(OWNER_PREFERENCE).getValue().toString())); + } else { + user.setCurrentOwner(this.ownerService.loadOwnersOfUser(user).get(0)); + preferences.set(OWNER_PREFERENCE, user.getCurrentOwner().getId()); + this.preferencesService.setPreference(user, OWNER_PREFERENCE, user.getCurrentOwner().getId()); + }*/ + return user; } } throw new MuttleySecurityUnauthorizedException(); } - /* @Override - public Authentication getCurrentAuthentication() { - return SecurityContextHolder.getContext().getAuthentication(); + @Override + public User getUserFromToken(XAPIToken token) { + if (token != null && !token.isEmpty()) { + //pegando o nome de usuário do tokens + final User user = this.xAPITokenService.loadUserByAPIToken(token.getToken()).getUser(); + final UserPreferences preferences = this.preferencesService.getUserPreferences(user); + user.setPreferences(preferences); + + final CurrentOwnerResolverEvent event = new CurrentOwnerResolverEvent(user); + this.eventPublisher.publishEvent(event); + + try { + user.setDataBindings(this.dataBindingService.listBy(user)); + } catch (MuttleyNoContentException ex) { + } + return user; + } + throw new MuttleySecurityUnauthorizedException(); } @Override - public JwtUser getCurrentJwtUser() { - return (JwtUser) getCurrentAuthentication().getPrincipal(); + public UserPreferences loadPreference(final User user) { + return this.preferencesService.getUserPreferences(user); } @Override - public JwtToken getCurrentToken() { - return new JwtToken( - ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()) - .getRequest() - .getHeader(this.tokenHeader) + public List getUsersFromPreference(final Preference preference) { + /** + * db.getCollection("muttley-users-preferences").aggregate([ + * {$match:{preferences:{$elemMatch: {"key":"UserColaborador", value:"5e28bd0d6f985c00017e7bb6"}}}}, + * {$project: {user:1}} + * ]) + */ + final AggregationResults results = this.template.aggregate( + newAggregation( + match(where("preferences").elemMatch(where("key").is(preference.getKey()).and("value").is(preference.getValue()))), + project("user") + ), + UserPreferences.class, + UserPreferences.class ); + if (results != null && !CollectionUtils.isEmpty(results.getMappedResults())) { + return results.getMappedResults() + .parallelStream() + .map(UserPreferences::getUser) + .collect(toList()); + } + throw new MuttleyNoContentException(User.class, null, "Nenhum usuário encontrado"); } @Override - public User getCurrentUser() { - return getCurrentJwtUser().getOriginUser(); - }*/ + public User getUserFromPreference(final Preference preference) { + /** + * db.getCollection("muttley-users-preferences").aggregate([ + * {$match:{preferences:{$elemMatch: {"key":"UserColaborador", value:"5e28bd0d6f985c00017e7bb6"}}}}, + * {$project: {user:1}}, + * {$limit:1} + * ]) + */ + final AggregationResults results = this.template.aggregate( + newAggregation( + match(where("preferences").elemMatch(where("key").is(preference.getKey()).and("value").is(preference.getValue()))), + project("user"), + limit(1) + ), + UserPreferences.class, + UserPreferences.class + ); + if (results != null && results.getUniqueMappedResult() != null) { + return results.getUniqueMappedResult().getUser(); + } + throw new MuttleyNotFoundException(User.class, null, "Nenhum usuário encontrado"); + } @Override - public UserPreferences loadPreference(final User user) { - return this.preferencesRepository.findByUser(user); + public boolean constainsPreference(final User user, final String keyPreference) { + /** + * db.getCollection("muttley-users-preferences").aggregate([ + * {$match:{"user.$id":ObjectId("5e84ed76e684d90007e94718")}}, + * {$project:{preferences:1}}, + * {$unwind:"$preferences"}, + * {$match:{"preferences.key":"UserColaborador"}}, + * {$count:"result"} + * ]) + */ + + if (StringUtils.isEmpty(keyPreference)) { + return false; + } + + final AggregationResults result = this.template.aggregate( + newAggregation( + match(where("user.$id").is(new ObjectId(user.getId()))), + project("preferences"), + unwind("$preferences"), + match(where("preferences.key").is(keyPreference)), + Aggregation.count().as("result") + ), + UserPreferences.class, + BasicAggregateResultCount.class + ); + if (result == null || result.getUniqueMappedResult() == null) { + return false; + } + return result.getUniqueMappedResult().getResult() > 0; } + @Override + public RecoveryPasswordResponse recoveryPassword(RecoveryPayload recovery) { + if (this.validateFoneNumber) { + //verificando se o recovery está com todas as informações necessárias + if (isNullOrEmpty(recovery.getEmail())) { + throw new MuttleyBadRequestException(null, "email", "Informe um e-mail válido"); + } + final User user = this.findUserByEmailOrUserNameOrNickUser(recovery.getEmail()); + final ValidatePasswordRecoveryEvent passwordRecoveryEvent = new ValidatePasswordRecoveryEvent(recovery.setFone(user.getFone())); + this.eventPublisher.publishEvent(passwordRecoveryEvent); + final RecoveryPasswordResponse response = new RecoveryPasswordResponse(); + if (passwordRecoveryEvent.numberIsValid()) { + final SendNewPasswordRecoveredEvent sendNewPasswordRecoveredEvent = new SendNewPasswordRecoveredEvent(user, generateNewPassword()); + this.passwordService.resetePasswordFor(user, sendNewPasswordRecoveredEvent.getHallPassword()); + + response.setPassword(sendNewPasswordRecoveredEvent.getHallPassword()); + return response; + //this.eventPublisher.publishEvent(sendNewPasswordRecoveredEvent); + } + return response.setFone(user.getFone()); + } + //SE CHEGOU AQUI É SINAL QUE A VALIDAÇÃO POR FONE NAO TA ATIVA + throw new MuttleyException("ERRO NO PROCESSO DE RECOVERY"); + } + + @Override + public User addOrUpdateSecundaryEmail(@RequestBody IncludeSecundaryEmail request) { + + + if (request == null || request.getEmailSecundary() == null || request.getEmailSecundary().isEmpty()) { + throw new IllegalArgumentException("Requisição inválida: email secundário não fornecido."); + } + + + User currentUser = authService.getCurrentUser(); + if (currentUser == null) { + throw new MuttleySecurityUserNotFoundException(User.class, "currentUser", "Usuário atual não encontrado."); + } + + + final User user = this.findUserByEmailOrUserNameOrNickUser(currentUser.getEmail()); + if (user == null) { + throw new MuttleySecurityUserNotFoundException(User.class, "email", "Usuário não encontrado pelo email atual."); + } + + + User existingUserWithSecundaryEmail = repository.findByEmailOrEmailSecundario(request.getEmailSecundary(),request.getEmailSecundary()); + if (existingUserWithSecundaryEmail != null) { + throw new MuttleySecurityUserNotFoundException(User.class, "email", "Este e-mail secundário já está associado a outro usuário."); + } + + + user.setEmailSecundario(request.getEmailSecundary()); + repository.save(user); + + return user; + } + + private User merge(final User user) { - if (user.getName() == null || user.getName().length() < 4) { - throw new MuttleySecurityBadRequestException(User.class, "nome", "O campo nome deve ter de 4 a 200 caracteres!"); + checkNameIsValid(user); + + //validando infos do usuário + user.validateBasicInfoForLogin(); + + //se não preencheu nada bloqueamos + if (StringUtils.isEmpty(user.getUserName()) && StringUtils.isEmpty(user.getEmail()) && CollectionUtils.isEmpty(user.getNickUsers())) { + throw new MuttleySecurityBadRequestException(User.class, null, "Informe o email, userName ou os nickUsers"); } - if (!user.isValidEmail()) { - throw new MuttleySecurityBadRequestException(User.class, "email", "Informe um email válido!"); + + //caso não tenha se inform um userName, vamos gerar um randomicamente + if (StringUtils.isEmpty(user.getUserName())) { + final String startUserName; + if (!StringUtils.isEmpty(user.getEmail())) { + startUserName = user.getEmail().substring(0, user.getEmail().indexOf("@")); + } else { + startUserName = user.getName().trim(); + } + user.setUserName(startUserName + new ObjectId(new Date()).toString()); } + if (user.getId() == null) { + //validando se já existe esse usuário no sistema - try { - if (findByEmail(user.getEmail()) != null) { - throw new MuttleySecurityConflictException(User.class, "email", "Email já cadastrado!"); - } - } catch (MuttleySecurityNotFoundException ex) { - } + //devemos garantir que qualquer info de email, userName e ou nickUsers sejam unicos + /*checkIsUniqueUserName(user); + checkIsUniqueEmail(user); + checkIsUniqueNickUsers(user);*/ + this.checkIndexUser(user); + //validando se preencheu a senha corretamente - if (!user.isValidPasswd()) { + /*if (!user.isValidPasswd()) { throw new MuttleySecurityBadRequestException(User.class, "passwd", "Informe uma senha válida!"); - } + }*/ return repository.save(user); } else { final User self = findById(user.getId()); - if (!self.getEmail().equals(user.getEmail())) { - throw new MuttleySecurityBadRequestException(User.class, "email", "O email não pode ser modificado!").setStatus(HttpStatus.NOT_ACCEPTABLE); + /*checkIsUniqueUserName(user); + checkIsUniqueEmail(user); + checkIsUniqueNickUsers(user);*/ + this.checkIndexUser(user); + +/* + if (!self.getUserName().equals(user.getUserName())) { + throw new MuttleySecurityBadRequestException(User.class, "userName", "O userName não pode ser modificado!").setStatus(HttpStatus.NOT_ACCEPTABLE); } +*/ //garantindo que a senha não irá ser modificada - user.setPasswd(self); + //user.setPasswd(self); return repository.save(user); } } + private void checkIndexUser(final User user) { + final Set userNames = new HashSet<>(user.getNickUsers()); + if (!StringUtils.isEmpty(user.getEmail())) { + userNames.add(user.getEmail()); + } + if (!StringUtils.isEmpty(user.getUserName())) { + userNames.add(user.getUserName()); + } + + final Criteria basicCriteria = new Criteria().orOperator( + where("userName").in(userNames), + where("email").in(userNames), + where("nickUsers").in(userNames) + ); + + Aggregation aggregation = null; + + if (StringUtils.isEmpty(user.getId())) { + aggregation = newAggregation( + match(basicCriteria), + Aggregation.count().as("count") + ); + } else { + aggregation = newAggregation( + match(basicCriteria.and("_id").ne(new ObjectId(user.getId()))), + Aggregation.count().as("count") + ); + } + + final AggregationResults result = template.aggregate(aggregation, User.class, UserViewServiceImpl.ResultCount.class); + + if (result != null) { + final UserViewServiceImpl.ResultCount resultCount = result.getUniqueMappedResult(); + if (resultCount != null && resultCount.getCount() > 0) { + final MuttleySecurityConflictException ex = new MuttleySecurityConflictException(User.class, null, "Por favor, busque utilizar outra opção!"); + //para devolver uma resposta adequada, vamos verificar quais nomes estão indisponíveis; + ex.addDetails("indisponivel", userNames + .stream() + .filter(u -> this.existUserName(user.getId(), u)) + .collect(toList())); + throw ex; + } + } + + } + + /** + * Metodo usado para verificar se um determinado nome esta disponivel ou não + */ + private boolean existUserName(final String id, final String userName) { + if (!StringUtils.isEmpty(userName)) { + final AggregationResults result; + if (StringUtils.isEmpty(id)) { + result = template.aggregate(newAggregation( + match( + new Criteria().orOperator( + where("userName").in(userName), + where("email").in(userName), + where("nickUsers").in(userName), + where("emailSecundario").is(userName) + + ) + ), + Aggregation.count().as("count") + ), User.class, UserViewServiceImpl.ResultCount.class); + } else { + result = template.aggregate(newAggregation( + match( + new Criteria().orOperator( + where("userName").in(userName), + where("email").in(userName), + where("nickUsers").in(userName), + where("emailSecundario").is(userName) + + ).and("_id").ne(new ObjectId(id)) + ), + Aggregation.count().as("count") + ), User.class, UserViewServiceImpl.ResultCount.class); + } + if (result != null) { + final UserViewServiceImpl.ResultCount resultCount = result.getUniqueMappedResult(); + return resultCount != null && resultCount.getCount() > 0; + } + } + return false; + } + public Long count(final User user, final Map allRequestParams) { //return repository.count(allRequestParams); throw new UnsupportedOperationException("Implemente o methodo"); @@ -194,13 +764,96 @@ public List findAll(final User user, final Map allRequestP //return repository.findAll(allRequestParams); } + /** + * Retorna true caso encontre algum nickUser com idUser diferente do informado + */ + private boolean existNickUsers(final ObjectId idUser, final String nickUsers) { + final AggregationResults result = template.aggregate(newAggregation( + match(where("_id").ne(idUser).and("nickUsers").in(nickUsers)), + Aggregation.count().as("count") + ), User.class, UserViewServiceImpl.ResultCount.class); + + if (result == null) { + return false; + } + return result.getUniqueMappedResult().getCount() > 0; + } + + /** + * Retorna true caso encontre algum nickUser + */ + private boolean existNickUsers(final String nickUsers) { + + + final AggregationResults result = template.aggregate(newAggregation( + match(where("nickUsers").in(nickUsers)), + Aggregation.count().as("count") + ), User.class, UserViewServiceImpl.ResultCount.class); + + if (result == null) { + return false; + } + return result.getUniqueMappedResult().getCount() > 0; + } + + private String generateNewPassword() { + final int leftLimit = 48; // numeral '0' + final int leftNumber = 57; // numeral '0' + return new Random() + .ints(leftLimit, leftNumber + 1) + .parallel() + .limit(6) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + } + @Override - public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { - final User user = repository.findByEmail(username); - if (user == null) { - throw new UsernameNotFoundException("Usuário não encontrado"); - } else { - return new JwtUser(user); + public User updateProfilePic(User user) { + if (user == null || user.getId() == null) { + throw new IllegalArgumentException("Id de usuario nao pode ser nulo"); + } + + final User userCurrently = this.repository.findOne(user.getId()); + + if (userCurrently == null) { + throw new MuttleySecurityNotFoundException(User.class, "Usuario nao encontrado", "User not found"); + } + + if (user.getFoto() != null) { + userCurrently.setUserFoto(user.getFoto()); + this.repository.save(userCurrently); } + + return userCurrently; } + + /*private void validatePreferences(final UserPreferences preferences) { + //se o serviço foi injetado, devemos validar + //se a preferencia do usuário já tiver o id, devemo validar + if (this.inmutablesPreferencesService != null && !StringUtils.isEmpty(preferences.getId())) { + final Set inmutableKeys = this.inmutablesPreferencesService.getInmutablesKeysPreferences(); + if (!CollectionUtils.isEmpty(inmutableKeys)) { + //recuperando as preferencias sem alterações + final UserPreferences otherPreferences = this.preferencesRepository.findByUser(preferences.getUser()); + if (otherPreferences != null) { + //percorrendo todas a keys que nsão proibidas as alterações + inmutableKeys.forEach(inmutableKey -> { + if (!StringUtils.isEmpty(inmutableKey)) { + //se as preferencias existir no banco + if (otherPreferences.contains(inmutableKey)) { + //recuperando a preferencia do objeto atual + final Preference pre = preferences.get(inmutableKey); + //se a preferencial atual for null ou tiver sido modificada + if (pre == null || !pre.getValue().equals(otherPreferences.get(inmutableKey).getValue())) { + throw new MuttleyBadRequestException(Preference.class, "key", "Não é possível fazer a alteração da preferencia [" + inmutableKey + ']') + .addDetails("key", inmutableKey) + .addDetails("currentValue", otherPreferences.get(inmutableKey).getValue()); + } + } + } + }); + } + } + } + }*/ } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserViewServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserViewServiceImpl.java new file mode 100644 index 00000000..95b943ec --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/UserViewServiceImpl.java @@ -0,0 +1,209 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.domain.service.impl.ServiceImpl; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyNoContentException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.exception.throwables.security.MuttleySecurityNotFoundException; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserView; +import br.com.muttley.security.server.config.model.DocumentNameConfig; +import br.com.muttley.security.server.service.UserViewService; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static java.util.Arrays.asList; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; + +/** + * @author Joel Rodrigues Moreira on 29/04/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class UserViewServiceImpl extends ServiceImpl implements UserViewService { + private static final String[] basicRoles = new String[]{"user_view"}; + private final DocumentNameConfig documentNameConfig; + + @Autowired + public UserViewServiceImpl(final MongoTemplate mongoTemplate, final DocumentNameConfig documentNameConfig) { + super(null, mongoTemplate, UserView.class); + this.documentNameConfig = documentNameConfig; + } + + @Override + public String[] getBasicRoles() { + return basicRoles; + } + + @Override + public void checkPrecondictionSave(final User user, final UserView value) { + throw new MuttleyBadRequestException(UserView.class, null, "Não se pode inserir registro em uma view"); + } + + @Override + public void checkPrecondictionUpdate(final User user, final UserView value) { + throw new MuttleyBadRequestException(UserView.class, null, "Não se pode alterar registro em uma view"); + } + + @Override + public void checkPrecondictionDelete(final User user, final String id) { + throw new MuttleyBadRequestException(UserView.class, null, "Não se pode deletar registro em uma view"); + } + + @Override + public UserView findById(final User user, final String id) { + final UserView view = this.mongoTemplate.findOne( + query(where("_id").is(new ObjectId(id))), + UserView.class, + this.documentNameConfig.getNameViewCollectionUser() + ); + if (view == null) { + throw new MuttleyNotFoundException(UserView.class, "id", "Registro não encontrado"); + } + return view; + } + + @Override + public UserView findFirst(final User user) { + final UserView view = this.mongoTemplate.aggregate( + newAggregation( + limit(1) + ), this.documentNameConfig.getNameViewCollectionUser(), UserView.class + ).getUniqueMappedResult(); + if (view == null) { + throw new MuttleyNotFoundException(UserView.class, null, "Registro não encontrado"); + } + return view; + } + + @Override + public Long count(final User user, final Map allRequestParams) { + throw new NotImplementedException(); + } + + @Override + public List findAll(final User user, final Map allRequestParams) { + throw new NotImplementedException(); + } + + @Override + public UserView findByUserName(final String userName, final String idOwner) { + final List operations = new LinkedList<>(asList( + match( + new Criteria().orOperator( + where("name").is(userName), + where("userName").is(userName), + where("email").is(userName), + where("nickUsers").is(userName) + ) + ) + )); + + if (!StringUtils.isEmpty(idOwner)) { + operations.add( + match( + where("owner.$id").is(new ObjectId(idOwner)) + ) + ); + } + + this.mongoTemplate.aggregate(newAggregation(operations), this.documentNameConfig.getNameViewCollectionUser(), UserView.class); + + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation(operations), this.documentNameConfig.getNameViewCollectionUser(), UserView.class + ); + if (results == null || results.getUniqueMappedResult() == null) { + throw new MuttleyNotFoundException(UserView.class, "userName", "Usuário não encontrado"); + } + return results.getUniqueMappedResult(); + } + + @Override + public List list(final String criterio, final String idOwner) { + final List operations = this.createQuery(criterio, idOwner); + if (operations.isEmpty()) { + operations.add(project("_id", "name", "userName", "email", "nickUsers", "owner")); + } + final List views = this.mongoTemplate.aggregate( + newAggregation(operations), this.documentNameConfig.getNameViewCollectionUser(), UserView.class + ).getMappedResults(); + if (views == null || views.isEmpty()) { + throw new MuttleyNoContentException(UserView.class, "", "nenhum registro encontrado"); + } + return views; + } + + + + + @Override + public long count(final String criterio, final String idOwner) { + final List operations = this.createQuery(criterio, idOwner); + operations.add(Aggregation.count().as("count")); + final AggregationResults result = this.mongoTemplate.aggregate( + newAggregation(operations), + this.documentNameConfig.getNameViewCollectionUser(), + ResultCount.class + ); + return result.getUniqueMappedResult() != null ? result.getUniqueMappedResult().getCount() : 0L; + } + + private List createQuery(final String criterio, final String idOwner) { + final List operations = new ArrayList<>(2); + if (!StringUtils.isEmpty(criterio)) { + operations.add( + match( + new Criteria().orOperator( + where("name").regex(criterio, "si"), + where("userName").regex(criterio, "si"), + where("email").regex(criterio, "si"), + where("nickUsers").in(criterio) + ) + ) + ); + } + if (!StringUtils.isEmpty(idOwner)) { + operations.add( + match( + where("owner.$id").is(new ObjectId(idOwner)) + ) + ); + } + return operations; + } + + protected final class ResultCount { + private Long count; + + protected ResultCount() { + } + + public Long getCount() { + return this.count; + } + + public ResultCount setCount(Long count) { + this.count = count; + return this; + } + } +} diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/WorkTeamServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/WorkTeamServiceImpl.java index f5e099bb..57c7a8f7 100644 --- a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/WorkTeamServiceImpl.java +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/WorkTeamServiceImpl.java @@ -1,52 +1,758 @@ package br.com.muttley.security.server.service.impl; +import br.com.muttley.exception.throwables.MuttleyBadRequestException; import br.com.muttley.exception.throwables.MuttleyNoContentException; import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.localcache.services.LocalWorkTeamService; +import br.com.muttley.model.BasicAggregateResultCount; import br.com.muttley.model.security.Owner; import br.com.muttley.model.security.User; -import br.com.muttley.model.security.WorkTeam; +import br.com.muttley.model.workteam.WorkTeam; +import br.com.muttley.model.workteam.WorkTeamDomain; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.server.config.model.DocumentNameConfig; import br.com.muttley.security.server.repository.WorkTeamRepository; +import br.com.muttley.security.server.service.OwnerService; +import br.com.muttley.security.server.service.UserBaseService; import br.com.muttley.security.server.service.WorkTeamService; +import com.mongodb.BasicDBObject; +import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; -import static java.util.Objects.isNull; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toSet; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.query.Criteria.where; /** - * @author Joel Rodrigues Moreira on 26/02/18. + * @author Joel Rodrigues Moreira on 03/03/2022. * e-mail: joel.databox@gmail.com * @project muttley-cloud - * Service do owner do odin */ @Service public class WorkTeamServiceImpl extends SecurityServiceImpl implements WorkTeamService { private final WorkTeamRepository repository; + private final OwnerService ownerService; + private final DocumentNameConfig documentNameConfig; + private final UserBaseService userBaseService; + private final RedisService redisService; @Autowired - public WorkTeamServiceImpl(final WorkTeamRepository repository) { - super(repository, WorkTeam.class); + public WorkTeamServiceImpl(final WorkTeamRepository repository, final MongoTemplate mongoTemplate, OwnerService ownerService, final DocumentNameConfig documentNameConfig, UserBaseService userBaseService, RedisService redisService) { + super(repository, mongoTemplate, WorkTeam.class); this.repository = repository; + this.ownerService = ownerService; + this.documentNameConfig = documentNameConfig; + this.userBaseService = userBaseService; + this.redisService = redisService; } @Override - public WorkTeam findByName(final Owner owner, final String name) { - final WorkTeam cwt = repository.findByName(owner, name); - if (isNull(cwt)) { - throw new MuttleyNotFoundException(WorkTeam.class, "name", "Registro não encontrado") - .addDetails("name", name); + public void beforeSave(User user, WorkTeam workTeam) { + //garantindo informações cruciais + workTeam.setOwner(user); + //this.removeUsersMasterFromMembers(user, workTeam); + + super.beforeSave(user, workTeam); + } + + @Override + public void afterSave(User user, WorkTeam value) { + this.expire(value); + super.afterSave(user, value); + } + + @Override + public void afterSave(User user, Collection values) { + values.forEach(this::expire); + super.afterSave(user, values); + } + + @Override + public void checkPrecondictionSave(User user, WorkTeam workTeam) { + this.checkOwnerIsPresent(user, workTeam); + this.checkUsersHasBeenPresent(user, workTeam); + this.checkCircularDependence(user, workTeam); + this.checkUsersMasterHasBeenInMembers(user, workTeam); + super.checkPrecondictionSave(user, workTeam); + } + + @Override + public void beforeUpdate(User user, WorkTeam workTeam) { + //garantindo que não será alterado informações cruciais + workTeam.setOwner(user.getCurrentOwner()); + //this.removeUsersMasterFromMembers(user, workTeam); + super.beforeUpdate(user, workTeam); + } + + @Override + public void checkPrecondictionUpdate(User user, WorkTeam workTeam) { + this.checkOwnerIsPresent(user, workTeam); + this.checkUsersHasBeenPresent(user, workTeam); + this.checkCircularDependence(user, workTeam); + this.checkUsersMasterHasBeenInMembers(user, workTeam); + super.checkPrecondictionUpdate(user, workTeam); + } + + @Override + public void afterUpdate(User user, WorkTeam value) { + this.expire(value); + super.afterUpdate(user, value); + } + + @Override + public void afterUpdate(User user, Collection values) { + values.forEach(it -> this.expire(it)); + super.afterUpdate(user, values); + } + + @Override + public WorkTeamDomain loadDomain(final User user) { + final List operations = this.createBasicQueryViewWorkTeamDomain(user, asList(user)); + //adicionando o critério de filtro inicial + operations.add(match(where("_id").is(user.getObjectId()))); + + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation(operations), + documentNameConfig.getNameCollectionUser(), + WorkTeamDomain.class + ); + final WorkTeamDomain domain = results.getUniqueMappedResult(); + if (domain != null) { + return domain + .setUserMaster(user) + //adicionando membro do owner + .addSupervisors(user.getCurrentOwner().getUserMaster()); + } + //se chegou até aqui é sinal que o usuário não está presente em um time + //logo devemos retornar apenas ele e o usuário do owner para acesso aos dados + return new WorkTeamDomain() + .setUserMaster(user) + //adicionando membro do owner + .addSupervisors(user.getCurrentOwner().getUserMaster()); + } + + @Override + public List findAll(User user, Map allRequestParams) { + /** + *db.getCollection("muttley-work-teams").aggregate([ + * {$match:{$or:[{"userMaster.$id": ObjectId("5d49cca5a1d16f19595be983")}, {"members.$id":ObjectId("5d49cca5a1d16f19595be983")}]}}, + * ]) + */ + final AggregationResults workTeamResults = this.mongoTemplate.aggregate( + newAggregation( + match( + new Criteria().orOperator( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + ) + ) + ) + , WorkTeam.class, WorkTeam.class); + if (workTeamResults == null) { + throw new MuttleyNoContentException(WorkTeam.class, null, "Nenhum grupo encontrado para o usuário informado"); } - return cwt; + final List workTeams = workTeamResults.getMappedResults(); + if (CollectionUtils.isEmpty(workTeams)) { + throw new MuttleyNoContentException(WorkTeam.class, null, "Nenhum grupo encontrado para o usuário informado"); + } + return workTeams; } @Override - public List findByUserMaster(final Owner owner, final User user) { - final List itens = repository.findByUserMaster(owner, user); - if (CollectionUtils.isEmpty(itens)) { - throw new MuttleyNoContentException(WorkTeam.class, "name", "Nenhum time de trabalho encontrado"); + public List findByUser(User user) { + /** + *db.getCollection("muttley-work-teams").aggregate([ + * {$match:{$or:[{"userMaster.$id": ObjectId("5d49cca5a1d16f19595be983")}, {"members.$id":ObjectId("5d49cca5a1d16f19595be983")}]}}, + * ]) + */ + final AggregationResults workTeamResults = this.mongoTemplate.aggregate( + newAggregation( + match( + new Criteria().orOperator( + where("userMaster.$id").is(new ObjectId(user.getId())), + where("members.$id").is(new ObjectId(user.getId())) + ) + ) + ) + , WorkTeam.class, WorkTeam.class); + if (workTeamResults == null) { + throw new MuttleyNotFoundException(WorkTeam.class, "members", "Nenhum grupo encontrado para o usuário informado"); + } + final List workTeams = workTeamResults.getMappedResults(); + if (CollectionUtils.isEmpty(workTeams)) { + throw new MuttleyNotFoundException(WorkTeam.class, "members", "Nenhum grupo encontrado para o usuário informado"); + } + return workTeams; + } + + private List createBasicQueryViewWorkTeamDomain(final User user, final Collection users) { + /** + * var $owner = ObjectId("629f37d4e684d90007552522"); + * //var $userMaster = ObjectId("648c7726277657c615334a06"); + * var $userMaster = ObjectId("652e7879e2a7287ff5da2ff5"); + * + * db.getCollection("muttley-users").aggregate([ + * {$match:{_id:$userMaster}}, + * //carregando times de trabalho onde o mesmo faz parte de mebros + * //isso se faz necessário para que carregamos os supervisores e os colegas + * {$lookup:{ + * from: "muttley-work-teams", + * pipeline:[ + * {$match:{ + * $expr: { + * $and:[ + * {$eq:["$owner.$id", $owner]}, + * {$or: [ + * { $in: [$userMaster, "$members.$id"] } + * ]} + * ], + * } + * }}, + * //preparando dados para retorno + * {$project:{ + * usersMaster:1, + * members:{ + * //vamos remover o usuário buscado como membro + * $filter:{ + * input: "$members", + * as:"item", + * cond:{ + * $not:[ + * {$in:["$$item.$id", [$userMaster]]} + * ] + * } + * } + * } + * }}, + * //agrupando registros + * {$group:{_id:null, usersMaster:{$addToSet:"$usersMaster"}, members:{$addToSet:"$members"}}}, + * {$project:{ + * supervisors:{ + * $reduce:{ + * input:"$usersMaster", + * initialValue: [], + * in:{$setUnion:["$$value", "$$this"]} + * } + * }, + * colleagues:{ + * $reduce:{ + * input:"$members", + * initialValue: [], + * in:{$setUnion:["$$value", "$$this"]} + * } + * } + * }} + * ], + * as:"supervisorsColleagues" + * }}, + * //carregando times de trabalho onde o mesmo faz parte de supervisores + * //isso se faz necessário para que carregamos os colegas e os subordinados + * //Os usuraios contido em usersMaster serão colegas e os usuários contidos em members serão subordinados + * {$lookup:{ + * from: "muttley-work-teams", + * pipeline:[ + * {$match:{ + * $expr: { + * $and:[ + * {$eq:["$owner.$id", $owner]}, + * {$or: [ + * { $in: [$userMaster, "$usersMaster.$id"] } + * ]} + * ], + * } + * }}, + * //preparando dados para retorno + * {$project:{ + * usersMaster:{ + * //vamos remover o usuário buscado como userMaster + * $filter:{ + * input: "$usersMaster", + * as:"item", + * cond:{ + * $not:[ + * {$in:["$$item.$id", [$userMaster]]} + * ] + * } + * } + * }, + * members:1 + * }}, + * //agrupando registros + * {$group:{_id:null, usersMaster:{$addToSet:"$usersMaster"}, members:{$addToSet:"$members"}}}, + * {$project:{ + * usersMaster:{ + * $reduce:{ + * input:"$usersMaster", + * initialValue: [], + * in:{$setUnion:["$$value", "$$this"]} + * } + * }, + * members:{ + * $reduce:{ + * input:"$members", + * initialValue: [], + * in:{$setUnion:["$$value", "$$this"]} + * } + * } + * }}, + * //fazendo as consulta recursivamente para montar as dependencias + * {$graphLookup:{ + * from:"muttley-work-teams", + * startWith:"$members", + * connectFromField: "members", + * connectToField:"usersMaster", + * as: "treeTeams", + * restrictSearchWithMatch:{ + * //garantindo filtro pelo owner corrente + * "owner.$id": $owner, + * //garantindo que pegaria apenas registros onde o usuario seja membro e nao um administrador + * "usersMaster.$id":{$nin:[$userMaster]} + * } + * }}, + * //pengando todos os subordinados encontrado e agrupando + * {$project:{ + * usersMaster:1, + * members:1, + * //executando reduce no campo treeTeams para concatenarmos os membros e usersMaster + * //idependente de qual o resultado podemos concatenalos, pois, todos estaram no mesmo nivel da cascata + * membersTree:{ + * $reduce:{ + * input: "$treeTeams", + * initialValue:[], + * //concatenando tudo + * in:{$setUnion:["$$value", "$$this.members", "$$this.usersMaster"]} + * } + * } + * }}, + * //ajustando dados para supervisores, colegas e subordinados + * {$project:{ + * colleagues:"$usersMaster", + * subordinates:{$setUnion:["$members", "$membersTree"]} + * }} + * ], + * as:"colleaguesSubordinates" + * }}, + * //ajustando dados para retorno + * {$project:{ + * supervisors: { + * $ifNull:[ + * {$map:{ + * input:{$arrayElemAt:["$supervisorsColleagues.supervisors", 0]}, + * as:"item", + * //por padrao so sera acessado dados publicos dos supervisores + * in:{user:"$$item", domain:"PUBLIC"} + * }} + * , + * [] + * ] + * }, + * colleagues:{ + * $ifNull:[ + * {$map:{ + * input:{ + * $setUnion:[ + * {$arrayElemAt:["$supervisorsColleagues.colleagues", 0]}, + * {$arrayElemAt:["$colleaguesSubordinates.collegues", 0]} + * ] + * }, + * as:"item", + * //por padrao so sera acessado dados publicos ou restritos dos colegas + * in:{user:"$$item", domain:"RESTRICTED"} + * }} + * , + * []] + * }, + * subordinates: { + * $ifNull:[ + * {$map:{ + * input:{$arrayElemAt:["$colleaguesSubordinates.subordinates", 0]}, + * as:"item", + * //por padrao podemos acessar qualquer registros de subordinados + * in:{user:"$$item", domain:null} + * }} + * , + * [] + * ] + * } + * }} + * ]) + */ + + final Set membersId = users.parallelStream().map(User::getObjectId).collect(Collectors.toSet()); + final List operations = new LinkedList<>(); + //operations.add(match(where("_id").is(user.getObjectId()))); + //carregando times de trabalho onde o mesmo faz parte de mebros + //isso se faz necessário para que carregamos os supervisores e os colegas + operations.add( + //carregando times de trabalho onde o mesmo faz parte de mebros + //isso se faz necessário para que carregamos os supervisores e os colegas + context -> new BasicDBObject("$lookup", + new BasicDBObject("from", this.documentNameConfig.getNameCollectionWorkTeam()) + .append("pipeline", asList( + new BasicDBObject("$match", + new BasicDBObject("$expr", + new BasicDBObject("$and", asList( + new BasicDBObject("$eq", asList("$owner.$id", user.getCurrentOwner().getObjectId())), + new BasicDBObject("$or", + membersId.parallelStream() + .map(it -> new BasicDBObject("$in", asList(it, "$members.$id"))) + .collect(Collectors.toSet()) + + ) + )) + ) + ), + //preparando dados para retorno + new BasicDBObject("$project", + new BasicDBObject("usersMaster", 1) + .append("members", + new BasicDBObject("$filter", + new BasicDBObject("input", "$members") + .append("as", "item") + .append("cond", + new BasicDBObject("$not", asList( + new BasicDBObject("$in", asList("$$item.$id", membersId)) + )) + ) + ) + ).append("aux", "_null") + ), + //agrupando registros + new BasicDBObject("$group", + new BasicDBObject("_id", "$aux") + .append("usersMaster", new BasicDBObject("$addToSet", "$usersMaster")) + .append("members", new BasicDBObject("$addToSet", "$members")) + ), + new BasicDBObject("$project", + new BasicDBObject("supervisors", + new BasicDBObject("$reduce", + new BasicDBObject("input", "$usersMaster") + .append("initialValue", asList()) + .append("in", + new BasicDBObject("$setUnion", asList("$$value", "$$this")) + ) + ) + ).append("colleagues", + new BasicDBObject("$reduce", + new BasicDBObject("input", "$members") + .append("initialValue", asList()) + .append("in", + new BasicDBObject("$setUnion", asList("$$value", "$$this")) + ) + ) + ) + ) + )).append("as", "supervisorsColleagues") + ) + ); + operations.add( + //carregando times de trabalho onde o mesmo faz parte de supervisores + //isso se faz necessário para que carregamos os colegas e os subordinados + //Os usuraios contido em usersMaster serão colegas e os usuários contidos em members serão subordinados + context -> new BasicDBObject("$lookup", + new BasicDBObject("from", this.documentNameConfig.getNameCollectionWorkTeam()) + .append("pipeline", asList( + new BasicDBObject("$match", + new BasicDBObject("$expr", + new BasicDBObject("$and", asList( + new BasicDBObject("$eq", asList("$owner.$id", user.getCurrentOwner().getObjectId())), + new BasicDBObject("$or", + membersId.parallelStream() + .map(it -> new BasicDBObject("$in", asList(it, "$usersMaster.$id"))) + .collect(Collectors.toSet()) + + ) + )) + ) + ), + //preparando dados para retorno + new BasicDBObject("$project", + new BasicDBObject("usersMaster", + //vamos remover o usuário buscado como userMaster + new BasicDBObject("$filter", + new BasicDBObject("input", "$usersMaster") + .append("as", "item") + .append("cond", + new BasicDBObject("$not", asList( + new BasicDBObject("$in", asList("$$item.$id", membersId)) + )) + ) + ) + ).append("members", 1) + ), + //agrupando registros + new BasicDBObject("$group", + new BasicDBObject("_id", null) + .append("usersMaster", new BasicDBObject("$addToSet", "$usersMaster")) + .append("members", new BasicDBObject("$addToSet", "$members")) + ), + + new BasicDBObject("$project", + new BasicDBObject("usersMaster", + new BasicDBObject("$reduce", + new BasicDBObject("input", "$usersMaster") + .append("initialValue", asList()) + .append("in", new BasicDBObject("$setUnion", asList("$$value", "$$this"))) + ) + ).append("members", + new BasicDBObject("$reduce", + new BasicDBObject("input", "$members") + .append("initialValue", asList()) + .append("in", new BasicDBObject("$setUnion", asList("$$value", "$$this"))) + ) + ) + ), + new BasicDBObject("$graphLookup", + new BasicDBObject("from", this.documentNameConfig.getNameCollectionWorkTeam()) + .append("startWith", "$members") + .append("connectFromField", "members") + .append("connectToField", "usersMaster") + .append("restrictSearchWithMatch", + //garantindo filtro pelo owner corrente + new BasicDBObject("owner.$id", user.getCurrentOwner().getObjectId()) + //garantindo que pegaria apenas registros onde o usuario seja membro e nao um administrador + .append("usersMaster.$id", new BasicDBObject("$nin", asList(user.getObjectId()))) + ).append("as", "treeTeams") + ), + //pengando todos os subordinados encontrado e agrupando + new BasicDBObject("$project", + new BasicDBObject("usersMaster", 1) + .append("members", 1) + //executando reduce no campo treeTeams para concatenarmos os membros e usersMaster + //idependente de qual o resultado podemos concatenalos, pois, todos estaram no mesmo nivel da cascata + .append("membersTree", + new BasicDBObject("$reduce", + new BasicDBObject("input", "$treeTeams") + .append("initialValue", asList()) + //concatenando tudo + .append("in", + new BasicDBObject("$setUnion", asList("$$value", "$$this.members", "$$this.usersMaster")) + ) + ) + ) + ), + //ajustando dados para supervisores, colegas e subordinados + new BasicDBObject("$project", + new BasicDBObject("colleagues", "$usersMaster") + .append("subordinates", new BasicDBObject("$setUnion", asList("$members", "$membersTree"))) + ) + )).append("as", "colleaguesSubordinates") + ) + ); + operations.add( + //ajustando dados para retorno + project() + .and(context1 -> + new BasicDBObject("$ifNull", asList( + new BasicDBObject("$map", + new BasicDBObject("input", + new BasicDBObject("$arrayElemAt", asList("$supervisorsColleagues.supervisors", 0)) + ).append("as", "item") + //por padrao so sera acessado dados publicos dos supervisores + .append("in", + new BasicDBObject("user", "$$item").append("domain", "PUBLIC") + ) + ), + asList() + )) + ).as("supervisors") + .and(context1 -> + new BasicDBObject("$ifNull", asList( + new BasicDBObject("$map", + new BasicDBObject("input", + new BasicDBObject("$setUnion", asList( + new BasicDBObject("$arrayElemAt", asList("$supervisorsColleagues.colleagues", 0)), + new BasicDBObject("$arrayElemAt", asList("$colleaguesSubordinates.collegues", 0)) + )) + ).append("as", "item") + //por padrao so sera acessado dados publicos ou restritos dos colegas + .append("in", + new BasicDBObject("user", "$$item").append("domain", "RESTRICTED") + ) + ), + asList() + )) + ).as("colleagues") + .and(context1 -> + new BasicDBObject("$ifNull", asList( + new BasicDBObject("$map", + new BasicDBObject("input", + new BasicDBObject("$arrayElemAt", asList("$colleaguesSubordinates.subordinates", 0)) + ).append("as", "item") + //por padrao podemos acessar qualquer registros de subordinados + .append("in", + new BasicDBObject("user", "$$item").append("domain", null) + ) + ), + asList() + )) + ).as("subordinates") + ); + return operations; + } + + /** + * Precisamos garantir que o usuários master não estará jundo listado aos seus membros + */ + /*private void removeUsersMasterFromMembers(final User user, final WorkTeam workTeam) { + + workTeam.setMembers( + workTeam.getMembers() + .parallelStream() + .filter(it -> workTeam.getUsersMaster().parallelStream().filter(iit -> it.equals(iit)).count() == 0) + .collect(toSet()) + ); + }*/ + private void checkOwnerIsPresent(final User user, final WorkTeam workTeam) { + final Owner owner = user.getCurrentOwner(); + final User userMaster; + if (owner.getUserMaster() != null) { + userMaster = owner.getUserMaster(); + } else { + userMaster = ownerService.loadCurrentOwner(user).getUserMaster(); + } + + if (workTeam.containsMember(userMaster)) { + throw new MuttleyBadRequestException(WorkTeam.class, "members", "O owner do sistema não pode estar entre os membros do time de trabalho"); + } + } + + /** + * Verifica se todos os usuários inclusos fazem parte da mesma base de usuário que o user master + */ + private void checkUsersHasBeenPresent(final User user, final WorkTeam workTeam) { + //verficando se tem algum usuário presente no workteam + if (workTeam.containsAnyUser()) { + //para evitar consultas desmasiadas, vamos pegar todos os usuario e verificar se estão presentes + //na base de usuários + if (!this.userBaseService.allHasBeenIncludedGroup(user, workTeam.getAllUsers())) { + //se chegou até aqui é sinal que existe algum usuário que não está presente na base + //logo precisamos checar um a um para garantir + + final Map details = new HashMap<>(); + + //verificando o user master + if (!workTeam.usersMasterIsEmpty()) { + workTeam.getUsersMaster() + .stream() + .filter(it -> !this.userBaseService.hasBeenIncludedAnyGroup(user, it)) + .forEach(it -> { + details.put("usersMaster." + it.getUserName(), "O usuário " + it.getName() + " não está presente na base de dados"); + }); + } + + //verificando demais membros + workTeam.getMembers() + .stream() + .filter(it -> !this.userBaseService.hasBeenIncludedAnyGroup(user, it)) + .forEach(it -> { + details.put("members." + it.getUserName(), "O usuário " + it.getName() + " não está presente na base de dados"); + }); + + throw new MuttleyBadRequestException(WorkTeam.class, null, null).addDetails(details); + } + } + } + + private void checkCircularDependence(final User user, final WorkTeam workTeam) { + final List operations = this.createBasicQueryViewWorkTeamDomain(user, workTeam.getMembers()); + //para realizar a checkagem de dependencia circular, precisamos verificar se os membros estão acima do usermaster + //para isso devemos buscar os membro como userMaster e o userMaster atual não pode ser listado como membro na consulta + + operations.add(0, + match( + where("_id").in(workTeam.getMembers().parallelStream().map(User::getObjectId).collect(toSet())) + ) + ); + + operations.addAll( + asList( + match(where("subordinates.user.$id").in(workTeam.getUsersMaster().parallelStream().map(User::getObjectId).collect(toSet()))), + Aggregation.count().as("result") + ) + ); + + final AggregationResults globalResults = this.mongoTemplate.aggregate( + newAggregation(operations), + documentNameConfig.getNameCollectionUser(), + BasicAggregateResultCount.class + ); + + //se o resultado é maior que zero logo tem uma dependencia circular e precisamos verificar qual usuário que está causando isso + //para isso vamo consultar usuário por usuário + final BasicAggregateResultCount globalResultCount = globalResults.getUniqueMappedResult(); + if (globalResultCount != null && globalResultCount.getResult() > 0) { + + + final Set usersNames = new HashSet<>(); + + workTeam.getMembers().forEach(it -> { + //removendo o filtro inicial para fazer a consulta por usuário + operations.remove(0); + + operations.add(0, + match( + where("owner.$id").is(user.getCurrentOwner().getObjectId()) + .and("usersMaster.$id").in(it.getObjectId()) + ) + ); + + final AggregationResults results = this.mongoTemplate.aggregate( + newAggregation(operations), + WorkTeam.class, + BasicAggregateResultCount.class + ); + if (results != null && results.getUniqueMappedResult() != null && results.getUniqueMappedResult().getResult() > 0) { + usersNames.add(it.getName()); + } + + }); + + throw new MuttleyBadRequestException(WorkTeam.class, "members", "Existe membros que são superiores ao supervisor selecionado") + .addDetails("userNames", usersNames); } - return itens; + + } + + private void checkUsersMasterHasBeenInMembers(final User user, final WorkTeam workTeam) { + //verificando tem usuários masters como membros + final Set users = workTeam.getUsersMaster().parallelStream() + .filter(it -> workTeam.getMembers().contains(it)) + .collect(toSet()); + if (!users.isEmpty()) { + final MuttleyBadRequestException exception = new MuttleyBadRequestException(WorkTeam.class, "members", "Gestores não podem estar presentes como membros a serem geridos"); + exception.addDetails("members", users.parallelStream().map(User::getName).collect(toSet())); + throw exception; + } + } + + + /** + * Expirando itens presente no cache do serviço + */ + private void expire(final WorkTeam workTeam) { + redisService.deleteByExpression(LocalWorkTeamService.getBasicKeyExpressionOwner(workTeam.getOwner()) + "*"); + /*workTeam.getUsersMaster().forEach(it -> { + //deletando item do cache + this.redisService.delete(LocalWorkTeamService.getBasicKey(it.getCurrentOwner(), it)); + });*/ } } diff --git a/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/XAPITokenServiceImpl.java b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/XAPITokenServiceImpl.java new file mode 100644 index 00000000..c8bfd24c --- /dev/null +++ b/muttley-security-server/src/main/java/br/com/muttley/security/server/service/impl/XAPITokenServiceImpl.java @@ -0,0 +1,104 @@ +package br.com.muttley.security.server.service.impl; + +import br.com.muttley.exception.throwables.MuttleyBadRequestException; +import br.com.muttley.exception.throwables.MuttleyNotFoundException; +import br.com.muttley.headers.components.MuttleyCurrentVersion; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.security.domain.Domain; +import br.com.muttley.security.server.components.RSAPairKeyComponent; +import br.com.muttley.security.server.repository.XAPITokenRepository; +import br.com.muttley.security.server.service.XAPITokenService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Date; + +import static br.com.muttley.model.security.rsa.RSAUtil.generateRandomString; + +/** + * @author Joel Rodrigues Moreira on 09/08/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class XAPITokenServiceImpl extends SecurityServiceImpl implements XAPITokenService { + private final RSAPairKeyComponent rsaPairKeyComponent; + private final XAPITokenRepository repository; + + private final MuttleyCurrentVersion currentVersion; + + @Autowired + public XAPITokenServiceImpl(final XAPITokenRepository repository, final MongoTemplate mongoTemplate, final RSAPairKeyComponent rsaPairKeyComponent, final MuttleyCurrentVersion currentVersion) { + super(repository, mongoTemplate, XAPIToken.class); + this.repository = repository; + this.rsaPairKeyComponent = rsaPairKeyComponent; + this.currentVersion = currentVersion; + } + + @Override + public void beforeSave(User user, XAPIToken value) { + //setando data de criação + value.setDtCreate(new Date()) + .setLocaSeed(generateRandomString(20)) + //gerando token de acesso + .setToken(this.rsaPairKeyComponent.encryptMessage(value.generateSeedHash())); + } + + @Override + public void checkPrecondictionSave(User user, XAPIToken value) { + super.checkPrecondictionSave(user, value); + //verificando se o conteudo bate com a seed + if (!this.rsaPairKeyComponent.decryptMessage(value.getToken()).equals(value.generateSeedHash())) { + throw new MuttleyBadRequestException(XAPIToken.class, "token", "Token inválido!"); + } + } + + @Override + public void checkPrecondictionUpdate(User user, XAPIToken value) { + throw new MuttleyBadRequestException(XAPIToken.class, "", "Não é permitido fazer alteração de um token"); + } + + @Override + public void checkPrecondictionUpdate(User user, Collection values) { + throw new MuttleyBadRequestException(XAPIToken.class, "", "Não é permitido fazer alteração de um token"); + } + + @Override + public XAPIToken loadUserByAPIToken(String token) { + final XAPIToken XAPIToken = this.repository.findByToken(token); + if (XAPIToken == null) { + throw new MuttleyNotFoundException(XAPIToken.class, "token", "Token não identificado"); + } + return XAPIToken; + } + + @Override + public XAPIToken generateXAPIToken(final User user, final String description) { + return this.save(user, + new XAPIToken() + .setDescription(description) + .setUser(user) + .setOwner(user.getCurrentOwner()) + .setVersion(this.currentVersion.getCurrenteFromServer()) + ); + } + + @Override + public void afterDelete(User user, String id) { + super.afterDelete(user, id); + + } + + @Override + public void afterDelete(User user, XAPIToken value) { + super.afterDelete(user, value); + } + + @Override + protected void generateNewMetadataFor(User user, XAPIToken value, Domain domain) { + super.generateNewMetadataFor(user, value, Domain.PRIVATE); + } +} diff --git a/muttley-security-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/muttley-security-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000..e69de29b diff --git a/muttley-security-server/src/main/resources/application.properties b/muttley-security-server/src/main/resources/application.properties new file mode 100644 index 00000000..e69de29b diff --git a/muttley-security-server/src/main/resources/maxxsoftLogo.png b/muttley-security-server/src/main/resources/maxxsoftLogo.png new file mode 100644 index 00000000..d7a53106 Binary files /dev/null and b/muttley-security-server/src/main/resources/maxxsoftLogo.png differ diff --git a/muttley-security-server/src/main/resources/recovery-password-template.html b/muttley-security-server/src/main/resources/recovery-password-template.html new file mode 100644 index 00000000..09f52023 --- /dev/null +++ b/muttley-security-server/src/main/resources/recovery-password-template.html @@ -0,0 +1,118 @@ + + + + + + + Recuperação de Senha + + + +

+
+ Logo Maxxsoft +
+
+

Recuperação de Senha

+

Olá,

+

Recebemos uma solicitação para redefinir a sua senha. Para continuar, clique no botão abaixo:

+ Redefinir + Senha +

Se você não solicitou essa alteração, ignore este e-mail. Sua senha permanecerá a mesma.

+
+ +
+ + diff --git a/muttley-security/pom.xml b/muttley-security/pom.xml index 3eff2369..c5a5ae53 100644 --- a/muttley-security/pom.xml +++ b/muttley-security/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar @@ -24,6 +24,12 @@ br.com.muttley muttley-redis
+ + + br.com.muttley + muttley-local-cache + + br.com.muttley muttley-exception diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/AccessPlanServiceClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/AccessPlanServiceClient.java index 239040f6..b6361aea 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/feign/AccessPlanServiceClient.java +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/AccessPlanServiceClient.java @@ -1,16 +1,21 @@ package br.com.muttley.security.feign; import br.com.muttley.feign.service.config.FeignTimeoutConfig; +import br.com.muttley.feign.service.interceptors.HeadersMetadataInterceptor; import br.com.muttley.model.security.AccessPlan; import br.com.muttley.security.infra.security.server.FeignClientConfig; import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; /** * @author Joel Rodrigues Moreira on 18/04/18. * e-mail: joel.databox@gmail.com * @project muttley-cloud */ -@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/access-plan", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class}) +@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/access-plan", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class, HeadersMetadataInterceptor.class}) public interface AccessPlanServiceClient extends RestControllerClient { - + @RequestMapping(value = "/find-by-owner", method = RequestMethod.GET) + AccessPlan findByOwner(@RequestParam("owner") final String idOwner); } diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/OwnerServiceClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/OwnerServiceClient.java index 443a3989..3c2874b2 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/feign/OwnerServiceClient.java +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/OwnerServiceClient.java @@ -1,16 +1,30 @@ package br.com.muttley.security.feign; import br.com.muttley.feign.service.config.FeignTimeoutConfig; +import br.com.muttley.feign.service.interceptors.HeadersMetadataInterceptor; import br.com.muttley.model.security.Owner; +import br.com.muttley.model.security.OwnerData; import br.com.muttley.security.infra.security.server.FeignClientConfig; import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.util.List; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; /** * @author Joel Rodrigues Moreira on 18/04/18. * e-mail: joel.databox@gmail.com * @project muttley-cloud */ -@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/owners", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class}) +@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/owners", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class, HeadersMetadataInterceptor.class}) public interface OwnerServiceClient extends RestControllerClient { + @RequestMapping(value = "/by-user", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + List findByUser(); + @RequestMapping(value = "/by-user-and-id/{id}", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + Owner findByUserAndId(@PathVariable("id") final String id); } diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/PassaportServiceClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/PassaportServiceClient.java new file mode 100644 index 00000000..3b3ef2a9 --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/PassaportServiceClient.java @@ -0,0 +1,42 @@ +package br.com.muttley.security.feign; + +import br.com.muttley.feign.service.config.FeignTimeoutConfig; +import br.com.muttley.feign.service.interceptors.HeadersMetadataInterceptor; +import br.com.muttley.model.security.Passaport; +import br.com.muttley.model.security.Role; +import br.com.muttley.security.infra.security.server.FeignClientConfig; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; +import java.util.Set; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +/** + * @author Joel Rodrigues Moreira on 18/04/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/passaports", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class, HeadersMetadataInterceptor.class}) +public interface PassaportServiceClient extends RestControllerClient { + + @RequestMapping(value = "/find-by-name", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + public Passaport findByName(@RequestParam(name = "name", defaultValue = "") final String name); + + @RequestMapping(value = "/roles/current-roles", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + public Set loadCurrentRoles(); + + @RequestMapping(value = "/avaliable-roles", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + public Set loadAvaliableRoles(); + + @RequestMapping(value = "/find-by-user", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + List findByUser(); + + @RequestMapping(value = "/create-passaport-for", method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + Passaport createPassaportFor(@RequestParam(required = false, value = "ownerId", defaultValue = "") final String ownerId, @RequestBody final Passaport passaport); +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/RestControllerClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/RestControllerClient.java index 7cd7934f..aec9dca6 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/feign/RestControllerClient.java +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/RestControllerClient.java @@ -1,6 +1,5 @@ package br.com.muttley.security.feign; -import br.com.muttley.model.Historic; import br.com.muttley.security.infra.resource.PageableResource; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -26,6 +25,9 @@ public interface RestControllerClient { @RequestMapping(method = POST, consumes = {APPLICATION_JSON_UTF8_VALUE}) T save(@RequestBody T value, @RequestParam(required = false, value = "returnEntity", defaultValue = "") String returnEntity); + @RequestMapping(method = POST, consumes = {APPLICATION_JSON_UTF8_VALUE}) + T save(@RequestBody T value); + @RequestMapping(value = "/{id}", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) T update(@PathVariable("id") String id, @RequestBody T model); @@ -35,12 +37,12 @@ public interface RestControllerClient { @RequestMapping(value = "/{id}", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) T findById(@PathVariable("id") String id); + @RequestMapping(value = "/reference/{id}", method = GET, produces = APPLICATION_JSON_UTF8_VALUE) + T findReferenceById(@PathVariable("id") String id); + @RequestMapping(value = "/first", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) T first(); - @RequestMapping(value = "/{id}/historic", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) - Historic loadHistoric(@PathVariable("id") String id); - @RequestMapping(method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) PageableResource list(@RequestParam Map allRequestParams); @@ -48,7 +50,7 @@ public interface RestControllerClient { PageableResource list(); @RequestMapping(value = "/count", method = GET, consumes = TEXT_PLAIN_VALUE) - Long count(@RequestParam Map allRequestParams); + Long count(@RequestParam Map allRequestParams); @RequestMapping(value = "/count", method = GET, consumes = TEXT_PLAIN_VALUE) Long count(); diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/UserBaseServiceClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/UserBaseServiceClient.java new file mode 100644 index 00000000..51afdd8a --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/UserBaseServiceClient.java @@ -0,0 +1,43 @@ +package br.com.muttley.security.feign; + +import br.com.muttley.feign.service.config.FeignTimeoutConfig; +import br.com.muttley.feign.service.interceptors.HeadersMetadataInterceptor; +import br.com.muttley.model.security.UserBaseItem; +import br.com.muttley.security.infra.security.server.FeignClientConfig; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Set; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; + +/** + * @author Joel Rodrigues Moreira 24/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/users-base", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class, HeadersMetadataInterceptor.class}) +public interface UserBaseServiceClient { + + @RequestMapping(value = "/userNamesIsAvaliable", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + boolean userNameIsAvaliable(@RequestParam(value = "userNameFor", required = false) final String userNameFor, @RequestParam(value = "userNames") final Set userNames); + + @RequestMapping(value = "/userByEmailOrUserName", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + boolean userNameIsAvaliable(@RequestParam(value = "emailOrUsername") final String emailOrUsername); + + @RequestMapping(value = "/create-new-user-and-add", method = RequestMethod.POST, consumes = APPLICATION_JSON_UTF8_VALUE) + void createNewUserAndAdd(@RequestBody final UserBaseItem item); + + @RequestMapping(value = "/merge-user-item-if-exists", method = RequestMethod.POST, consumes = APPLICATION_JSON_UTF8_VALUE) + void mergeUserItemIfExists(@RequestBody final UserBaseItem item); + + @RequestMapping(value = "/add-if-not-exists", method = RequestMethod.POST, consumes = APPLICATION_JSON_UTF8_VALUE) + void addIfNotExists(@RequestBody final UserBaseItem item); + + @RequestMapping(value = "/remove-user/{userName}", method = RequestMethod.DELETE, consumes = APPLICATION_JSON_UTF8_VALUE) + void removeUser(@PathVariable(value = "userName") final String userName); +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/UserDataBindingClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/UserDataBindingClient.java new file mode 100644 index 00000000..116b8ba1 --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/UserDataBindingClient.java @@ -0,0 +1,84 @@ +package br.com.muttley.security.feign; + +import br.com.muttley.feign.service.config.FeignTimeoutConfig; +import br.com.muttley.feign.service.interceptors.HeadersMetadataInterceptor; +import br.com.muttley.model.security.KeyUserDataBinding; +import br.com.muttley.model.security.UserData; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.security.infra.security.server.FeignClientConfig; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; + +/** + * @author Joel Rodrigues Moreira 15/01/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/users-databinding", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class, HeadersMetadataInterceptor.class}) +public interface UserDataBindingClient { + @RequestMapping(method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + UserDataBinding save(@RequestBody final UserDataBinding value, @RequestParam(required = false, value = "returnEntity", defaultValue = "") final boolean returnEntity); + + @RequestMapping(value = "/{id}", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + void update(@PathVariable("id") final String id, @RequestBody final UserDataBinding model); + + @RequestMapping(method = RequestMethod.GET) + List list(); + + @RequestMapping(value = "/by-username/{userName}", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + List listByUserName(@PathVariable("userName") final String userName); + + @RequestMapping(value = "/by-username/{userName}", method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) + UserDataBinding saveByUserName( + @RequestParam(required = false, value = "returnEntity", defaultValue = "") final Boolean returnEntity, + @PathVariable("userName") final String userName, + @RequestBody final UserDataBinding model); + + @RequestMapping(value = "/by-username/{userName}", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + UserDataBinding updateByUserName(@PathVariable("userName") final String userName, @RequestBody final UserDataBinding model); + + @RequestMapping(value = "/merge/{userName}", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + void merger(@PathVariable("userName") final String userName, @RequestBody final UserDataBinding model); + + @RequestMapping(value = "/key/{key}", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + UserDataBinding getKey(@PathVariable("key") final KeyUserDataBinding key); + + @RequestMapping(value = "/key/{key}", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + UserDataBinding getKey(@PathVariable("key") final String key); + + @RequestMapping(value = "/by-username/{userName}/key/{key}", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + UserDataBinding getKeyByUserName(@PathVariable("userName") final String userName, @PathVariable("key") final KeyUserDataBinding key); + + @RequestMapping(value = "/by-username/{userName}/key/{key}", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + UserDataBinding getKeyByUserName(@PathVariable("userName") final String userName, @PathVariable("key") final String key); + + @RequestMapping(value = "/contains/key/{key}", method = RequestMethod.GET, consumes = TEXT_PLAIN_VALUE) + boolean contains(@PathVariable("key") final KeyUserDataBinding key); + + @RequestMapping(value = "/contains/key/{key}", method = RequestMethod.GET, consumes = TEXT_PLAIN_VALUE) + boolean contains(@PathVariable("key") final String key); + + @RequestMapping(value = "/by-username/{userName}/contains/key/{key}", method = RequestMethod.GET, consumes = TEXT_PLAIN_VALUE) + boolean containsByUserName(@PathVariable("userName") final String userName, @PathVariable("key") final KeyUserDataBinding key); + + @RequestMapping(value = "/by-username/{userName}/contains/key/{key}", method = RequestMethod.GET, consumes = TEXT_PLAIN_VALUE) + boolean containsByUserName(@PathVariable("userName") final String userName, @PathVariable("key") final String key); + + @RequestMapping(value = "/user-by", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + UserData getUserBy(@RequestParam(value = "key") final KeyUserDataBinding key, @RequestParam(required = false, value = "value", defaultValue = "") final String value); + + @RequestMapping(value = "/user-by", method = RequestMethod.GET, consumes = APPLICATION_JSON_UTF8_VALUE) + UserData getUserBy(@RequestParam(required = false, value = "key", defaultValue = "") final String key, @RequestParam(required = false, value = "value", defaultValue = "") final String value); +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/UserPreferenceServiceClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/UserPreferenceServiceClient.java index 16fb53a5..6166bd74 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/feign/UserPreferenceServiceClient.java +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/UserPreferenceServiceClient.java @@ -1,6 +1,8 @@ package br.com.muttley.security.feign; import br.com.muttley.feign.service.config.FeignTimeoutConfig; +import br.com.muttley.feign.service.interceptors.HeadersMetadataInterceptor; +import br.com.muttley.model.security.User; import br.com.muttley.model.security.preference.Preference; import br.com.muttley.model.security.preference.UserPreferences; import br.com.muttley.security.infra.security.server.FeignClientConfig; @@ -8,26 +10,52 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; +import java.util.Set; import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.DELETE; import static org.springframework.web.bind.annotation.RequestMethod.GET; -import static org.springframework.web.bind.annotation.RequestMethod.POST; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; /** * @author Joel Rodrigues Moreira on 18/04/18. * e-mail: joel.databox@gmail.com * @project muttley-cloud */ -@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/user-preferences", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class}) +@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/user-preferences", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class, HeadersMetadataInterceptor.class}) public interface UserPreferenceServiceClient { - @RequestMapping(value = "/{idUser}", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) - public UserPreferences getPreferences(@PathVariable("idUser") String idUser); + @RequestMapping(method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + UserPreferences getUserPreferences(); + + @RequestMapping(value = "/preferences", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + void setPreference(@RequestBody final Preference preference); + + @RequestMapping(value = "/preferences/{key}", method = DELETE, consumes = APPLICATION_JSON_UTF8_VALUE) + void removePreference(@PathVariable("key") final String key); + + @RequestMapping(value = "/preferences/contains", method = GET, produces = TEXT_PLAIN_VALUE) + boolean containsPreferences(@RequestParam(name = "key", required = false) final String keyPreference); + + @RequestMapping(value = "/preferences", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + Set getCurrentPreferences(); + + @RequestMapping(value = "/preferences/{key}", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + void setPreferences(@PathVariable("key") final String key, @RequestParam(value = "value", required = false) final String value); + + @RequestMapping(value = "/preferences/{key}", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + Preference getPreference(@PathVariable("key") final String key); + + @RequestMapping(value = "/preferences/{key}/value", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + String getPreferenceValue(@PathVariable("key") final String key); - @RequestMapping(value = "/{idUser}/preferences", method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) - public void setPreference(@PathVariable("idUser") final String idUser, @RequestBody final Preference preference); + @RequestMapping(value = "/users-from-preference", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + List getUsersFromPreference(@RequestParam(name = "key", required = false) final String key, @RequestParam(name = "value", required = false) final String value); - @RequestMapping(value = "/{idUser}/preferences/{key}", method = DELETE, consumes = APPLICATION_JSON_UTF8_VALUE) - public void removePreference(@PathVariable("idUser") final String idUser, @PathVariable("key") final String key); + @RequestMapping(value = "/user-from-preference", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + User getUserFromPreference(@RequestParam(name = "key", required = false) final String key, @RequestParam(name = "value", required = false) final String value); } diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/UserServiceClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/UserServiceClient.java index 83bc8667..961d815b 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/feign/UserServiceClient.java +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/UserServiceClient.java @@ -1,8 +1,10 @@ package br.com.muttley.security.feign; import br.com.muttley.feign.service.config.FeignTimeoutConfig; +import br.com.muttley.feign.service.interceptors.HeadersMetadataInterceptor; import br.com.muttley.model.security.JwtToken; -import br.com.muttley.model.security.Passwd; +import br.com.muttley.model.security.PasswdPayload; +import br.com.muttley.model.security.Password; import br.com.muttley.model.security.User; import br.com.muttley.model.security.UserPayLoad; import br.com.muttley.security.infra.security.server.FeignClientConfig; @@ -11,8 +13,11 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import java.util.Set; + import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.DELETE; import static org.springframework.web.bind.annotation.RequestMethod.GET; @@ -24,28 +29,43 @@ * e-mail: joel.databox@gmail.com * @project muttley-cloud */ -@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/users", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class}) +@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/users", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class, HeadersMetadataInterceptor.class}) public interface UserServiceClient { @RequestMapping(method = POST, consumes = APPLICATION_JSON_UTF8_VALUE) public User save(@RequestBody UserPayLoad value, @RequestParam(required = false, value = "returnEntity", defaultValue = "") String returnEntity); - @RequestMapping(value = "/{email}", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) - public User update(@PathVariable("email") final String email, @RequestHeader("${muttley.security.jwt.controller.tokenHeader}") final String token, @RequestBody final User user); + @RequestMapping(value = "/{userName}", method = PUT, consumes = APPLICATION_JSON_UTF8_VALUE) + public User update(@PathVariable("userName") final String userName, @RequestHeader("${muttley.security.jwt.controller.tokenHeader}") final String token, @RequestBody final User user); - @RequestMapping(value = "/passwd", method = PUT) - public void updatePasswd(@RequestBody final Passwd passwd); + @RequestMapping(value = "/password", method = PUT) + public void updatePasswd(@RequestBody final PasswdPayload passwdPayload); @RequestMapping(method = DELETE) - void deleteByEmail(@RequestParam("email") String email); + void deleteByUserName(@RequestParam("userName") String userName); @Deprecated @RequestMapping(value = "/ad$/{id}", method = GET) User findById(@PathVariable("id") String id); - @RequestMapping(method = GET) - User findByEmail(@RequestParam("email") String email); + @RequestMapping(method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + User findByUserName(@RequestParam("userName") String userName); + + @RequestMapping(value = "/{id}/password", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + public Password loadPasswordById(@RequestParam("id") final String id); @RequestMapping(value = "/user-from-token", method = GET) public User getUserFromToken(@RequestBody final JwtToken token); + + @RequestMapping(value = "/email-or-username-or-nickUsers", method = GET) + public User findUserByEmailOrUserNameOrNickUsers(@RequestParam(value = "email", required = false) final String email, @RequestParam(value = "userName", required = false) final String userName, @RequestParam(value = "nickUsers", required = false) final Set nickUsers); + + @RequestMapping(value = "/exist-email-or-username-or-nickUsers", method = RequestMethod.GET) + public boolean existUserByEmailOrUserNameOrNickUsers(@RequestParam(value = "email", required = false) final String email, @RequestParam(value = "userName", required = false) final String userName, @RequestParam(value = "nickUsers", required = false) final Set nickUsers); + + @RequestMapping(value = "/userNamesIsAvaliable", method = RequestMethod.GET) + public boolean userNameIsAvaliable(@RequestParam(value = "userNames") final Set userNames); + + + } diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/WorkTeamServiceClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/WorkTeamServiceClient.java index fced17a1..27834a0c 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/feign/WorkTeamServiceClient.java +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/WorkTeamServiceClient.java @@ -1,16 +1,23 @@ package br.com.muttley.security.feign; import br.com.muttley.feign.service.config.FeignTimeoutConfig; -import br.com.muttley.model.security.WorkTeam; +import br.com.muttley.feign.service.interceptors.HeadersMetadataInterceptor; +import br.com.muttley.model.workteam.WorkTeamDomain; import br.com.muttley.security.infra.security.server.FeignClientConfig; import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.GET; /** - * @author Joel Rodrigues Moreira on 18/04/18. + * @author Joel Rodrigues Moreira on 21/03/2022. * e-mail: joel.databox@gmail.com * @project muttley-cloud */ -@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/work-teams", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class}) -public interface WorkTeamServiceClient extends RestControllerClient { +@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/work-teams", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class, HeadersMetadataInterceptor.class}) +public interface WorkTeamServiceClient { + @RequestMapping(value = "/domain", method = GET, consumes = APPLICATION_JSON_UTF8_VALUE) + WorkTeamDomain loadDomain(); } diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/XAPITokenClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/XAPITokenClient.java new file mode 100644 index 00000000..b0c08a86 --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/XAPITokenClient.java @@ -0,0 +1,17 @@ +package br.com.muttley.security.feign; + +import br.com.muttley.feign.service.config.FeignTimeoutConfig; +import br.com.muttley.feign.service.interceptors.HeadersMetadataInterceptor; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.security.infra.security.server.FeignClientConfig; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +@FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/x-api-tokens", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class, HeadersMetadataInterceptor.class}) +public interface XAPITokenClient { + @RequestMapping(value = "/token", method = GET) + XAPIToken getByToken(@RequestParam("token") final String token); +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/auth/AuthenticationRestServiceClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/auth/AuthenticationRestServiceClient.java index 1f1a4c82..36f570fc 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/feign/auth/AuthenticationRestServiceClient.java +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/auth/AuthenticationRestServiceClient.java @@ -2,6 +2,8 @@ import br.com.muttley.feign.service.config.FeignTimeoutConfig; import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.RecoveryPasswordResponse; +import br.com.muttley.model.security.RecoveryPayload; import br.com.muttley.model.security.UserPayLoadLogin; import br.com.muttley.security.infra.security.server.FeignClientConfig; import org.springframework.cloud.netflix.feign.FeignClient; @@ -24,4 +26,7 @@ public interface AuthenticationRestServiceClient { @RequestMapping(value = "/refresh", method = POST) public JwtToken refreshAndGetAuthenticationToken(@RequestBody JwtToken token); + + @RequestMapping(value = "/reset-password", method = POST) + RecoveryPasswordResponse recoveryPassword(@RequestBody RecoveryPayload recovery); } diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/auth/AuthenticationTokenServiceClient.java b/muttley-security/src/main/java/br/com/muttley/security/feign/auth/AuthenticationTokenServiceClient.java index e762dd9b..49e99502 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/feign/auth/AuthenticationTokenServiceClient.java +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/auth/AuthenticationTokenServiceClient.java @@ -6,8 +6,10 @@ import br.com.muttley.security.infra.security.server.FeignClientConfig; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; + +import static org.springframework.web.bind.annotation.RequestMethod.POST; /** * Aplica o filtro de autenticação necessario @@ -15,6 +17,9 @@ @FeignClient(value = "${muttley.security.name-server}", path = "/api/v1/users/authentication", configuration = {FeignClientConfig.class, FeignTimeoutConfig.class}) public interface AuthenticationTokenServiceClient { - @RequestMapping(value = "/user-from-token", method = RequestMethod.POST) + @RequestMapping(value = "/user-from-token", method = POST) public JwtUser getUserFromToken(final @RequestBody JwtToken token); + + @RequestMapping(value = "/login-by-api-token", method = POST) + public JwtToken getUserFromXAPIToken(@RequestHeader(value = "X-Api-Token", defaultValue = "") final String tokenValue); } diff --git a/muttley-security/src/main/java/br/com/muttley/security/feign/formatter/KeyUserDataBindingFormatterRegistrar.java b/muttley-security/src/main/java/br/com/muttley/security/feign/formatter/KeyUserDataBindingFormatterRegistrar.java new file mode 100644 index 00000000..362704cb --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/feign/formatter/KeyUserDataBindingFormatterRegistrar.java @@ -0,0 +1,19 @@ +package br.com.muttley.security.feign.formatter; + +import br.com.muttley.model.security.KeyUserDataBinding; +import org.springframework.cloud.netflix.feign.FeignFormatterRegistrar; +import org.springframework.format.FormatterRegistry; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira 26/02/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class KeyUserDataBindingFormatterRegistrar implements FeignFormatterRegistrar { + @Override + public void registerFormatters(final FormatterRegistry formatterRegistry) { + formatterRegistry.addConverter(KeyUserDataBinding.class, String.class, keyUserDataBinding -> keyUserDataBinding.getKey()); + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/component/AuthenticationTokenFilterClient.java b/muttley-security/src/main/java/br/com/muttley/security/infra/component/AuthenticationTokenFilterClient.java index 8dda2249..513e2426 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/component/AuthenticationTokenFilterClient.java +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/component/AuthenticationTokenFilterClient.java @@ -1,11 +1,13 @@ package br.com.muttley.security.infra.component; import br.com.muttley.exception.throwables.security.MuttleySecurityUnauthorizedException; -import br.com.muttley.security.infra.service.CacheUserAuthenticationService; +import br.com.muttley.localcache.services.LocalUserAuthenticationService; +import br.com.muttley.localcache.services.LocalXAPITokenService; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.JwtUser; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter; @@ -23,38 +25,54 @@ public class AuthenticationTokenFilterClient extends OncePerRequestFilter { private final String tokenHeader; - private final CacheUserAuthenticationService cacheAuth; + protected final String xAPIToken; + private final LocalUserAuthenticationService localUserAuthentication; + protected final LocalXAPITokenService apiTokenService; - public AuthenticationTokenFilterClient( - @Value("${muttley.security.jwt.controller.tokenHeader-jwt:Authorization-jwt}") final String tokenHeader, - final CacheUserAuthenticationService cacheAuth) { - this.cacheAuth = cacheAuth; + + public AuthenticationTokenFilterClient(@Value("${muttley.security.jwt.controller.tokenHeader-jwt:Authorization-jwt}") final String tokenHeader, @Value("${muttley.security.jwt.controller.xAPITokenHeader:X-Api-Token}") final String xAPIToken, final LocalUserAuthenticationService localUserAuthentication, LocalXAPITokenService apiTokenService) { this.tokenHeader = tokenHeader; + this.xAPIToken = xAPIToken; + this.localUserAuthentication = localUserAuthentication; + this.apiTokenService = apiTokenService; } @Override protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws ServletException, IOException { //recuperando o possivel token presente no cabeçalho final String authToken = request.getHeader(this.tokenHeader); + final String xAPIToken = request.getHeader(this.xAPIToken); - if (!isNullOrEmpty(authToken)) { - - if (SecurityContextHolder.getContext().getAuthentication() == null) { - try { - //buscando o usuário no cache - final UserDetails userDetails = this.cacheAuth.get(authToken); - - //verificando a validade do token - if (userDetails != null) { - final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - } catch (MuttleySecurityUnauthorizedException ex) { - } + if (SecurityContextHolder.getContext().getAuthentication() == null) { + if (!isNullOrEmpty(authToken)) { + this.loadJWTUser(request, authToken); + } else if (!isNullOrEmpty(xAPIToken)) { + this.loadXAPIToken(request, xAPIToken); } } //dispachando a requisição chain.doFilter(request, response); } + + private void loadXAPIToken(HttpServletRequest request, String xAPIToken) { + JwtToken jwtToken = this.apiTokenService.loadJwtTokenFrom(xAPIToken); + if (jwtToken != null) { + request.setAttribute(xAPIToken, jwtToken); + this.loadJWTUser(request, jwtToken.getToken()); + } + } + + private void loadJWTUser(final HttpServletRequest request, final String authToken) { + try { + //buscando o usuário presente no token + //final JwtUser userDetails = this.tokenServiceClient.getUserFromToken(new JwtToken(authToken)); + final JwtUser userDetails = this.localUserAuthentication.getJwtUserFrom(new JwtToken(authToken)); + if (userDetails != null) { + final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (MuttleySecurityUnauthorizedException ex) { + } + } } diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/component/AuthenticationTokenFilterGateway.java b/muttley-security/src/main/java/br/com/muttley/security/infra/component/AuthenticationTokenFilterGateway.java index 25be1197..0d9d70a6 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/component/AuthenticationTokenFilterGateway.java +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/component/AuthenticationTokenFilterGateway.java @@ -1,13 +1,12 @@ package br.com.muttley.security.infra.component; import br.com.muttley.exception.throwables.security.MuttleySecurityUnauthorizedException; +import br.com.muttley.localcache.services.LocalUserAuthenticationService; +import br.com.muttley.localcache.services.LocalXAPITokenService; import br.com.muttley.model.security.JwtToken; import br.com.muttley.model.security.JwtUser; -import br.com.muttley.security.feign.auth.AuthenticationTokenServiceClient; -import br.com.muttley.security.infra.service.CacheUserAuthenticationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; @@ -28,48 +27,60 @@ public class AuthenticationTokenFilterGateway extends OncePerRequestFilter { protected final String tokenHeader; - protected final AuthenticationTokenServiceClient tokenServiceClient; - protected final CacheUserAuthenticationService cacheAuth; - protected final ApplicationEventPublisher eventPublisher; + protected final String xAPIToken; + protected final LocalUserAuthenticationService localUserAuthentication; + protected final LocalXAPITokenService apiTokenService; @Autowired public AuthenticationTokenFilterGateway( @Value("${muttley.security.jwt.controller.tokenHeader:Authorization}") final String tokenHeader, - final AuthenticationTokenServiceClient userServiceClient, - final CacheUserAuthenticationService cacheAuth, - final ApplicationEventPublisher eventPublisher) { + @Value("${muttley.security.jwt.controller.xAPITokenHeader:X-Api-Token}") final String xAPIToken, + final LocalUserAuthenticationService localUserAuthentication, + final LocalXAPITokenService apiTokenService) { this.tokenHeader = tokenHeader; - this.tokenServiceClient = userServiceClient; - this.cacheAuth = cacheAuth; - this.eventPublisher = eventPublisher; + this.xAPIToken = xAPIToken; + this.localUserAuthentication = localUserAuthentication; + this.apiTokenService = apiTokenService; } @Override protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws ServletException, IOException { //recuperando o possivel token presente no cabeçalho final String authToken = request.getHeader(this.tokenHeader); + final String xAPIToken = request.getHeader(this.xAPIToken); - if (!isNullOrEmpty(authToken)) { - if (SecurityContextHolder.getContext().getAuthentication() == null) { - try { - //buscando o usuário presente no token - final JwtUser userDetails = this.tokenServiceClient.getUserFromToken(new JwtToken(authToken)); - - - final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authentication); - if (!this.cacheAuth.contains(authToken)) { - //salvando no cache - this.cacheAuth.set(authToken, userDetails); - } - } catch (MuttleySecurityUnauthorizedException ex) { - - } + if (SecurityContextHolder.getContext().getAuthentication() == null) { + if (!isNullOrEmpty(authToken)) { + this.loadJWTUser(request, authToken); + } else if (!isNullOrEmpty(xAPIToken)) { + this.loadXAPIToken(request, xAPIToken); } } //dispachando a requisição chain.doFilter(request, response); } + + private void loadXAPIToken(HttpServletRequest request, String xAPIToken) { + JwtToken jwtToken = this.apiTokenService.loadJwtTokenFrom(xAPIToken); + if (jwtToken != null) { + //setando atributo + request.setAttribute(xAPIToken, jwtToken); + this.loadJWTUser(request, jwtToken.getToken()); + } + } + + private void loadJWTUser(final HttpServletRequest request, final String authToken) { + try { + //buscando o usuário presente no token + //final JwtUser userDetails = this.tokenServiceClient.getUserFromToken(new JwtToken(authToken)); + final JwtUser userDetails = this.localUserAuthentication.getJwtUserFrom(new JwtToken(authToken)); + if (userDetails != null) { + final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (MuttleySecurityUnauthorizedException ex) { + } + } } diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/component/DeserializeUserPreferencesEventListener.java b/muttley-security/src/main/java/br/com/muttley/security/infra/component/DeserializeUserPreferencesEventListener.java new file mode 100644 index 00000000..30007fca --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/component/DeserializeUserPreferencesEventListener.java @@ -0,0 +1,43 @@ +package br.com.muttley.security.infra.component; + +import br.com.muttley.localcache.services.LocalOwnerService; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.events.DeserializeUserPreferencesEvent; +import br.com.muttley.model.security.preference.Preference; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import static br.com.muttley.model.security.preference.UserPreferences.OWNER_PREFERENCE; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + *

+ * Esse lister recupera apenas as preferencias básica necessárias como no caso do onwer + */ +@Component +public class DeserializeUserPreferencesEventListener implements ApplicationListener { + private final LocalOwnerService ownerService; + + public DeserializeUserPreferencesEventListener(final LocalOwnerService ownerService) { + this.ownerService = ownerService; + } + + @Override + public void onApplicationEvent(final DeserializeUserPreferencesEvent event) { + if (event.getPreferences().contains(OWNER_PREFERENCE)) { + final Preference preference = event.getPreferences().get(OWNER_PREFERENCE); + if (!preference.isResolved()) { + final OwnerData ownerData = ownerService.loadOwnerById(preference.getValue().toString()); + preference.setResolved(ownerData); + event.getUser().setCurrentOwner(ownerData); + } + } else { + final OwnerData ownerData = ownerService.loadOwnerAny(); + final Preference preference = new Preference(OWNER_PREFERENCE, ownerData.getId()); + preference.setResolved(ownerData); + event.getUser().setCurrentOwner(ownerData); + } + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/component/UserAfterCacheLoadListener.java b/muttley-security/src/main/java/br/com/muttley/security/infra/component/UserAfterCacheLoadListener.java index 755c4db2..57cffc67 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/component/UserAfterCacheLoadListener.java +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/component/UserAfterCacheLoadListener.java @@ -1,15 +1,20 @@ package br.com.muttley.security.infra.component; +import br.com.muttley.localcache.services.LocalDatabindingService; +import br.com.muttley.localcache.services.LocalOwnerService; +import br.com.muttley.localcache.services.LocalRolesService; +import br.com.muttley.localcache.services.LocalUserPreferenceService; +import br.com.muttley.localcache.services.LocalWorkTeamService; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.OwnerData; import br.com.muttley.model.security.User; import br.com.muttley.model.security.events.UserAfterCacheLoadEvent; -import br.com.muttley.model.security.preference.UserPreferences; -import br.com.muttley.security.feign.UserPreferenceServiceClient; -import br.com.muttley.security.feign.WorkTeamServiceClient; -import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; +import static br.com.muttley.model.security.preference.UserPreferences.OWNER_PREFERENCE; + /** * @author Joel Rodrigues Moreira on 17/05/18. * e-mail: joel.databox@gmail.com @@ -18,22 +23,66 @@ @Component public class UserAfterCacheLoadListener implements ApplicationListener { - private final UserPreferenceServiceClient preferenceServiceClient; - private final WorkTeamServiceClient workteamService; + private final LocalUserPreferenceService localUserPreferenceService; + private final LocalOwnerService ownerService; + private final LocalRolesService rolesService; + private final LocalDatabindingService databindingService; + private final LocalWorkTeamService localWorkTeamService; @Autowired - public UserAfterCacheLoadListener(final UserPreferenceServiceClient preferenceServiceClient, final WorkTeamServiceClient workteamService) { - this.preferenceServiceClient = preferenceServiceClient; - this.workteamService = workteamService; + public UserAfterCacheLoadListener(final LocalUserPreferenceService localUserPreferenceService, final LocalOwnerService ownerService, final LocalRolesService rolesService, final LocalDatabindingService databindingService, LocalWorkTeamService localWorkTeamService) { + this.localUserPreferenceService = localUserPreferenceService; + this.ownerService = ownerService; + this.rolesService = rolesService; + this.databindingService = databindingService; + this.localWorkTeamService = localWorkTeamService; } @Override public void onApplicationEvent(final UserAfterCacheLoadEvent event) { - final User user = event.getSource(); + final User user = event.getUser(); + final JwtToken token = event.getJwtToken(); //carregando preferencias - final UserPreferences preferences = preferenceServiceClient.getPreferences(user.getId()); - final ObjectId idWorkTeam = new ObjectId(preferences.get(UserPreferences.WORK_TEAM_PREFERENCE).getValue().toString()); - user.setPreferences(preferences); - user.setCurrentWorkTeam(workteamService.findById(idWorkTeam.toString())); + user.setPreferences(this.localUserPreferenceService.getUserPreferences(token, user)); + //setando o owner + user.setCurrentOwner((OwnerData) user.getPreferences().get(OWNER_PREFERENCE).getResolved()); + user.setAuthorities(this.rolesService.loadCurrentRoles(event.getJwtToken(), event.getUser())); + user.setDataBindings(this.databindingService.getUserDataBindings(token, user)); + user.setWorkTeamDomain(this.localWorkTeamService.getWorkTeamDomain(token, user)); + + /*final List dataBindings = dataBindingService.list(); + try { + if (!preferences.contains(OWNER_PREFERENCE)) { + final OwnerData owner = this.ownerService.loadOwnerAny(); + //salvando o owner nas preferencias + final Preference preference = new Preference(OWNER_PREFERENCE, owner.getId()).setResolved(owner); + preferences.set(preference); + preferenceService.setPreference(preference); + //setando o owner atual + user.setCurrentOwner(owner); + //carregando authorities + //user.setAuthorities(this.workTeamService.loadCurrentRoles()); + this.loadRoles(user); + user.setDataBindings(dataBindings); + } else { + final ObjectId idOwner = new ObjectId(preferences.get(OWNER_PREFERENCE).getValue().toString()); + user.setPreferences(preferences); + user.setDataBindings(dataBindings); + final Owner owner = ownerService.findByUserAndId(idOwner.toString()); + user.setCurrentOwner(owner); + //carregando authorities + //user.setAuthorities(this.workTeamService.loadCurrentRoles()); + this.loadRoles(user); + } + } catch (MuttleyNotFoundException ex) { + throw new MuttleySecurityCredentialException("Não foi possível recuperar informações do seu usuáiro"); + }*/ } +/* + private void loadRoles(final User user) { + try { + user.setAuthorities(this.workTeamService.loadCurrentRoles()); + } catch (final MuttleyNotFoundException ex) { + } + }*/ } diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/component/authentication/AuthenticationAfterCacheLoadEventListener.java b/muttley-security/src/main/java/br/com/muttley/security/infra/component/authentication/AuthenticationAfterCacheLoadEventListener.java new file mode 100644 index 00000000..2e691ab3 --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/component/authentication/AuthenticationAfterCacheLoadEventListener.java @@ -0,0 +1,52 @@ +package br.com.muttley.security.infra.component.authentication; + +import br.com.muttley.localcache.services.LocalDatabindingService; +import br.com.muttley.localcache.services.LocalOwnerService; +import br.com.muttley.localcache.services.LocalRolesService; +import br.com.muttley.localcache.services.LocalUserPreferenceService; +import br.com.muttley.localcache.services.LocalWorkTeamService; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.security.authentication.Authentication; +import br.com.muttley.model.security.events.authentication.AuthenticationAfterCacheLoadEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import static br.com.muttley.model.security.preference.UserPreferences.OWNER_PREFERENCE; + +/** + * @author Joel Rodrigues Moreira on 19/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class AuthenticationAfterCacheLoadEventListener implements ApplicationListener { + private final LocalUserPreferenceService localUserPreferenceService; + private final LocalOwnerService ownerService; + private final LocalRolesService rolesService; + private final LocalDatabindingService databindingService; + private final LocalWorkTeamService localWorkTeamService; + + @Autowired + public AuthenticationAfterCacheLoadEventListener(final LocalUserPreferenceService localUserPreferenceService, final LocalOwnerService ownerService, final LocalRolesService rolesService, final LocalDatabindingService databindingService, final LocalWorkTeamService localWorkTeamService) { + this.localUserPreferenceService = localUserPreferenceService; + this.ownerService = ownerService; + this.rolesService = rolesService; + this.databindingService = databindingService; + this.localWorkTeamService = localWorkTeamService; + } + + @Override + public void onApplicationEvent(final AuthenticationAfterCacheLoadEvent event) { + final XAPIToken token = event.getToken(); + final Authentication authentication = event.getAuthentication(); + //final JwtToken token = event.getJwtToken(); + //carregando preferencias + authentication.setPreferences(this.localUserPreferenceService.getUserPreferences(token, authentication.getCurrentUser())); + + authentication.setAuthorities(this.rolesService.loadCurrentRoles(event.getToken(), event.getAuthentication().getCurrentUser())); + authentication.setDataBindings(this.databindingService.getUserDataBindings(token, authentication.getCurrentUser())); + authentication.setWorkTeam(this.localWorkTeamService.getWorkTeamDomain(token, authentication.getCurrentUser())); + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/controller/AccessPlanControler.java b/muttley-security/src/main/java/br/com/muttley/security/infra/controller/AccessPlanControler.java new file mode 100644 index 00000000..13a3d80a --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/controller/AccessPlanControler.java @@ -0,0 +1,31 @@ +package br.com.muttley.security.infra.controller; + +import br.com.muttley.security.feign.AccessPlanServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author Joel Rodrigues Moreira on 23/09/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ + +public class AccessPlanControler { + private final AccessPlanServiceClient accessPlanService; + + @Autowired + public AccessPlanControler(AccessPlanServiceClient accessPlanService) { + this.accessPlanService = accessPlanService; + } + + @RequestMapping(value = "/api/v1/access-plan/find-by-owner", method = RequestMethod.GET) + @ResponseStatus(HttpStatus.OK) + public final ResponseEntity findByOwner(@RequestParam("owner") final String owner) { + return ResponseEntity.ok(this.accessPlanService.findByOwner(owner)); + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/controller/AuthenticationRestController.java b/muttley-security/src/main/java/br/com/muttley/security/infra/controller/AuthenticationRestController.java index 651a0571..ed98b72b 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/controller/AuthenticationRestController.java +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/controller/AuthenticationRestController.java @@ -2,18 +2,21 @@ import br.com.muttley.exception.throwables.security.MuttleySecurityBadRequestException; import br.com.muttley.exception.throwables.security.MuttleySecurityUserNameOrPasswordInvalidException; +import br.com.muttley.localcache.services.LocalUserAuthenticationService; import br.com.muttley.model.security.JwtToken; import br.com.muttley.model.security.JwtUser; +import br.com.muttley.model.security.RecoveryPasswordResponse; +import br.com.muttley.model.security.RecoveryPayload; import br.com.muttley.model.security.UserPayLoadLogin; import br.com.muttley.model.security.events.UserLoggedEvent; import br.com.muttley.security.feign.auth.AuthenticationRestServiceClient; -import br.com.muttley.security.infra.service.CacheUserAuthenticationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -25,6 +28,8 @@ import javax.servlet.http.HttpServletRequest; import java.util.Map; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + /** * RestController responsavel por despachar novas requisições de token * @@ -41,7 +46,7 @@ public class AuthenticationRestController { protected final ApplicationEventPublisher eventPublisher; protected final AuthenticationManager authenticationManager; protected final AuthenticationRestServiceClient authenticationRestService; - protected final CacheUserAuthenticationService cacheAuthService; + protected final LocalUserAuthenticationService localUserAuthenticationService; @Autowired public AuthenticationRestController( @@ -49,12 +54,12 @@ public AuthenticationRestController( final AuthenticationManager authenticationManager, final AuthenticationRestServiceClient authenticationRestService, final ApplicationEventPublisher eventPublisher, - final CacheUserAuthenticationService cacheAuthService) { + final LocalUserAuthenticationService localUserAuthenticationService) { this.tokenHeader = tokenHeader; this.authenticationManager = authenticationManager; this.authenticationRestService = authenticationRestService; this.eventPublisher = eventPublisher; - this.cacheAuthService = cacheAuthService; + this.localUserAuthenticationService = localUserAuthenticationService; } @RequestMapping(value = "${muttley.security.jwt.controller.loginEndPoint}", method = RequestMethod.POST) @@ -65,8 +70,6 @@ public ResponseEntity createAuthenticationToken(@RequestBody final Map refreshAndGetAuthenticationToken(HttpServletRequest request) { final JwtToken currentToken = new JwtToken(request.getHeader(tokenHeader)); final JwtToken newToken = this.authenticationRestService.refreshAndGetAuthenticationToken(currentToken); - cacheAuthService.refreshToken(currentToken, newToken); + this.localUserAuthenticationService.refreshToken(currentToken, newToken); return ResponseEntity.ok(newToken); } + @RequestMapping(value = "${muttley.security.jwt.controller.resetPassword}", method = POST) + public RecoveryPasswordResponse recoveryPassword(final HttpServletRequest request, final @RequestBody RecoveryPayload recovery) { + return this.authenticationRestService.recoveryPassword(recovery); + } + /** * O Metodo é notificado toda vez que um token é gerado para um determinado usuário * diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/controller/CreateUserController.java b/muttley-security/src/main/java/br/com/muttley/security/infra/controller/CreateUserController.java index eb1e100b..5de45579 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/controller/CreateUserController.java +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/controller/CreateUserController.java @@ -4,16 +4,20 @@ import br.com.muttley.model.security.User; import br.com.muttley.model.security.UserPayLoad; import br.com.muttley.model.security.events.UserCreatedEvent; +import br.com.muttley.model.security.preference.Foto; import br.com.muttley.security.feign.UserServiceClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseStatus; import javax.servlet.http.HttpServletResponse; +import java.util.HashSet; +import java.util.List; import java.util.Map; /** @@ -23,11 +27,21 @@ */ public class CreateUserController { + protected final ApplicationEventPublisher eventPublisher; protected UserServiceClient service; protected static final String NOME = "name"; + protected static final String DECRIPTION = "description"; + protected static final String USER_NAME = "userName"; + protected static final String FOTO = "foto"; protected static final String EMAIL = "email"; + protected static final String EMAIL_SECUNDARIO = "emailSecundario"; protected static final String PASSWD = "password"; + protected static final String NICK_NAMES = "nickNames"; + protected static final String FONE = "fone"; + protected static final String CODE_VERIFICATION = "code"; + + protected static final String RENEW_CODE_VERIFICATION = "renewCode"; @Autowired public CreateUserController(final ApplicationEventPublisher eventPublisher, final UserServiceClient service) { @@ -37,21 +51,52 @@ public CreateUserController(final ApplicationEventPublisher eventPublisher, fina @RequestMapping(value = "${muttley.security.jwt.controller.createEndPoint}", method = RequestMethod.POST) @ResponseStatus(HttpStatus.CREATED) - public void save(@RequestBody Map payload, HttpServletResponse response) { - if (payload.isEmpty() || payload.size() < 3 || !payload.containsKey(NOME) || !payload.containsKey(EMAIL) || !payload.containsKey(PASSWD)) { - throw new MuttleySecurityBadRequestException(User.class, null, "Informe o nome, email e a senha") + public ResponseEntity save(@RequestBody Map payload, HttpServletResponse response) { + + if ( + !((payload.containsKey("name") && payload.containsKey("password") && payload.containsKey("email")) || + (payload.containsKey("name") && payload.containsKey("password") && payload.containsKey("userName")) || + (payload.containsKey("name") && payload.containsKey("password") && payload.containsKey("nickNames"))) + ) { + throw new MuttleySecurityBadRequestException(User.class, null, "Informe o nome, email e ou userName juntamente com a senha") .addDetails(NOME, "Nome completo") - .addDetails(EMAIL, "Informe um email válido") - .addDetails(PASSWD, "Informe uma senha válida"); + .addDetails(USER_NAME, "Informe um userName válido") + .addDetails(PASSWD, "Informe uma senha válida") + .addDetails(NICK_NAMES, "Informe possíveis nickNames"); } + /*if (payload.isEmpty() || payload.size() < 3 || !payload.containsKey(NOME) || !payload.containsKey(USER_NAME) || !payload.containsKey(PASSWD)) { - if (payload.size() > 3) { - throw new MuttleySecurityBadRequestException(User.class, null, "Por favor informe somente o nome, email e a senha") + }*/ + + /*if (payload.size() > 4) { + throw new MuttleySecurityBadRequestException(User.class, null, "Por favor informe somente o nome, userName, nickNames e a senha") .addDetails(NOME, "Nome completo") - .addDetails(EMAIL, "Informe um email válido") - .addDetails(PASSWD, "Informe uma senha válida"); + .addDetails(USER_NAME, "Informe um userName válido") + .addDetails(PASSWD, "Informe uma senha válida") + .addDetails(NICK_NAMES, "Informe possíveis nickNames"); + }*/ + + final UserPayLoad user = new UserPayLoad( + (String) payload.get(NOME), + (String) payload.get(DECRIPTION), + (String) payload.get(EMAIL), + (String) payload.get(EMAIL_SECUNDARIO), + (String) payload.get(USER_NAME), + (Foto) payload.get(FOTO), + payload.containsKey(NICK_NAMES) ? new HashSet((List) payload.get(NICK_NAMES)) : null, + (String) payload.get(PASSWD), + (String) payload.get(FONE), + false, + null, + (String) payload.get(CODE_VERIFICATION), + (payload.get(RENEW_CODE_VERIFICATION) == null? false: (Boolean)payload.get(RENEW_CODE_VERIFICATION)) + ); + final User salvedUser = service.save(user, "true"); + if (salvedUser == null) { + return ResponseEntity.status(HttpStatus.ACCEPTED).build(); + } else { + this.eventPublisher.publishEvent(new UserCreatedEvent(salvedUser)); } - final UserPayLoad user = new UserPayLoad(payload.get(NOME), payload.get(EMAIL), payload.get(PASSWD)); - this.eventPublisher.publishEvent(new UserCreatedEvent(service.save(user, "true"))); + return ResponseEntity.status(HttpStatus.CREATED).build(); } } diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/controller/UserManagerController.java b/muttley-security/src/main/java/br/com/muttley/security/infra/controller/UserManagerController.java index 9b25529e..0d8bef25 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/controller/UserManagerController.java +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/controller/UserManagerController.java @@ -1,7 +1,7 @@ package br.com.muttley.security.infra.controller; import br.com.muttley.model.security.JwtToken; -import br.com.muttley.model.security.Passwd; +import br.com.muttley.model.security.PasswdPayload; import br.com.muttley.model.security.User; import br.com.muttley.model.security.resource.UserResource; import br.com.muttley.security.feign.UserServiceClient; @@ -26,6 +26,7 @@ public class UserManagerController { protected UserServiceClient service; + @Autowired public UserManagerController(final UserServiceClient service) { this.service = service; @@ -34,9 +35,11 @@ public UserManagerController(final UserServiceClient service) { @RequestMapping(value = "${muttley.security.jwt.controller.managerUserEndPoint}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseStatus(HttpStatus.OK) public ResponseEntity update(@RequestBody User user, final @RequestHeader("${muttley.security.jwt.controller.tokenHeader}") String tokenHeader) { - return ResponseEntity.ok(service.update(user.getEmail(), tokenHeader, user)); + return ResponseEntity.ok(service.update(user.getUserName(), tokenHeader, user)); } + + @RequestMapping(value = "${muttley.security.jwt.controller.managerUserEndPoint}", method = RequestMethod.GET, consumes = MediaType.ALL_VALUE) @ResponseStatus(HttpStatus.OK) public UserResource get(final @RequestHeader("${muttley.security.jwt.controller.tokenHeader}") String tokenHeader) { @@ -45,9 +48,9 @@ public UserResource get(final @RequestHeader("${muttley.security.jwt.controller. @RequestMapping(value = "${muttley.security.jwt.controller.managerUserEndPoint}/password", method = RequestMethod.PUT, consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.OK) - public ResponseEntity updatePasswd(@RequestBody @Valid Passwd passwd, final @RequestHeader("${muttley.security.jwt.controller.tokenHeader}") String tokenHeader) { - passwd.setToken(new JwtToken(tokenHeader)); - service.updatePasswd(passwd); + public ResponseEntity updatePasswd(@RequestBody @Valid PasswdPayload passwdPayload, final @RequestHeader("${muttley.security.jwt.controller.tokenHeader}") String tokenHeader) { + passwdPayload.setToken(new JwtToken(tokenHeader)); + service.updatePasswd(passwdPayload); return ResponseEntity.ok().build(); } diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/controller/UserNameIsAvaliableController.java b/muttley-security/src/main/java/br/com/muttley/security/infra/controller/UserNameIsAvaliableController.java new file mode 100644 index 00000000..80f20868 --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/controller/UserNameIsAvaliableController.java @@ -0,0 +1,29 @@ +package br.com.muttley.security.infra.controller; + +import br.com.muttley.security.feign.UserServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira on 16/12/2020. + * e-mail: joel.databox@gmail.com + * @project spring-cloud + */ +public class UserNameIsAvaliableController { + protected UserServiceClient service; + + @Autowired + public UserNameIsAvaliableController(final UserServiceClient service) { + this.service = service; + } + + @RequestMapping(value = "${muttley.security.jwt.controller.usesrNameIsAvaliable}", method = RequestMethod.GET) + public ResponseEntity userNameIsAvaliable(@RequestParam(value = "userNames") final Set userNames) { + return ResponseEntity.ok(this.service.userNameIsAvaliable(userNames)); + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/security/server/BasicAuthorizationJWTRequestInterceptor.java b/muttley-security/src/main/java/br/com/muttley/security/infra/security/server/BasicAuthorizationJWTRequestInterceptor.java index 304d500e..ca1ae0a5 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/security/server/BasicAuthorizationJWTRequestInterceptor.java +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/security/server/BasicAuthorizationJWTRequestInterceptor.java @@ -1,10 +1,12 @@ package br.com.muttley.security.infra.security.server; +import br.com.muttley.model.security.JwtToken; import feign.RequestInterceptor; import feign.RequestTemplate; import feign.Util; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.crypto.codec.Base64; +import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -23,8 +25,11 @@ public class BasicAuthorizationJWTRequestInterceptor implements RequestIntercept //${muttley.security.jwt.client.tokenHeader:Athorization-JWT} @Value("${muttley.security.jwt.controller.tokenHeader-jwt:Authorization-jwt}") private String authorizationJwt; + @Value("${muttley.security.jwt.controller.xAPITokenHeader:X-Api-Token}") + private String xAPIToken; @Value("${muttley.security.jwt.controller.tokenHeader:Authorization}") private String authorization; + private final String headerValue; public BasicAuthorizationJWTRequestInterceptor(final String username, final String password) { @@ -45,6 +50,16 @@ public void apply(final RequestTemplate template) { final String AUTH = this.getAuthorizationJWT(); if (!isEmpty(AUTH)) { template.header(authorizationJwt, AUTH); + } else { + final String jwtToken = this.getAuthorizationJwtFromXAPIToken(); + if (jwtToken != null) { + template.header(this.authorizationJwt, jwtToken); + } else { + final String xAPITokenValue = this.getxAPIToken(); + if (xAPITokenValue != null) { + template.header(this.xAPIToken, xAPITokenValue); + } + } } } catch (IllegalStateException ex) { ex.printStackTrace(); @@ -55,14 +70,63 @@ public void apply(final RequestTemplate template) { * Deve retornar o token do usuário corrente na requisição */ private String getAuthorizationJWT() { - final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); - //Talvez a requisão já advem de outro subserviço, ou seja já contem no header o "Authorization-jwt" - final String jwtToken = request.getHeader(this.authorizationJwt); - if (!isEmpty(jwtToken)) { - return jwtToken; + final RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + final HttpServletRequest request = ((ServletRequestAttributes) attributes).getRequest(); + //Talvez a requisão já advem de outro subserviço, ou seja já contem no header o "Authorization-jwt" + final String jwtToken = request.getHeader(this.authorizationJwt); + if (!isEmpty(jwtToken)) { + return jwtToken; + } else { + //talvez seja uma request interna usando xapitoken, logo, talvez o token jwt esteja nos atributos da request + //Talvez a requisão já advem de outro subserviço, ou seja já contem no header o "Authorization-jwt" + final String xAPIToken = request.getHeader(this.xAPIToken); + if (!isEmpty(xAPIToken)) { + final JwtToken token = (JwtToken) request.getAttribute(xAPIToken); + return token != null ? token.getToken() : null; + } + } + //se chegou até aqui quer dizer que ninguem ainda não fez esse tratamento + //devemos pegar o token no "Authorization" + return request.getHeader(this.authorization); + } + return null; + } + + /** + * Deve retornar o token do usuário corrente na requisição + */ + private String getAuthorizationJwtFromXAPIToken() { + final RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + final HttpServletRequest request = ((ServletRequestAttributes) attributes).getRequest(); + //Talvez a requisão já advem de outro subserviço, ou seja já contem no header o "Authorization-jwt" + final String xAPIToken = request.getHeader(this.xAPIToken); + if (!isEmpty(xAPIToken)) { + final JwtToken token = (JwtToken) request.getAttribute(xAPIToken); + return token != null ? token.getToken() : null; + } + + /*if (!isEmpty(xAPIToken)) { + return xAPIToken; + }*/ + } + return null; + } + + /** + * Deve retornar o token do usuário corrente na requisição + */ + private String getxAPIToken() { + final RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + final HttpServletRequest request = ((ServletRequestAttributes) attributes).getRequest(); + //Talvez a requisão já advem de outro subserviço, ou seja já contem no header o "Authorization-jwt" + final String xAPIToken = request.getHeader(this.xAPIToken); + if (!isEmpty(xAPIToken)) { + return xAPIToken; + } } - //se chegou até aqui quer dizer que ninguem ainda não fez esse tratamento - //devemos pegar o token no "Authorization" - return request.getHeader(this.authorization); + return null; } -} \ No newline at end of file +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/security/server/FeignClientConfig.java b/muttley-security/src/main/java/br/com/muttley/security/infra/security/server/FeignClientConfig.java index 18be017c..35e83dda 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/security/server/FeignClientConfig.java +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/security/server/FeignClientConfig.java @@ -1,5 +1,11 @@ package br.com.muttley.security.infra.security.server; +import br.com.muttley.feign.service.interceptors.HeadersMetadataInterceptor; +import br.com.muttley.feign.service.interceptors.PropagateHeadersInterceptor; +import br.com.muttley.feign.service.service.MuttleyHeadersMetadataService; +import br.com.muttley.feign.service.service.MuttleyPropagateHeadersService; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -18,4 +24,14 @@ public BasicAuthorizationJWTRequestInterceptor createBasicAuthRequestInterceptor @Value("${muttley.config-server.security.user.password}") final String passWord) { return new BasicAuthorizationJWTRequestInterceptor(userName, passWord); } + + @Bean + public PropagateHeadersInterceptor createPropagateHeadersInterceptor(@Autowired final ObjectProvider muttleyPropagateHeadersService) { + return new PropagateHeadersInterceptor(muttleyPropagateHeadersService.getIfAvailable()); + } + + @Bean + public HeadersMetadataInterceptor createHeadersMetadataInterceptor(@Autowired final ObjectProvider headersMetadataService) { + return new HeadersMetadataInterceptor(headersMetadataService); + } } diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/AuthService.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/AuthService.java index 645cfb41..2983ad26 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/service/AuthService.java +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/AuthService.java @@ -3,15 +3,16 @@ import br.com.muttley.model.security.JwtToken; import br.com.muttley.model.security.JwtUser; import br.com.muttley.model.security.User; +import br.com.muttley.model.security.preference.Preference; +import br.com.muttley.model.security.preference.UserPreferences; import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UserDetailsService; /** * @author Joel Rodrigues Moreira on 19/04/18. * e-mail: joel.databox@gmail.com * @project muttley-cloud */ -public interface AuthService extends UserDetailsService { +public interface AuthService { Authentication getCurrentAuthentication(); JwtUser getCurrentJwtUser(); @@ -19,4 +20,8 @@ public interface AuthService extends UserDetailsService { JwtToken getCurrentToken(); User getCurrentUser(); + + UserPreferences getUserPreferences(); + + Preference getPreference(final String key); } diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/CacheUserAuthenticationService.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/CacheUserAuthenticationService.java deleted file mode 100644 index f06d2a02..00000000 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/service/CacheUserAuthenticationService.java +++ /dev/null @@ -1,27 +0,0 @@ -package br.com.muttley.security.infra.service; - -import br.com.muttley.model.security.JwtToken; -import br.com.muttley.model.security.JwtUser; - -/** - * @author Joel Rodrigues Moreira on 09/01/18. - * @project demo - */ -public interface CacheUserAuthenticationService { - void set(final String token, final JwtUser user); - - JwtUser get(final String token); - - boolean contains(final String token); - - void remove(final JwtToken token); - - /** - * Faz a atualização de um determinado token preservando as informações já persistidas - * - * @param currentToken -> token que será atualizado - * @param newToken -> novo token que substituira o antigo - * @return true -> se de fato ocorreu essa atualização - */ - boolean refreshToken(final JwtToken currentToken, final JwtToken newToken); -} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/AuthServiceImpl.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/AuthServiceImpl.java index ea12b653..c102298b 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/AuthServiceImpl.java +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/AuthServiceImpl.java @@ -1,14 +1,17 @@ package br.com.muttley.security.infra.service.impl; +import br.com.muttley.localcache.services.LocalUserAuthenticationService; import br.com.muttley.model.security.JwtToken; import br.com.muttley.model.security.JwtUser; import br.com.muttley.model.security.User; +import br.com.muttley.model.security.preference.Preference; +import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.security.feign.UserDataBindingClient; +import br.com.muttley.security.feign.UserPreferenceServiceClient; import br.com.muttley.security.infra.service.AuthService; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -21,10 +24,16 @@ @Service public class AuthServiceImpl implements AuthService { - private final String tokenHeader; + protected final String tokenHeader; + protected final UserPreferenceServiceClient preferenceService; + protected final UserDataBindingClient dataBindingService; + protected final LocalUserAuthenticationService localUserAuthenticationService; - public AuthServiceImpl(@Value("${muttley.security.jwt.controller.tokenHeader:Authorization}") final String tokenHeader) { + public AuthServiceImpl(@Value("${muttley.security.jwt.controller.tokenHeader:Authorization}") final String tokenHeader, final UserPreferenceServiceClient preferenceService, final UserDataBindingClient dataBindingService, final LocalUserAuthenticationService localUserAuthenticationService) { this.tokenHeader = tokenHeader; + this.preferenceService = preferenceService; + this.dataBindingService = dataBindingService; + this.localUserAuthenticationService = localUserAuthenticationService; } @Override @@ -34,7 +43,11 @@ public Authentication getCurrentAuthentication() { @Override public JwtUser getCurrentJwtUser() { - return (JwtUser) getCurrentAuthentication().getPrincipal(); + final Authentication authentication = this.getCurrentAuthentication(); + if (authentication != null) { + return (JwtUser) authentication.getPrincipal(); + } + return this.localUserAuthenticationService.getJwtUserFrom(getCurrentToken()); } @Override @@ -52,7 +65,20 @@ public User getCurrentUser() { } @Override - public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { - return new JwtUser(new User()); + public UserPreferences getUserPreferences() { + final User user = this.getCurrentUser(); + if (user.getPreferences() == null || user.getPreferences().isEmpty()) { + final UserPreferences preferences = this.preferenceService.getUserPreferences(); + if (preferences != null) { + user.setPreferences(preferences); + } + } + return this.getCurrentUser().getPreferences(); } + + @Override + public Preference getPreference(final String key) { + return this.getUserPreferences().get(key); + } + } diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/CacheUserAuthenticationServiceImpl.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/CacheUserAuthenticationServiceImpl.java deleted file mode 100644 index 9d1fcd95..00000000 --- a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/CacheUserAuthenticationServiceImpl.java +++ /dev/null @@ -1,71 +0,0 @@ -package br.com.muttley.security.infra.service.impl; - -import br.com.muttley.model.security.JwtToken; -import br.com.muttley.model.security.JwtUser; -import br.com.muttley.model.security.events.UserAfterCacheLoadEvent; -import br.com.muttley.model.security.events.UserBeforeCacheSaveEvent; -import br.com.muttley.redis.service.RedisService; -import br.com.muttley.security.infra.service.CacheUserAuthenticationService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; - -/** - * @author Joel Rodrigues Moreira on 09/01/18. - * @project demo - */ -@Service -public class CacheUserAuthenticationServiceImpl implements CacheUserAuthenticationService { - - private final RedisService redisService; - private final ApplicationEventPublisher eventPublisher; - private final int expiration; - - @Autowired - public CacheUserAuthenticationServiceImpl(final RedisService redisService, final @Value("${muttley.security.jwt.token.expiration}") int expiration, final ApplicationEventPublisher eventPublisher) { - this.redisService = redisService; - this.expiration = expiration; - this.eventPublisher = eventPublisher; - } - - @Override - public void set(final String token, final JwtUser user) { - //notificando que será salvo um usuário no cache do sistema - this.eventPublisher.publishEvent(new UserBeforeCacheSaveEvent(user.getOriginUser())); - this.redisService.set(token, user, expiration); - } - - @Override - public JwtUser get(final String token) { - final JwtUser jwtUser = (JwtUser) redisService.get(token); - if (jwtUser != null) { - //Notificando que foi carregado um usuário do cache do sistema - this.eventPublisher.publishEvent(new UserAfterCacheLoadEvent(jwtUser.getOriginUser())); - } - return jwtUser; - } - - @Override - public boolean contains(final String token) { - return redisService.hasKey(token); - } - - @Override - public void remove(final JwtToken token) { - this.redisService.delete(token.getToken()); - } - - @Override - public boolean refreshToken(final JwtToken currentToken, final JwtToken newToken) { - //verificando se o token exite - if (contains(currentToken.getToken())) { - //alterando o token do usuário - set(newToken.getToken(), get(currentToken.getToken())); - //removendo token anterior - remove(currentToken); - return true; - } - return false; - } -} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalDatabindingServiceImpl.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalDatabindingServiceImpl.java new file mode 100644 index 00000000..412e7411 --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalDatabindingServiceImpl.java @@ -0,0 +1,60 @@ +package br.com.muttley.security.infra.service.impl; + +import br.com.muttley.localcache.services.LocalDatabindingService; +import br.com.muttley.localcache.services.impl.AbstractLocalDatabindingServiceImpl; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.UserDataBinding; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.feign.UserDataBindingClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class LocalDatabindingServiceImpl extends AbstractLocalDatabindingServiceImpl implements LocalDatabindingService { + private final UserDataBindingClient dataBindingService; + + @Autowired + public LocalDatabindingServiceImpl(final RedisService redisService, final UserDataBindingClient dataBindingService) { + super(redisService); + this.dataBindingService = dataBindingService; + } + + @Override + public List getUserDataBindings(final JwtToken jwtUser, final User user) { + final List dataBindings; + //verificando se já existe no cache + if (this.redisService.hasKey(this.getBasicKey(user))) { + //recuperando dos itens + dataBindings = this.getDatabinDataBindingsInCache(jwtUser, user); + } else { + dataBindings = this.dataBindingService.list(); + //salvando no cache + this.saveDatabindingsInCache(jwtUser, user, dataBindings); + } + return dataBindings; + } + + @Override + public List getUserDataBindings(XAPIToken token, User user) { + final List dataBindings; + //verificando se já existe no cache + if (this.redisService.hasKey(this.getBasicKey(user))) { + //recuperando dos itens + dataBindings = this.getDatabinDataBindingsInCache(token, user); + } else { + dataBindings = this.dataBindingService.list(); + //salvando no cache + this.saveDatabindingsInCache(token, user, dataBindings); + } + return dataBindings; + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalJwtTokenUtilService.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalJwtTokenUtilService.java new file mode 100644 index 00000000..aaf4117f --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalJwtTokenUtilService.java @@ -0,0 +1,79 @@ +package br.com.muttley.security.infra.service.impl; + +import br.com.muttley.redis.service.RedisService; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import org.springframework.util.StringUtils; + +import java.util.Date; + +/** + * @author Joel Rodrigues Moreira 24/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +class LocalJwtTokenUtilService { + private final LocalSecretService secretService; + + LocalJwtTokenUtilService(final RedisService redisService) { + this.secretService = new LocalSecretService(redisService); + } + + private Claims getClaims(final boolean retry, final String token) { + try { + return Jwts.parser() + .setSigningKey(secretService.getHS512SecretBytes()) + .parseClaimsJws(token) + .getBody(); + } catch (Exception ex) { + if (retry) { + //se deu pau é sinal que as chaves são inválidas. + //vamos tentar atualizar as mesmas e tentar novamente + this.secretService.refreshSecrets(); + return this.getClaims(false, token); + } + throw ex; + } + } + + private Claims getClaims(final String token) { + return this.getClaims(true, token); + } + + public boolean isValidToken(final String token) { + if (StringUtils.isEmpty(token)) { + return false; + } + //fazendo parser do token + final Claims claims; + try { + claims = this.getClaims(token); + + return !this.isTokenExpired(claims) && this.existsUserName(claims); + } catch (Exception e) { + //se deu exception, logo o token é inválido + return false; + } + } + + public Date getExpiration(final String token) { + return this.getExpiration(this.getClaims(token)); + } + + private Date getExpiration(final Claims claims) { + return claims.getExpiration(); + } + + + private final boolean existsUserName(final Claims claims) { + return !StringUtils.isEmpty(claims.getSubject()); + } + + private final boolean isTokenExpired(final Claims claims) { + final Date expiration = this.getExpiration(claims); + if (expiration != null) { + return expiration.before(new Date()); + } + return true; + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalOwnerServiceImpl.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalOwnerServiceImpl.java new file mode 100644 index 00000000..5e50dc3f --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalOwnerServiceImpl.java @@ -0,0 +1,51 @@ +package br.com.muttley.security.infra.service.impl; + +import br.com.muttley.localcache.services.LocalOwnerService; +import br.com.muttley.localcache.services.impl.AbstractLocalOwnerServiceImpl; +import br.com.muttley.model.security.OwnerData; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.feign.OwnerServiceClient; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class LocalOwnerServiceImpl extends AbstractLocalOwnerServiceImpl implements LocalOwnerService { + private final OwnerServiceClient ownerServiceClient; + + @Autowired + public LocalOwnerServiceImpl(final RedisService redisService, final OwnerServiceClient ownerServiceClient) { + super(redisService); + this.ownerServiceClient = ownerServiceClient; + } + + @Override + public OwnerData loadOwnerAny() { + final List owners = this.ownerServiceClient.findByUser(); + final OwnerData firt = owners.get(0); + //salvando esse owner no cache + this.saveOwnerInCache(firt); + return firt; + } + + @Override + public OwnerData loadOwnerById(final String id) { + final OwnerData owner; + //verificando se existe esse registro em cache + if (this.redisService.hasKey(this.getBasicKey(id))) { + owner = this.loadOwerInCache(id); + } else { + //recuperando o owner do servidor + owner = this.ownerServiceClient.findByUserAndId(id); + //salvando o owner recuperado no cache + this.saveOwnerInCache(owner); + } + return owner; + } + + +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalRSAKeyPairServiceImpl.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalRSAKeyPairServiceImpl.java new file mode 100644 index 00000000..565f7483 --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalRSAKeyPairServiceImpl.java @@ -0,0 +1,14 @@ +package br.com.muttley.security.infra.service.impl; + +import br.com.muttley.localcache.services.LocalRSAKeyPairService; +import br.com.muttley.localcache.services.impl.AbstractLocalRSAKeyPairService; +import br.com.muttley.redis.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; + +public class LocalRSAKeyPairServiceImpl extends AbstractLocalRSAKeyPairService implements LocalRSAKeyPairService { + + @Autowired + public LocalRSAKeyPairServiceImpl(RedisService service) { + super(service); + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalRolesServiceImpl.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalRolesServiceImpl.java new file mode 100644 index 00000000..7b7d125b --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalRolesServiceImpl.java @@ -0,0 +1,58 @@ +package br.com.muttley.security.infra.service.impl; + +import br.com.muttley.localcache.services.LocalRolesService; +import br.com.muttley.localcache.services.impl.AbstractLocalRolesServiceImpl; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.Role; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.feign.PassaportServiceClient; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Set; + +/** + * @author Joel Rodrigues Moreira 25/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class LocalRolesServiceImpl extends AbstractLocalRolesServiceImpl implements LocalRolesService { + private final PassaportServiceClient passaportService; + + @Autowired + public LocalRolesServiceImpl(final RedisService redisService, final PassaportServiceClient passaportService) { + super(redisService); + this.passaportService = passaportService; + } + + @Override + public Set loadCurrentRoles(final JwtToken token, final User user) { + final Set roles; + //verificando se já existe roles para esse usuário + if (this.redisService.hasKey(this.getBasicKey(user))) { + roles = this.loadRolesInCache(user); + } else { + //se chegou até aqui precisaremos buscar as roles do server + roles = this.passaportService.loadCurrentRoles(); + //salvando as roles no cache + this.saveRolesInCache(token, user, roles); + } + return roles; + } + + @Override + public Set loadCurrentRoles(XAPIToken token, User user) { + final Set roles; + //verificando se já existe roles para esse usuário + if (this.redisService.hasKey(this.getBasicKey(user))) { + roles = this.loadRolesInCache(user); + } else { + //se chegou até aqui precisaremos buscar as roles do server + roles = this.passaportService.loadCurrentRoles(); + //salvando as roles no cache + this.saveRolesInCache(token, user, roles); + } + return roles; + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalSecretService.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalSecretService.java new file mode 100644 index 00000000..26302637 --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalSecretService.java @@ -0,0 +1,83 @@ +package br.com.muttley.security.infra.service.impl; + +import br.com.muttley.exception.throwables.MuttleyException; +import br.com.muttley.redis.service.RedisService; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +import static io.jsonwebtoken.SignatureAlgorithm.HS256; +import static io.jsonwebtoken.SignatureAlgorithm.HS384; +import static io.jsonwebtoken.SignatureAlgorithm.HS512; +import static io.jsonwebtoken.impl.TextCodec.BASE64; + + +/** + * @author Joel Rodrigues Moreira 24/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + *

+ * Classe responsável por prover acesso as chaves de assinatura dos tokens gerados pelo servidor de segurança + */ +class LocalSecretService { + private static final String BASIC_KEY = "BASIC_SERVER_KEY"; + private Map secrets; + private final RedisService redisService; + private boolean initialized = false; + + public LocalSecretService(final RedisService redisService) { + this.secrets = new HashMap(3); + this.redisService = redisService; + } + + public byte[] getHS256SecretBytes() { + this.init(); + return BASE64.decode(secrets.get(HS256.getValue())); + } + + public byte[] getHS384SecretBytes() { + this.init(); + return BASE64.decode(secrets.get(HS384.getValue())); + } + + public byte[] getHS512SecretBytes() { + this.init(); + return BASE64.decode(secrets.get(HS512.getValue())); + } + + private void init() { + if (!this.initialized) { + this.initialized = true; + this.refreshSecrets(); + } + } + + + public final void refreshSecrets() { + try { + //secrets.put(HS256.getValue(), BASE64.encode(generateKey(HS256).getEncoded())); + this.addSecret(HS256.getValue()); + //secrets.put(HS384.getValue(), BASE64.encode(generateKey(HS384).getEncoded())); + this.addSecret(HS384.getValue()); + //secrets.put(HS512.getValue(), BASE64.encode(generateKey(HS512).getEncoded())); + this.addSecret(HS512.getValue()); + } catch (Exception exception) { + LoggerFactory.getLogger(LocalSecretService.class).error("ATENÇÃO! NÃO FOI ENCONTRADO CHAVE GERADAS PELO SERVIDOR DE SERGURANÇA"); + exception.printStackTrace(); + } + //return secrets; + } + + private final void addSecret(final String key) { + if (redisService.hasKey(this.getBasicKey(key))) { + this.secrets.put(key, (String) redisService.get(this.getBasicKey(key))); + } else { + throw new MuttleyException("Não foi encontrado as chaves necessárias no sistema"); + } + } + + private String getBasicKey(final String key) { + return BASIC_KEY + ":" + key; + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalUserAuthenticationServiceImpl.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalUserAuthenticationServiceImpl.java new file mode 100644 index 00000000..40678d13 --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalUserAuthenticationServiceImpl.java @@ -0,0 +1,101 @@ +package br.com.muttley.security.infra.service.impl; + +import br.com.muttley.localcache.services.LocalRSAKeyPairService; +import br.com.muttley.localcache.services.LocalUserAuthenticationService; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.JwtUser; +import br.com.muttley.model.security.events.UserAfterCacheLoadEvent; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.feign.auth.AuthenticationTokenServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +/** + * @author Joel Rodrigues Moreira 24/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class LocalUserAuthenticationServiceImpl implements LocalUserAuthenticationService { + protected static final String BASIC_KEY = "JWT-TOKEN:"; + protected final RedisService redisService; + protected final AuthenticationTokenServiceClient tokenServiceClient; + private final LocalJwtTokenUtilService utilService; + private final LocalRSAKeyPairService rsaKeyPairService; + protected final ApplicationEventPublisher eventPublisher; + + @Autowired + public LocalUserAuthenticationServiceImpl(final RedisService redisService, final AuthenticationTokenServiceClient tokenServiceClient, final LocalRSAKeyPairService rsaKeyPairService, final ApplicationEventPublisher eventPublisher) { + this.redisService = redisService; + this.tokenServiceClient = tokenServiceClient; + this.utilService = new LocalJwtTokenUtilService(redisService); + this.rsaKeyPairService = rsaKeyPairService; + this.eventPublisher = eventPublisher; + } + + + @Override + public JwtUser getJwtUserFrom(String apiToken) { + return null; + } + + @Override + public JwtUser getJwtUserFrom(final JwtToken token) { + //verfificando se o token informado é válido + if (this.isValidToken(token.getToken())) { + final JwtUser jwtUser; + //verificando se já existe esse token salvo no redis + if (this.redisService.hasKey(this.getBasicKey(token))) { + //caso exista recuperamos localmente as infos + jwtUser = (JwtUser) redisService.get(this.getBasicKey(token)); + } else { + //se ainda não existe devemos buscar do servidor de segurança + jwtUser = this.tokenServiceClient.getUserFromToken(token); + //salvando o usuário recuperado no cache + this.set(token, jwtUser); + } + if (jwtUser != null) { + //Notificando que foi carregado um usuário do cache do sistema + this.eventPublisher.publishEvent(new UserAfterCacheLoadEvent(token, jwtUser.getOriginUser())); + } + return jwtUser; + } else { + //se chegou aqui, logo podemos inferir que o token é inválido + //logo pode ser removido + this.remove(token); + redisService.delete(token.getToken()); + } + return null; + } + + protected void set(final JwtToken token, final JwtUser user) { + this.redisService.set(this.getBasicKey(token), user, token.getDtExpiration()); + } + + @Override + public LocalUserAuthenticationService remove(final JwtToken token) { + if (token != null && !token.isEmpty()) { + this.redisService.delete(token.getToken()); + } + return this; + } + + @Override + public void refreshToken(final JwtToken currentToken, final JwtToken newToken) { + //verificando se o novo token é válido + if (this.isValidToken(newToken.getToken())) { + //ronomenando a chave de acesso caso existe em cache + this.redisService.changeKey(this.getBasicKey(currentToken), this.getBasicKey(newToken)); + } + } + + protected String getBasicKey(final JwtToken token) { + return BASIC_KEY + token.getToken(); + } + + + protected boolean isValidToken(final String token) { + return this.utilService.isValidToken(token); + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalUserPrefenceServiceImpl.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalUserPrefenceServiceImpl.java new file mode 100644 index 00000000..f749d2da --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalUserPrefenceServiceImpl.java @@ -0,0 +1,88 @@ +package br.com.muttley.security.infra.service.impl; + +import br.com.muttley.exception.throwables.security.MuttleySecurityBadRequestException; +import br.com.muttley.localcache.services.LocalUserPreferenceService; +import br.com.muttley.localcache.services.impl.AbstractLocalUserPrefenceServiceImpl; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.security.events.DeserializeUserPreferencesEvent; +import br.com.muttley.model.security.events.DeserializeUserPreferencesEvent.UserPreferencesResolverEventItem; +import br.com.muttley.model.security.preference.UserPreferences; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.feign.OwnerServiceClient; +import br.com.muttley.security.feign.UserPreferenceServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +/** + * @author Joel Rodrigues Moreira 24/03/2021 + * joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class LocalUserPrefenceServiceImpl extends AbstractLocalUserPrefenceServiceImpl implements LocalUserPreferenceService { + private final UserPreferenceServiceClient userPreferenceService; + private final OwnerServiceClient ownerService; + + @Autowired + public LocalUserPrefenceServiceImpl(final RedisService redisService, final UserPreferenceServiceClient userPreferenceService, OwnerServiceClient ownerService, ApplicationEventPublisher publisher) { + super(redisService, publisher); + this.userPreferenceService = userPreferenceService; + this.ownerService = ownerService; + } + + @Override + public UserPreferences getUserPreferences(final JwtToken jwtUser, final User user) { + if (jwtUser.getUsername().equals(user.getUserName())) { + final UserPreferences preferences; + //verificando se já existe a chave + if (this.redisService.hasKey(this.getBasicKey(user))) { + //recuperando as preferencias do usuário do cache + preferences = this.getPreferenceInCache(jwtUser, user); + } else { + //se chegou aqui é sinal que ainda não exite um preferencia carregada em cache, logo devemo buscar + //recuperando as preferencias do servidor de segurança + preferences = userPreferenceService.getUserPreferences(); + //salvando no cache para evitar loops desnecessários + this.savePreferenceInCache(jwtUser, user, preferences); + } + if (preferences != null) { + //por comodidade vamo disparar o evento para resolução dos itens das preferencias + final DeserializeUserPreferencesEvent event = new DeserializeUserPreferencesEvent(new UserPreferencesResolverEventItem(user, preferences)); + this.publisher.publishEvent(event); + //salvando no cache novamente para guardar as modificações + this.savePreferenceInCache(jwtUser, user, preferences); + } + return preferences; + } + throw new MuttleySecurityBadRequestException(UserPreferences.class, "user", "O usuário informado é diferente do presente no token"); + } + + @Override + public UserPreferences getUserPreferences(XAPIToken token, User user) { + final UserPreferences preferences; + //verificando se já existe a chave + if (this.redisService.hasKey(this.getBasicKey(user))) { + //recuperando as preferencias do usuário do cache + preferences = this.getPreferenceInCache(token, user); + } else { + //se chegou aqui é sinal que ainda não exite um preferencia carregada em cache, logo devemo buscar + //recuperando as preferencias do servidor de segurança + preferences = userPreferenceService.getUserPreferences(); + //salvando no cache para evitar loops desnecessários + this.savePreferenceInCache(token, user, preferences); + } + if (preferences != null) { + //por comodidade vamo disparar o evento para resolução dos itens das preferencias + final DeserializeUserPreferencesEvent event = new DeserializeUserPreferencesEvent(new UserPreferencesResolverEventItem(user, preferences)); + this.publisher.publishEvent(event); + //salvando no cache novamente para guardar as modificações + this.savePreferenceInCache(token, user, preferences); + } + return preferences; + } + + +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalWorkTeamServiceImpl.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalWorkTeamServiceImpl.java new file mode 100644 index 00000000..98fd07dc --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalWorkTeamServiceImpl.java @@ -0,0 +1,60 @@ +package br.com.muttley.security.infra.service.impl; + +import br.com.muttley.localcache.services.LocalWorkTeamService; +import br.com.muttley.localcache.services.impl.AbstractLocalWorkTemaServiceImpl; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.User; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.model.workteam.WorkTeamDomain; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.feign.WorkTeamServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import static br.com.muttley.localcache.services.LocalWorkTeamService.getBasicKey; + +/** + * @author Joel Rodrigues Moreira on 21/03/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Service +public class LocalWorkTeamServiceImpl extends AbstractLocalWorkTemaServiceImpl implements LocalWorkTeamService { + private final WorkTeamServiceClient client; + + @Autowired + public LocalWorkTeamServiceImpl(RedisService redisService, WorkTeamServiceClient client) { + super(redisService); + this.client = client; + } + + @Override + public WorkTeamDomain getWorkTeamDomain(JwtToken token, User user) { + final WorkTeamDomain workTeamDomain; + //verificando se existe esse registro em cache + if (this.redisService.hasKey(getBasicKey(user.getCurrentOwner(), user))) { + workTeamDomain = this.loadWorkTeamDomainInCache(user); + } else { + //recuperando o workteamdomaina do servidor + workTeamDomain = this.client.loadDomain(); + //salvando o workteamdomaina recuperado no cache + this.save(token, user, workTeamDomain); + } + return workTeamDomain; + } + + @Override + public WorkTeamDomain getWorkTeamDomain(XAPIToken token, User user) { + final WorkTeamDomain workTeamDomain; + //verificando se existe esse registro em cache + if (this.redisService.hasKey(getBasicKey(user.getCurrentOwner(), user))) { + workTeamDomain = this.loadWorkTeamDomainInCache(user); + } else { + //recuperando o workteamdomaina do servidor + workTeamDomain = this.client.loadDomain(); + //salvando o workteamdomaina recuperado no cache + this.save(token, user, workTeamDomain); + } + return workTeamDomain; + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalXAPITokenServiceImpl.java b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalXAPITokenServiceImpl.java new file mode 100644 index 00000000..295847da --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/infra/service/impl/LocalXAPITokenServiceImpl.java @@ -0,0 +1,48 @@ +package br.com.muttley.security.infra.service.impl; + +import br.com.muttley.localcache.services.LocalXAPITokenService; +import br.com.muttley.localcache.services.impl.AbstractLocalXAPITokenServiceImpl; +import br.com.muttley.model.security.JwtToken; +import br.com.muttley.model.security.XAPIToken; +import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.feign.XAPITokenClient; +import br.com.muttley.security.feign.auth.AuthenticationTokenServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class LocalXAPITokenServiceImpl extends AbstractLocalXAPITokenServiceImpl implements LocalXAPITokenService { + private final XAPITokenClient XAPITokenClient; + private final AuthenticationTokenServiceClient authenticationTokenServiceClient; + + @Autowired + public LocalXAPITokenServiceImpl(RedisService redisService, XAPITokenClient XAPITokenClient, final AuthenticationTokenServiceClient authenticationTokenServiceClient) { + super(redisService); + this.XAPITokenClient = XAPITokenClient; + this.authenticationTokenServiceClient = authenticationTokenServiceClient; + } + + @Override + public XAPIToken loadAPIToken(String token) { + final XAPIToken XAPIToken; + if (this.redisService.hasKey(getBasicKey(token, Type.XAPIToken))) { + XAPIToken = super.loadAPIToken(token); + } else { + XAPIToken = this.XAPITokenClient.getByToken(token); + this.saveInCache(XAPIToken); + } + return XAPIToken; + } + + @Override + public JwtToken loadJwtTokenFrom(String xAPIToken) { + final JwtToken jwtToken; + if (this.redisService.hasKey(getBasicKey(xAPIToken, Type.JWTToken))) { + jwtToken = super.loadJwtTokenFrom(xAPIToken); + } else { + jwtToken = this.authenticationTokenServiceClient.getUserFromXAPIToken(xAPIToken); + this.saveInCache(xAPIToken, jwtToken); + } + return jwtToken; + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/zuul/client/service/AbstractWebSecurityClient.java b/muttley-security/src/main/java/br/com/muttley/security/zuul/client/service/AbstractWebSecurityClient.java index 144aded9..de6ec6bb 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/zuul/client/service/AbstractWebSecurityClient.java +++ b/muttley-security/src/main/java/br/com/muttley/security/zuul/client/service/AbstractWebSecurityClient.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -48,19 +49,10 @@ protected void configure(HttpSecurity http) throws Exception { //desativando o controle de sessão .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() - //.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() - // permite acesso a qualquer recurso estatico - //.antMatchers( - // HttpMethod.GET, - // "/", - // "/*.html", - // "/**/*.{png,jpg,jpeg,svg.ico}", - // "/**/*.{html,css,js,svg,woff,woff2}", - // //endpoit padrão da aplicação - // "/login", - // "/create-user", - // "/home/**" - // ).permitAll() + .antMatchers(HttpMethod.GET, this.endPointPermitAllToGet()).permitAll() + .antMatchers(HttpMethod.POST, this.endPointPermitAllToPost()).permitAll() + .antMatchers(HttpMethod.PUT, this.endPointPermitAllToPut()).permitAll() + .antMatchers(HttpMethod.DELETE, this.endPointPermitAllToDelete()).permitAll() //permitindo acesso aos endpoint de login //.antMatchers(loginEndPoint, refreshTokenEndPoin, createEndPoint).permitAll() //barrando qualquer outra requisição não autenticada @@ -73,5 +65,27 @@ protected void configure(HttpSecurity http) throws Exception { http.headers().cacheControl(); } + /** + * Informa uma lista de endpoits que são livres de segurança. + * Por exemplo, deve-se listar aqui os end points referente a arquivos estaticos + * + * @return um array de padrões de urls + */ + protected String[] endPointPermitAllToGet() { + return new String[]{}; + } + + protected String[] endPointPermitAllToPost() { + return new String[]{}; + } + + protected String[] endPointPermitAllToPut() { + return new String[]{}; + } + + protected String[] endPointPermitAllToDelete() { + return new String[]{}; + } + } diff --git a/muttley-security/src/main/java/br/com/muttley/security/zuul/client/service/config/WebSecurityConfig.java b/muttley-security/src/main/java/br/com/muttley/security/zuul/client/service/config/WebSecurityConfig.java index 61afcab9..394b541d 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/zuul/client/service/config/WebSecurityConfig.java +++ b/muttley-security/src/main/java/br/com/muttley/security/zuul/client/service/config/WebSecurityConfig.java @@ -1,20 +1,41 @@ package br.com.muttley.security.zuul.client.service.config; +import br.com.muttley.localcache.services.LocalDatabindingService; +import br.com.muttley.localcache.services.LocalOwnerService; +import br.com.muttley.localcache.services.LocalRSAKeyPairService; +import br.com.muttley.localcache.services.LocalRolesService; +import br.com.muttley.localcache.services.LocalUserAuthenticationService; +import br.com.muttley.localcache.services.LocalUserPreferenceService; +import br.com.muttley.localcache.services.LocalWorkTeamService; +import br.com.muttley.localcache.services.LocalXAPITokenService; import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.feign.OwnerServiceClient; +import br.com.muttley.security.feign.PassaportServiceClient; +import br.com.muttley.security.feign.UserDataBindingClient; import br.com.muttley.security.feign.UserPreferenceServiceClient; import br.com.muttley.security.feign.WorkTeamServiceClient; +import br.com.muttley.security.feign.XAPITokenClient; +import br.com.muttley.security.feign.auth.AuthenticationTokenServiceClient; import br.com.muttley.security.infra.component.AuthenticationTokenFilterClient; +import br.com.muttley.security.infra.component.DeserializeUserPreferencesEventListener; import br.com.muttley.security.infra.component.UnauthorizedHandler; import br.com.muttley.security.infra.component.UserAfterCacheLoadListener; import br.com.muttley.security.infra.service.AuthService; -import br.com.muttley.security.infra.service.CacheUserAuthenticationService; import br.com.muttley.security.infra.service.impl.AuthServiceImpl; -import br.com.muttley.security.infra.service.impl.CacheUserAuthenticationServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalDatabindingServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalOwnerServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalRSAKeyPairServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalRolesServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalUserAuthenticationServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalUserPrefenceServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalWorkTeamServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalXAPITokenServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; /** * Configurações dos beans necessários para segurança do client @@ -31,24 +52,77 @@ public UnauthorizedHandler createUnauthorizedHandler(@Value("${muttley.security. @Bean @Autowired - public AuthenticationTokenFilterClient createAuthenticationTokenFilterClient(@Value("${muttley.security.jwt.controller.tokenHeader-jwt:Authorization-jwt}") final String tokenHeader, final CacheUserAuthenticationService cacheAuth) { - return new AuthenticationTokenFilterClient(tokenHeader, cacheAuth); + public AuthenticationTokenFilterClient createAuthenticationTokenFilterClient( + @Value("${muttley.security.jwt.controller.tokenHeader-jwt:Authorization-jwt}") final String tokenHeader, + @Value("${muttley.security.jwt.controller.xAPITokenHeader:X-Api-Token}") final String xAPIToken, + final LocalUserAuthenticationService localUserAuthentication, + final LocalXAPITokenService apiTokenService) { + return new AuthenticationTokenFilterClient(tokenHeader, xAPIToken, localUserAuthentication, apiTokenService); } @Bean @Autowired - public CacheUserAuthenticationService createCacheUserAuthenticationService(final RedisService redisService, final @Value("${muttley.security.jwt.token.expiration}") int expiration, final ApplicationEventPublisher eventPublisher) { - return new CacheUserAuthenticationServiceImpl(redisService, expiration, eventPublisher); + public LocalUserAuthenticationService createLocalUserAuthenticationService(final RedisService redisService, final AuthenticationTokenServiceClient authenticationTokenService, final LocalRSAKeyPairService localRSAKeyPairService, final ApplicationEventPublisher eventPublisher) { + return new LocalUserAuthenticationServiceImpl(redisService, authenticationTokenService, localRSAKeyPairService, eventPublisher); } @Bean - public AuthService createAuthService(@Value("${muttley.security.jwt.controller.tokenHeader-jwt:Authorization-jwt}") final String tokenHeader) { - return new AuthServiceImpl(tokenHeader); + @Primary + public AuthService createAuthService(@Value("${muttley.security.jwt.controller.tokenHeader-jwt:Authorization-jwt}") final String tokenHeader, @Autowired final UserPreferenceServiceClient userPreferenceServiceClient, final UserDataBindingClient dataBindingClient, final LocalUserAuthenticationService localUserAuthenticationService) { + return new AuthServiceImpl(tokenHeader, userPreferenceServiceClient, dataBindingClient, localUserAuthenticationService); } @Bean @Autowired - public UserAfterCacheLoadListener creaUserAfterCacheLoadListener(final UserPreferenceServiceClient userPreferenceServiceClient, final WorkTeamServiceClient workTeamServiceClient) { - return new UserAfterCacheLoadListener(userPreferenceServiceClient, workTeamServiceClient); + public UserAfterCacheLoadListener creaUserAfterCacheLoadListener(final LocalUserPreferenceService userPreferenceService, final LocalOwnerService ownerService, final LocalRolesService rolesService, final LocalDatabindingService localDatabindingService, final LocalWorkTeamService localWorkTeamService) { + return new UserAfterCacheLoadListener(userPreferenceService, ownerService, rolesService, localDatabindingService, localWorkTeamService); + } + + @Bean + @Autowired + public LocalUserPreferenceService createLocalUserPreferenceService(final RedisService redisService, final UserPreferenceServiceClient userPreferenceServiceClient, final OwnerServiceClient ownerServiceClient, final ApplicationEventPublisher publisher) { + return new LocalUserPrefenceServiceImpl(redisService, userPreferenceServiceClient, ownerServiceClient, publisher); + } + + @Bean + @Autowired + public LocalOwnerService createLocalOwnerService(final RedisService redisService, final OwnerServiceClient ownerServiceClient) { + return new LocalOwnerServiceImpl(redisService, ownerServiceClient); + } + + @Bean + @Autowired + public LocalRolesService createLocalRolesService(final RedisService redisService, final PassaportServiceClient passaportServiceClient) { + return new LocalRolesServiceImpl(redisService, passaportServiceClient); + } + + @Bean + @Autowired + public LocalRSAKeyPairService createLocalRSAKeyPairService(final RedisService redisService) { + return new LocalRSAKeyPairServiceImpl(redisService); + } + + @Bean + @Autowired + public LocalDatabindingService createLocalDatabindingService(final RedisService redisService, final UserDataBindingClient userDataBindingClient) { + return new LocalDatabindingServiceImpl(redisService, userDataBindingClient); + } + + @Bean + @Autowired + public LocalWorkTeamService createLocalWorkTeamService(final RedisService redisService, final WorkTeamServiceClient workTeamServiceClient) { + return new LocalWorkTeamServiceImpl(redisService, workTeamServiceClient); + } + + @Bean + @Autowired + public DeserializeUserPreferencesEventListener createUserPreferencesResolverEventListener(final LocalOwnerService ownerService) { + return new DeserializeUserPreferencesEventListener(ownerService); + } + + @Bean + @Autowired + public LocalXAPITokenService createLocalXAPITokenService(final RedisService redisService, final XAPITokenClient XAPITokenClient, final AuthenticationTokenServiceClient authenticationTokenServiceClient) { + return new LocalXAPITokenServiceImpl(redisService, XAPITokenClient, authenticationTokenServiceClient); } } diff --git a/muttley-security/src/main/java/br/com/muttley/security/zuul/gateway/AbstractWebSecurityGateway.java b/muttley-security/src/main/java/br/com/muttley/security/zuul/gateway/AbstractWebSecurityGateway.java index c5fe51ac..21310b66 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/zuul/gateway/AbstractWebSecurityGateway.java +++ b/muttley-security/src/main/java/br/com/muttley/security/zuul/gateway/AbstractWebSecurityGateway.java @@ -1,6 +1,8 @@ package br.com.muttley.security.zuul.gateway; import br.com.muttley.model.security.JwtUser; +import br.com.muttley.model.security.Password; +import br.com.muttley.model.security.User; import br.com.muttley.security.feign.UserServiceClient; import br.com.muttley.security.infra.component.AuthenticationTokenFilterGateway; import br.com.muttley.security.infra.component.UnauthorizedHandler; @@ -14,7 +16,6 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** @@ -30,7 +31,9 @@ public abstract class AbstractWebSecurityGateway extends WebSecurityConfigurerAdapter { protected final String loginEndPoint; protected final String refreshTokenEndPoin; + protected final String forgotPasswordEndPoint; protected final String createEndPoint; + protected final String resetPassword; protected final UnauthorizedHandler unauthorizedHandler; protected final AuthenticationTokenFilterGateway authenticationTokenFilterGateway; protected final UserServiceClient userServiceClient; @@ -39,15 +42,18 @@ public abstract class AbstractWebSecurityGateway extends WebSecurityConfigurerAd public AbstractWebSecurityGateway( @Value("${muttley.security.jwt.controller.loginEndPoint}") final String loginEndPoint, @Value("${muttley.security.jwt.controller.refreshEndPoint}") final String refreshTokenEndPoin, + @Value("${muttley.security.jwt.controller.forgotPasswordEndPoint}") final String forgotPassword, @Value("${muttley.security.jwt.controller.createEndPoint}") final String createEndPoint, + @Value("${muttley.security.jwt.controller.resetPassword}") final String resetPassword, final UnauthorizedHandler unauthorizedHandler, final AuthenticationTokenFilterGateway authenticationTokenFilterGateway, final UserServiceClient userServiceClient) { this.loginEndPoint = loginEndPoint; this.refreshTokenEndPoin = refreshTokenEndPoin; + this.forgotPasswordEndPoint = forgotPassword; this.createEndPoint = createEndPoint; + this.resetPassword = resetPassword; this.unauthorizedHandler = unauthorizedHandler; - this.authenticationTokenFilterGateway = authenticationTokenFilterGateway; this.userServiceClient = userServiceClient; } @@ -59,10 +65,15 @@ protected void configureAuthentication(AuthenticationManagerBuilder authenticati .userDetailsService(new UserDetailsService() { @Override public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { - return new JwtUser(userServiceClient.findByEmail(username)); + final User user = userServiceClient.findByUserName(username); + final Password password = userServiceClient.loadPasswordById(user.getId()); + return JwtUser.Builder.newInstance() + .set(user) + .setPassword(password) + .build(); } }) - .passwordEncoder(new BCryptPasswordEncoder()); + .passwordEncoder(Password.BuilderPasswordEncoder.getPasswordEncoder()); } @Override @@ -82,8 +93,11 @@ protected void configure(HttpSecurity http) throws Exception { HttpMethod.GET, this.endPointPermitAllToGet() ).permitAll() + .antMatchers(HttpMethod.POST, this.endPointPermitAllToPost()).permitAll() + .antMatchers(HttpMethod.PUT, this.endPointPermitAllToPut()).permitAll() + .antMatchers(HttpMethod.DELETE, this.endPointPermitAllToDelete()).permitAll() //permitindo acesso aos endpoint de login - .antMatchers(loginEndPoint, refreshTokenEndPoin, createEndPoint).permitAll() + .antMatchers(loginEndPoint, refreshTokenEndPoin, forgotPasswordEndPoint, createEndPoint, resetPassword).permitAll() //barrando qualquer outra requisição não autenticada .anyRequest().authenticated(); @@ -101,4 +115,16 @@ protected void configure(HttpSecurity http) throws Exception { * @return um array de padrões de urls */ protected abstract String[] endPointPermitAllToGet(); + + protected String[] endPointPermitAllToPost() { + return new String[]{}; + } + + protected String[] endPointPermitAllToPut() { + return new String[]{}; + } + + protected String[] endPointPermitAllToDelete() { + return new String[]{}; + } } diff --git a/muttley-security/src/main/java/br/com/muttley/security/zuul/gateway/listener/UserEventResolverListener.java b/muttley-security/src/main/java/br/com/muttley/security/zuul/gateway/listener/UserEventResolverListener.java new file mode 100644 index 00000000..a5537962 --- /dev/null +++ b/muttley-security/src/main/java/br/com/muttley/security/zuul/gateway/listener/UserEventResolverListener.java @@ -0,0 +1,27 @@ +package br.com.muttley.security.zuul.gateway.listener; + +import br.com.muttley.model.security.events.UserResolverEvent; +import br.com.muttley.security.feign.UserServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * @author Joel Rodrigues Moreira on 04/04/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Component +public class UserEventResolverListener implements ApplicationListener { + private final UserServiceClient service; + + @Autowired + public UserEventResolverListener(final UserServiceClient service) { + this.service = service; + } + + @Override + public void onApplicationEvent(final UserResolverEvent userEventResolver) { + userEventResolver.setValueResolved(this.service.findByUserName(userEventResolver.getUserName())); + } +} diff --git a/muttley-security/src/main/java/br/com/muttley/security/zuul/gateway/service/config/WebSecurityConfig.java b/muttley-security/src/main/java/br/com/muttley/security/zuul/gateway/service/config/WebSecurityConfig.java index 8080e43b..db98e2a7 100644 --- a/muttley-security/src/main/java/br/com/muttley/security/zuul/gateway/service/config/WebSecurityConfig.java +++ b/muttley-security/src/main/java/br/com/muttley/security/zuul/gateway/service/config/WebSecurityConfig.java @@ -1,11 +1,22 @@ package br.com.muttley.security.zuul.gateway.service.config; +import br.com.muttley.localcache.services.LocalOwnerService; +import br.com.muttley.localcache.services.LocalRSAKeyPairService; +import br.com.muttley.localcache.services.LocalUserAuthenticationService; +import br.com.muttley.localcache.services.LocalXAPITokenService; import br.com.muttley.redis.service.RedisService; +import br.com.muttley.security.feign.OwnerServiceClient; +import br.com.muttley.security.feign.UserServiceClient; +import br.com.muttley.security.feign.XAPITokenClient; import br.com.muttley.security.feign.auth.AuthenticationTokenServiceClient; import br.com.muttley.security.infra.component.AuthenticationTokenFilterGateway; +import br.com.muttley.security.infra.component.DeserializeUserPreferencesEventListener; import br.com.muttley.security.infra.component.UnauthorizedHandler; -import br.com.muttley.security.infra.service.CacheUserAuthenticationService; -import br.com.muttley.security.infra.service.impl.CacheUserAuthenticationServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalOwnerServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalRSAKeyPairServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalUserAuthenticationServiceImpl; +import br.com.muttley.security.infra.service.impl.LocalXAPITokenServiceImpl; +import br.com.muttley.security.zuul.gateway.listener.UserEventResolverListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; @@ -23,12 +34,14 @@ public class WebSecurityConfig { @Bean @Autowired - public AuthenticationTokenFilterGateway createAuthenticationTokenFilter( - @Value("${muttley.security.jwt.controller.tokenHeader}") final String tokenHeader, - final AuthenticationTokenServiceClient authenticationTokenServiceClient, - final CacheUserAuthenticationService cacheAuth, - final ApplicationEventPublisher eventPublisher) { - return new AuthenticationTokenFilterGateway(tokenHeader, authenticationTokenServiceClient, cacheAuth, eventPublisher); + public AuthenticationTokenFilterGateway createAuthenticationTokenFilter(@Value("${muttley.security.jwt.controller.tokenHeader}") final String tokenHeader, @Value("${muttley.security.jwt.controller.xAPITokenHeader:X-Api-Token}") final String xAPIToken, final LocalUserAuthenticationService localUserAuthentication, final LocalXAPITokenService localXAPITokenService) { + return new AuthenticationTokenFilterGateway(tokenHeader, xAPIToken, localUserAuthentication, localXAPITokenService); + } + + @Bean + @Autowired + public LocalUserAuthenticationService createLocalUserAuthenticationService(final RedisService redisService, final AuthenticationTokenServiceClient authenticationTokenService, final LocalRSAKeyPairService rsaKeyPairService, final ApplicationEventPublisher eventPublisher) { + return new LocalUserAuthenticationServiceImpl(redisService, authenticationTokenService, rsaKeyPairService, eventPublisher); } @Bean @@ -38,8 +51,32 @@ public UnauthorizedHandler createUnauthorizedHandler(@Value("${muttley.security. @Bean @Autowired - public CacheUserAuthenticationService createCacheUserAuthenticationService(final RedisService redisService, final @Value("${muttley.security.jwt.token.expiration}") int expiration, final ApplicationEventPublisher eventPublisher) { - return new CacheUserAuthenticationServiceImpl(redisService, expiration, eventPublisher); + public LocalOwnerService createLocalOwnerService(final RedisService redisService, final OwnerServiceClient ownerServiceClient) { + return new LocalOwnerServiceImpl(redisService, ownerServiceClient); + } + + @Bean + @Autowired + public DeserializeUserPreferencesEventListener createUserPreferencesResolverEventListener(final LocalOwnerService ownerService) { + return new DeserializeUserPreferencesEventListener(ownerService); + } + + + @Bean + @Autowired + public UserEventResolverListener createUserEventResolverListener(final UserServiceClient userServiceClient) { + return new UserEventResolverListener(userServiceClient); + } + + @Bean + @Autowired + public LocalRSAKeyPairService createLocalRSAKeyPairService(final RedisService redisService) { + return new LocalRSAKeyPairServiceImpl(redisService); } + @Bean + @Autowired + public LocalXAPITokenService createLocalXAPITokenService(final RedisService redisService, final XAPITokenClient XAPITokenClient, final AuthenticationTokenServiceClient authenticationTokenServiceClient) { + return new LocalXAPITokenServiceImpl(redisService, XAPITokenClient, authenticationTokenServiceClient); + } } diff --git a/muttley-security/src/test/java/br/com/muttley/muttleydiscoveryserver/MuttleyDiscoveryServerApplicationTests.java b/muttley-security/src/test/java/br/com/muttley/muttleydiscoveryserver/MuttleyDiscoveryServerApplicationTests.java new file mode 100644 index 00000000..3e5afe99 --- /dev/null +++ b/muttley-security/src/test/java/br/com/muttley/muttleydiscoveryserver/MuttleyDiscoveryServerApplicationTests.java @@ -0,0 +1,36 @@ +package br.com.muttley.muttleydiscoveryserver; + +import br.com.muttley.model.security.Password; +import com.sun.org.apache.xpath.internal.operations.String; +import org.apache.commons.lang.CharEncoding; +import org.junit.Test; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.util.Date; + +import static io.jsonwebtoken.SignatureAlgorithm.HS256; +import static io.jsonwebtoken.SignatureAlgorithm.HS512; +import static io.jsonwebtoken.impl.TextCodec.BASE64; +import static io.jsonwebtoken.impl.crypto.MacProvider.generateKey; + +/*@RunWith(SpringRunner.class) +@SpringBootTest*/ +public class MuttleyDiscoveryServerApplicationTests { + // private static final String TIME24HOURS_PATTERN = "(([+-]|)([01]?[0-9]|2[0-3]):[0-5][0-9])|(([+-]|)([01]?[0-9]|2[0-3])([0-5][0-9]))"; + + @Test + public void contextLoads() throws ParseException { + final InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("rsa/id_rsa_sfa"); + System.out.println(stream); + final java.lang.String s = new java.lang.String(generateKey(HS256).getEncoded(), StandardCharsets.UTF_8); + System.out.println(s); + System.out.println(BASE64.encode(generateKey(HS512).getEncoded())); + /*System.out.println(new Password(null, null, "12345", new Date(), null, null, null). + setPassword("12345").getPassword());*/ + + System.out.println(Password.BuilderPasswordEncoder.getPasswordEncoder().encode("123456")); + } + +} diff --git a/muttley-utils/.gitignore b/muttley-utils/.gitignore new file mode 100644 index 00000000..2af7cefb --- /dev/null +++ b/muttley-utils/.gitignore @@ -0,0 +1,24 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ \ No newline at end of file diff --git a/muttley-utils/.mvn/wrapper/maven-wrapper.jar b/muttley-utils/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..9cc84ea9 Binary files /dev/null and b/muttley-utils/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-utils/.mvn/wrapper/maven-wrapper.properties b/muttley-utils/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..9dda3b65 --- /dev/null +++ b/muttley-utils/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip diff --git a/muttley-utils/mvnw b/muttley-utils/mvnw new file mode 100755 index 00000000..5bf251c0 --- /dev/null +++ b/muttley-utils/mvnw @@ -0,0 +1,225 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-utils/mvnw.cmd b/muttley-utils/mvnw.cmd new file mode 100644 index 00000000..019bd74d --- /dev/null +++ b/muttley-utils/mvnw.cmd @@ -0,0 +1,143 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/muttley-utils/pom.xml b/muttley-utils/pom.xml new file mode 100644 index 00000000..4a85eed6 --- /dev/null +++ b/muttley-utils/pom.xml @@ -0,0 +1,29 @@ + + + + br.com.muttley + muttley-cloud + ${revision} + + 4.0.0 + jar + + muttley-utils + + muttley-utils + Demo project for Spring Boot + + + + org.projectlombok + lombok + true + + + + commons-beanutils + commons-beanutils + + + diff --git a/muttley-utils/src/main/java/br/com/muttley/annotations/valitators/MuttleyFileUtils.java b/muttley-utils/src/main/java/br/com/muttley/annotations/valitators/MuttleyFileUtils.java new file mode 100644 index 00000000..53347706 --- /dev/null +++ b/muttley-utils/src/main/java/br/com/muttley/annotations/valitators/MuttleyFileUtils.java @@ -0,0 +1,55 @@ +package br.com.muttley.annotations.valitators; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * @author Joel Rodrigues Moreira on 17/03/20. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class MuttleyFileUtils { + + /** + * Retorna um InputStream apartir de uma URL + * Caso tenha algum problema coma URL informada será retornado null automaticamente + */ + public static InputStream fromURL(final String url) { + try { + return fromURL(new URL(url)); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Retorna um InputStream apartir de uma URL + * Caso tenha algum problema coma URL informada será retornado null automaticamente + */ + public static InputStream fromURL(final URL url) { + try { + return url.openStream(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Retorna um InputStream apartir de uma URL + * Caso tenha algum problema coma URL informada será retornado um InputStream da URL padrão informada + * + * @param url -> url que tentaremos resolver + * @param urlDefault -> url que será usada caso de algum problema com a outra informada + */ + public static InputStream fromURL(final String url, final String urlDefault) { + final InputStream inputStream = fromURL(url); + if (inputStream != null) { + return inputStream; + } + return fromURL(urlDefault); + } +} diff --git a/muttley-utils/src/main/java/br/com/muttley/builders/DecimalFormatBuilder.java b/muttley-utils/src/main/java/br/com/muttley/builders/DecimalFormatBuilder.java new file mode 100644 index 00000000..162f20cd --- /dev/null +++ b/muttley-utils/src/main/java/br/com/muttley/builders/DecimalFormatBuilder.java @@ -0,0 +1,48 @@ +package br.com.muttley.builders; + +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +/** + * @author Joel Rodrigues Moreira on 28/06/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Setter +@Accessors(chain = true) +public class DecimalFormatBuilder { + private Locale locale; + private Character decimalSeparator = ','; + private Character groupingSeparator = '.'; + private String currencySymbol; + private Character digit; + private String exponentSeparator; + + + public DecimalFormatBuilder() { + } + + public DecimalFormat build(final String pattern) { + final DecimalFormatSymbols symbols = this.locale != null ? new DecimalFormatSymbols(this.locale) : new DecimalFormatSymbols(); + if (this.currencySymbol != null) { + symbols.setCurrencySymbol(this.currencySymbol); + } + if (this.digit != null) { + symbols.setDigit(this.digit); + } + if (this.exponentSeparator != null) { + symbols.setExponentSeparator(this.exponentSeparator); + } + if (this.groupingSeparator != null) { + symbols.setGroupingSeparator(this.groupingSeparator); + } + symbols.setDecimalSeparator(this.decimalSeparator); + symbols.setGroupingSeparator(this.groupingSeparator); + return new DecimalFormat(pattern, symbols); + } + +} diff --git a/muttley-utils/src/main/java/br/com/muttley/utils/BigDecimalUtils.java b/muttley-utils/src/main/java/br/com/muttley/utils/BigDecimalUtils.java new file mode 100644 index 00000000..12d4ded7 --- /dev/null +++ b/muttley-utils/src/main/java/br/com/muttley/utils/BigDecimalUtils.java @@ -0,0 +1,31 @@ +package br.com.muttley.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * @author Joel Rodrigues Moreira on 18/06/19. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class BigDecimalUtils { + public static BigDecimal newZero() { + return BigDecimal.ZERO.setScale(15, RoundingMode.HALF_EVEN); + } + + public static BigDecimal setDefaultValueIfIsnull(final BigDecimal value, final BigDecimal defaultValue) { + return value == null ? defaultValue : value; + } + + public static BigDecimal setDefaultScale(final BigDecimal bigDecimal) { + return setScale(bigDecimal, 15, RoundingMode.HALF_EVEN); + } + + public static BigDecimal setScale(final BigDecimal bigDecimal, final int scale, final RoundingMode roundingMode) { + if (bigDecimal == null) { + return null; + } + return bigDecimal.setScale(scale, roundingMode); + } + +} diff --git a/muttley-utils/src/main/java/br/com/muttley/utils/CollectionUtils.java b/muttley-utils/src/main/java/br/com/muttley/utils/CollectionUtils.java new file mode 100644 index 00000000..2ac88230 --- /dev/null +++ b/muttley-utils/src/main/java/br/com/muttley/utils/CollectionUtils.java @@ -0,0 +1,31 @@ +package br.com.muttley.utils; + +import java.util.Collection; +import java.util.Objects; + +/** + * @author Joel Rodrigues Moreira 17/11/2020 + * joel.databox@gmail.com + * @project muttley-cloud + */ +public class CollectionUtils { + public static boolean isEquals(final Collection first, final Collection second) { + if ((first == null || first.isEmpty()) && (second == null || second.isEmpty())) { + return true; + } + if ((first == null && second != null) || (first != null && second == null)) { + return false; + } + if (first.size() != second.size()) { + return false; + } + + return first + .parallelStream() + .filter(f -> + second.parallelStream() + .filter(s -> Objects.equals(s, f)) + .count() < 1 + ).count() == 0; + } +} diff --git a/muttley-utils/src/main/java/br/com/muttley/utils/DateUtils.java b/muttley-utils/src/main/java/br/com/muttley/utils/DateUtils.java new file mode 100644 index 00000000..d5acbcab --- /dev/null +++ b/muttley-utils/src/main/java/br/com/muttley/utils/DateUtils.java @@ -0,0 +1,255 @@ +package br.com.muttley.utils; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +import static br.com.muttley.utils.TimeZoneUtils.getTimezoneFromId; +import static java.time.temporal.TemporalAdjusters.firstDayOfMonth; +import static java.time.temporal.TemporalAdjusters.lastDayOfMonth; + +/** + * @author Joel Rodrigues Moreira on 18/06/18. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class DateUtils { + public static final String DATE_REGEX = "(\\d{4}|\\d{5}|\\d{6}|\\d{7})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|[3][01])"; + public static final String DATE_TIME_REGEX = "(\\d{4}|\\d{5}|\\d{6}|\\d{7})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|[3][01])T(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23):(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59):(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59)([.])\\d{3}([+-])\\d{4}"; + public static final DateTimeFormatter DEFAULT_ISO_ZONED_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + public static final DateTimeFormatter DEFAULT_ISO_LOCAL_DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + public static LocalDate toLocalDate(final Date date) { + return toLocalDate(date, ZoneId.systemDefault()); + } + + public static LocalDate toLocalDate(final Date date, final ZoneId zoneId) { + return date.toInstant() + .atZone(zoneId) + .toLocalDate(); + } + + public static LocalDateTime toLocalDateTime(final Date date) { + return toLocalDateTime(date, ZoneId.systemDefault()); + } + + public static LocalDateTime toLocalDateTime(final LocalDate local) { + return local.atTime(0, 0); + } + + public static LocalDateTime toLocalDateTime(final Date date, final ZoneId zoneId) { + return LocalDateTime.ofInstant(date.toInstant(), zoneId); + } + + public static boolean isEquals(final Date dateIni, final Date dateEnd) { + return toLocalDateTime(dateIni).isEqual(toLocalDateTime(dateEnd)); + } + + public static boolean isAfter(final Date dateIni, final Date dateEnd) { + return toLocalDateTime(dateIni).isAfter(toLocalDateTime(dateEnd)); + } + + public static boolean isBefore(final Date dateIni, final Date dateEnd) { + return toLocalDateTime(dateIni).isBefore(toLocalDateTime(dateEnd)); + } + + public static boolean lessThanOrEqualTo(final Date dateIni, final Date dateEnd) { + final LocalDateTime localDateIni = toLocalDateTime(dateIni); + final LocalDateTime localDateEnd = toLocalDateTime(dateEnd); + return localDateIni.isEqual(localDateEnd) || localDateIni.isBefore(localDateEnd); + } + + public static boolean lessThanOrEqualTo(final LocalDateTime dateIni, final LocalDateTime dateEnd) { + return dateIni.isEqual(dateEnd) || dateIni.isBefore(dateEnd); + } + + public static boolean greaterthanOrEqualTo(final Date dateIni, final Date dateEnd) { + final LocalDateTime localDateIni = toLocalDateTime(dateIni); + final LocalDateTime localDateEnd = toLocalDateTime(dateEnd); + return localDateIni.isEqual(localDateEnd) || localDateIni.isAfter(localDateEnd); + } + + public static boolean greaterthanOrEqualTo(final LocalDateTime localDateIni, final LocalDateTime localDateEnd) { + return localDateIni.isEqual(localDateEnd) || localDateIni.isAfter(localDateEnd); + } + + public static boolean isToday(final Date date) { + return isEquals(toFirstHour(date), toFirstHour(new Date())); + } + + public static boolean isToday(final LocalDateTime dateTime) { + return toFirstHour(dateTime).equals(toFirstHour(LocalDateTime.now())); + } + + public static boolean isToday(final ZonedDateTime dateTime) { + final ZonedDateTime today; + if (dateTime.getZone() == null) { + today = ZonedDateTime.now(); + } else { + today = ZonedDateTime.now(dateTime.getZone()); + } + return toFirstHour(today).isEqual(toFirstHour(dateTime)); + } + + public static Date toFirstHour(final Date date) { + return toDate(toFirstHour(toLocalDateTime(date))); + } + + public static LocalDateTime toFirstHour(final LocalDateTime date) { + return date + .withHour(0) + .withMinute(0) + .withSecond(0) + .withNano(0); + } + + public static ZonedDateTime toFirstHour(final ZonedDateTime dateTime) { + return dateTime + .withHour(0) + .withMinute(0) + .withSecond(0) + .withNano(0); + } + + public static Date toLastHour(final Date date) { + return toDate(toLastHour(toLocalDateTime(date))); + } + + public static LocalDateTime toLastHour(final LocalDateTime date) { + return date + .withHour(23) + .withMinute(59) + .withSecond(59) + .withNano(999999999); + } + + public static ZonedDateTime toLastHour(final ZonedDateTime dateTime) { + return dateTime.withHour(23) + .withMinute(59) + .withSecond(59) + .withNano(999999999); + } + + public static Date toFirstDayOfMonth(final Date date) { + return toDate(toFirstDayOfMonth(toLocalDateTime(date))); + } + + public static LocalDateTime toFirstDayOfMonth(final LocalDateTime date) { + return date.with(firstDayOfMonth()); + } + + public static Date toLastDayOfMonth(final Date date) { + return toDate(toLastDayOfMonth(toLocalDateTime(date))); + } + + public static LocalDateTime toLastDayOfMonth(final LocalDateTime date) { + return date.with(lastDayOfMonth()); + } + + public static Date toDate(final LocalDate date) { + return toDate(date, ZoneId.systemDefault()); + } + + public static Date toDate(final LocalDate date, final ZoneId zoneOffset) { + return Date.from(date.atStartOfDay(zoneOffset).toInstant()); + } + + public static Date toDate(final LocalDateTime date) { + return Date.from( + date.atZone(ZoneId.systemDefault()).toInstant() + ); + } + + public static Date toDate(final LocalDateTime date, final ZoneOffset zoneOffset) { + return Date.from( + date.atZone(zoneOffset).toInstant() + ); + } + + public static Date toDate(final ZonedDateTime date) { + return toDate(date, date.getOffset()); + } + + public static Date toDate(final ZonedDateTime date, ZoneOffset zoneOffset) { + return Date.from(date.toLocalDateTime().toInstant(zoneOffset)); + } + + public static ZonedDateTime toZonedDateTime(final Date date) { + return ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + } + + public static ZonedDateTime toZonedDateTime(final Date date, final String offset) { + return ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.of(offset)); + } + + public static Date toUTC(final Date date) { + return toDate(ZonedDateTime.ofInstant(date.toInstant(), ZoneId.of("+0000"))); + } + + public static ZonedDateTime toUTC(final ZonedDateTime zonedDateTime) { + return ZonedDateTime.ofInstant(zonedDateTime.toInstant(), ZoneId.of("+0000")); + } + + /** + * Applica um deslocamento na data e hora levando em consideração o offset + */ + public static ZonedDateTime applyOffset(final ZonedDateTime zonedDateTime) { + //recuperando o offset + final String offset = getTimezoneFromId(zonedDateTime.getOffset().toString()); + //transformando em data + final LocalTime hour = LocalTime.parse(offset.replaceAll("[-+]", "")); + //applicando o deslocamento necessário + return offset.startsWith("-") ? + zonedDateTime + .minusHours(hour.getHour()) + .minusMinutes(hour.getMinute()) + : + zonedDateTime + .plusHours(hour.getHour()) + .plusMinutes(hour.getMinute()); + } + + public static Date applyLocalSystemOffset(final Date date) { + return applyOffset(date, getTimezoneFromId(ZoneId.systemDefault().toString())); + } + + public static Date applyOffset(final Date date, final ZoneId zoneId) { + return applyOffset(date, getTimezoneFromId(zoneId.toString())); + } + + public static Date applyOffset(final Date date, final ZoneOffset offsetTime) { + return applyOffset(date, getTimezoneFromId(offsetTime.toString())); + } + + public static Date applyOffset(final Date date, final String offset) { + //transformando em data + final LocalTime hour = LocalTime.parse(offset.replaceAll("[-+]", "")); + //applicando o deslocamento necessário + final ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.of(offset)); + + return Date.from(offset.startsWith("-") ? + zonedDateTime + .minusHours(hour.getHour()) + .minusMinutes(hour.getMinute()) + .toInstant() + : + zonedDateTime + .plusHours(hour.getHour()) + .plusMinutes(hour.getMinute()) + .toInstant() + ); + } + + public static boolean isValidDate(final String value) { + return String.valueOf((Object) value).matches(DATE_REGEX); + } + + public static boolean isValidDateTime(final String value) { + return String.valueOf((Object) value).matches(DATE_TIME_REGEX); + } +} diff --git a/muttley-utils/src/main/java/br/com/muttley/utils/FilesUtils.java b/muttley-utils/src/main/java/br/com/muttley/utils/FilesUtils.java new file mode 100644 index 00000000..0e4b3763 --- /dev/null +++ b/muttley-utils/src/main/java/br/com/muttley/utils/FilesUtils.java @@ -0,0 +1,112 @@ +package br.com.muttley.utils; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +/** + * @author Joel Rodrigues Moreira on 28/07/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class FilesUtils { + /** + * @param localDownload -> Url que contem o arquivo a ser baixado + * @param destination -> Destino para onde que deve ser baixado juntamente com o nome do arquivo + * @param replaceIfExists -> Caso o arquivo exista o mesmo será substituido + */ + public static void downloadFile(final URL localDownload, final Path destination, final boolean replaceIfExists) { + + //verificando se o arquivo já existe e se o mesmo deve ser substituido + if (Files.notExists(destination) || replaceIfExists) { + //pegan o diretório que deve ser armazenado o arquivo + final Path directory = destination.getParent(); + try { + //criando diretório caso ele não exista + if (Files.notExists(directory)) { + Files.createDirectories(directory); + } + //fazendo o download do arquivo + try (final ReadableByteChannel readableByteChannel = Channels.newChannel(localDownload.openStream())) { + try (final FileChannel fileChannel = new FileOutputStream(destination.toFile()).getChannel()) { + fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); + } + } + } catch (final IOException exception) { + throw new RuntimeException(exception); + } + } + } + + public static Path resolveLocalFile(Path parentLocation, final URL url) { + //pegando o caminho complento onde será armazenado o arquivo + final Path localPath = Paths.get(parentLocation.toString(), getPathFromURL(url).toString()); + //fazendo download loca caso necessário + downloadFile(url, localPath, false); + return localPath; + } + + /** + * @param file -> Arquivo para ser deletado + * @param dropParentIfEmpty -> caso o diretório fique fazio o mesmo será removido + */ + public static void removeFile(final Path file, boolean dropParentIfEmpty) { + + //removendo arquivo + try { + //verificando se é realmente é um arquivo + if (!Files.isDirectory(file)) { + Files.deleteIfExists(file); + + //deletando diretório caso necessário + if (dropParentIfEmpty) { + try (final Stream filesStream = Files.list(file.getParent())) { + //vefirifincando se o diretório está vazio + if (!filesStream.findFirst().isPresent()) { + Files.deleteIfExists(file.getParent()); + } + } + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Cria uma representação de diretório local com base na url informada + * Exemplo "www.mysite.com.br/teste/my-files/image.jpg" será transformado para + * "/teste/my-files/image.jpg" + * + * @param url -> URL esperada + */ + public static Path getPathFromURL(final URL url) { + return getPathFromURL(url, "UTF-8"); + } + + /** + * Cria uma representação de diretório local com base na url informada + * Exemplo "www.mysite.com.br/teste/my-files/image.jpg" será transformado para + * "/teste/my-files/image.jpg" + * + * @param url -> URL esperada + * @param enconder -> encoder necessário para resolver a url + */ + public static Path getPathFromURL(final URL url, final String enconder) { + try { + return Paths.get(URLDecoder.decode(Paths.get(url.getPath()).getFileName().toString(), enconder)); + } catch (UnsupportedEncodingException ex) { + throw new RuntimeException(ex); + } + } +} + diff --git a/muttley-utils/src/main/java/br/com/muttley/utils/MapUtils.java b/muttley-utils/src/main/java/br/com/muttley/utils/MapUtils.java new file mode 100644 index 00000000..4a587180 --- /dev/null +++ b/muttley-utils/src/main/java/br/com/muttley/utils/MapUtils.java @@ -0,0 +1,35 @@ +package br.com.muttley.utils; + +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira on 12/01/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class MapUtils { + + public static Object getValueByNavigation(final String key, final Map map) { + if (map == null) { + return null; + } + //se tem ponto, logo devemo fazer navegação por nível + if (key.contains(".")) { + //pegando a primeira chave + final String firstKey = key.substring(0, key.indexOf(".")); + //pegando o objeto referente a chave para recursão + final Object item = map.get(firstKey); + //verificando se item é um map + //caso não for devemos retornar null + if (!(item instanceof Map)) { + if (item != null) { + System.out.println("###ATTENTION. A MAP was expected instead of an object of type " + item.getClass()); + } + return null; + } + //pegando o item recuperado e fazendo recursão para pegar o proxímo nível + return getValueByNavigation(key.substring(key.indexOf(".") + 1), (Map) item); + } + return map.get(key); + } +} diff --git a/muttley-utils/src/main/java/br/com/muttley/utils/StringBuilderUtils.java b/muttley-utils/src/main/java/br/com/muttley/utils/StringBuilderUtils.java new file mode 100644 index 00000000..fbcfd9c6 --- /dev/null +++ b/muttley-utils/src/main/java/br/com/muttley/utils/StringBuilderUtils.java @@ -0,0 +1,33 @@ +package br.com.muttley.utils; + +import org.springframework.util.StringUtils; + +/** + * @author Joel Rodrigues Moreira on 22/12/2022. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class StringBuilderUtils { + public static StringBuilder append(final StringBuilder builder, final StringBuilder content) { + if (!StringUtils.isEmpty(content)) { + if (builder.length() > 0) { + builder.append(", ").append(content); + } else { + builder.append(content); + } + } + return builder; + } + + public static StringBuilder append(final StringBuilder builder, final String content) { + if (!StringUtils.isEmpty(content)) { + if (builder.length() > 0) { + builder.append(", ").append(content); + } else { + builder.append(content); + } + } + return builder; + } + +} diff --git a/muttley-utils/src/main/java/br/com/muttley/utils/TimeZoneUtils.java b/muttley-utils/src/main/java/br/com/muttley/utils/TimeZoneUtils.java new file mode 100644 index 00000000..cd6eb7f4 --- /dev/null +++ b/muttley-utils/src/main/java/br/com/muttley/utils/TimeZoneUtils.java @@ -0,0 +1,85 @@ +package br.com.muttley.utils; + +import sun.util.calendar.ZoneInfo; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.time.ZoneOffset; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +/** + * @author Joel Rodrigues Moreira on 28/06/2023. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class TimeZoneUtils { + public static final String TIMEZONE_REGEX = "(([+-]|)([01]?[0-9]|2[0-3]):[0-5][0-9])|(([+-]|)([01]?[0-9]|2[0-3])([0-5][0-9]))"; + public static final Pattern TIMEZONE_PATTERN = Pattern.compile(TIMEZONE_REGEX); + + public static boolean isValidTimeZone(final String timezone) { + return timezone != null ? TIMEZONE_PATTERN.matcher(timezone).matches() : false; + } + + + /***/ + + public static String getTimezoneFromId(String zoneId) { + if ("z".equalsIgnoreCase(zoneId)) { + return "+00:00"; + } + //verificando se zona informada é válida + if (isValidTimeZone(zoneId)) { + //checando se esta formatada com dois pontos + if (!zoneId.contains("+") && !zoneId.contains("-")) { + zoneId = "+" + zoneId; + } + //adicionando os dois pontos caso não tenha + if (!zoneId.contains(":")) { + zoneId = zoneId.substring(0, zoneId.length() - 2) + ":" + zoneId.substring(zoneId.length() - 2, zoneId.length()); + } + //se for válida só retorna a mesma + + return zoneId; + } + + //Se chegou até aqui quer dizer que não mandou no formato de hora + //logo devemos recuperar pelo timezone de fato + final TimeZone timeZone = ZoneInfo.getTimeZone(zoneId); + + //se não recuperou um timezone válido, + //podemos parar o processo + if (timeZone == null) { + return null; + } + + final long hours = TimeUnit.MILLISECONDS.toHours(timeZone.getRawOffset()); + long minutes = TimeUnit.MILLISECONDS.toMinutes(timeZone.getRawOffset()) - TimeUnit.HOURS.toMinutes(hours); + // avoid -4:-30 issue + minutes = Math.abs(minutes); + + if (hours == 0 && minutes == 0) { + return "+00:00"; + } + + final NumberFormat numberFormat = new DecimalFormat("00"); + final String hoursFormated = numberFormat.format(hours); + + if (hours > 0) { + return "+" + hoursFormated + ":" + String.format("%02d", minutes); + } else if (hours == 0 && minutes > 0) { + return "+" + hoursFormated + ":" + String.format("%02d", minutes); + } else if (hours < 0) { + return hoursFormated + ":" + String.format("%02d", minutes); + } else if (hours == 0 && minutes < 0) { + return hoursFormated + ":" + String.format("%02d", minutes); + } + return null; + + } + + public static String TimezoneFromId(ZoneOffset offset) { + return getTimezoneFromId(offset.toString()); + } +} diff --git a/muttley-utils/src/main/resources/application.properties b/muttley-utils/src/main/resources/application.properties new file mode 100644 index 00000000..e69de29b diff --git a/muttley-validators/.gitignore b/muttley-validators/.gitignore new file mode 100644 index 00000000..549e00a2 --- /dev/null +++ b/muttley-validators/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/muttley-validators/.mvn/wrapper/MavenWrapperDownloader.java b/muttley-validators/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..a45eb6ba --- /dev/null +++ b/muttley-validators/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/muttley-validators/.mvn/wrapper/maven-wrapper.jar b/muttley-validators/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..2cc7d4a5 Binary files /dev/null and b/muttley-validators/.mvn/wrapper/maven-wrapper.jar differ diff --git a/muttley-validators/.mvn/wrapper/maven-wrapper.properties b/muttley-validators/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..ffdc10e5 --- /dev/null +++ b/muttley-validators/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/muttley-validators/mvnw b/muttley-validators/mvnw new file mode 100755 index 00000000..a16b5431 --- /dev/null +++ b/muttley-validators/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/muttley-validators/mvnw.cmd b/muttley-validators/mvnw.cmd new file mode 100644 index 00000000..c8d43372 --- /dev/null +++ b/muttley-validators/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/muttley-validators/pom.xml b/muttley-validators/pom.xml new file mode 100644 index 00000000..99b4d251 --- /dev/null +++ b/muttley-validators/pom.xml @@ -0,0 +1,35 @@ + + + + br.com.muttley + muttley-cloud + ${revision} + + 4.0.0 + jar + + muttley-validators + + muttley-validators + Demo project for Spring Boot + + + + org.projectlombok + lombok + true + + + + br.com.muttley + muttley-model + + + + br.com.muttley + muttley-headers + + + + diff --git a/muttley-validators/src/main/java/br/com/muttley/validators/MuttleyValidator.java b/muttley-validators/src/main/java/br/com/muttley/validators/MuttleyValidator.java new file mode 100644 index 00000000..fa497093 --- /dev/null +++ b/muttley-validators/src/main/java/br/com/muttley/validators/MuttleyValidator.java @@ -0,0 +1,86 @@ +package br.com.muttley.validators; + +import br.com.muttley.headers.components.MuttleyUserAgent; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.lang.annotation.Annotation; + +/** + * @author Joel Rodrigues Moreira on 04/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Getter +public abstract class MuttleyValidator implements ConstraintValidator { + + protected A annotation; + + @Autowired + protected MuttleyUserAgent userAgent; + + @Override + public void initialize(final A constraintAnnotation) { + this.annotation = constraintAnnotation; + } + + @Override + public final boolean isValid(final T value, final ConstraintValidatorContext context) { + + //é uma anotação que está em uma classe e não em um campo? + /*if (isAnnotationForClass() || !(value instanceof Document)) { + return true; + } +*/ + + //é para ignorar essa validação com base no userAgenteName? + if (this.isIgnoreValidation(value, context)) { + return true; + } + /*if (isAnnotationForClass() || !(value instanceof Document)) { + return true; + } +*/ + return this.isValidValue(value, context); + + /*//Devemos validar essa info nessa requisição? + if (!this.isIgnoreValidation(value, context)) { + //é uma anotação que está em uma classe e não em um campo? + if (isAnnotationForClass() || !(value instanceof Document)) { + return true; + } + } + return isValidValue(value, context);*/ + } + + /** + * Informa se deve ingnorar ou não a validação durante a requisição + */ + protected boolean isIgnoreValidation(Object value, ConstraintValidatorContext context) { + if (!this.isEmpty(this.getIgnoreForClients()) && this.getUserAgent().containsValidValue()) { + for (final String client : this.getIgnoreForClients()) { + if (this.getUserAgent().getCurrentValue().equals(client)) { + return true; + } + } + } + return false; + } + + protected abstract boolean isValidValue(final T value, final ConstraintValidatorContext context); + + /** + * Informa se é uma anotação de classe ou não + */ + protected boolean isAnnotationForClass() { + return false; + } + + private boolean isEmpty(final String[] values) { + return values == null || values.length == 0; + } + + protected abstract String[] getIgnoreForClients(); +} diff --git a/muttley-validators/src/main/java/br/com/muttley/validators/email/Email.java b/muttley-validators/src/main/java/br/com/muttley/validators/email/Email.java new file mode 100644 index 00000000..add57dd4 --- /dev/null +++ b/muttley-validators/src/main/java/br/com/muttley/validators/email/Email.java @@ -0,0 +1,31 @@ +package br.com.muttley.validators.email; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Constraint(validatedBy = EmailValidator.class) +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Email { + String message() default "email inválido"; + + String[] ignoreForClients() default {}; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/muttley-validators/src/main/java/br/com/muttley/validators/email/EmailValidator.java b/muttley-validators/src/main/java/br/com/muttley/validators/email/EmailValidator.java new file mode 100644 index 00000000..3b810908 --- /dev/null +++ b/muttley-validators/src/main/java/br/com/muttley/validators/email/EmailValidator.java @@ -0,0 +1,35 @@ +package br.com.muttley.validators.email; + +import br.com.muttley.validators.MuttleyValidator; + +import javax.validation.ConstraintValidatorContext; +import java.util.regex.Pattern; + +/** + * @author Joel Rodrigues Moreira on 05/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class EmailValidator extends MuttleyValidator { + private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@" + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; + private final Pattern pattern; + + public EmailValidator() { + this.pattern = Pattern.compile(EMAIL_PATTERN); + } + + @Override + protected boolean isValidValue(String email, ConstraintValidatorContext context) { + if (email != null && !this.pattern.matcher(email).matches()) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(getAnnotation().message()) + .addConstraintViolation(); + } + return true; + } + + @Override + protected String[] getIgnoreForClients() { + return this.getAnnotation().ignoreForClients(); + } +} diff --git a/muttley-validators/src/main/java/br/com/muttley/validators/notblank/NotBlank.java b/muttley-validators/src/main/java/br/com/muttley/validators/notblank/NotBlank.java new file mode 100644 index 00000000..ac1773e5 --- /dev/null +++ b/muttley-validators/src/main/java/br/com/muttley/validators/notblank/NotBlank.java @@ -0,0 +1,33 @@ +package br.com.muttley.validators.notblank; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Joel Rodrigues Moreira on 04/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Constraint(validatedBy = NotBlankValidator.class) +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RUNTIME) +public @interface NotBlank { + String message() default "Informe um valor válido"; + + String[] ignoreForClients() default {}; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} + diff --git a/muttley-validators/src/main/java/br/com/muttley/validators/notblank/NotBlankValidator.java b/muttley-validators/src/main/java/br/com/muttley/validators/notblank/NotBlankValidator.java new file mode 100644 index 00000000..3cb2dbc6 --- /dev/null +++ b/muttley-validators/src/main/java/br/com/muttley/validators/notblank/NotBlankValidator.java @@ -0,0 +1,52 @@ +package br.com.muttley.validators.notblank; + +import br.com.muttley.validators.MuttleyValidator; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import javax.validation.ConstraintValidatorContext; +import java.util.Collection; +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira on 04/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class NotBlankValidator extends MuttleyValidator { + @Override + public boolean isValidValue(final Object value, final ConstraintValidatorContext context) { + if (value == null) { + buildMessageError(context); + return false; + } + + if (value instanceof String && (StringUtils.isEmpty(value))) { + buildMessageError(context); + return false; + } + + if (value instanceof Collection && (CollectionUtils.isEmpty((Collection) value))) { + buildMessageError(context); + return false; + } + + if (value instanceof Map && (CollectionUtils.isEmpty((Map) value))) { + buildMessageError(context); + return false; + } + + return true; + } + + private void buildMessageError(final ConstraintValidatorContext context) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(getAnnotation().message()) + .addConstraintViolation(); + } + + @Override + protected String[] getIgnoreForClients() { + return this.getAnnotation().ignoreForClients(); + } +} diff --git a/muttley-validators/src/main/java/br/com/muttley/validators/notnegative/NotNegative.java b/muttley-validators/src/main/java/br/com/muttley/validators/notnegative/NotNegative.java new file mode 100644 index 00000000..7337f94b --- /dev/null +++ b/muttley-validators/src/main/java/br/com/muttley/validators/notnegative/NotNegative.java @@ -0,0 +1,34 @@ +package br.com.muttley.validators.notnegative; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Joel Rodrigues Moreira on 04/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Constraint(validatedBy = NotNegativeValidator.class) +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RUNTIME) +public @interface NotNegative { + String message() default "Informe um valor positivo"; + + //Por padrão aceitamos valores nulos como válido + boolean nullable() default true; + + String[] ignoreForClients() default {}; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/muttley-validators/src/main/java/br/com/muttley/validators/notnegative/NotNegativeValidator.java b/muttley-validators/src/main/java/br/com/muttley/validators/notnegative/NotNegativeValidator.java new file mode 100644 index 00000000..96daf8b4 --- /dev/null +++ b/muttley-validators/src/main/java/br/com/muttley/validators/notnegative/NotNegativeValidator.java @@ -0,0 +1,44 @@ +package br.com.muttley.validators.notnegative; + +import br.com.muttley.validators.MuttleyValidator; + +import javax.validation.ConstraintValidatorContext; +import java.math.BigDecimal; + +/** + * @author Joel Rodrigues Moreira on 04/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class NotNegativeValidator extends MuttleyValidator { + @Override + protected boolean isValidValue(final Object value, final ConstraintValidatorContext context) { + if (getAnnotation().nullable() && value == null) { + return true; + } + + if ((!getAnnotation().nullable()) && value == null) { + return buildMessageError(context); + } + + + final BigDecimal number = new BigDecimal(String.valueOf(value)); + + if (number.compareTo(BigDecimal.ZERO) < 0) { + return buildMessageError(context); + } + return true; + } + + @Override + protected String[] getIgnoreForClients() { + return this.getAnnotation().ignoreForClients(); + } + + private boolean buildMessageError(final ConstraintValidatorContext context) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(getAnnotation().message()) + .addConstraintViolation(); + return false; + } +} diff --git a/muttley-validators/src/main/java/br/com/muttley/validators/notnull/NotNull.java b/muttley-validators/src/main/java/br/com/muttley/validators/notnull/NotNull.java new file mode 100644 index 00000000..a0928adc --- /dev/null +++ b/muttley-validators/src/main/java/br/com/muttley/validators/notnull/NotNull.java @@ -0,0 +1,33 @@ +package br.com.muttley.validators.notnull; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Joel Rodrigues Moreira on 04/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Constraint(validatedBy = NotNullValidator.class) +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RUNTIME) +public @interface NotNull { + + //Mensagem caso o cpfCnpj seja inválido + String message() default "Informe um valor válido"; + + String[] ignoreForClients() default {}; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/muttley-validators/src/main/java/br/com/muttley/validators/notnull/NotNullValidator.java b/muttley-validators/src/main/java/br/com/muttley/validators/notnull/NotNullValidator.java new file mode 100644 index 00000000..e07ddf60 --- /dev/null +++ b/muttley-validators/src/main/java/br/com/muttley/validators/notnull/NotNullValidator.java @@ -0,0 +1,28 @@ +package br.com.muttley.validators.notnull; + +import br.com.muttley.validators.MuttleyValidator; + +import javax.validation.ConstraintValidatorContext; + +/** + * @author Joel Rodrigues Moreira on 04/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class NotNullValidator extends MuttleyValidator { + @Override + protected boolean isValidValue(Object value, ConstraintValidatorContext context) { + if (value == null) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(getAnnotation().message()) + .addConstraintViolation(); + return false; + } + return true; + } + + @Override + protected String[] getIgnoreForClients() { + return this.getAnnotation().ignoreForClients(); + } +} diff --git a/muttley-validators/src/main/java/br/com/muttley/validators/size/Size.java b/muttley-validators/src/main/java/br/com/muttley/validators/size/Size.java new file mode 100644 index 00000000..26c678fc --- /dev/null +++ b/muttley-validators/src/main/java/br/com/muttley/validators/size/Size.java @@ -0,0 +1,39 @@ +package br.com.muttley.validators.size; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.Long.MAX_VALUE; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Joel Rodrigues Moreira on 04/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +@Constraint(validatedBy = SizeValidator.class) +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RUNTIME) +public @interface Size { + + String message() default "Item com tamanho inapropriado "; + + String[] ignoreForClients() default {}; + + long min() default 0l; + + long max() default MAX_VALUE; + + boolean nullable() default true; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/muttley-validators/src/main/java/br/com/muttley/validators/size/SizeValidator.java b/muttley-validators/src/main/java/br/com/muttley/validators/size/SizeValidator.java new file mode 100644 index 00000000..c99f5988 --- /dev/null +++ b/muttley-validators/src/main/java/br/com/muttley/validators/size/SizeValidator.java @@ -0,0 +1,63 @@ +package br.com.muttley.validators.size; + +import br.com.muttley.validators.MuttleyValidator; + +import javax.validation.ConstraintValidatorContext; +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Map; + +/** + * @author Joel Rodrigues Moreira on 04/05/2021. + * e-mail: joel.databox@gmail.com + * @project muttley-cloud + */ +public class SizeValidator extends MuttleyValidator { + @Override + protected boolean isValidValue(Object value, ConstraintValidatorContext context) { + if (value == null) { + if (!getAnnotation().nullable()) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(getAnnotation().message()) + .addConstraintViolation(); + return false; + } + return true; + } else { + final int size = getLenght(value); + + if (!(size <= getAnnotation().max() && size >= getAnnotation().min())) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(getAnnotation().message()) + .addConstraintViolation(); + return false; + } + return true; + } + } + + @Override + protected String[] getIgnoreForClients() { + return this.getAnnotation().ignoreForClients(); + } + + protected int getLenght(final Object value) { + if (value instanceof Collection) { + return ((Collection) value).size(); + } + + if (value.getClass().isArray()) { + return Array.getLength(value); + } + + if (value instanceof Map) { + return ((Map) value).size(); + } + + if (value instanceof CharSequence) { + return ((CharSequence) value).length(); + } + + throw new IllegalArgumentException("Tipo de dado não tratado "); + } +} diff --git a/muttley-validators/src/main/resources/application.properties b/muttley-validators/src/main/resources/application.properties new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/muttley-validators/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/muttley-validators/src/test/java/br/com/muttley/validators/MuttleyValidatorsApplicationTests.java b/muttley-validators/src/test/java/br/com/muttley/validators/MuttleyValidatorsApplicationTests.java new file mode 100644 index 00000000..9090f2c9 --- /dev/null +++ b/muttley-validators/src/test/java/br/com/muttley/validators/MuttleyValidatorsApplicationTests.java @@ -0,0 +1,11 @@ +package br.com.muttley.validators; + +import org.junit.Test; + +public class MuttleyValidatorsApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/muttley-zuul/pom.xml b/muttley-zuul/pom.xml index 54ad79c6..42973c6e 100644 --- a/muttley-zuul/pom.xml +++ b/muttley-zuul/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} 4.0.0 jar diff --git a/pom.xml b/pom.xml index 536bf372..47fb0071 100644 --- a/pom.xml +++ b/pom.xml @@ -1,17 +1,17 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> org.springframework.boot spring-boot-starter-parent - 1.5.14.RELEASE + 1.5.22.RELEASE 4.0.0 br.com.muttley muttley-cloud - 0.0.2-SNAPSHOT + ${revision} pom muttley-cloud @@ -22,24 +22,39 @@ UTF-8 UTF-8 1.8 + 0.0.3-SNAPSHOT + 1.2.5 Edgware.SR3 1.9.3 + 3.10 + 6.20.6 + 3.4.0 + 1.70 + muttley-admin-server.pom muttley-security-server muttley-config-server muttley-discovery-server muttley-exception muttley-model + muttley-utils muttley-redis muttley-security muttley-mongo + muttley-headers + muttley-notification + muttley-hermes-server.pom muttley-rest muttley-domain-service muttley-jackson muttley-feign + muttley-files muttley-zuul + muttley-report + muttley-local-cache + muttley-validators @@ -64,82 +79,160 @@ + + br.com.muttley + muttley-admin-server.pom + ${revision} + + + + br.com.muttley + muttley-admin-server + ${revision} + + br.com.muttley muttley-security-server - 0.0.2-SNAPSHOT + ${revision} br.com.muttley muttley-config-server - 0.0.2-SNAPSHOT + ${revision} + + + + br.com.muttley + muttley-hermes-server.pom + ${revision} + + + + br.com.muttley + muttley-hermes-server + ${revision} + + + + br.com.muttley + muttley-hermes-api + ${revision} br.com.muttley muttley-discovery-server - 0.0.2-SNAPSHOT + ${revision} br.com.muttley muttley-exception - 0.0.2-SNAPSHOT + ${revision} + + + + br.com.muttley + muttley-report + ${revision} br.com.muttley muttley-model - 0.0.2-SNAPSHOT + ${revision} + + + + br.com.muttley + muttley-utils + ${revision} br.com.muttley muttley-redis - 0.0.2-SNAPSHOT + ${revision} br.com.muttley muttley-security - 0.0.2-SNAPSHOT + ${revision} br.com.muttley muttley-mongo - 0.0.2-SNAPSHOT + ${revision} + + + + br.com.muttley + muttley-headers + ${revision} + + + + br.com.muttley + muttley-notification + ${revision} br.com.muttley muttley-rest - 0.0.2-SNAPSHOT + ${revision} br.com.muttley muttley-domain-service - 0.0.2-SNAPSHOT + ${revision} br.com.muttley muttley-jackson - 0.0.2-SNAPSHOT + ${revision} br.com.muttley muttley-feign + ${revision} + + + + br.com.muttley + muttley-files 0.0.2-SNAPSHOT br.com.muttley muttley-zuul - 0.0.2-SNAPSHOT + ${revision} + + + + br.com.muttley + muttley-local-cache + ${revision} + + + + br.com.muttley + muttley-validators + ${revision} + + + + org.bouncycastle + bcprov-jdk15on + ${bouncycastle.version} @@ -148,6 +241,38 @@ ${commons-beanutils.version} + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + + net.sf.jasperreports + jasperreports + ${jasperreports.version} + + + + net.sf.jasperreports + jasperreports-fonts + ${jasperreports.version} + + + + + com.google.zxing + core + ${zxing-parent.version} + + + + com.google.zxing + javase + ${zxing-parent.version} + + + org.springframework.cloud spring-cloud-dependencies @@ -157,4 +282,34 @@ + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten.version} + + true + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + +