From 06e5cf978231e49e6cd361e81a8b67fba11c4313 Mon Sep 17 00:00:00 2001 From: Weinan Qiu Date: Wed, 2 Dec 2015 14:33:15 -0500 Subject: [PATCH] initial check in (model, tokenizer) --- .gitignore | 80 ++++++ pom.xml | 12 + scim/.mvn/wrapper/maven-wrapper.properties | 1 + scim/mvnw | 233 ++++++++++++++++++ scim/mvnw.cmd | 145 +++++++++++ scim/pom.xml | 92 +++++++ .../identity/scim/evaluation/PathToken.java | 8 + .../scim/evaluation/PathTokenFactory.java | 8 + .../identity/scim/evaluation/Tokenizer.java | 175 +++++++++++++ .../scim/resource/constant/ScimConstants.java | 68 +++++ .../identity/scim/resource/core/Meta.groovy | 35 +++ .../scim/resource/core/Resource.groovy | 24 ++ .../identity/scim/resource/group/Group.groovy | 39 +++ .../scim/resource/misc/ResourceType.groovy | 48 ++++ .../identity/scim/resource/misc/Schema.groovy | 52 ++++ .../misc/ServiceProviderConfig.groovy | 99 ++++++++ .../identity/scim/resource/user/User.groovy | 184 ++++++++++++++ .../scim/utils/JsonDateSerializer.java | 25 ++ .../scim/utils/ScimConstantUtils.java | 48 ++++ .../kumiq/identity/scim/utils/TypeUtils.java | 65 +++++ .../src/main/resources/application.properties | 0 .../scim/evaluation/TokenizerTests.groovy | 56 +++++ 22 files changed, 1497 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 scim/.mvn/wrapper/maven-wrapper.properties create mode 100644 scim/mvnw create mode 100644 scim/mvnw.cmd create mode 100644 scim/pom.xml create mode 100644 scim/src/main/java/com/kumiq/identity/scim/evaluation/PathToken.java create mode 100644 scim/src/main/java/com/kumiq/identity/scim/evaluation/PathTokenFactory.java create mode 100644 scim/src/main/java/com/kumiq/identity/scim/evaluation/Tokenizer.java create mode 100644 scim/src/main/java/com/kumiq/identity/scim/resource/constant/ScimConstants.java create mode 100644 scim/src/main/java/com/kumiq/identity/scim/resource/core/Meta.groovy create mode 100644 scim/src/main/java/com/kumiq/identity/scim/resource/core/Resource.groovy create mode 100644 scim/src/main/java/com/kumiq/identity/scim/resource/group/Group.groovy create mode 100644 scim/src/main/java/com/kumiq/identity/scim/resource/misc/ResourceType.groovy create mode 100644 scim/src/main/java/com/kumiq/identity/scim/resource/misc/Schema.groovy create mode 100644 scim/src/main/java/com/kumiq/identity/scim/resource/misc/ServiceProviderConfig.groovy create mode 100644 scim/src/main/java/com/kumiq/identity/scim/resource/user/User.groovy create mode 100644 scim/src/main/java/com/kumiq/identity/scim/utils/JsonDateSerializer.java create mode 100644 scim/src/main/java/com/kumiq/identity/scim/utils/ScimConstantUtils.java create mode 100644 scim/src/main/java/com/kumiq/identity/scim/utils/TypeUtils.java create mode 100644 scim/src/main/resources/application.properties create mode 100644 scim/src/test/java/com/kumiq/identity/scim/evaluation/TokenizerTests.groovy diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..115bb7f --- /dev/null +++ b/.gitignore @@ -0,0 +1,80 @@ + +# Created by https://www.gitignore.io/api/java,maven,intellij + +### Java ### +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries +# .idea/shelf + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..454907a --- /dev/null +++ b/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + com.kumiq + identity + 1.0.0 + + + \ No newline at end of file diff --git a/scim/.mvn/wrapper/maven-wrapper.properties b/scim/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..eb91947 --- /dev/null +++ b/scim/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip \ No newline at end of file diff --git a/scim/mvnw b/scim/mvnw new file mode 100644 index 0000000..a1ba1bf --- /dev/null +++ b/scim/mvnw @@ -0,0 +1,233 @@ +#!/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 + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + 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 + +# 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"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# 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} "$@" diff --git a/scim/mvnw.cmd b/scim/mvnw.cmd new file mode 100644 index 0000000..2b934e8 --- /dev/null +++ b/scim/mvnw.cmd @@ -0,0 +1,145 @@ +@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 + +set MAVEN_CMD_LINE_ARGS=%* + +@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="".\.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_CMD_LINE_ARGS% +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% \ No newline at end of file diff --git a/scim/pom.xml b/scim/pom.xml new file mode 100644 index 0000000..de48dc3 --- /dev/null +++ b/scim/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + com.kumiq.identity + scim + 1.0.0 + jar + + scim + SCIM component for Kumiq Identity + + + org.springframework.boot + spring-boot-starter-parent + 1.2.7.RELEASE + + + + + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.codehaus.groovy + groovy + + + com.fasterxml.jackson.core + jackson-databind + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.hamcrest + hamcrest-all + 1.3 + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + maven-compiler-plugin + + groovy-eclipse-compiler + + + + org.codehaus.groovy + groovy-eclipse-compiler + 2.9.1-01 + + + org.codehaus.groovy + groovy-eclipse-batch + 2.3.7-01 + + + + + org.codehaus.groovy + groovy-eclipse-compiler + 2.9.1-01 + true + + + + + + diff --git a/scim/src/main/java/com/kumiq/identity/scim/evaluation/PathToken.java b/scim/src/main/java/com/kumiq/identity/scim/evaluation/PathToken.java new file mode 100644 index 0000000..21f18c1 --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/evaluation/PathToken.java @@ -0,0 +1,8 @@ +package com.kumiq.identity.scim.evaluation; + +/** + * @author Weinan Qiu + * @since 1.0.0 + */ +public abstract class PathToken { +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/evaluation/PathTokenFactory.java b/scim/src/main/java/com/kumiq/identity/scim/evaluation/PathTokenFactory.java new file mode 100644 index 0000000..38aea47 --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/evaluation/PathTokenFactory.java @@ -0,0 +1,8 @@ +package com.kumiq.identity.scim.evaluation; + +/** + * @author Weinan Qiu + * @since 1.0.0 + */ +public class PathTokenFactory { +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/evaluation/Tokenizer.java b/scim/src/main/java/com/kumiq/identity/scim/evaluation/Tokenizer.java new file mode 100644 index 0000000..ce6aaa0 --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/evaluation/Tokenizer.java @@ -0,0 +1,175 @@ +package com.kumiq.identity.scim.evaluation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.kumiq.identity.scim.resource.constant.ScimConstants.*; + +/** + * Generic tokenizer providing functionality for {@link com.kumiq.identity.scim.evaluation.Tokenizer.PathTokenizer} + * + * + * Subclass can call {@code nextSequence} to obtain the next token and call {@link i} + * + * @author Weinan Qiu + * @since 1.0.0 + */ +public abstract class Tokenizer { + + private static final String PERIOD = "."; + private static final String SPACE = " "; + + private final CharSequence charSequence; + private List delimiters; + private List keywordTokens; + private int cursorStartPos; + private int cursorEndPos; + private boolean lastMatchWasDelimiter = true; + private boolean sequenceHasExhausted = false; + + protected Tokenizer(CharSequence charSequence) { + this.charSequence = charSequence; + this.delimiters = Arrays.asList(PERIOD); + this.keywordTokens = new ArrayList<>(); + this.cursorEndPos = 0; + syncStartPosition(); + } + + public CharSequence nextSequence() throws NoMoreSequenceException { + if (sequenceHasExhausted) { + throw new NoMoreSequenceException(); + } + + try { + while (isEndPosInBounds()) { + CharSequence current = currentSubSequence(); + + for (CharSequence delimiter : delimiters) { + if (currentMatches(delimiter)) { + lastMatchWasDelimiter = true; + incrementPosition(delimiter.length()); + syncStartPosition(); + + if (current.length() > 0) + return current; + else + continue; + } + } + + for (CharSequence keywordToken : keywordTokens) { + if ((currentMatches(keywordToken) && lastMatchWasDelimiter) || + keywordToken.equals(current)) { + lastMatchWasDelimiter = false; + syncStartPosition(); + + if (current.length() > 0) + return current; + else + continue; + } + } + + incrementPosition(1); + } + + sequenceHasExhausted = true; + CharSequence remaining = this.charSequence.subSequence(this.cursorStartPos, this.charSequence.length()); + if (remaining.length() > 0) + return remaining; + else + throw new NoMoreSequenceException(); + } catch (IndexOutOfBoundsException ex) { + throw new NoMoreSequenceException(); + } + } + + public void syncStartPosition() { + this.cursorStartPos = this.cursorEndPos; + } + + public int incrementPosition(int count) { + this.cursorEndPos += count; + return this.cursorEndPos; + } + + public CharSequence currentSubSequence() { + return this.charSequence.subSequence(this.cursorStartPos, this.cursorEndPos); + } + + public boolean currentMatches(CharSequence symbol) { + if (!inBounds(this.cursorEndPos + symbol.length())) { + return false; + } + + return symbol.equals(this.charSequence.subSequence(this.cursorEndPos, this.cursorEndPos + symbol.length())); + } + + public boolean isEndPosInBounds() { + return inBounds(this.cursorEndPos); + } + + public boolean inBounds(int index) { + return (index >= 0) && (index <= this.charSequence.length()); + } + + public List getDelimiters() { + return delimiters; + } + + public void setDelimiters(List delimiters) { + this.delimiters = delimiters; + } + + public CharSequence getCharSequence() { + return charSequence; + } + + public List getKeywordTokens() { + return keywordTokens; + } + + public void setKeywordTokens(List keywordTokens) { + this.keywordTokens = keywordTokens; + } + + /** + * Tokenizer for SCIM paths. For example, path like name.firstName will be tokenized + * to name and firstName + * + */ + public static class PathTokenizer extends Tokenizer { + + public PathTokenizer(CharSequence charSequence) { + super(charSequence); + super.setDelimiters(Arrays.asList(PERIOD)); + } + } + + /** + * Tokenizer for SCIM filters. For example, (value eq 100) and (name sw "A"). + */ + public static class FilterTokenizer extends Tokenizer { + + public static final String LEFT_BRACKET = "("; + public static final String RIGHT_BRACKET = ")"; + public static final String LEFT_SQUARE_BRACKET = "["; + public static final String RIGHT_SQUARE_BRACKET = "]"; + + public FilterTokenizer(CharSequence charSequence) { + super(charSequence); + super.setDelimiters(Arrays.asList(SPACE, LEFT_SQUARE_BRACKET, RIGHT_SQUARE_BRACKET)); + super.setKeywordTokens(Arrays.asList( + LEFT_BRACKET, + RIGHT_BRACKET + )); + } + } + + /** + * Placeholder exception thrown when token sequence is exhausted. + */ + public static class NoMoreSequenceException extends Exception { + } +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/resource/constant/ScimConstants.java b/scim/src/main/java/com/kumiq/identity/scim/resource/constant/ScimConstants.java new file mode 100644 index 0000000..f07771c --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/resource/constant/ScimConstants.java @@ -0,0 +1,68 @@ +package com.kumiq.identity.scim.resource.constant; + +/** + * @author Weinan Qiu + * @since 0.1.0 + */ +public class ScimConstants { + + public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + + public static final String RESOURCE_TYPE_USER = "User"; + public static final String RESOURCE_TYPE_GROUP = "Group"; + public static final String RESOURCE_TYPE_SERVICE_PROVIDER_CONFIG = "ServiceProviderConfig"; + public static final String RESOURCE_TYPE_RESOURCE_TYPE = "ResourceType"; + public static final String RESOURCE_TYPE_SCHEMA = "Schema"; + + public static final String URN_USER = "urn:ietf:params:scim:schemas:core:2.0:User"; + public static final String URN_ENTERPRISE_USER_EXTENSION = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"; + public static final String URN_GROUP = "urn:ietf:params:scim:schemas:core:2.0:Group"; + public static final String URN_SERVICE_PROVIDER_CONFIG = "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"; + public static final String URN_RESOURCE_TYPE = "urn:ietf:params:scim:schemas:core:2.0:ResourceType"; + public static final String URN_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Schema"; + + public static final String ATTR_TYPES_STRING = "string"; + public static final String ATTR_TYPES_BOOLEAN = "boolean"; + public static final String ATTR_TYPES_DECIMAL = "decimal"; + public static final String ATTR_TYPES_INTEGER = "integer"; + public static final String ATTR_TYPES_DATETIME = "datetime"; + public static final String ATTR_TYPES_REFERENCE = "reference"; + public static final String ATTR_TYPES_COMPLEX = "complex"; + + public static final String MUTABILITY_READONLY = "readOnly"; + public static final String MUTABILITY_READWRITE = "readWrite"; + public static final String MUTABILITY_IMMUTABLE = "immutable"; + public static final String MUTABILITY_WRITEONLY = "writeOnly"; + + public static final String RETURNED_ALWAYS = "always"; + public static final String RETURNED_NEVER = "never"; + public static final String RETURNED_DEFAULT = "default"; + public static final String RETURNED_REQUEST = "request"; + + public static final String UNIQUENESS_NONE = "none"; + public static final String UNIQUENESS_SERVER = "server"; + public static final String UNIQUENESS_GLOBAL = "global"; + + public static final String REF_TYPE_SCIM = "scim"; + public static final String REF_TYPE_EXTERNAL = "external"; + public static final String REF_TYPE_URI = "uri"; + public static final String REF_TYPE_USER = "User"; + public static final String REF_TYPE_GROUP = "Group"; + + public static final String FILTER_AND = "and"; + public static final String FILTER_OR = "or"; + public static final String FILTER_NOT = "not"; + public static final String FILTER_EQUAL = "eq"; + public static final String FILTER_NOT_EQUAL = "ne"; + public static final String FILTER_CONTAINS = "co"; + public static final String FILTER_STARTS_WITH= "sw"; + public static final String FILTER_ENDS_WITH = "ew"; + public static final String FILTER_PRESENT = "pr"; + public static final String FILTER_GREATER_THAN = "gt"; + public static final String FILTER_GREATER_EQUAL = "ge"; + public static final String FILTER_LESS_THAN = "lt"; + public static final String FILTER_LESS_EQUAL = "le"; + + + +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/resource/core/Meta.groovy b/scim/src/main/java/com/kumiq/identity/scim/resource/core/Meta.groovy new file mode 100644 index 0000000..441817c --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/resource/core/Meta.groovy @@ -0,0 +1,35 @@ +package com.kumiq.identity.scim.resource.core + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.annotation.JsonSerialize +import com.kumiq.identity.scim.utils.JsonDateSerializer +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString + +/** + * Meta section of core schema + * + * @author Weinan Qiu + * @since 1.0.0 + */ +@ToString +@EqualsAndHashCode +class Meta { + + @JsonProperty('resourceType') + String resourceType + + @JsonProperty('created') + @JsonSerialize(using = JsonDateSerializer) + Date created + + @JsonProperty('lastModified') + @JsonSerialize(using = JsonDateSerializer) + Date lastModified + + @JsonProperty('location') + String location + + @JsonProperty('version') + Long version +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/resource/core/Resource.groovy b/scim/src/main/java/com/kumiq/identity/scim/resource/core/Resource.groovy new file mode 100644 index 0000000..817a1ed --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/resource/core/Resource.groovy @@ -0,0 +1,24 @@ +package com.kumiq.identity.scim.resource.core + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * Base for all SCIM resource, contains elements in core schema. + * + * @author Weinan Qiu + * @since 0.1.0 + */ +abstract class Resource { + + @JsonProperty('schemas') + List schemas = [] + + @JsonProperty('id') + String id + + @JsonProperty('externalId') + String externalId + + @JsonProperty('meta') + Meta meta +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/resource/group/Group.groovy b/scim/src/main/java/com/kumiq/identity/scim/resource/group/Group.groovy new file mode 100644 index 0000000..985859a --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/resource/group/Group.groovy @@ -0,0 +1,39 @@ +package com.kumiq.identity.scim.resource.group + +import com.fasterxml.jackson.annotation.JsonProperty +import com.kumiq.identity.scim.resource.core.Meta +import com.kumiq.identity.scim.resource.core.Resource +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString + +import static com.kumiq.identity.scim.resource.constant.ScimConstants.* + +/** + * Group resource + * + * @author Weinan Qiu + * @since 1.0.0 + */ +@ToString +@EqualsAndHashCode +class Group extends Resource { + + Group() { + this.schemas = [URN_GROUP] + this.meta = new Meta(resourceType: RESOURCE_TYPE_GROUP) + } + + @JsonProperty('displayName') + String displayName + + @JsonProperty('members') + List members = [] + + @ToString + @EqualsAndHashCode + static class Member { + @JsonProperty('value') String value + @JsonProperty('type') String type + @JsonProperty('$ref') String $ref + } +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/resource/misc/ResourceType.groovy b/scim/src/main/java/com/kumiq/identity/scim/resource/misc/ResourceType.groovy new file mode 100644 index 0000000..11c1f6b --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/resource/misc/ResourceType.groovy @@ -0,0 +1,48 @@ +package com.kumiq.identity.scim.resource.misc + +import com.fasterxml.jackson.annotation.JsonProperty +import com.kumiq.identity.scim.resource.core.Meta +import com.kumiq.identity.scim.resource.core.Resource +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString + +import static com.kumiq.identity.scim.resource.constant.ScimConstants.RESOURCE_TYPE_RESOURCE_TYPE +import static com.kumiq.identity.scim.resource.constant.ScimConstants.URN_RESOURCE_TYPE + +/** + * Resource type resource + * + * @author Weinan Qiu + * @since 1.0.0 + */ +@ToString +@EqualsAndHashCode +final class ResourceType extends Resource { + + ResourceType() { + this.schemas = [URN_RESOURCE_TYPE] + this.meta = new Meta(resourceType: RESOURCE_TYPE_RESOURCE_TYPE) + } + + @JsonProperty('name') + String name + + @JsonProperty('description') + String description + + @JsonProperty('endpoint') + String endpoint + + @JsonProperty('schema') + String schema + + @JsonProperty('schemaExtensions') + List schemaExtensions = [] + + @ToString + @EqualsAndHashCode + static class SchemaExtension { + @JsonProperty('schema') String schema + @JsonProperty('required') boolean required + } +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/resource/misc/Schema.groovy b/scim/src/main/java/com/kumiq/identity/scim/resource/misc/Schema.groovy new file mode 100644 index 0000000..bc4ae51 --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/resource/misc/Schema.groovy @@ -0,0 +1,52 @@ +package com.kumiq.identity.scim.resource.misc + +import com.fasterxml.jackson.annotation.JsonProperty +import com.kumiq.identity.scim.resource.core.Meta +import com.kumiq.identity.scim.resource.core.Resource +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString + +import static com.kumiq.identity.scim.resource.constant.ScimConstants.RESOURCE_TYPE_SCHEMA +import static com.kumiq.identity.scim.resource.constant.ScimConstants.URN_SCHEMA + +/** + * Schema resource + * + * @author Weinan Qiu + * @since 1.0.0 + */ +@ToString +@EqualsAndHashCode +final class Schema extends Resource { + + Schema() { + this.schemas = [URN_SCHEMA] + this.meta = new Meta(resourceType: RESOURCE_TYPE_SCHEMA) + } + + @JsonProperty('name') + String name + + @JsonProperty('description') + String description + + @JsonProperty('attributes') + List attributes = [] + + @ToString + @EqualsAndHashCode + static class Attribute { + @JsonProperty('name') String name + @JsonProperty('description') String description + @JsonProperty('type') String type + @JsonProperty('mutability') String mutability + @JsonProperty('returned') String returned + @JsonProperty('uniqueness') String uniqueness + @JsonProperty('required') boolean required + @JsonProperty('multiValued') boolean multiValued + @JsonProperty('caseExact') boolean caseExact + @JsonProperty('canonicalValues') List canonicalValues = [] + @JsonProperty('referenceTypes') List referenceTypes = [] + @JsonProperty('subAttributes') List subAttributes = [] + } +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/resource/misc/ServiceProviderConfig.groovy b/scim/src/main/java/com/kumiq/identity/scim/resource/misc/ServiceProviderConfig.groovy new file mode 100644 index 0000000..95ab066 --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/resource/misc/ServiceProviderConfig.groovy @@ -0,0 +1,99 @@ +package com.kumiq.identity.scim.resource.misc + +import com.fasterxml.jackson.annotation.JsonProperty +import com.kumiq.identity.scim.resource.core.Meta +import com.kumiq.identity.scim.resource.core.Resource +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString + +import static com.kumiq.identity.scim.resource.constant.ScimConstants.RESOURCE_TYPE_SERVICE_PROVIDER_CONFIG +import static com.kumiq.identity.scim.resource.constant.ScimConstants.URN_SERVICE_PROVIDER_CONFIG + +/** + * Service provider configuration + * + * @author Weinan Qiu + * @since 1.0.0 + */ +@ToString +@EqualsAndHashCode +final class ServiceProviderConfig extends Resource { + + static final String SINGLETON_ID = 'ServiceProviderConfig' + + ServiceProviderConfig() { + this.schemas = [URN_SERVICE_PROVIDER_CONFIG] + this.meta = new Meta(resourceType: RESOURCE_TYPE_SERVICE_PROVIDER_CONFIG) + } + + @JsonProperty('documentationUri') + String documentationUri + + @JsonProperty('patch') + Patch patch + + @JsonProperty('bulk') + Bulk bulk + + @JsonProperty('filter') + Filter filter + + @JsonProperty('changePassword') + ChangePassword changePassword + + @JsonProperty('sort') + Sort sort + + @JsonProperty('authenticationSchemes') + List authenticationSchemes = [] + + @ToString + @EqualsAndHashCode + static class Patch { + @JsonProperty('supported') boolean supported + } + + @ToString + @EqualsAndHashCode + static class Bulk { + @JsonProperty('supported') boolean supported + @JsonProperty('maxOperations') int maxOperations + @JsonProperty('maxPayloadSize') int maxPayloadSize + } + + @ToString + @EqualsAndHashCode + static class Filter { + @JsonProperty('supported') boolean supported + @JsonProperty('maxResults') int maxResults + } + + @ToString + @EqualsAndHashCode + static class ChangePassword { + @JsonProperty('supported') boolean supported + } + + @ToString + @EqualsAndHashCode + static class Sort { + @JsonProperty('supported') boolean supported + } + + @ToString + @EqualsAndHashCode + static class ETag { + @JsonProperty('supported') boolean supported + } + + @ToString + @EqualsAndHashCode + static class AuthenticationScheme { + @JsonProperty('type') String type + @JsonProperty('name') String name + @JsonProperty('description') String description + @JsonProperty('specUri') String specUri + @JsonProperty('documentationUri') String documentationUri + @JsonProperty('primary') boolean primary + } +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/resource/user/User.groovy b/scim/src/main/java/com/kumiq/identity/scim/resource/user/User.groovy new file mode 100644 index 0000000..a8819b3 --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/resource/user/User.groovy @@ -0,0 +1,184 @@ +package com.kumiq.identity.scim.resource.user + +import com.fasterxml.jackson.annotation.JsonProperty +import com.kumiq.identity.scim.resource.core.Meta +import com.kumiq.identity.scim.resource.core.Resource +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString + +import static com.kumiq.identity.scim.resource.constant.ScimConstants.RESOURCE_TYPE_USER +import static com.kumiq.identity.scim.resource.constant.ScimConstants.URN_USER + +/** + * User resource + * + * @author Weinan Qiu + * @since 1.0.0 + */ +@ToString +@EqualsAndHashCode +class User extends Resource { + + User() { + this.schemas = [URN_USER] + this.meta = new Meta(resourceType: RESOURCE_TYPE_USER) + } + + @JsonProperty('userName') + String userName + + @JsonProperty('name') + Name name + + @JsonProperty('displayName') + String displayName + + @JsonProperty('nickName') + String nickName + + @JsonProperty('profileUrl') + String profileUrl + + @JsonProperty('title') + String title + + @JsonProperty('userType') + String userType + + @JsonProperty('preferredLanguage') + String preferredLanguage + + @JsonProperty('locale') + String locale + + @JsonProperty('timezone') + String timezone + + @JsonProperty('active') + boolean active + + @JsonProperty('password') + String password + + @JsonProperty('emails') + List emails = [] + + @JsonProperty('phoneNumbers') + List phoneNumbers = [] + + @JsonProperty('ims') + List ims = [] + + @JsonProperty('photos') + List photos = [] + + @JsonProperty('addresses') + List
addresses = [] + + @JsonProperty('groups') + List groups = [] + + @JsonProperty('entitlements') + List entitlements = [] + + @JsonProperty('roles') + List roles = [] + + @JsonProperty('x509Certificates') + List x509Certificates = [] + + @ToString + @EqualsAndHashCode + static class Name { + @JsonProperty('formatted') String formatted + @JsonProperty('familyName') String familyName + @JsonProperty('givenName') String givenName + @JsonProperty('middleName') String middleName + @JsonProperty('honorificPrefix') String honorificPrefix + @JsonProperty('honorificSuffix') String honorificSuffix + } + + @ToString + @EqualsAndHashCode + static class Email { + @JsonProperty('value') String value + @JsonProperty('display') String display + @JsonProperty('type') String type + @JsonProperty('primary') boolean primary + } + + @ToString + @EqualsAndHashCode + static class PhoneNumber { + @JsonProperty('value') String value + @JsonProperty('display') String display + @JsonProperty('type') String type + @JsonProperty('primary') boolean primary + } + + @ToString + @EqualsAndHashCode + static class IMS { + @JsonProperty('value') String value + @JsonProperty('display') String display + @JsonProperty('type') String type + @JsonProperty('primary') boolean primary + } + + @ToString + @EqualsAndHashCode + static class Photo { + @JsonProperty('value') String value + @JsonProperty('type') String type + @JsonProperty('primary') boolean primary + } + + @ToString + @EqualsAndHashCode + static class Address { + @JsonProperty('formatted') String formatted + @JsonProperty('streetAddress') String streetAddress + @JsonProperty('locality') String locality + @JsonProperty('region') String region + @JsonProperty('postalCode') String postalCode + @JsonProperty('country') String country + @JsonProperty('type') String type + @JsonProperty('primary') boolean primary + } + + @ToString + @EqualsAndHashCode + static class Group { + @JsonProperty('value') String value + @JsonProperty('display') String display + @JsonProperty('$ref') String $ref + @JsonProperty('type') String type + } + + @ToString + @EqualsAndHashCode + static class Entitlement { + @JsonProperty('value') String value + @JsonProperty('display') String display + @JsonProperty('type') String type + @JsonProperty('primary') boolean primary + } + + @ToString + @EqualsAndHashCode + static class Role { + @JsonProperty('value') String value + @JsonProperty('display') String display + @JsonProperty('type') String type + @JsonProperty('primary') boolean primary + } + + @ToString + @EqualsAndHashCode + static class X509Certificate { + @JsonProperty('value') String value + @JsonProperty('display') String display + @JsonProperty('type') String type + @JsonProperty('primary') boolean primary + } +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/utils/JsonDateSerializer.java b/scim/src/main/java/com/kumiq/identity/scim/utils/JsonDateSerializer.java new file mode 100644 index 0000000..d4123fd --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/utils/JsonDateSerializer.java @@ -0,0 +1,25 @@ +package com.kumiq.identity.scim.utils; + +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 com.kumiq.identity.scim.resource.constant.ScimConstants; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @author Weinan Qiu + * @since 0.1.0 + */ +public class JsonDateSerializer extends JsonSerializer { + + private static final SimpleDateFormat dateFormat = new SimpleDateFormat(ScimConstants.DATE_FORMAT); + + @Override + public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeString(dateFormat.format(value)); + } +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/utils/ScimConstantUtils.java b/scim/src/main/java/com/kumiq/identity/scim/utils/ScimConstantUtils.java new file mode 100644 index 0000000..5768e88 --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/utils/ScimConstantUtils.java @@ -0,0 +1,48 @@ +package com.kumiq.identity.scim.utils; + +import static com.kumiq.identity.scim.resource.constant.ScimConstants.*; + +/** + * @author Weinan Qiu + * @since 1.0.0 + */ +public class ScimConstantUtils { + + public static boolean isValidAttributeType(String value) { + return ATTR_TYPES_STRING.equals(value) || + ATTR_TYPES_BOOLEAN.equals(value) || + ATTR_TYPES_DECIMAL.equals(value) || + ATTR_TYPES_INTEGER.equals(value) || + ATTR_TYPES_DATETIME.equals(value) || + ATTR_TYPES_REFERENCE.equals(value) || + ATTR_TYPES_COMPLEX.equals(value); + } + + public static boolean isValidMutability(String value) { + return MUTABILITY_READONLY.equals(value) || + MUTABILITY_READWRITE.equals(value) || + MUTABILITY_IMMUTABLE.equals(value) || + MUTABILITY_WRITEONLY.equals(value); + } + + public static boolean isValidReturned(String value) { + return RETURNED_ALWAYS.equals(value) || + RETURNED_DEFAULT.equals(value) || + RETURNED_NEVER.equals(value) || + RETURNED_REQUEST.equals(value); + } + + public static boolean isValidUniqueness(String value) { + return UNIQUENESS_NONE.equals(value) || + UNIQUENESS_GLOBAL.equals(value) || + UNIQUENESS_SERVER.equals(value); + } + + public static boolean isValidReferenceType(String value) { + return REF_TYPE_SCIM.equals(value) || + REF_TYPE_EXTERNAL.equals(value) || + REF_TYPE_URI.equals(value) || + REF_TYPE_USER.equals(value) || + REF_TYPE_GROUP.equals(value); + } +} diff --git a/scim/src/main/java/com/kumiq/identity/scim/utils/TypeUtils.java b/scim/src/main/java/com/kumiq/identity/scim/utils/TypeUtils.java new file mode 100644 index 0000000..670acef --- /dev/null +++ b/scim/src/main/java/com/kumiq/identity/scim/utils/TypeUtils.java @@ -0,0 +1,65 @@ +package com.kumiq.identity.scim.utils; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * @author Weinan Qiu + * @since 1.0.0 + */ +public class TypeUtils { + + public static boolean isInteger(Object object) { + return object instanceof Integer; + } + + public static boolean isDecimal(Object object) { + return (object instanceof Float) || + (object instanceof Double) || + (object instanceof BigDecimal); + } + + public static boolean isBoolean(Object object) { + return object instanceof Boolean; + } + + public static boolean isString(Object object) { + return object instanceof String; + } + + public static boolean isDate(Object object) { + return object instanceof Date; + } + + public static boolean isComplex(Object object) { + return !isInteger(object) && + !isDecimal(object) && + !isBoolean(object) && + !isString(object) && + !isDate(object); + } + + public static Integer asInteger(Object object) { + if (!isInteger(object)) + throw new IllegalArgumentException(object.toString() + " cannot be cast to integer"); + return (Integer) object; + } + + public static Boolean asBoolean(Object object) { + if (!isBoolean(object)) + throw new IllegalArgumentException(object.toString() + " cannot be cast to boolean"); + return (Boolean) object; + } + + public static String asString(Object object) { + if (!isString(object)) + throw new IllegalArgumentException(object.toString() + " cannot be cast to string"); + return (String) object; + } + + public static Date asDate(Object object) { + if (!isDate(object)) + throw new IllegalArgumentException(object.toString() + " cannot be cast to date"); + return (Date) object; + } +} diff --git a/scim/src/main/resources/application.properties b/scim/src/main/resources/application.properties new file mode 100644 index 0000000..e69de29 diff --git a/scim/src/test/java/com/kumiq/identity/scim/evaluation/TokenizerTests.groovy b/scim/src/test/java/com/kumiq/identity/scim/evaluation/TokenizerTests.groovy new file mode 100644 index 0000000..ee0941f --- /dev/null +++ b/scim/src/test/java/com/kumiq/identity/scim/evaluation/TokenizerTests.groovy @@ -0,0 +1,56 @@ +package com.kumiq.identity.scim.evaluation + +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** + * + * + * @author Weinan Qiu + * @since 1.0.0 + */ +@RunWith(JUnit4) +class TokenizerTests { + + @Test + void testPathTokenizer() { + Tokenizer tokenizer = new Tokenizer.PathTokenizer('foo.bar[x eq 100].foobar') + + Assert.assertEquals('foo', tokenizer.nextSequence()) + Assert.assertEquals('bar[x eq 100]', tokenizer.nextSequence()) + Assert.assertEquals('foobar', tokenizer.nextSequence()) + + try { + tokenizer.nextSequence() + Assert.fail('tokenizer should have failed after being exhausted') + } catch (Exception ex) { + Assert.assertTrue(ex instanceof Tokenizer.NoMoreSequenceException) + } + } + + @Test + void testFilterTokenizer() { + Tokenizer tokenizer = new Tokenizer.FilterTokenizer('[(value eq 100) and (name sw "A")]'); + + Assert.assertEquals('(', tokenizer.nextSequence()) + Assert.assertEquals('value', tokenizer.nextSequence()) + Assert.assertEquals('eq', tokenizer.nextSequence()) + Assert.assertEquals('100', tokenizer.nextSequence()) + Assert.assertEquals(')', tokenizer.nextSequence()) + Assert.assertEquals('and', tokenizer.nextSequence()) + Assert.assertEquals('(', tokenizer.nextSequence()) + Assert.assertEquals('name', tokenizer.nextSequence()) + Assert.assertEquals('sw', tokenizer.nextSequence()) + Assert.assertEquals('"A"', tokenizer.nextSequence()) + Assert.assertEquals(')', tokenizer.nextSequence()) + + try { + tokenizer.nextSequence() + Assert.fail('tokenizer should have failed after being exhausted') + } catch (Exception ex) { + Assert.assertTrue(ex instanceof Tokenizer.NoMoreSequenceException) + } + } +}