commit 79529ecc409a727533a2cac5c4be735c718c8c73 Author: Isaac Parenteau Date: Sat Jul 20 12:39:03 2019 -0500 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf49b86 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +.classpath +.idea +.project +.settings +filerepo_client/dist/ +filerepo_client/nbproject/private/ +filerepo_client/node/ +filerepo_client/node_modules/ +filerepo_client/test_out/ +filerepo_client/temp/ +filerepo_client/app/compiled-less +filerepo_webapp/.externalToolBuilders/ +npm-debug.log +phantomjs +tags +*.DS_Store +*/bin/ +**/.classpath +**/.settings +**/*.swp +**/target +**/.tern-project +**/*.iml +**/git.properties diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..2a17299 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,177 @@ +#!groovy + +// Required Jenkins plugins: +// https://wiki.jenkins-ci.org/display/JENKINS/Timestamper +// https://wiki.jenkins-ci.org/display/JENKINS/Static+Code+Analysis+Plug-ins +// https://wiki.jenkins-ci.org/display/JENKINS/Checkstyle+Plugin ? +// https://wiki.jenkins-ci.org/display/JENKINS/FindBugs+Plugin +// https://wiki.jenkins-ci.org/display/JENKINS/PMD+Plugin ? +// https://wiki.jenkins-ci.org/display/JENKINS/DRY+Plugin ? +// https://wiki.jenkins-ci.org/display/JENKINS/Task+Scanner+Plugin +// https://wiki.jenkins-ci.org/display/JENKINS/Javadoc+Plugin +// https://wiki.jenkins-ci.org/display/JENKINS/JaCoCo+Plugin ? + + +init() + +def branch_name +def branch_name_base +def build_number +def build_url +def git_commit +def job_name +def tag +def version +def build_type +def display_name + +def init() { + + // Keep the 5 most recent builds + properties([[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '5']]]) + + build_number = env.BUILD_NUMBER + build_url = env.BUILD_URL + job_name = "${env.JOB_NAME}" + branch_name = env.BRANCH_NAME + branch_name_docker = branch_name.replaceAll(/\//,'.') + + // execute the branch type specific pipeline code + try { + + if (branch_name.indexOf('release/')==0) build_type='release' + if (branch_name.indexOf('feature/')==0) build_type='feature' + if (branch_name.indexOf('develop')==0) build_type='develop' + if (branch_name.indexOf('hotfix')==0) build_type='hotfix' + if (branch_name.indexOf('bugfix')==0) build_type='bugfix' + + // common pipeline elements + node('master') { + Initialize() + SetVersion(build_type) + print_vars() // after SetVersion - all variables now defined + set_result('INPROGRESS') + Build() // builds database via flyway migration + } + + node('master') { + // StopContainers() + set_result('SUCCESS') + } + + } catch (err) { + node() { + set_result('FAILURE') + } + throw err + } +} + +def Build() { + stage ('build') { + mvn "install -DskipTests=true -Dbuild.revision=${git_commit}" + //mvn "$mvn_cmd" + step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar', fingerprint: true]) + } +} + +def Initialize() { + stage ('initialize') { + + // deleteDir() + + // get new code + checkout scm + + git_commit = getSha1() + } +} + +def Deploy() { + stage ('deploy') { + mvn "deploy -DskipTests=true -Dbuild.number=${build_number} -Dbuild.revision=${git_commit}" + } +} + +def getSha1() { + sha1 = sh(script: 'git rev-parse HEAD', returnStdout: true).trim() + echo "sha1 is ${sha1}" + return sha1 +} + +def mvn(args) { + // add node and maven tools to path before calling maven + def mvnHome = tool name: 'maven-3.6.1' + env.PATH = "${mvnHome}/bin:${env.PATH}" + sh "mvn ${args}" +} + +def mvn_initial(args) { + // add node and maven tools to path before calling maven + def mvnHome = tool name: 'maven-3.6.1' + env.PATH = "${mvnHome}/bin:${env.PATH}" + sh "mvn ${args}" +} + +def set_result(status) { + if ( status == 'SUCCESS' ) { + currentBuild.result = status + notify_bitbucket('SUCCESSFUL') + } else if ( status == 'FAILURE' ) { + currentBuild.result = status + notify_bitbucket('FAILED') + } else if ( status == 'INPROGRESS' ) { + notify_bitbucket('INPROGRESS') + } else { + error ("unknown status") + } + + // save in persistence file for access the status page + // make sure the directory exists first + sh "mkdir -p $persist && echo $status > $persist/build.result" +} + +def notify_bitbucket(state) { +} + +def print_vars() { + echo "build_number = ${build_number}" + echo "build_url = ${build_url}" + echo "job_name = ${job_name}" + echo "branch_name = ${branch_name}" + echo "branch_name_base = ${branch_name_base}" + echo "build_type = ${build_type}" + echo "display_name = ${currentBuild.displayName}" + echo "version = ${version}" + echo "branch_name_docker = ${branch_name_docker}" + echo "git_commit = ${git_commit}" + +} + +def SetVersion( v ) { + stage ('set version') { + echo "set version ${v}" + branch_name_base = (branch_name =~ /([^\/]+$)/)[0][0] + if ( v == 'release' ) { + // for release branches, where the branch is named "release/1.2.3", + // derive the version and display name derive from the numeric suffix and append the build number + // 3.2.1.100 + version = branch_name_base + "." + build_number + //version = branch_name.substring('release/'.length()) + "." + build_number + currentBuild.displayName = version + } + else { + // for all other branches the version number is 0 with an appended build number + // and for the display name use the jenkins default #n and add the branch name + // #900 - develop + // #101 - feature/user/foo + //version = '0.' + build_number + version = branch_name_base + "." + build_number + currentBuild.displayName = "#" + build_number + " - " + branch_name_base + } + display_name = currentBuild.displayName + mvn_initial "versions:set -DnewVersion=${version}" + } +} + +return this diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe1da5e --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +Locusworks Commons += + +- Library with common generic functions + +Install +== + +Maven +=== +```xml + + net.locusworks + locusworks-commons + 1.1.0-RELEASE + +``` \ No newline at end of file diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml new file mode 100644 index 0000000..8a373e0 --- /dev/null +++ b/bitbucket-pipelines.yml @@ -0,0 +1,14 @@ +# This is a sample build configuration for Java (Maven). +# Check our guides at https://confluence.atlassian.com/x/zd-5Mw for more examples. +# Only use spaces to indent your .yml configuration. +# ----- +# You can specify a custom docker image from Docker Hub as your build environment. +image: maven:3.3.9 + +pipelines: + default: + - step: + caches: + - maven + script: # Modify the commands below to build your repository. + - mvn -B verify # -B batch mode makes Maven less verbose \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b12565e --- /dev/null +++ b/pom.xml @@ -0,0 +1,161 @@ + + 4.0.0 + net.locusworks + locusworks-commons + 0.0.1-SNAPSHOT + locusworks_commons + Common library for locusworks projects + + + Locusworks + http://www.locusworks.net + + + + locus2k@bitbucket.org:locus2k/commons.git + + + + 2.12.0 + 1.7.26 + 2.9.9 + 1.8 + 1.8 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20.1 + + true + true + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.owasp + dependency-check-maven + 5.1.0 + + + + check + + + + + + + + + + junit + junit + 4.12 + test + + + org.flywaydb + flyway-core + 5.1.4 + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + + + + org.apache.httpcomponents + httpclient + 4.5.5 + + + org.apache.httpcomponents + httpmime + 4.5.5 + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + 2.9.9.1 + + + + com.google.code.gson + gson + 2.8.5 + + + + + + + nexus-proxy-public + http://devops.locusworks.net:8081/repository/maven-public/ + + + + + nexus-proxy-public + http://devops.locusworks.net:8081/repository/maven-public/ + + + + + + nexus-snapshot + http://devops.locusworks.net:8081/repository/locusworks-snapshot/ + + + nexus-release + http://devops.locusworks.net:8081/repository/locusworks-release/ + + + + \ No newline at end of file diff --git a/src/main/java/net/locusworks/common/Charsets.java b/src/main/java/net/locusworks/common/Charsets.java new file mode 100644 index 0000000..85f05ee --- /dev/null +++ b/src/main/java/net/locusworks/common/Charsets.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 The Guava 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 + * + * 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. + */ + +package net.locusworks.common; + +import java.nio.charset.Charset; + +/** + * Contains constant definitions for the six standard {@link Charset} instances, which are + * guaranteed to be supported by all Java platform implementations. + * + *

Assuming you're free to choose, note that {@link #UTF_8} is widely preferred. + * + *

See the Guava User Guide article on {@code Charsets}. + * + * Please do not add new Charset references to this class, unless those character encodings are + * part of the set required to be supported by all Java platform implementations! Any Charsets + * initialized here may cause unexpected delays when this class is loaded. See the Charset + * Javadocs for the list of built-in character encodings. + * + */ +public final class Charsets { + private Charsets() {} + + /** + * US-ASCII: seven-bit ASCII, the Basic Latin block of the Unicode character set (ISO646-US). + */ + public static final Charset US_ASCII = java.nio.charset.StandardCharsets.US_ASCII; + + /** + * ISO-8859-1: ISO Latin Alphabet Number 1 (ISO-LATIN-1). + */ + public static final Charset ISO_8859_1 = java.nio.charset.StandardCharsets.ISO_8859_1; + + /** + * UTF-8: eight-bit UCS Transformation Format. + */ + public static final Charset UTF_8 = java.nio.charset.StandardCharsets.UTF_8; + + /** + * UTF-16BE: sixteen-bit UCS Transformation Format, big-endian byte order. + */ + public static final Charset UTF_16BE = java.nio.charset.StandardCharsets.UTF_16BE; + + /** + * UTF-16LE: sixteen-bit UCS Transformation Format, little-endian byte order. + */ + public static final Charset UTF_16LE = java.nio.charset.StandardCharsets.UTF_16LE; + + /** + * UTF-16: sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order + * mark. + */ + public static final Charset UTF_16 = java.nio.charset.StandardCharsets.UTF_16; + +} \ No newline at end of file diff --git a/src/main/java/net/locusworks/common/annotations/MapValue.java b/src/main/java/net/locusworks/common/annotations/MapValue.java new file mode 100644 index 0000000..b1845e7 --- /dev/null +++ b/src/main/java/net/locusworks/common/annotations/MapValue.java @@ -0,0 +1,24 @@ +package net.locusworks.common.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that will map a POJO to a map object. + * This is at the field level and will map the + * filed name as the key and the field value as the value + * in the map + * @author Isaac Parenteau + * + */ +@Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface MapValue { + + String value() default ""; + + boolean ignore() default false; + +} diff --git a/src/main/java/net/locusworks/common/configuration/ConfigurationCallback.java b/src/main/java/net/locusworks/common/configuration/ConfigurationCallback.java new file mode 100644 index 0000000..cc4a75c --- /dev/null +++ b/src/main/java/net/locusworks/common/configuration/ConfigurationCallback.java @@ -0,0 +1,6 @@ +package net.locusworks.common.configuration; + +@FunctionalInterface +public interface ConfigurationCallback { + void results(String msg); +} diff --git a/src/main/java/net/locusworks/common/configuration/ConfigurationManager.java b/src/main/java/net/locusworks/common/configuration/ConfigurationManager.java new file mode 100644 index 0000000..975d1f7 --- /dev/null +++ b/src/main/java/net/locusworks/common/configuration/ConfigurationManager.java @@ -0,0 +1,149 @@ +package net.locusworks.common.configuration; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; + +import net.locusworks.common.Charsets; +import net.locusworks.common.crypto.AES; +import net.locusworks.common.exceptions.ApplicationException; +import net.locusworks.common.interfaces.PersistableRequest; + +import net.locusworks.common.utils.Utils; + +public class ConfigurationManager { + + private Properties configuration; + private Properties defaults = null; + private File conf = null; + + protected AES aes; + + private ConfigurationCallback callback; + + protected void init(String baseDir, String propertiesFile, ConfigurationCallback callback) throws IOException { + init(baseDir, propertiesFile, this.getClass().getName().getBytes(Charsets.UTF_8), callback); + } + + protected void init(String baseDir, String propertiesFile, byte[] aesKey, ConfigurationCallback callback) throws IOException { + aes = aesKey.length > 0 ? AES.createInstance(aesKey) : AES.createInstance(); + this.callback = callback; + try { + defaults = PropertiesManager.loadConfiguration(this.getClass(), propertiesFile); + } catch (IOException ex) { + throw ex; + } + // create patchrepoConf File object + conf = new File(String.format("%s/%s", baseDir, propertiesFile)); + + loadConfiguration(); + } + + private void loadConfiguration() { + // load the active config file + // ignore read error, we can continue with an empty configuration map + // and all default items will be added below and the file created + + callbackMessage("Loading config file: " + conf); + try { + configuration = PropertiesManager.loadConfiguration(conf); + } catch (Exception e) { + callbackMessage("Config file: " + conf + " will be created from template"); + configuration = new Properties(); + } + + Map results = PropertiesManager.addConfiguration(configuration, defaults); + boolean changed = !results.isEmpty(); + if (!results.isEmpty()) { + StringBuilder sb = new StringBuilder("Added new configuration items:\n"); + for (Entry entry : results.entrySet()) { + sb.append(String.format("%s=%s\n", entry.getKey(), entry.getValue())); + } + callbackMessage(sb.toString()); + } + + results = PropertiesManager.removeConfiguration(configuration, defaults); + changed |= !results.isEmpty(); + if (!results.isEmpty()) { + StringBuilder sb = new StringBuilder("Added new configuration items:\n"); + for (Entry entry : results.entrySet()) { + sb.append(String.format("%s=%s\n", entry.getKey(), entry.getValue())); + } + callbackMessage(sb.toString()); + } + + if (changed) { + PropertiesManager.saveConfiguration(configuration, conf, "Patch Repository properties file"); + } + } + + /** + * Save the configuration values to file + * @param confs Configuration property values to save + * @throws Exception general exception + */ + public void saveToConf(Properties confs) throws Exception { + PropertiesManager.saveConfiguration(confs, conf, conf.getName()); + callbackMessage("Saved config file: " + conf + ", " + confs.size() + " entries"); + loadConfiguration(); + } + + public String getPropertyValue(String key) { + return getPropertyValue(key, null); + } + public String getPropertyValue(String key, String defaultValue) { + return configuration.containsKey(key) ? configuration.getProperty(key) : defaultValue; + } + + public Properties getConfiguration() { + return configuration; + } + + public void saveConfiguration(PersistableRequest request, Set fieldsToSave, Set excryptedFields) throws Exception { + if (fieldsToSave == null || fieldsToSave.isEmpty()) { + throw ApplicationException.generic("No fields to save were defined"); + } + if (excryptedFields == null) { + excryptedFields = new HashSet<>(); + } + try { + Properties props = new Properties(); + + //copy what is current in the configuration settings into the new properties file + configuration.entrySet().forEach(item -> props.setProperty(String.valueOf(item.getKey()), String.valueOf(item.getValue()))); + boolean changed = false; + for (Field f : request.getClass().getDeclaredFields()) { + f.setAccessible(true); + String fieldName = f.getName(); + String fieldValue = String.valueOf(f.get(request)); + + //Ensures we are only saving values that are already configured + if (!fieldsToSave.contains(fieldName)) continue; + + //Check to see if the old value changed + String oldValue = props.getProperty(fieldName); + if (Utils.isNotValid(oldValue, fieldValue) || oldValue.equals(fieldValue)) { continue; } + + changed = true; + + fieldValue = excryptedFields.contains(fieldName) ? aes.encrypt(fieldValue) : fieldValue; + + props.setProperty(fieldName, fieldValue); + } + if (changed) { + saveToConf(props); + } + } catch (Exception ex) { + throw ApplicationException.actionNotPermitted(ex.getMessage()); + } + } + + private void callbackMessage(String msg) { + if (callback != null) callback.results(msg); + } +} diff --git a/src/main/java/net/locusworks/common/configuration/PropertiesManager.java b/src/main/java/net/locusworks/common/configuration/PropertiesManager.java new file mode 100644 index 0000000..54da672 --- /dev/null +++ b/src/main/java/net/locusworks/common/configuration/PropertiesManager.java @@ -0,0 +1,125 @@ +package net.locusworks.common.configuration; + +import static net.locusworks.common.Charsets.UTF_8; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +import net.locusworks.common.immutables.Pair; +/** + * Properties manager class to help load and read properties + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + */ +public class PropertiesManager { + /** + * Load a configuration from resource + * @param clazz class loader + * @param src source of the file + * @return properties + * @throws IOException Exception thrown the file can't be read + */ + public static Properties loadConfiguration(Class clazz, String src) throws IOException { + InputStream is = clazz.getResourceAsStream(src); + if (is == null) { + is = clazz.getClassLoader().getResourceAsStream(src); + } + if (is == null) { + return null; + } + BufferedReader br = new BufferedReader(new InputStreamReader(is, UTF_8)); + return loadConfiguration(br); + } + + /** + * Load configuration from a file + * @param file File to load + * @return properties + * @throws IOException Exception thrown the file can't be read + */ + public static Properties loadConfiguration(File file) throws IOException { + if (!file.exists()) { + return new Properties(); + } + BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), UTF_8)); + return loadConfiguration(br); + } + + /** + * Load configuration from a buffered reader + * @param reader Buffered reader to read the properties values from + * @return properties + * @throws IOException Exception thrown the file can't be read + */ + public static Properties loadConfiguration(BufferedReader reader) throws IOException { + Properties config = new Properties(); + config.load(reader); + return config; + } + + /** + * Add configurations from one properties file to another + * @param to Properties file to copy values to + * @param from Properties file to copy values from + * @return a map containing the results of the values added + */ + public static Map addConfiguration(Properties to, Properties from) { + Map results = from.entrySet() + .stream() + .filter(entry -> !to.containsKey(entry.getKey())) + .map(entry -> { + String key = entry.getKey().toString(); + String value = entry.getValue().toString(); + to.put(key, value); + return new Pair(key, value); + }) + .collect(Collectors.toMap(key -> key.getValue1(), value -> value.getValue2())); + + return results; + } + + /** + * Removes configuration values that are not present in the comparedTo + * @param from Properties file to remove values from + * @param comparedTo Properties file to compare to + * @return a map containing the results of the values removed + */ + public static Map removeConfiguration(Properties from, Properties comparedTo) { + Map results = from.keySet() + .stream() + .filter(key -> !comparedTo.containsKey(key)) //only get the items that are not in the comparedTo properties + .map(key -> new Pair(String.valueOf(key), String.valueOf(from.get(key)))) + .collect(Collectors.toList()) //Create a list of paired items (key value) of the items that were filtered + .stream() + .map(pair -> { //remove those pairs from the from properties + from.remove(pair.getValue1()); + return pair; + }) + .collect(Collectors.toMap(key -> key.getValue1(), value -> value.getValue2())); //create a map of what was removed + + return results; + } + + /** + * Save the properties file to disk + * @param props Properties file to save + * @param fileToSave File to save to + * @param comment Any comments to add + */ + public static void saveConfiguration(Properties props, File fileToSave, String comment) { + try(FileOutputStream fos = new FileOutputStream(fileToSave)) { + props.store(fos, comment == null ? "" : comment); + } catch (IOException ex) { + throw new RuntimeException(ex.getMessage(), ex); + } + } +} diff --git a/src/main/java/net/locusworks/common/crypto/AES.java b/src/main/java/net/locusworks/common/crypto/AES.java new file mode 100644 index 0000000..34c3422 --- /dev/null +++ b/src/main/java/net/locusworks/common/crypto/AES.java @@ -0,0 +1,162 @@ +package net.locusworks.common.crypto; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; + +import net.locusworks.common.utils.RandomString; +import net.locusworks.common.utils.Utils; +import static net.locusworks.common.Charsets.UTF_8; + + +/** + * AES encryption/decryption class + * This class will encrypt/decrypt data. The encryption key is never known. + * Instead it is is generated by the provided seed. As long as the seed stays the same + * the key will remain the same and the encryption/decryption will work. This + * provides and added security. + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + * + */ +public class AES { + + private static final String ENCRYPTION_TYPE = "AES"; + private static final String ENCRYPTION_ALGORITH = "AES/CBC/PKCS5Padding"; + private static final String PROVIDER = "SunJCE"; + private static final String ALGORITHM = "SHA1PRNG"; + + private Cipher cipher; + + private SecretKeySpec secretKeySpec; + + private IvParameterSpec ivParamSpec; + + private String seed; + + private void initSecureKey(String seed) { + try { + SecureRandom sr = getSecureRandom(seed); + KeyGenerator generator = KeyGenerator.getInstance(ENCRYPTION_TYPE); + generator.init(128, sr); + init(generator.generateKey().getEncoded(), sr); + } catch (Exception ex) { + System.err.println(ex); + throw new IllegalArgumentException("Unable to initalize encryption:", ex); + } + } + + /** + * Initializes the secure random object to be the same across all platforms + * with regard to provider and algorithm used + * @param seed Seed to initialize SecureRandom with + * @return SecureRandom object + * @throws NoSuchAlgorithmException thrown when algorithm can't be used + * @throws NoSuchProviderException thrown when the provider cant be found + */ + private static SecureRandom getSecureRandom(String seed) throws NoSuchAlgorithmException { + SecureRandom sr = SecureRandom.getInstance(ALGORITHM); + sr.setSeed(seed.getBytes(UTF_8)); + return sr; + } + + /** + * Initialize the aes engine + * @param key secret key to use + * @param sr + */ + private void init(final byte[] key, SecureRandom sr) { + try { + this.cipher = Cipher.getInstance(ENCRYPTION_ALGORITH, PROVIDER); + this.secretKeySpec = new SecretKeySpec(key, ENCRYPTION_TYPE); + this.ivParamSpec = new IvParameterSpec(RandomString.getBytes(16, sr)); + } catch (Exception ex) { + System.err.println(ex); + throw new IllegalArgumentException("Unable to initalize encryption:", ex); + } + } + + /*** + * Encrypt a text string + * @param plainText String to encrypt + * @return encrypted string + */ + public String encrypt(String plainText) { + if (Utils.isEmptyString(plainText)) { + plainText = ""; + } + try { + cipher.init(Cipher.ENCRYPT_MODE, this.secretKeySpec, this.ivParamSpec); + byte[] cypherText = cipher.doFinal(plainText.getBytes(UTF_8)); + return new String(Base64.getEncoder().encode(cypherText), UTF_8); + } catch (Exception ex) { + throw new IllegalArgumentException(ex.getMessage(), ex); + } + } + + /*** + * Decrypt an encrypted string + * @param cipherString encrypted string to decrypt + * @return unecrypted string + */ + public String decrypt(String cipherString) { + if (Utils.isEmptyString(cipherString)) { + return ""; + } + byte[] cipherText = Base64.getDecoder().decode(cipherString.getBytes(UTF_8)); + try { + cipher.init(Cipher.DECRYPT_MODE, this.secretKeySpec, this.ivParamSpec); + return new String(cipher.doFinal(cipherText), UTF_8); + } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + public AES setSeed(String seed) { + if (this.seed == null || !this.seed.equals(seed)) { + initSecureKey(seed); + } + this.seed = seed; + return this; + } + + public final String getSeed() { + return this.seed; + } + + public static AES createInstance() { + return createInstance(RandomString.getString(16)); + } + + public static AES createInstance(byte[] byteSeed) { + String seed = new String(byteSeed, UTF_8); + return createInstance(seed); + } + + public static AES createInstance(String seed) { + AES aes = new AES(); + aes.setSeed(seed); + return aes; + } + + public static void main(String[] args) throws NoSuchAlgorithmException { + if (args == null || !(args.length > 0)) { + throw new IllegalArgumentException("No args provided. Need password as argument"); + } + if (args.length % 2 == 0) { + System.out.println(AES.createInstance(String.valueOf(args[1])).encrypt(String.valueOf(args[0]))); + } else { + System.out.println(AES.createInstance().encrypt(String.valueOf(args[0]))); + } + } +} diff --git a/src/main/java/net/locusworks/common/crypto/AESKey.java b/src/main/java/net/locusworks/common/crypto/AESKey.java new file mode 100644 index 0000000..b64d6b8 --- /dev/null +++ b/src/main/java/net/locusworks/common/crypto/AESKey.java @@ -0,0 +1,30 @@ +package net.locusworks.common.crypto; + +import java.security.PrivateKey; + +import net.locusworks.common.Charsets; + +public class AESKey implements PrivateKey { + + private static final long serialVersionUID = -8452357427706386362L; + private String seed; + + public AESKey(String seed) { + this.seed = seed; + } + + @Override + public String getAlgorithm() { + return "aes"; + } + + @Override + public String getFormat() { + return "aes-seed"; + } + + @Override + public byte[] getEncoded() { + return this.seed.getBytes(Charsets.UTF_8); + } +} diff --git a/src/main/java/net/locusworks/common/crypto/AESKeySpec.java b/src/main/java/net/locusworks/common/crypto/AESKeySpec.java new file mode 100644 index 0000000..a29c54f --- /dev/null +++ b/src/main/java/net/locusworks/common/crypto/AESKeySpec.java @@ -0,0 +1,45 @@ +package net.locusworks.common.crypto; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; +import java.util.Base64; + +import net.locusworks.common.Charsets; +import net.locusworks.common.io.IOUtils; + +import static net.locusworks.common.utils.Checks.checkArguments; +import static net.locusworks.common.utils.Utils.get; +import static net.locusworks.common.utils.Utils.size; + +public class AESKeySpec extends EncodedKeySpec { + + private static final String AES_MARKER = "aes-seed"; + + public AESKeySpec(byte[] encodedKey) { + super(encodedKey); + } + + public AESKey generateKey() throws InvalidKeySpecException { + try { + byte[] data = this.getEncoded(); + InputStream stream = new ByteArrayInputStream(data); + Iterable parts = Arrays.asList(IOUtils.toString(stream, Charsets.UTF_8).split(" ")); + + checkArguments(size(parts) == 2 && AES_MARKER.equals(get(parts, 0)), "Bad format, should be: aes-seed AAB3..."); + stream = new ByteArrayInputStream(Base64.getDecoder().decode(String.valueOf(get(parts, 1)))); + String marker = IOUtils.toString(stream, Charsets.UTF_8); + return new AESKey(marker); + } catch (Exception ex) { + throw new InvalidKeySpecException(ex); + } + } + + @Override + public String getFormat() { + return "aes"; + } + +} diff --git a/src/main/java/net/locusworks/common/crypto/EncryptionKeyFactory.java b/src/main/java/net/locusworks/common/crypto/EncryptionKeyFactory.java new file mode 100644 index 0000000..e21d4f7 --- /dev/null +++ b/src/main/java/net/locusworks/common/crypto/EncryptionKeyFactory.java @@ -0,0 +1,38 @@ +package net.locusworks.common.crypto; + +import java.security.KeyFactory; +import java.security.KeyFactorySpi; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +import sun.security.jca.GetInstance; +import sun.security.jca.GetInstance.Instance; + +public class EncryptionKeyFactory extends KeyFactory { + + protected EncryptionKeyFactory(KeyFactorySpi keyFacSpi, Provider provider, String algorithm) { + super(keyFacSpi, provider, algorithm); + } + + public PrivateKey generatePrivateKey(KeySpec keySpec) throws InvalidKeySpecException { + if (keySpec instanceof AESKeySpec) { + return ((AESKeySpec)keySpec).generateKey(); + } + return super.generatePrivate(keySpec); + } + + public PublicKey generatePublicKey(KeySpec keySpec) throws InvalidKeySpecException { + keySpec = keySpec instanceof SSHEncodedKeySpec ? ((SSHEncodedKeySpec)keySpec).convertToRSAPubKeySpec() : keySpec; + return super.generatePublic(keySpec); + } + + public static EncryptionKeyFactory getInstance(String algorithm) throws NoSuchAlgorithmException { + Instance instance = GetInstance.getInstance("KeyFactory", KeyFactorySpi.class, algorithm); + return new EncryptionKeyFactory((KeyFactorySpi)instance.impl, instance.provider, algorithm); + } + +} diff --git a/src/main/java/net/locusworks/common/crypto/HashSalt.java b/src/main/java/net/locusworks/common/crypto/HashSalt.java new file mode 100644 index 0000000..1c84004 --- /dev/null +++ b/src/main/java/net/locusworks/common/crypto/HashSalt.java @@ -0,0 +1,189 @@ +package net.locusworks.common.crypto; + +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +/** + * The type Hash salt. + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + */ +public class HashSalt { + /** + * The constant PBKDF2_ALGORITHM. + */ + private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256"; + + /** + * The constant SALT_BYTE_SIZE. + */ + private static final int SALT_BYTE_SIZE = 24; + + /** + * The constant HASH_BYTE_SIZE. + */ + private static final int HASH_BYTE_SIZE = 24; + + /** + * The constant PBKDF2_ITERATIONS. + */ + private static final int PBKDF2_ITERATIONS = 1000; + + /** + * The constant ITERATION_INDEX. + */ + private static final int ITERATION_INDEX = 0; + /** + * The constant SALT_INDEX. + */ + private static final int SALT_INDEX = 1; + /** + * The constant PBKDF2_INDEX. + */ + private static final int PBKDF2_INDEX = 2; + + /** + * Returns a salted PBKDF2 hash of the password. + * + * @param password the password to hash + * + * @return a salted PBKDF2 hash of the password + * @throws NoSuchAlgorithmException the no such algorithm exception + * @throws InvalidKeySpecException the invalid key spec exception + */ + public static String createHash(String password) throws NoSuchAlgorithmException, InvalidKeySpecException { + return createHash(password.toCharArray()); + } + + /** + * Returns a salted PBKDF2 hash of the password. + * + * @param password the password to hash + * + * @return a salted PBKDF2 hash of the password + * @throws NoSuchAlgorithmException the no such algorithm exception + * @throws InvalidKeySpecException the invalid key spec exception + */ + public static String createHash(char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException { + // Generate a random salt + SecureRandom random = new SecureRandom(); + byte[] salt = new byte[SALT_BYTE_SIZE]; + random.nextBytes(salt); + + // Hash the password + byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE); + // format iterations:salt:hash + return PBKDF2_ITERATIONS + ":" + toHex(salt) + ":" + toHex(hash); + } + + /** + * Validates a password using a hash. + * + * @param password the password to check + * @param correctHash the hash of the valid password + * + * @return true if the password is correct, false if not + * @throws NoSuchAlgorithmException the no such algorithm exception + * @throws InvalidKeySpecException the invalid key spec exception + */ + public static boolean validatePassword(String password, String correctHash) throws NoSuchAlgorithmException, InvalidKeySpecException { + return validatePassword(password.toCharArray(), correctHash); + } + + /** + * Validates a password using a hash. + * + * @param password the password to check + * @param correctHash the hash of the valid password + * + * @return true if the password is correct, false if not + * @throws NoSuchAlgorithmException the no such algorithm exception + * @throws InvalidKeySpecException the invalid key spec exception + */ + public static boolean validatePassword(char[] password, String correctHash) throws NoSuchAlgorithmException, InvalidKeySpecException { + // Decode the hash into its parameters + String[] params = correctHash.split(":"); + int iterations = Integer.parseInt(params[ITERATION_INDEX]); + byte[] salt = fromHex(params[SALT_INDEX]); + byte[] hash = fromHex(params[PBKDF2_INDEX]); + // Compute the hash of the provided password, using the same salt, + // iteration count, and hash length + byte[] testHash = pbkdf2(password, salt, iterations, hash.length); + // Compare the hashes in constant time. The password is correct if + // both hashes match. + return slowEquals(hash, testHash); + } + + /** + * Compares two byte arrays in length-constant time. This comparison method + * is used so that password hashes cannot be extracted from an on-line + * system using a timing attack and then attacked off-line. + * + * @param a the first byte array + * @param b the second byte array + * @return true if both byte arrays are the same, false if not + */ + private static boolean slowEquals(byte[] a, byte[] b) { + int diff = a.length ^ b.length; + for(int i = 0; i < a.length && i < b.length; i++) + diff |= a[i] ^ b[i]; + return diff == 0; + } + + /** + * Computes the PBKDF2 hash of a password. + * + * @param password the password to hash. + * @param salt the salt + * @param iterations the iteration count (slowness factor) + * @param bytes the length of the hash to compute in bytes + * @return the PBDKF2 hash of the password + */ + private static byte[] pbkdf2(char[] password, byte[] salt, int iterations, int bytes) throws NoSuchAlgorithmException, InvalidKeySpecException { + PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, bytes * 8); + SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM); + return skf.generateSecret(spec).getEncoded(); + } + + /** + * Converts a string of hexadecimal characters into a byte array. + * + * @param hex the hex string + * @return the hex string decoded into a byte array + */ + private static byte[] fromHex(String hex) { + byte[] binary = new byte[hex.length() / 2]; + for(int i = 0; i < binary.length; i++) { + binary[i] = (byte)Integer.parseInt(hex.substring(2*i, 2*i+2), 16); + } + return binary; + } + + /** + * Converts a byte array into a hexadecimal string. + * + * @param array the byte array to convert + * @return a length*2 character string encoding the byte array + */ + private static String toHex(byte[] array) { + BigInteger bi = new BigInteger(1, array); + String hex = bi.toString(16); + int paddingLength = (array.length * 2) - hex.length(); + if(paddingLength > 0) + return String.format("%0" + paddingLength + "d", 0) + hex; + else + return hex; + } + + public static void main (String[] args) throws Exception { + if (args == null || !(args.length > 0)) { + throw new IllegalArgumentException("No args provided. Need password as argument"); + } + System.out.println(HashSalt.createHash(String.valueOf(args[0]))); + } +} \ No newline at end of file diff --git a/src/main/java/net/locusworks/common/crypto/KeyFile.java b/src/main/java/net/locusworks/common/crypto/KeyFile.java new file mode 100644 index 0000000..e512c04 --- /dev/null +++ b/src/main/java/net/locusworks/common/crypto/KeyFile.java @@ -0,0 +1,213 @@ +package net.locusworks.common.crypto; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.security.Key; +import java.security.PrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; + +import net.locusworks.common.Charsets; +import net.locusworks.common.io.IOUtils; +import net.locusworks.common.utils.DataOutputStreamHelper; +import net.locusworks.common.utils.Utils; + +import static net.locusworks.common.utils.Utils.handleExceptionWrapper; +import static net.locusworks.common.utils.Splitter.fixedLengthSplit; +import static java.lang.String.join; + +public class KeyFile implements AutoCloseable { + + public enum EncryptionType { + RSA, + AES, + SSH + } + + private Key key; + private String description; + private Writer writer; + private EncryptionType encryptionType; + + public KeyFile(Key key) { + this(key, null); + } + + public KeyFile(Key key, String description) { + this(key, description, EncryptionType.valueOf(key.getAlgorithm().toUpperCase())); + } + + public KeyFile(Key key, String description, EncryptionType encryptionType) { + this.key = key; + this.description = description; + this.encryptionType = encryptionType; + } + + private KeyFile() {} + + private void loadFromFile(String fileName) { + if (Utils.isEmptyString(fileName)) return; + + this.key = null; + try { + File keyFile = new File(fileName); + if (!keyFile.exists()) { + throw new IllegalArgumentException(String.format("Unable to find file with name %s. Please check path", fileName)); + } + + String contentStr = IOUtils.toString(new FileInputStream(keyFile), Charsets.UTF_8); + + boolean rsaFormat = !contentStr.startsWith("ssh-rsa") && !contentStr.startsWith("aes-seed"); + if (rsaFormat) { + contentStr = contentStr.replace("-----.*", ""); + } + + contentStr = contentStr.replace("\\r?\\n", ""); + + byte[] content = rsaFormat ? Base64.getDecoder().decode(contentStr): contentStr.getBytes(Charsets.UTF_8); + + EncryptionKeyFactory kf = EncryptionKeyFactory.getInstance("RSA"); + + List keySpecs = Utils.toList( + new KeySpecHelper(new AESKeySpec(content), true, EncryptionType.AES), + new KeySpecHelper(new SSHEncodedKeySpec(content), false, EncryptionType.SSH), + new KeySpecHelper(new PKCS8EncodedKeySpec(content), true, EncryptionType.RSA), + new KeySpecHelper(new X509EncodedKeySpec(content), false, EncryptionType.RSA) + ); + + for (KeySpecHelper ksh : keySpecs) { + try { + this.key = ksh.isPrivate() ? kf.generatePrivateKey(ksh.getKeySpec()) : kf.generatePublicKey(ksh.getKeySpec()); + this.encryptionType = ksh.getEncryptionType(); + return; + } catch (NullPointerException | InvalidKeySpecException ikse) { continue; } + } + + throw new InvalidKeySpecException(String.format("Unable to determine if file %s is a private or public key. Not type of PKCS8, X509, SSH or AES spec", fileName)); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public void write(String fileName) { + try { + String data; + switch(this.encryptionType) { + case AES: + data = String.format("%s %s", this.key.getFormat(), Base64.getEncoder().encodeToString(this.key.getEncoded())); + IOUtils.writeStringToFile(fileName, data); + break; + case SSH: + try (DataOutputStreamHelper dosh = new DataOutputStreamHelper()) { + getSSHPubKeyBytes(this.key) + .forEach(handleExceptionWrapper(item ->{ + dosh.writeInt(item.length); + dosh.write(item); + })); + data = String.format("ssh-rsa", dosh.base64Encoded(), this.description); + IOUtils.writeStringToFile(fileName, data); + } + break; + default: + writePem(fileName); + } + } catch (Exception ex) { + throw new IllegalArgumentException(ex); + } + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + if (Utils.isEmptyString(this.description)) { + return this.key instanceof PrivateKey ? "PRIVATE KEY" : "PUBLIC KEY"; + } + return this.description; + } + + public Key getKey() { + return this.key; + } + + @Override + public void close() { + try { + if (this.writer != null) { + this.writer.flush(); + this.writer.close(); + this.writer = null; + } + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public static KeyFile read(String fileName) { + try (KeyFile kf = new KeyFile()) { + kf.loadFromFile(fileName); + return kf; + } + } + + private void writePem(String fileName) { + try { + String desc = getDescription(); + this.writer = new OutputStreamWriter(new FileOutputStream(fileName), Charsets.UTF_8); + this.writer.write(String.format("-----BEGIN RSA %s-----", desc)); + + String encoded = Base64.getEncoder().encodeToString(this.key.getEncoded()); + String out = join("\n", fixedLengthSplit(60).split(encoded)); + + this.writer.write(out); + this.writer.write(String.format("-----END RSA %s-----", desc)); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private List getSSHPubKeyBytes(Key key) { + RSAPublicKey rpk = (RSAPublicKey)key; + return Arrays.asList("ssh-rsa".getBytes(Charsets.UTF_8), + rpk.getPublicExponent().toByteArray(), + rpk.getModulus().toByteArray() + ); + } + + private static class KeySpecHelper { + private KeySpec keySpec; + private boolean isPrivate; + private EncryptionType encryptionType; + + public KeySpecHelper(KeySpec keySpec, boolean isPrivate, EncryptionType encryptionType) { + super(); + this.keySpec = keySpec; + this.isPrivate = isPrivate; + this.encryptionType = encryptionType; + } + + public synchronized final KeySpec getKeySpec() { + return keySpec; + } + + public synchronized final boolean isPrivate() { + return isPrivate; + } + + public synchronized final EncryptionType getEncryptionType() { + return encryptionType; + } + } + +} diff --git a/src/main/java/net/locusworks/common/crypto/RSA.java b/src/main/java/net/locusworks/common/crypto/RSA.java new file mode 100644 index 0000000..3ba85aa --- /dev/null +++ b/src/main/java/net/locusworks/common/crypto/RSA.java @@ -0,0 +1,156 @@ +package net.locusworks.common.crypto; + +import java.io.ByteArrayInputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.Key; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.interfaces.RSAKey; +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.IllegalBlockSizeException; + +import net.locusworks.common.Charsets; +import net.locusworks.common.crypto.KeyFile.EncryptionType; +import net.locusworks.common.io.IOUtils; + +public class RSA { + + private static final String ENCRYPTION_TYPE = "RSA"; + private static final String ENCRYPTION_ALGORITHM = "RSA/ECB/PKCS10PADDING"; + private static final String PROVIDER = "SunJCE"; + private static final String RANDOM_ALGORITHM = "SHA1PRNG"; + + private static final int PADDING_LENGTH = 11; + private static final int DEFAULT_KEY_LENGTH = 2048; + + public static KeyPair generateKeyPair() { + return generateKeyPair(DEFAULT_KEY_LENGTH); + } + + public static KeyPair generateKeyPair(int keyLength) { + try { + KeyPairGenerator kpg = KeyPairGenerator.getInstance(ENCRYPTION_TYPE); + SecureRandom sr = SecureRandom.getInstance(RANDOM_ALGORITHM); + kpg.initialize(keyLength, sr); + return kpg.genKeyPair(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public static KeyPair loadPrivateKey(String privateKeyFileName) { + return loadKeyPair(null, privateKeyFileName); + } + + public static KeyPair loadPublicKey(String publicKeyFileName) { + return loadKeyPair(publicKeyFileName, null); + } + + public static KeyPair loadKeyPair(String publicKey, String privateKey) { + KeyFile pubKey = KeyFile.read(publicKey); + KeyFile prvKey = KeyFile.read(privateKey); + return new KeyPair((PublicKey)pubKey.getKey(), (PrivateKey)prvKey.getKey()); + } + + public static boolean generateAndWriteSSHKeys() { + KeyPair kp = generateKeyPair(); + return writePrivateKey(kp) && writePublicKey(kp, true); + } + + public static boolean generateAndWriteKeyPair() { + return generateAndWriteKeyPair(DEFAULT_KEY_LENGTH); + } + + public static boolean generateAndWriteKeyPair(String keyPairName) { + return generateAndWriteKeyPair(keyPairName, DEFAULT_KEY_LENGTH); + } + + public static boolean generateAndWriteKeyPair(int keyLength) { + return generateAndWriteKeyPair("id_rsa", keyLength); + } + + public static boolean generateAndWriteKeyPair(String keyPairName, int keyLength) { + KeyPair kp = generateKeyPair(keyLength); + return writePrivateKey(kp, keyPairName, "PRIVATE KEY") && writePublicKey(kp, keyPairName + ".pub", "PUBLIC KEY"); + } + + public static boolean writePrivateKey(KeyPair kp) { + return writePrivateKey(kp, "id_rsa", "PRIVATE KEY"); + } + + public static boolean writePrivateKey(KeyPair kp, String fileName, String description) { + return writePemFile(kp.getPrivate(), fileName, description); + } + + public static boolean writePublicKey(KeyPair kp) { + return writePublicKey(kp, false); + } + + public static boolean writePublicKey(KeyPair kp, boolean sshFormat) { + return writePublicKey(kp, "id_rsa.pub", "PUBLIC KEY", sshFormat); + } + + public static boolean writePublicKey(KeyPair kp, String fileName, String description) { + return writePemFile(kp.getPublic(), fileName, description, false); + } + + public static boolean writePublicKey(KeyPair kp, String fileName, String description, boolean sshFormat) { + return writePemFile(kp.getPublic(), fileName, description, sshFormat); + } + + public static int calculateRequiredKeyLength(String message) { + return (message.getBytes(Charsets.UTF_8).length + PADDING_LENGTH) * 8; + } + + public static String encrypt(Key key, String message) { + try { + calculateKeyLength(key, message); + Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM, PROVIDER); + cipher.init(Cipher.ENCRYPT_MODE, key); + CipherInputStream cis = new CipherInputStream(new ByteArrayInputStream(message.getBytes(Charsets.UTF_8)), cipher); + byte[] encrypted = IOUtils.toByteArray(cis); + return Base64.getEncoder().encodeToString(encrypted); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public static String decrypt(Key key, String message) { + try { + Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM, PROVIDER); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] decoded = Base64.getDecoder().decode(message); + byte[] plainTextArray = cipher.doFinal(decoded); + return new String(plainTextArray, Charsets.UTF_8); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private static boolean writePemFile(Key key, String fileName, String description) { + return writePemFile(key, fileName, description, false); + } + + private static boolean writePemFile(Key key, String fileName, String description, boolean sshFormat) { + try(KeyFile kf = sshFormat ? new KeyFile(key, description, EncryptionType.SSH) : new KeyFile(key, description)) { + kf.write(fileName); + } + return Files.exists(Paths.get(fileName)); + } + + private static void calculateKeyLength(Key key, String message) throws IllegalBlockSizeException { + int keyLength = ((RSAKey)key).getModulus().bitLength(); + int requiredKeyLength = calculateRequiredKeyLength(message); + if (keyLength < requiredKeyLength) { + throw new IllegalBlockSizeException(String.format("RSA key size of %d is not large enough to encrypt message of length %d. " + + "Increase key size to a minimum of %d and re-encrypt with new key", keyLength, message.length(), requiredKeyLength)); + } + } +} diff --git a/src/main/java/net/locusworks/common/crypto/SSHEncodedKeySpec.java b/src/main/java/net/locusworks/common/crypto/SSHEncodedKeySpec.java new file mode 100644 index 0000000..0572364 --- /dev/null +++ b/src/main/java/net/locusworks/common/crypto/SSHEncodedKeySpec.java @@ -0,0 +1,65 @@ +package net.locusworks.common.crypto; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; +import java.util.Arrays; +import java.util.Base64; + +import net.locusworks.common.Charsets; +import net.locusworks.common.io.IOUtils; + +import static net.locusworks.common.utils.Checks.checkArguments; +import static net.locusworks.common.utils.Utils.get; +import static net.locusworks.common.utils.Utils.size; + +public class SSHEncodedKeySpec extends EncodedKeySpec { + + private static final String SSH_MARKER = "ssh-rsa"; + + public SSHEncodedKeySpec(byte[] encodedKey) { + super(encodedKey); + } + + public RSAPublicKeySpec convertToRSAPubKeySpec() throws InvalidKeySpecException { + try { + byte[] data = this.getEncoded(); + InputStream stream = new ByteArrayInputStream(data); + Iterable parts = Arrays.asList(IOUtils.toString(stream, Charsets.UTF_8).split(" ")); + + checkArguments(size(parts) >= 2 && SSH_MARKER.equals(get(parts, 0)), "Bad format, should be: ssh-rsa AAB3..."); + stream = new ByteArrayInputStream(Base64.getDecoder().decode(String.valueOf(get(parts, 1)))); + String marker = new String(readLengthFirst(stream)); + checkArguments(SSH_MARKER.equals(marker), "Looking for marker %s but received %s", SSH_MARKER, marker); + BigInteger publicExponent = new BigInteger(readLengthFirst(stream)); + BigInteger modulus = new BigInteger(readLengthFirst(stream)); + RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, publicExponent); + return keySpec; + } catch (Exception ex) { + throw new InvalidKeySpecException(ex); + } + } + + @Override + public String getFormat() { + return null; + } + + private static byte[] readLengthFirst(InputStream in) throws IOException { + int[] bytes = new int[] {in.read(), in.read(), in.read(), in.read()}; + int length = 0; + int shift = 24; + for (int i = 0; i < bytes.length; i++) { + length += bytes[i] << shift; + shift -= 8; + } + byte[] val = new byte[length]; + in.read(val); + return val; + } + +} diff --git a/src/main/java/net/locusworks/common/crypto/package-info.java b/src/main/java/net/locusworks/common/crypto/package-info.java new file mode 100644 index 0000000..99ddca3 --- /dev/null +++ b/src/main/java/net/locusworks/common/crypto/package-info.java @@ -0,0 +1,5 @@ +/** + * Package contains classes that help encrypting, decrypting, salting and hashing objects + * @author Isaac Parenteau + */ +package net.locusworks.common.crypto; \ No newline at end of file diff --git a/src/main/java/net/locusworks/common/exceptions/ApplicationException.java b/src/main/java/net/locusworks/common/exceptions/ApplicationException.java new file mode 100644 index 0000000..0ade43a --- /dev/null +++ b/src/main/java/net/locusworks/common/exceptions/ApplicationException.java @@ -0,0 +1,108 @@ +package net.locusworks.common.exceptions; + +/*** + * Custom exception class for the patch repository + * @author Isaac Parenteau + * + */ +public class ApplicationException extends Exception { + private final Integer code; + boolean success = false; + private static final long serialVersionUID = 1L; + + public static ApplicationException egregiousServer() { + return new ApplicationException(9001, "Something went wrong. Please see logs for details"); + } + + public static ApplicationException invalidCreds() { + return new ApplicationException(9001, "Invalid credentials provided"); + } + + public static ApplicationException notLoggedIn() { + return new ApplicationException(9002, "Not logged in"); + } + + public static ApplicationException invalidEmailAddress() { + return new ApplicationException(9005, "Invalid email address"); + } + + public static ApplicationException actionNotPermitted() { + return new ApplicationException(9007, "Action not permitted"); + } + + public static ApplicationException actionNotPermitted(String message) { + return new ApplicationException(9007, "Action not permitted: " + message); + } + + public static ApplicationException passwordsNotEqual() { + return new ApplicationException(9008, "Passwords do not match"); + } + + public static ApplicationException unAuthorized() { + return new ApplicationException(9009, "unauthorized"); + } + + public static ApplicationException duplicateEntry(String message) { + return new ApplicationException(9100, message); + } + + public static ApplicationException duplicateEntry(String messageFmt, Object... args) { + return new ApplicationException(9100, String.format(messageFmt, args)); + } + + public static ApplicationException noEntryExists(String message) { + return new ApplicationException(9101, message); + } + + public static ApplicationException noEntryExists(String messageFmt, Object... items) { + return new ApplicationException(9101, String.format(messageFmt, items)); + } + + public static ApplicationException constraintViolation(String message) { + return new ApplicationException(9102, message); + } + + public static ApplicationException constraintViolation(String messageFmt, Object... items) { + return new ApplicationException(9102, String.format(messageFmt, items)); + } + + public static ApplicationException illegalArgument(String message) { + return new ApplicationException(9103, message); + } + + public static ApplicationException generic(String message) { + return new ApplicationException(9999, message); + } + + public static ApplicationException fromException(Throwable e) { + return new ApplicationException(9999, e); + } + + public ApplicationException(int code, Throwable e) { + this(code, e.getMessage(), e); + } + + public ApplicationException(int code, String message) { + super(message); + this.code = code; + } + + public ApplicationException(int code, String message, Throwable e) { + super(message, e); + this.code = code; + } + + public boolean getSuccess() { + return success; + } + + public Integer getCode() { + return code; + } + + @Override + public String getMessage() { + return super.getMessage(); + } + +} diff --git a/src/main/java/net/locusworks/common/immutables/Pair.java b/src/main/java/net/locusworks/common/immutables/Pair.java new file mode 100644 index 0000000..36149c3 --- /dev/null +++ b/src/main/java/net/locusworks/common/immutables/Pair.java @@ -0,0 +1,55 @@ +package net.locusworks.common.immutables; + +/** + * Class that holds two immutable objects as a pair + * @author Isaac Parenteau + * @version 1.0.0 + * @param class type of object 1 + * @param class type of object 2 + */ +public class Pair extends Unit { + + private V2 value2; + + /** + * Constructor with no values + */ + public Pair() { + super(); + } + + /** + * Constructor + * @param value1 Object 1 + * @param value2 Object 2 + */ + public Pair(V1 value1, V2 value2) { + super(value1); + this.value2 = value2; + } + + /** + * Set value2 + * @param value2 value to set it + */ + public void setValue2(V2 value2) { + this.value2 = value2; + } + + /** + * Get value2 + * @return value2 + */ + public V2 getValue2() { + return this.value2; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Pair)) return false; + + Pair otherPair = (Pair)other; + + return super.equals(otherPair) && this.getValue2().equals(otherPair.getValue2()); + } +} \ No newline at end of file diff --git a/src/main/java/net/locusworks/common/immutables/Triplet.java b/src/main/java/net/locusworks/common/immutables/Triplet.java new file mode 100644 index 0000000..5ff5c83 --- /dev/null +++ b/src/main/java/net/locusworks/common/immutables/Triplet.java @@ -0,0 +1,57 @@ +package net.locusworks.common.immutables; + +/** + * Class that holds three immutable objects as triplets + * @author Isaac Parenteau + * @version 1.0.0 + * @param class type of object 1 + * @param class type of object 2 + * @param class type of object 3 + */ +public class Triplet extends Pair { + + private V3 value3; + + /** + * default constructor with no values + */ + public Triplet() { + super(); + } + + /** + * Constructor + * @param value1 Object 1 + * @param value2 Object 2 + * @param value3 Object 3 + */ + public Triplet(V1 value1, V2 value2, V3 value3) { + super(value1, value2); + this.value3 = value3; + } + + /** + * Set value 3 + * @param value3 value 3 + */ + public void setValue3(V3 value3) { + this.value3 = value3; + } + + /** + * Get value 3 + * @return value 3 + */ + public V3 getValue3() { + return value3; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Triplet)) return false; + + Triplet otherTriplet = (Triplet)other; + + return super.equals(otherTriplet) && this.getValue3().equals(otherTriplet.getValue3()); + } +} \ No newline at end of file diff --git a/src/main/java/net/locusworks/common/immutables/Unit.java b/src/main/java/net/locusworks/common/immutables/Unit.java new file mode 100644 index 0000000..b0a0971 --- /dev/null +++ b/src/main/java/net/locusworks/common/immutables/Unit.java @@ -0,0 +1,50 @@ +package net.locusworks.common.immutables; + +/** + * Class that holds three immutable objects as triplets + * @author Isaac Parenteau + * @version 1.0.0 + * @param class type of object 1 + */ +public class Unit { + + private V1 value1; + + /** + * Default constructor with no values + */ + public Unit() {} + + /** + * Constuctor + * @param value1 value 1 + */ + public Unit(V1 value1) { + this.value1 = value1; + } + + /** + * Set value 1 + * @param value1 value 1 + */ + public void setValue1(V1 value1) { + this.value1 = value1; + } + + /** + * Get value 1 + * @return value1 + */ + public V1 getValue1() { + return value1; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Unit)) return false; + + Unit otherUnit = (Unit)other; + + return this.getValue1().equals(otherUnit.getValue1()); + } +} \ No newline at end of file diff --git a/src/main/java/net/locusworks/common/interfaces/AutoCloseableIterator.java b/src/main/java/net/locusworks/common/interfaces/AutoCloseableIterator.java new file mode 100644 index 0000000..d494fcb --- /dev/null +++ b/src/main/java/net/locusworks/common/interfaces/AutoCloseableIterator.java @@ -0,0 +1,10 @@ +package net.locusworks.common.interfaces; + +import java.util.Iterator; + +public interface AutoCloseableIterator extends Iterator, AutoCloseable { + + @Override + public void close(); + +} diff --git a/src/main/java/net/locusworks/common/interfaces/PersistableRequest.java b/src/main/java/net/locusworks/common/interfaces/PersistableRequest.java new file mode 100644 index 0000000..9bbfc69 --- /dev/null +++ b/src/main/java/net/locusworks/common/interfaces/PersistableRequest.java @@ -0,0 +1,5 @@ +package net.locusworks.common.interfaces; + +public interface PersistableRequest { + +} diff --git a/src/main/java/net/locusworks/common/interfaces/ThrowingConsumer.java b/src/main/java/net/locusworks/common/interfaces/ThrowingConsumer.java new file mode 100644 index 0000000..cde492e --- /dev/null +++ b/src/main/java/net/locusworks/common/interfaces/ThrowingConsumer.java @@ -0,0 +1,6 @@ +package net.locusworks.common.interfaces; + +@FunctionalInterface +public interface ThrowingConsumer { + void accept(T t) throws E; +} diff --git a/src/main/java/net/locusworks/common/io/IOUtils.java b/src/main/java/net/locusworks/common/io/IOUtils.java new file mode 100644 index 0000000..11cb25a --- /dev/null +++ b/src/main/java/net/locusworks/common/io/IOUtils.java @@ -0,0 +1,346 @@ +/* + * 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. + */ +package net.locusworks.common.io; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.locusworks.common.Charsets; + +public class IOUtils { + + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + public static final int EOF = -1; + + /** + * Gets the contents of an InputStream as a list of Strings, + * one entry per line, using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from, not null + * @param encoding the encoding to use, null means platform default + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static List readLines(final InputStream stream, final Charset charset) throws IOException { + final InputStreamReader reader = new InputStreamReader(stream, charset); + return readLines(reader); + } + + /** + * Gets the contents of a Reader as a list of Strings, + * one entry per line. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from, not null + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static List readLines(final Reader input) throws IOException { + final BufferedReader reader = toBufferedReader(input); + final List list = new ArrayList<>(); + for(String line = reader.readLine(); line != null; line = reader.readLine()) { + list.add(line); + } + return list; + } + + /** + * Returns the given reader if it is a {@link BufferedReader}, otherwise creates a BufferedReader from the given + * reader. + * + * @param reader the reader to wrap or return (not null) + * @return the given reader or a new {@link BufferedReader} for the given reader + * @throws NullPointerException if the input parameter is null + * @see #buffer(Reader) + */ + public static BufferedReader toBufferedReader(final Reader reader) { + return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); + } + + /** + * Gets the contents of an InputStream as a byte[]. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(final InputStream input) throws IOException { + try (final ByteArrayOutputStream output = new ByteArrayOutputStream()) { + copy(input, output); + return output.toByteArray(); + } + } + + /** + * Copies bytes from an InputStream to an + * OutputStream. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * Large streams (over 2GB) will return a bytes copied value of + * -1 after the copy has completed since the correct + * number of bytes cannot be returned as an int. For large streams + * use the copyLarge(InputStream, OutputStream) method. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @return the number of bytes copied, or -1 if > Integer.MAX_VALUE + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + */ + public static int copy(final InputStream input, final OutputStream output) throws IOException { + final long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + /** + * Copies bytes from an InputStream to an OutputStream using an internal buffer of the + * given size. + *

+ * This method buffers the input internally, so there is no need to use a BufferedInputStream. + *

+ * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param bufferSize the bufferSize used to copy from the input to the output + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + */ + public static long copy(final InputStream input, final OutputStream output, final int bufferSize) + throws IOException { + return copyLarge(input, output, new byte[bufferSize]); + } + + /** + * Copies bytes from an InputStream to chars on a + * Writer using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * This method uses {@link InputStreamReader}. + * + * @param input the InputStream to read from + * @param output the Writer to write to + * @param inputEncoding the encoding to use for the input stream, null means platform default + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + */ + public static void copy(final InputStream input, final Writer output, final Charset inputEncoding) + throws IOException { + final InputStreamReader in = new InputStreamReader(input, inputEncoding.toString()); + copy(in, output); + } + + /** + * Copies chars from a Reader to a Writer. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * Large streams (over 2GB) will return a chars copied value of + * -1 after the copy has completed since the correct + * number of chars cannot be returned as an int. For large streams + * use the copyLarge(Reader, Writer) method. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @return the number of characters copied, or -1 if > Integer.MAX_VALUE + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + */ + public static int copy(final Reader input, final Writer output) throws IOException { + final long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copies chars from a large (over 2GB) Reader to a Writer. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + */ + public static long copyLarge(final Reader input, final Writer output) throws IOException { + return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copies bytes from a large (over 2GB) InputStream to an + * OutputStream. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + */ + public static long copyLarge(final InputStream input, final OutputStream output) + throws IOException { + return copy(input, output, DEFAULT_BUFFER_SIZE); + } + + /** + * Copies bytes from a large (over 2GB) InputStream to an + * OutputStream. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedInputStream. + *

+ * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param buffer the buffer to use for the copy + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + */ + public static long copyLarge(final InputStream input, final OutputStream output, final byte[] buffer) + throws IOException { + long count = 0; + int n; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Copies chars from a large (over 2GB) Reader to a Writer. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedReader. + *

+ * + * @param input the Reader to read from + * @param output the Writer to write to + * @param buffer the buffer to be used for the copy + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + */ + public static long copyLarge(final Reader input, final Writer output, final char[] buffer) throws IOException { + long count = 0; + int n; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Gets the contents of an InputStream as a String + * using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * + * @param input the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static String toString(final InputStream input, final Charset encoding) throws IOException { + try (final StringWriter sw = new StringWriter()) { + copy(input, sw, encoding); + return sw.toString(); + } + } + + public static void writeStringToFile(String fileName, String data) throws IOException { + writeStringToFile(new File(fileName), data, Charsets.UTF_8); + } + + + public static void writeStringToFile(String fileName, String data, Charset charset) throws IOException { + writeStringToFile(new File(fileName), data, charset); + } + + public static void writeStringToFile(File file, String data) throws IOException { + writeStringToFile(file, data, Charsets.UTF_8); + } + + public static void writeStringToFile(File file, String data, Charset charset) throws IOException { + try(Writer writer = new OutputStreamWriter(new FileOutputStream(file), charset)) { + writer.write(data); + writer.flush(); + } + } + + public static void deleteFile(String fileName) { + deleteFile(new File(fileName)); + } + + public static void deleteFile(File file) { + if (file.exists()) { + file.delete(); + file.deleteOnExit(); + } + } + + public static void deleteFiles(String... fileNames) { + Arrays.asList(fileNames).forEach(file -> deleteFile(file)); + } +} diff --git a/src/main/java/net/locusworks/common/migration/BaseMigrationManager.java b/src/main/java/net/locusworks/common/migration/BaseMigrationManager.java new file mode 100644 index 0000000..8f96110 --- /dev/null +++ b/src/main/java/net/locusworks/common/migration/BaseMigrationManager.java @@ -0,0 +1,12 @@ +package net.locusworks.common.migration; + +import java.util.ArrayList; +import java.util.List; + +public abstract class BaseMigrationManager { + + protected List migrations = new ArrayList<>(); + + public abstract void migrate() throws Exception; + +} diff --git a/src/main/java/net/locusworks/common/migration/MigrationCallback.java b/src/main/java/net/locusworks/common/migration/MigrationCallback.java new file mode 100644 index 0000000..fc47e2f --- /dev/null +++ b/src/main/java/net/locusworks/common/migration/MigrationCallback.java @@ -0,0 +1,6 @@ +package net.locusworks.common.migration; + +@FunctionalInterface +public interface MigrationCallback { + void results(String msg); +} diff --git a/src/main/java/net/locusworks/common/migration/MigrationItem.java b/src/main/java/net/locusworks/common/migration/MigrationItem.java new file mode 100644 index 0000000..d520caa --- /dev/null +++ b/src/main/java/net/locusworks/common/migration/MigrationItem.java @@ -0,0 +1,74 @@ +package net.locusworks.common.migration; + +import java.util.Arrays; + +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.MigrationInfo; +import org.flywaydb.core.internal.info.MigrationInfoDumper; + +public class MigrationItem { + + protected Flyway flyway = null; + protected MigrationInfo[] pendingMigrations; + protected MigrationInfo[] allMigrations; + private MigrationCallback callback; + + public MigrationItem(Flyway flyway, MigrationCallback callback) { + this.allMigrations = flyway.info().all(); + this.pendingMigrations = flyway.info().pending(); + this.flyway=flyway; + this.callback = callback; + } + + public MigrationItem(Flyway flyway) { + this(flyway, null); + } + + public String[] getSchemas() { + return flyway.getSchemas(); + } + + public int qtyPending() { + return pendingMigrations.length; + } + + public void repair() { + // no harm in calling migrate even if none pending, log will contain + // assurance that the migrations were verified + try { + String schemas = Arrays.toString(getSchemas()).replace("[", "").replace("]", ""); + + String status = String.format("Repair status for %s:%n%s", schemas, getAllMigrationsLog()); + + callback(status); + + flyway.repair(); + } catch (Exception e) { + String message = String.format("%nDatabase migration error:%n %s %nPortal webapp cannot continue.", e.getMessage()); + throw new RuntimeException(message, e); + } + } + + public void migrate() { + // no harm in calling migrate even if none pending, log will contain + // assurance that the migrations were verified + try { + String schemas = Arrays.toString(getSchemas()).replace("[", "").replace("]", ""); + String status = String.format("Repair status for %s:%n%s", schemas, getAllMigrationsLog()); + callback(status); + flyway.migrate(); + } catch (Exception e) { + String message = String.format("%nDatabase migration error:%n %s %nPortal webapp cannot continue.", e.getMessage()); + throw new RuntimeException(message, e); + } + } + + public String getAllMigrationsLog() { + return MigrationInfoDumper.dumpToAsciiTable(allMigrations); + } + + private void callback(String msg) { + if (this.callback != null) this.callback.results(msg); + } + +} diff --git a/src/main/java/net/locusworks/common/net/HttpClientHelper.java b/src/main/java/net/locusworks/common/net/HttpClientHelper.java new file mode 100644 index 0000000..56039bf --- /dev/null +++ b/src/main/java/net/locusworks/common/net/HttpClientHelper.java @@ -0,0 +1,95 @@ +package net.locusworks.common.net; + +import java.security.SecureRandom; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; + +import net.locusworks.common.net.certmanagers.TrustAllCertsManager; +import net.locusworks.common.net.hostverifiers.AllHostValidVerifyer; + +public class HttpClientHelper { + + public enum HttpSchema { + HTTP, + HTTPS; + + public static HttpSchema findEnum(String value) { + for (HttpSchema schema : values()) { + if (value.equalsIgnoreCase(schema.toString())) { + return schema; + } + } + return null; + } + } + + private static final String[] TLS = new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"}; + + private HttpClient client; + private String baseUrl; + + /** + * Constructor to handle http connection + * @param protocol protocol to use (http or https) + * @param host the host url + * @param port the host port + * @throws Exception exception + */ + public HttpClientHelper(String protocol, String host, String port) throws Exception { + HttpSchema schema = HttpSchema.findEnum(protocol); + if (schema == null) { + throw new Exception("Unable to find http schema of " + protocol); + } + this.baseUrl = String.format("%s://%s:%s", schema.toString().toLowerCase(), host, port); + this.client = createClient(schema); + } + + private HttpClient createClient(HttpSchema schema) throws Exception { + HttpClientBuilder builder = HttpClientBuilder.create(); + + if (schema == HttpSchema.HTTP) { + return builder.build(); + } + + TrustManager[] trustAllCerts = new TrustManager[] { new TrustAllCertsManager() }; + //Setup the ssl instance using tls + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new SecureRandom()); + + SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, TLS, null, new AllHostValidVerifyer()); + + builder = builder.setSSLSocketFactory(sslsf); + + return builder.build(); + } + + /** + * Get the http GET response code + * @param endpoint endpoint to get the response from + * @return responseCode + * @throws Exception general exception + */ + public Integer getGetResponseCode(String endpoint) throws Exception { + String url = this.baseUrl + endpoint; + + HttpResponse response = this.client.execute(new HttpGet(url)); + + HttpEntity entity = response.getEntity(); + + Integer responseCode = response.getStatusLine().getStatusCode(); + + EntityUtils.consume(entity); + + return responseCode; + }; + +} diff --git a/src/main/java/net/locusworks/common/net/certmanagers/TrustAllCertsManager.java b/src/main/java/net/locusworks/common/net/certmanagers/TrustAllCertsManager.java new file mode 100644 index 0000000..d27e2f1 --- /dev/null +++ b/src/main/java/net/locusworks/common/net/certmanagers/TrustAllCertsManager.java @@ -0,0 +1,24 @@ +package net.locusworks.common.net.certmanagers; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +public class TrustAllCertsManager implements X509TrustManager { + + @Override + public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } + + @Override + public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } + + @Override + public X509Certificate[] getAcceptedIssuers() { return null; } + + public static TrustManager[] trustAllCerts() { + return new TrustManager[] { new TrustAllCertsManager() }; + } + +} diff --git a/src/main/java/net/locusworks/common/net/hostverifiers/AllHostValidVerifyer.java b/src/main/java/net/locusworks/common/net/hostverifiers/AllHostValidVerifyer.java new file mode 100644 index 0000000..c385876 --- /dev/null +++ b/src/main/java/net/locusworks/common/net/hostverifiers/AllHostValidVerifyer.java @@ -0,0 +1,11 @@ +package net.locusworks.common.net.hostverifiers; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +public class AllHostValidVerifyer implements HostnameVerifier { + + @Override + public boolean verify(String arg0, SSLSession arg1) { return true; } + +} diff --git a/src/main/java/net/locusworks/common/net/ssl/SSLManager.java b/src/main/java/net/locusworks/common/net/ssl/SSLManager.java new file mode 100644 index 0000000..1309b0b --- /dev/null +++ b/src/main/java/net/locusworks/common/net/ssl/SSLManager.java @@ -0,0 +1,30 @@ +package net.locusworks.common.net.ssl; + +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.HttpClientBuilder; + +import net.locusworks.common.net.certmanagers.TrustAllCertsManager; +import net.locusworks.common.net.hostverifiers.AllHostValidVerifyer; + +public class SSLManager { + + public static final String[] TLS = new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"}; + + public static HttpClient getTrustAllTLSClient() throws NoSuchAlgorithmException, KeyManagementException { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, new TrustManager[] { new TrustAllCertsManager() }, new SecureRandom()); + + SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(context, TLS, null, new AllHostValidVerifyer()); + + return HttpClientBuilder.create().setSSLSocketFactory(sslsf).build(); + } + +} diff --git a/src/main/java/net/locusworks/common/objectmapper/ObjectMapperError.java b/src/main/java/net/locusworks/common/objectmapper/ObjectMapperError.java new file mode 100644 index 0000000..1f90f29 --- /dev/null +++ b/src/main/java/net/locusworks/common/objectmapper/ObjectMapperError.java @@ -0,0 +1,12 @@ +package net.locusworks.common.objectmapper; + +/** + * Error handler for the object mapper class + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + * + */ +public interface ObjectMapperError { + void getError(Throwable e); +} diff --git a/src/main/java/net/locusworks/common/objectmapper/ObjectMapperHelper.java b/src/main/java/net/locusworks/common/objectmapper/ObjectMapperHelper.java new file mode 100644 index 0000000..017ef30 --- /dev/null +++ b/src/main/java/net/locusworks/common/objectmapper/ObjectMapperHelper.java @@ -0,0 +1,142 @@ +package net.locusworks.common.objectmapper; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Object mapper to map to convert objects to json string or + * json string back to object + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + */ +public class ObjectMapperHelper { + + private static ObjectMapper mapper; + static { + mapper = new ObjectMapper(); + mapper.setSerializationInclusion(Include.NON_NULL); + mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); + } + + /** + * Write an object out to json + * @param object Object to convert to json + * @return return a string representation of the object converted to json + */ + public static ObjectMapperResults writeValue(Object object) { + String results = ""; + try { + results = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(object); + } catch (Exception ex) { + try { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.setPrettyPrinting(); + Gson gson = gsonBuilder.create(); + results = gson.toJson(object); + } catch (Exception e) { + new ObjectMapperResults<>(e); + } + } + return new ObjectMapperResults<>(results); + } + + /** + * Convert binary data to an object + * @param src Binary data to convert + * @param clazz Class to convert the data to + * @param The expected class of the value + * @return the object populated with the data in the json string + */ + public static ObjectMapperResults readValue(byte[] src, Class clazz) { + try { + return new ObjectMapperResults(mapper.readValue(src, clazz)); + } catch (Exception ex) { + return new ObjectMapperResults(ex); + } + } + + /** + * Convert a json string to an object + * @param src Json String + * @param clazz Class to convert the json string to + * @param The expected class of the value + * @return the object populated with the data in the json string + */ + public static ObjectMapperResults readValue(String src, Class clazz) { + try { + return new ObjectMapperResults(mapper.readValue(src, clazz)); + } catch (Exception ex) { + return new ObjectMapperResults(ex); + } + } + + /** + * Convert an java object to a class + * @param src Object to convert + * @param clazz Class to convert the object to + * @param The expected class of the value + * @return the object populated with the data in the json string + */ + public static ObjectMapperResults readValue(Object src, Class clazz) { + try { + return readValue(mapper.writeValueAsString(src), clazz); + } catch (Exception ex) { + return new ObjectMapperResults(ex); + } + } + + /** + * Converts an object to a list + * @param object Object to convert + * @param objectClass Class to convert the object to + * @param The expected class of the object + * @return the object list populated with the data in the json string + */ + public static > ObjectMapperListResults> readListValue(Object object, Class objectClass) { + return readListValue(object, objectClass, ArrayList.class); + } + + /** + * Converts an object to a list + * @param object Object to convert + * @param objectClass Class to convert the object to + * @param listClass List type to make + * @param The expected class of the object + * @param The expect class of the list + * @return the object list populated with the data in the json string + */ + public static > ObjectMapperListResults> readListValue(Object object, Class objectClass, Class listClass) { + try { + return readListValue(mapper.writeValueAsString(object), objectClass, listClass); + } catch (Exception ex) { + return new ObjectMapperListResults<>(ex); + } + } + + /** + * Converts an object to a list + * @param src Source to convert + * @param objectClass Class to convert the object to + * @param listClass List type to make + * @param The expected class of the object + * @param The expect class of the list + * @return the object list populated with the data in the json string + */ + public static > ObjectMapperListResults> readListValue(String src, Class objectClass, Class listClass) { + try { + List item = mapper.readValue(src, mapper.getTypeFactory().constructCollectionType(listClass, objectClass)); + return new ObjectMapperListResults<>(item); + } catch (Exception ex) { + return new ObjectMapperListResults<>(ex); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/locusworks/common/objectmapper/ObjectMapperListResults.java b/src/main/java/net/locusworks/common/objectmapper/ObjectMapperListResults.java new file mode 100644 index 0000000..b1af466 --- /dev/null +++ b/src/main/java/net/locusworks/common/objectmapper/ObjectMapperListResults.java @@ -0,0 +1,51 @@ +package net.locusworks.common.objectmapper; + +import java.util.Collection; + +/** + * Holds the results from the object mapper list conversion + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + * @param class type of the object mapper + */ +public class ObjectMapperListResults> extends ObjectMapperResults { + + /** + * Constructor + * @param results results from the conversion + */ + public ObjectMapperListResults(T results) { + this(results, null); + } + + /** + * Constructor + * @param exception exception that was thrown during conversion + */ + public ObjectMapperListResults(Throwable exception) { + this(null, exception); + } + + /** + * Constructor + * @param results results from the conversion + * @param exception exception that was thrown during conversion + */ + public ObjectMapperListResults(T results, Throwable exception) { + super(results, exception); + } + + /** + * Add the error handler to the results to retrieve the error that caused + * the exception + * @param error the error handler to use + * @return this + */ + public ObjectMapperListResults withErrorHandler(ObjectMapperError error) { + if (this.hasError() && error != null) { + error.getError(this.getException()); + } + return this; + } +} \ No newline at end of file diff --git a/src/main/java/net/locusworks/common/objectmapper/ObjectMapperResults.java b/src/main/java/net/locusworks/common/objectmapper/ObjectMapperResults.java new file mode 100644 index 0000000..092be8f --- /dev/null +++ b/src/main/java/net/locusworks/common/objectmapper/ObjectMapperResults.java @@ -0,0 +1,93 @@ +package net.locusworks.common.objectmapper; + +/** + * Holds the results from the object mapper list conversion + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + * @param class type of the object being converted from json to object + */ +public class ObjectMapperResults { + + private Throwable exception; + private T results; + + /** + * Constructor + * @param results results from the conversion + */ + public ObjectMapperResults(T results) { + this(results, null); + } + + /** + * Constructor + * @param exception exception that was thrown during conversion + */ + public ObjectMapperResults(Throwable exception) { + this(null, exception); + } + + /** + * Constructor + * @param results results from the conversion + * @param exception exception that was thrown during conversion + */ + public ObjectMapperResults(T results, Throwable exception) { + this.results = results; + this.exception = exception; + } + + /** + * get the exception that happened during conversion + * @return exception + */ + public Throwable getException() { + return exception; + } + + /** + * Set the exception + * @param exception + */ + public void setException(Throwable exception) { + this.exception = exception; + } + + /** + * Get the result + * @return the converted results + */ + public T getResults() { + return results; + } + + /** + * set the results + * @param results results to set + */ + public void setResults(T results) { + this.results = results; + } + + /** + * Check to see if the conversion caused an error + * @return true if there is an error, false otherwise + */ + public boolean hasError() { + return this.exception != null; + } + + /** + * Add the error handler to the results to retrieve the error that caused + * the exception + * @param error the error handler to use + * @return this + */ + public ObjectMapperResults withErrorHandler(ObjectMapperError error) { + if (this.hasError() && error != null) { + error.getError(this.getException()); + } + return this; + } +} diff --git a/src/main/java/net/locusworks/common/properties/ImmutableProperties.java b/src/main/java/net/locusworks/common/properties/ImmutableProperties.java new file mode 100644 index 0000000..0884479 --- /dev/null +++ b/src/main/java/net/locusworks/common/properties/ImmutableProperties.java @@ -0,0 +1,31 @@ +package net.locusworks.common.properties; + +import java.util.Properties; + +public class ImmutableProperties extends Properties { + + private static final long serialVersionUID = 65942088008978137L; + + public ImmutableProperties() { + super(); + } + + public ImmutableProperties(Properties props) { + super(); + if (props == null || props.isEmpty()) return; + + props.entrySet().forEach(item -> this.put(item.getKey(), item.getValue())); + } + + @Override + public synchronized Object setProperty(String key, String value) { + return put(key, value); + } + + public synchronized Object put(Object key, Object value) { + if (containsKey(key)) + throw new RuntimeException("Cannot change key value once its set: " + key); + return super.put(key, value); + } + +} diff --git a/src/main/java/net/locusworks/common/properties/OrderedProperties.java b/src/main/java/net/locusworks/common/properties/OrderedProperties.java new file mode 100644 index 0000000..dcefa70 --- /dev/null +++ b/src/main/java/net/locusworks/common/properties/OrderedProperties.java @@ -0,0 +1,1026 @@ +package net.locusworks.common.properties; + +/* + * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.BufferedWriter; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * The {@code Properties} class represents a persistent set of + * properties. The {@code Properties} can be saved to a stream + * or loaded from a stream. Each key and its corresponding value in + * the property list is a string. + *

+ * A property list can contain another property list as its + * "defaults"; this second property list is searched if + * the property key is not found in the original property list. + *

+ * Because {@code Properties} inherits from {@code Hashtable}, the + * {@code put} and {@code putAll} methods can be applied to a + * {@code Properties} object. Their use is strongly discouraged as they + * allow the caller to insert entries whose keys or values are not + * {@code Strings}. The {@code setProperty} method should be used + * instead. If the {@code store} or {@code save} method is called + * on a "compromised" {@code Properties} object that contains a + * non-{@code String} key or value, the call will fail. Similarly, + * the call to the {@code propertyNames} or {@code list} method + * will fail if it is called on a "compromised" {@code Properties} + * object that contains a non-{@code String} key. + * + *

+ * The {@link #load(java.io.Reader) load(Reader)} / + * {@link #store(java.io.Writer, java.lang.String) store(Writer, String)} + * methods load and store properties from and to a character based stream + * in a simple line-oriented format specified below. + * + * The {@link #load(java.io.InputStream) load(InputStream)} / + * {@link #store(java.io.OutputStream, java.lang.String) store(OutputStream, String)} + * methods work the same way as the load(Reader)/store(Writer, String) pair, except + * the input/output stream is encoded in ISO 8859-1 character encoding. + * Characters that cannot be directly represented in this encoding can be written using + * Unicode escapes as defined in section 3.3 of + * The Java™ Language Specification; + * only a single 'u' character is allowed in an escape + * sequence. The native2ascii tool can be used to convert property files to and + * from other character encodings. + * + *

The {@link #loadFromXML(InputStream)} and {@link + * #storeToXML(OutputStream, String, String)} methods load and store properties + * in a simple XML format. By default the UTF-8 character encoding is used, + * however a specific encoding may be specified if required. Implementations + * are required to support UTF-8 and UTF-16 and may support other encodings. + * An XML properties document has the following DOCTYPE declaration: + * + *

+ * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+ * 
+ * Note that the system URI (http://java.sun.com/dtd/properties.dtd) is + * not accessed when exporting or importing properties; it merely + * serves as a string to uniquely identify the DTD, which is: + *
+ *    <?xml version="1.0" encoding="UTF-8"?>
+ *
+ *    <!-- DTD for properties -->
+ *
+ *    <!ELEMENT properties ( comment?, entry* ) >
+ *
+ *    <!ATTLIST properties version CDATA #FIXED "1.0">
+ *
+ *    <!ELEMENT comment (#PCDATA) >
+ *
+ *    <!ELEMENT entry (#PCDATA) >
+ *
+ *    <!ATTLIST entry key CDATA #REQUIRED>
+ * 
+ * + *

This class is thread-safe: multiple threads can share a single + * Properties object without the need for external synchronization. + * + * @see native2ascii tool for Solaris + * @see native2ascii tool for Windows + * + * @author Arthur van Hoff + * @author Michael McCloskey + * @author Xueming Shen + * @since JDK1.0 + */ +public class OrderedProperties extends LinkedHashMap { + /** + * use serialVersionUID from JDK 1.1.X for interoperability + */ + private static final long serialVersionUID = 4112578634023874840L; + + /** + * A property list that contains default values for any keys not + * found in this property list. + * + * @serial + */ + protected OrderedProperties defaults; + + /** + * Creates an empty property list with no default values. + */ + public OrderedProperties() { + this(null); + } + + /** + * Creates an empty property list with the specified defaults. + * + * @param defaults the defaults. + */ + public OrderedProperties(OrderedProperties defaults) { + this.defaults = defaults; + } + + /** + * Calls the Hashtable method {@code put}. Provided for + * parallelism with the getProperty method. Enforces use of + * strings for property keys and values. The value returned is the + * result of the Hashtable call to {@code put}. + * + * @param key the key to be placed into this property list. + * @param value the value corresponding to key. + * @return the previous value of the specified key in this property + * list, or {@code null} if it did not have one. + * @see #getProperty + * @since 1.2 + */ + public synchronized Object setProperty(String key, String value) { + return put(key, value); + } + + + /** + * Reads a property list (key and element pairs) from the input + * character stream in a simple line-oriented format. + *

+ * Properties are processed in terms of lines. There are two + * kinds of line, natural lines and logical lines. + * A natural line is defined as a line of + * characters that is terminated either by a set of line terminator + * characters ({@code \n} or {@code \r} or {@code \r\n}) + * or by the end of the stream. A natural line may be either a blank line, + * a comment line, or hold all or some of a key-element pair. A logical + * line holds all the data of a key-element pair, which may be spread + * out across several adjacent natural lines by escaping + * the line terminator sequence with a backslash character + * {@code \}. Note that a comment line cannot be extended + * in this manner; every natural line that is a comment must have + * its own comment indicator, as described below. Lines are read from + * input until the end of the stream is reached. + * + *

+ * A natural line that contains only white space characters is + * considered blank and is ignored. A comment line has an ASCII + * {@code '#'} or {@code '!'} as its first non-white + * space character; comment lines are also ignored and do not + * encode key-element information. In addition to line + * terminators, this format considers the characters space + * ({@code ' '}, {@code '\u005Cu0020'}), tab + * ({@code '\t'}, {@code '\u005Cu0009'}), and form feed + * ({@code '\f'}, {@code '\u005Cu000C'}) to be white + * space. + * + *

+ * If a logical line is spread across several natural lines, the + * backslash escaping the line terminator sequence, the line + * terminator sequence, and any white space at the start of the + * following line have no affect on the key or element values. + * The remainder of the discussion of key and element parsing + * (when loading) will assume all the characters constituting + * the key and element appear on a single natural line after + * line continuation characters have been removed. Note that + * it is not sufficient to only examine the character + * preceding a line terminator sequence to decide if the line + * terminator is escaped; there must be an odd number of + * contiguous backslashes for the line terminator to be escaped. + * Since the input is processed from left to right, a + * non-zero even number of 2n contiguous backslashes + * before a line terminator (or elsewhere) encodes n + * backslashes after escape processing. + * + *

+ * The key contains all of the characters in the line starting + * with the first non-white space character and up to, but not + * including, the first unescaped {@code '='}, + * {@code ':'}, or white space character other than a line + * terminator. All of these key termination characters may be + * included in the key by escaping them with a preceding backslash + * character; for example,

+ * + * {@code \:\=}

+ * + * would be the two-character key {@code ":="}. Line + * terminator characters can be included using {@code \r} and + * {@code \n} escape sequences. Any white space after the + * key is skipped; if the first non-white space character after + * the key is {@code '='} or {@code ':'}, then it is + * ignored and any white space characters after it are also + * skipped. All remaining characters on the line become part of + * the associated element string; if there are no remaining + * characters, the element is the empty string + * {@code ""}. Once the raw character sequences + * constituting the key and element are identified, escape + * processing is performed as described above. + * + *

+ * As an example, each of the following three lines specifies the key + * {@code "Truth"} and the associated element value + * {@code "Beauty"}: + *

+   * Truth = Beauty
+   *  Truth:Beauty
+   * Truth                    :Beauty
+   * 
+ * As another example, the following three lines specify a single + * property: + *
+   * fruits                           apple, banana, pear, \
+   *                                  cantaloupe, watermelon, \
+   *                                  kiwi, mango
+   * 
+ * The key is {@code "fruits"} and the associated element is: + *
"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"
+ * Note that a space appears before each {@code \} so that a space + * will appear after each comma in the final result; the {@code \}, + * line terminator, and leading white space on the continuation line are + * merely discarded and are not replaced by one or more other + * characters. + *

+ * As a third example, the line: + *

cheeses
+   * 
+ * specifies that the key is {@code "cheeses"} and the associated + * element is the empty string {@code ""}. + *

+ * + * Characters in keys and elements can be represented in escape + * sequences similar to those used for character and string literals + * (see sections 3.3 and 3.10.6 of + * The Java™ Language Specification). + * + * The differences from the character escape sequences and Unicode + * escapes used for characters and strings are: + * + *

    + *
  • Octal escapes are not recognized. + * + *
  • The character sequence {@code \b} does not + * represent a backspace character. + * + *
  • The method does not treat a backslash character, + * {@code \}, before a non-valid escape character as an + * error; the backslash is silently dropped. For example, in a + * Java string the sequence {@code "\z"} would cause a + * compile time error. In contrast, this method silently drops + * the backslash. Therefore, this method treats the two character + * sequence {@code "\b"} as equivalent to the single + * character {@code 'b'}. + * + *
  • Escapes are not necessary for single and double quotes; + * however, by the rule above, single and double quote characters + * preceded by a backslash still yield single and double quote + * characters, respectively. + * + *
  • Only a single 'u' character is allowed in a Unicode escape + * sequence. + * + *
+ *

+ * The specified stream remains open after this method returns. + * + * @param reader the input character stream. + * @throws IOException if an error occurred when reading from the + * input stream. + * @throws IllegalArgumentException if a malformed Unicode escape + * appears in the input. + * @since 1.6 + */ + public synchronized void load(Reader reader) throws IOException { + load0(new LineReader(reader)); + } + + /** + * Reads a property list (key and element pairs) from the input + * byte stream. The input stream is in a simple line-oriented + * format as specified in + * {@link #load(java.io.Reader) load(Reader)} and is assumed to use + * the ISO 8859-1 character encoding; that is each byte is one Latin1 + * character. Characters not in Latin1, and certain special characters, + * are represented in keys and elements using Unicode escapes as defined in + * section 3.3 of + * The Java™ Language Specification. + *

+ * The specified stream remains open after this method returns. + * + * @param inStream the input stream. + * @exception IOException if an error occurred when reading from the + * input stream. + * @throws IllegalArgumentException if the input stream contains a + * malformed Unicode escape sequence. + * @since 1.2 + */ + public synchronized void load(InputStream inStream) throws IOException { + load0(new LineReader(inStream)); + } + + private void load0 (LineReader lr) throws IOException { + char[] convtBuf = new char[1024]; + int limit; + int keyLen; + int valueStart; + char c; + boolean hasSep; + boolean precedingBackslash; + + while ((limit = lr.readLine()) >= 0) { + c = 0; + keyLen = 0; + valueStart = limit; + hasSep = false; + + //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">"); + precedingBackslash = false; + while (keyLen < limit) { + c = lr.lineBuf[keyLen]; + //need check if escaped. + if ((c == '=' || c == ':') && !precedingBackslash) { + valueStart = keyLen + 1; + hasSep = true; + break; + } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { + valueStart = keyLen + 1; + break; + } + if (c == '\\') { + precedingBackslash = !precedingBackslash; + } else { + precedingBackslash = false; + } + keyLen++; + } + while (valueStart < limit) { + c = lr.lineBuf[valueStart]; + if (c != ' ' && c != '\t' && c != '\f') { + if (!hasSep && (c == '=' || c == ':')) { + hasSep = true; + } else { + break; + } + } + valueStart++; + } + String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); + String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); + put(key, value); + } + } + + /* Read in a "logical line" from an InputStream/Reader, skip all comment + * and blank lines and filter out those leading whitespace characters + * (\u0020, \u0009 and \u000c) from the beginning of a "natural line". + * Method returns the char length of the "logical line" and stores + * the line in "lineBuf". + */ + class LineReader { + public LineReader(InputStream inStream) { + this.inStream = inStream; + inByteBuf = new byte[8192]; + } + + public LineReader(Reader reader) { + this.reader = reader; + inCharBuf = new char[8192]; + } + + byte[] inByteBuf; + char[] inCharBuf; + char[] lineBuf = new char[1024]; + int inLimit = 0; + int inOff = 0; + InputStream inStream; + Reader reader; + + int readLine() throws IOException { + int len = 0; + char c = 0; + + boolean skipWhiteSpace = true; + boolean isCommentLine = false; + boolean isNewLine = true; + boolean appendedLineBegin = false; + boolean precedingBackslash = false; + boolean skipLF = false; + + while (true) { + if (inOff >= inLimit) { + inLimit = (inStream==null)?reader.read(inCharBuf) + :inStream.read(inByteBuf); + inOff = 0; + if (inLimit <= 0) { + if (len == 0 || isCommentLine) { + return -1; + } + if (precedingBackslash) { + len--; + } + return len; + } + } + if (inStream != null) { + //The line below is equivalent to calling a + //ISO8859-1 decoder. + c = (char) (0xff & inByteBuf[inOff++]); + } else { + c = inCharBuf[inOff++]; + } + if (skipLF) { + skipLF = false; + if (c == '\n') { + continue; + } + } + if (skipWhiteSpace) { + if (c == ' ' || c == '\t' || c == '\f') { + continue; + } + if (!appendedLineBegin && (c == '\r' || c == '\n')) { + continue; + } + skipWhiteSpace = false; + appendedLineBegin = false; + } + if (isNewLine) { + isNewLine = false; + if (c == '#' || c == '!') { + isCommentLine = true; + continue; + } + } + + if (c != '\n' && c != '\r') { + lineBuf[len++] = c; + if (len == lineBuf.length) { + int newLength = lineBuf.length * 2; + if (newLength < 0) { + newLength = Integer.MAX_VALUE; + } + char[] buf = new char[newLength]; + System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length); + lineBuf = buf; + } + //flip the preceding backslash flag + if (c == '\\') { + precedingBackslash = !precedingBackslash; + } else { + precedingBackslash = false; + } + } + else { + // reached EOL + if (isCommentLine || len == 0) { + isCommentLine = false; + isNewLine = true; + skipWhiteSpace = true; + len = 0; + continue; + } + if (inOff >= inLimit) { + inLimit = (inStream==null) + ?reader.read(inCharBuf) + :inStream.read(inByteBuf); + inOff = 0; + if (inLimit <= 0) { + if (precedingBackslash) { + len--; + } + return len; + } + } + if (precedingBackslash) { + len -= 1; + //skip the leading whitespace characters in following line + skipWhiteSpace = true; + appendedLineBegin = true; + precedingBackslash = false; + if (c == '\r') { + skipLF = true; + } + } else { + return len; + } + } + } + } + } + + /* + * Converts encoded \uxxxx to unicode chars + * and changes special saved chars to their original forms + */ + private String loadConvert (char[] in, int off, int len, char[] convtBuf) { + if (convtBuf.length < len) { + int newLen = len * 2; + if (newLen < 0) { + newLen = Integer.MAX_VALUE; + } + convtBuf = new char[newLen]; + } + char aChar; + char[] out = convtBuf; + int outLen = 0; + int end = off + len; + + while (off < end) { + aChar = in[off++]; + if (aChar == '\\') { + aChar = in[off++]; + if(aChar == 'u') { + // Read the xxxx + int value=0; + for (int i=0; i<4; i++) { + aChar = in[off++]; + switch (aChar) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + value = (value << 4) + aChar - '0'; + break; + case 'a': case 'b': case 'c': + case 'd': case 'e': case 'f': + value = (value << 4) + 10 + aChar - 'a'; + break; + case 'A': case 'B': case 'C': + case 'D': case 'E': case 'F': + value = (value << 4) + 10 + aChar - 'A'; + break; + default: + throw new IllegalArgumentException( + "Malformed \\uxxxx encoding."); + } + } + out[outLen++] = (char)value; + } else { + if (aChar == 't') aChar = '\t'; + else if (aChar == 'r') aChar = '\r'; + else if (aChar == 'n') aChar = '\n'; + else if (aChar == 'f') aChar = '\f'; + out[outLen++] = aChar; + } + } else { + out[outLen++] = aChar; + } + } + return new String (out, 0, outLen); + } + + /* + * Converts unicodes to encoded \uxxxx and escapes + * special characters with a preceding slash + */ + private String saveConvert(String theString, + boolean escapeSpace, + boolean escapeUnicode) { + int len = theString.length(); + int bufLen = len * 2; + if (bufLen < 0) { + bufLen = Integer.MAX_VALUE; + } + StringBuffer outBuffer = new StringBuffer(bufLen); + + for(int x=0; x 61) && (aChar < 127)) { + if (aChar == '\\') { + outBuffer.append('\\'); outBuffer.append('\\'); + continue; + } + outBuffer.append(aChar); + continue; + } + switch(aChar) { + case ' ': + if (x == 0 || escapeSpace) + outBuffer.append('\\'); + outBuffer.append(' '); + break; + case '\t':outBuffer.append('\\'); outBuffer.append('t'); + break; + case '\n':outBuffer.append('\\'); outBuffer.append('n'); + break; + case '\r':outBuffer.append('\\'); outBuffer.append('r'); + break; + case '\f':outBuffer.append('\\'); outBuffer.append('f'); + break; + case '=': // Fall through + case ':': // Fall through + case '#': // Fall through + case '!': + outBuffer.append('\\'); outBuffer.append(aChar); + break; + default: + if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) { + outBuffer.append('\\'); + outBuffer.append('u'); + outBuffer.append(toHex((aChar >> 12) & 0xF)); + outBuffer.append(toHex((aChar >> 8) & 0xF)); + outBuffer.append(toHex((aChar >> 4) & 0xF)); + outBuffer.append(toHex( aChar & 0xF)); + } else { + outBuffer.append(aChar); + } + } + } + return outBuffer.toString(); + } + + private static void writeComments(BufferedWriter bw, String comments) + throws IOException { + bw.write("#"); + int len = comments.length(); + int current = 0; + int last = 0; + char[] uu = new char[6]; + uu[0] = '\\'; + uu[1] = 'u'; + while (current < len) { + char c = comments.charAt(current); + if (c > '\u00ff' || c == '\n' || c == '\r') { + if (last != current) + bw.write(comments.substring(last, current)); + if (c > '\u00ff') { + uu[2] = toHex((c >> 12) & 0xf); + uu[3] = toHex((c >> 8) & 0xf); + uu[4] = toHex((c >> 4) & 0xf); + uu[5] = toHex( c & 0xf); + bw.write(new String(uu)); + } else { + bw.newLine(); + if (c == '\r' && + current != len - 1 && + comments.charAt(current + 1) == '\n') { + current++; + } + if (current == len - 1 || + (comments.charAt(current + 1) != '#' && + comments.charAt(current + 1) != '!')) + bw.write("#"); + } + last = current + 1; + } + current++; + } + if (last != current) + bw.write(comments.substring(last, current)); + bw.newLine(); + } + + /** + * Calls the {@code store(OutputStream out, String comments)} method + * and suppresses IOExceptions that were thrown. + * + * @deprecated This method does not throw an IOException if an I/O error + * occurs while saving the property list. The preferred way to save a + * properties list is via the {@code store(OutputStream out, + * String comments)} method or the + * {@code storeToXML(OutputStream os, String comment)} method. + * + * @param out an output stream. + * @param comments a description of the property list. + * @exception ClassCastException if this {@code Properties} object + * contains any keys or values that are not + * {@code Strings}. + */ + @Deprecated + public void save(OutputStream out, String comments) { + try { + store(out, comments); + } catch (IOException e) { + } + } + + /** + * Writes this property list (key and element pairs) in this + * {@code Properties} table to the output character stream in a + * format suitable for using the {@link #load(java.io.Reader) load(Reader)} + * method. + *

+ * Properties from the defaults table of this {@code Properties} + * table (if any) are not written out by this method. + *

+ * If the comments argument is not null, then an ASCII {@code #} + * character, the comments string, and a line separator are first written + * to the output stream. Thus, the {@code comments} can serve as an + * identifying comment. Any one of a line feed ('\n'), a carriage + * return ('\r'), or a carriage return followed immediately by a line feed + * in comments is replaced by a line separator generated by the {@code Writer} + * and if the next character in comments is not character {@code #} or + * character {@code !} then an ASCII {@code #} is written out + * after that line separator. + *

+ * Next, a comment line is always written, consisting of an ASCII + * {@code #} character, the current date and time (as if produced + * by the {@code toString} method of {@code Date} for the + * current time), and a line separator as generated by the {@code Writer}. + *

+ * Then every entry in this {@code Properties} table is + * written out, one per line. For each entry the key string is + * written, then an ASCII {@code =}, then the associated + * element string. For the key, all space characters are + * written with a preceding {@code \} character. For the + * element, leading space characters, but not embedded or trailing + * space characters, are written with a preceding {@code \} + * character. The key and element characters {@code #}, + * {@code !}, {@code =}, and {@code :} are written + * with a preceding backslash to ensure that they are properly loaded. + *

+ * After the entries have been written, the output stream is flushed. + * The output stream remains open after this method returns. + *

+ * + * @param writer an output character stream writer. + * @param comments a description of the property list. + * @exception IOException if writing this property list to the specified + * output stream throws an IOException. + * @exception ClassCastException if this {@code Properties} object + * contains any keys or values that are not {@code Strings}. + * @exception NullPointerException if {@code writer} is null. + * @since 1.6 + */ + public void store(Writer writer, String comments) + throws IOException + { + store0((writer instanceof BufferedWriter)?(BufferedWriter)writer + : new BufferedWriter(writer), + comments, + false); + } + + /** + * Writes this property list (key and element pairs) in this + * {@code Properties} table to the output stream in a format suitable + * for loading into a {@code Properties} table using the + * {@link #load(InputStream) load(InputStream)} method. + *

+ * Properties from the defaults table of this {@code Properties} + * table (if any) are not written out by this method. + *

+ * This method outputs the comments, properties keys and values in + * the same format as specified in + * {@link #store(java.io.Writer, java.lang.String) store(Writer)}, + * with the following differences: + *

    + *
  • The stream is written using the ISO 8859-1 character encoding. + * + *
  • Characters not in Latin-1 in the comments are written as + * {@code \u005Cu}xxxx for their appropriate unicode + * hexadecimal value xxxx. + * + *
  • Characters less than {@code \u005Cu0020} and characters greater + * than {@code \u005Cu007E} in property keys or values are written + * as {@code \u005Cu}xxxx for the appropriate hexadecimal + * value xxxx. + *
+ *

+ * After the entries have been written, the output stream is flushed. + * The output stream remains open after this method returns. + *

+ * @param out an output stream. + * @param comments a description of the property list. + * @exception IOException if writing this property list to the specified + * output stream throws an IOException. + * @exception ClassCastException if this {@code Properties} object + * contains any keys or values that are not {@code Strings}. + * @exception NullPointerException if {@code out} is null. + * @since 1.2 + */ + public void store(OutputStream out, String comments) + throws IOException + { + store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")), + comments, + true); + } + + private void store0(BufferedWriter bw, String comments, boolean escUnicode) + throws IOException + { + if (comments != null) { + writeComments(bw, comments); + } + bw.write("#" + new Date().toString()); + bw.newLine(); + synchronized (this) { + for (Enumeration e = keys(); e.hasMoreElements();) { + String key = (String)e.nextElement(); + String val = (String)get(key); + key = saveConvert(key, true, escUnicode); + /* No need to escape embedded and trailing spaces for value, hence + * pass false to flag. + */ + val = saveConvert(val, false, escUnicode); + bw.write(key + "=" + val); + bw.newLine(); + } + } + bw.flush(); + } + + /** + * Searches for the property with the specified key in this property list. + * If the key is not found in this property list, the default property list, + * and its defaults, recursively, are then checked. The method returns + * {@code null} if the property is not found. + * + * @param key the property key. + * @return the value in this property list with the specified key value. + * @see #setProperty + * @see #defaults + */ + public String getProperty(String key) { + Object oval = super.get(key); + String sval = (oval instanceof String) ? (String)oval : null; + return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval; + } + + /** + * Searches for the property with the specified key in this property list. + * If the key is not found in this property list, the default property list, + * and its defaults, recursively, are then checked. The method returns the + * default value argument if the property is not found. + * + * @param key the hashtable key. + * @param defaultValue a default value. + * + * @return the value in this property list with the specified key value. + * @see #setProperty + * @see #defaults + */ + public String getProperty(String key, String defaultValue) { + String val = getProperty(key); + return (val == null) ? defaultValue : val; + } + + /** + * Returns an enumeration of all the keys in this property list, + * including distinct keys in the default property list if a key + * of the same name has not already been found from the main + * properties list. + * + * @return an enumeration of all the keys in this property list, including + * the keys in the default property list. + * @throws ClassCastException if any key in this property list + * is not a string. + * @see java.util.Enumeration + * @see java.util.Properties#defaults + * @see #stringPropertyNames + */ + public Enumeration propertyNames() { + LinkedHashMap h = new LinkedHashMap<>(); + enumerate(h); + return Collections.enumeration(h.keySet()); + } + + /** + * Returns a set of keys in this property list where + * the key and its corresponding value are strings, + * including distinct keys in the default property list if a key + * of the same name has not already been found from the main + * properties list. Properties whose key or value is not + * of type String are omitted. + *

+ * The returned set is not backed by the Properties object. + * Changes to this Properties are not reflected in the set, + * or vice versa. + * + * @return a set of keys in this property list where + * the key and its corresponding value are strings, + * including the keys in the default property list. + * @see java.util.Properties#defaults + * @since 1.6 + */ + public Set stringPropertyNames() { + LinkedHashMap h = new LinkedHashMap<>(); + enumerateStringProperties(h); + return h.keySet(); + } + + /** + * Prints this property list out to the specified output stream. + * This method is useful for debugging. + * + * @param out an output stream. + * @throws ClassCastException if any key in this property list + * is not a string. + */ + public void list(PrintStream out) { + out.println("-- listing properties --"); + LinkedHashMap h = new LinkedHashMap<>(); + enumerate(h); + for (Enumeration e = Collections.enumeration(h.keySet()) ; e.hasMoreElements() ;) { + String key = e.nextElement(); + String val = (String)h.get(key); + if (val.length() > 40) { + val = val.substring(0, 37) + "..."; + } + out.println(key + "=" + val); + } + } + + /** + * Prints this property list out to the specified output stream. + * This method is useful for debugging. + * + * @param out an output stream. + * @throws ClassCastException if any key in this property list + * is not a string. + * @since JDK1.1 + */ + /* + * Rather than use an anonymous inner class to share common code, this + * method is duplicated in order to ensure that a non-1.1 compiler can + * compile this file. + */ + public void list(PrintWriter out) { + out.println("-- listing properties --"); + LinkedHashMap h = new LinkedHashMap<>(); + enumerate(h); + for (Enumeration e = Collections.enumeration(h.keySet()) ; e.hasMoreElements() ;) { + String key = e.nextElement(); + String val = (String)h.get(key); + if (val.length() > 40) { + val = val.substring(0, 37) + "..."; + } + out.println(key + "=" + val); + } + } + + /** + * Enumerates all key/value pairs in the specified hashtable. + * @param h the hashtable + * @throws ClassCastException if any of the property keys + * is not of String type. + */ + private synchronized void enumerate(LinkedHashMap h) { + if (defaults != null) { + defaults.enumerate(h); + } + for (Enumeration e = keys() ; e.hasMoreElements() ;) { + String key = (String)e.nextElement(); + h.put(key, get(key)); + } + } + + private Enumeration keys() { + return Collections.enumeration(keySet()); + } + + /** + * Enumerates all key/value pairs in the specified hashtable + * and omits the property if the key or value is not a string. + * @param h the hashtable + */ + private synchronized void enumerateStringProperties(Map h) { + if (defaults != null) { + defaults.enumerateStringProperties(h); + } + for (Enumeration e = keys() ; e.hasMoreElements() ;) { + Object k = e.nextElement(); + Object v = get(k); + if (k instanceof String && v instanceof String) { + h.put((String) k, (String) v); + } + } + } + + /** + * Convert a nibble to a hex character + * @param nibble the nibble to convert. + */ + private static char toHex(int nibble) { + return hexDigit[(nibble & 0xF)]; + } + + /** A table of hex digits */ + private static final char[] hexDigit = { + '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' + }; +} diff --git a/src/main/java/net/locusworks/common/utils/Checks.java b/src/main/java/net/locusworks/common/utils/Checks.java new file mode 100644 index 0000000..85ef34b --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/Checks.java @@ -0,0 +1,25 @@ +package net.locusworks.common.utils; + +public class Checks { + + public static void checkArguments(boolean expression, String error) { + checkArguments(expression, "%s", error); + } + + public static void checkArguments(boolean expression, String errorFmt, Object... args) { + if (!expression) throw new IllegalArgumentException(String.format(errorFmt, args)); + } + + public static void checkState(boolean expression, String error) { + checkState(expression, "%s", error); + } + + public static void checkState(boolean expression, String errorFmt, Object... args) { + if (!expression) throw new IllegalStateException(String.format(errorFmt, args)); + } + + public static void checkNotNull(Object item, String error) { + if (item == null) throw new IllegalAccessError("Provided item is null"); + } + +} diff --git a/src/main/java/net/locusworks/common/utils/Constants.java b/src/main/java/net/locusworks/common/utils/Constants.java new file mode 100644 index 0000000..3b28c0c --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/Constants.java @@ -0,0 +1,19 @@ +package net.locusworks.common.utils; + +/** + * Class to hold final static constant values used across the system + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + */ +public class Constants { + + public static final short TRUE = (short)1; + public static final short FALSE = (short)0; + + public static final short EXIT_SUCCESS = (short)0; + public static final short EXIT_FAIL = (short)1; + + public static final String LOG4J_CONFIG_PROPERTY = "log4j.configurationFile"; + public static final String JUNIT_TEST_CHECK = "junit.test"; +} diff --git a/src/main/java/net/locusworks/common/utils/DataOutputStreamHelper.java b/src/main/java/net/locusworks/common/utils/DataOutputStreamHelper.java new file mode 100644 index 0000000..238c203 --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/DataOutputStreamHelper.java @@ -0,0 +1,50 @@ +package net.locusworks.common.utils; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Base64; + +import net.locusworks.common.Charsets; + +public class DataOutputStreamHelper extends DataOutputStream implements AutoCloseable{ + + public DataOutputStreamHelper() { + this(new ByteArrayOutputStream()); + } + + public DataOutputStreamHelper(OutputStream out) { + super(out); + } + + public byte[] toByteArray() { + if (super.out == null) { + return new byte[0]; + } + if (super.out instanceof ByteArrayOutputStream) { + return ((ByteArrayOutputStream)super.out).toByteArray(); + } + return super.out.toString().getBytes(Charsets.UTF_8); + } + + public String base64Encoded() { + return Base64.getEncoder().encodeToString(this.toByteArray()); + } + + @Override + public String toString() { + return new String(this.toByteArray(), Charsets.UTF_8); + } + + @Override + public void close() throws IOException { + if (super.out != null) { + try { + super.out.close(); + super.out = null; + } catch (Exception ex) {} + } + } + +} diff --git a/src/main/java/net/locusworks/common/utils/DateTimeStampDeserializer.java b/src/main/java/net/locusworks/common/utils/DateTimeStampDeserializer.java new file mode 100644 index 0000000..957b223 --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/DateTimeStampDeserializer.java @@ -0,0 +1,107 @@ +package net.locusworks.common.utils; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +/** + * Class to deseralize json data into a Date object back into a class that specifies this deserializer
+ * For example the below code: + *

+ * {@code @JsonDeserialize(using=DateTimeStampDeserializer.class)
+ * private Date purgeEndDate;}
+ * 
+ * Will specify to use this deserializer class when a json field {@code purgeEndDate} is encountered in the json + * string and will try to convert the string into a date object and inject the value back into the class + * @author Isaac Parenteau + * @version 1.0 + * @date 02/15/2018 + * @see com.fasterxml.jackson.databind.annotation.JsonDeserialize + */ +public class DateTimeStampDeserializer extends JsonDeserializer { + + private static final String DEFAULT = "MM/dd/yyyy"; + private static final String EXPANDED = "MM/dd/yyyy HH:mm:ss z"; + private static final String EXPANDED_WITH_TIMEZONE = "MMM d, yyyy HH:mm:ss z"; + private static final String EXPANDED_WITH_AM_PM = "MMM d, yyyy h:mm:ss a"; + + private static final String[] formats = new String[] { + DEFAULT, + EXPANDED, + EXPANDED_WITH_TIMEZONE, + EXPANDED_WITH_AM_PM, + }; + + private static final Integer[] styles = new Integer[] { + SimpleDateFormat.LONG, + SimpleDateFormat.FULL, + SimpleDateFormat.MEDIUM, + SimpleDateFormat.SHORT + }; + + @Override + public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + String value = p.getText(); + + Date date = null; + //First try to see if the value can be parsed into a long + try { + date = new Date(Long.parseLong(value)); + return date; + } catch (Exception ex) { } + + //Next iterate over the built in styles to see if it can be converted + for (Integer style: styles) { + date = formatDate(style, value); + if (date != null) { + return date; + } + } + + //Lastly iterate over the custom styles specified in format to see if it can be converted + for (String fmt : formats) { + date = formatDate(fmt, value); + if (date != null) { + return date; + } + } + + //Return null if date format can't be converted + return null; + } + + /** + * Convert a string value to a date object + * @param format The format to use in reference to the source + * @param source the source to convert + * @return Date object if the conversion was success; null otherwise + */ + private static Date formatDate(String format, String source) { + try { + return new SimpleDateFormat(format).parse(source); + } catch (Exception ex) { + return null; + } + } + + /** + * Convert a string value to a date object using SimpleDateFormats + * built in styles + * @param style The style to use + * @param source the source to convert + * @return Date object if the conversion was success; null otherwise + */ + private static Date formatDate(Integer style, String source) { + try { + return SimpleDateFormat.getDateInstance(style).parse(source); + } catch (Exception ex) { + return null; + } + } + +} diff --git a/src/main/java/net/locusworks/common/utils/DateTimeStampSerializer.java b/src/main/java/net/locusworks/common/utils/DateTimeStampSerializer.java new file mode 100644 index 0000000..2d13be6 --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/DateTimeStampSerializer.java @@ -0,0 +1,46 @@ +package net.locusworks.common.utils; + +import java.io.IOException; +import java.util.Date; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +/** + * Class to serialize a date object into a json string value
+ * This serializer will convert date objects to their timestamp representation
+ * For example the below code: + *
+ * {@code @JsonSerialize(using=DateTimeStampSerializer.class)
+ * private Date purgeEndDate;
+ * }
+ * 
+ * Will specify to use this serializer class when the {@code purgeEndDate} is encountered when converting to json + * and will try to convert the date object to its timestamp equivalent + * @author Isaac Parenteau + * @date 02/15/2018 + * @version 1.0 + * @see com.fasterxml.jackson.databind.annotation.JsonSerialize + * @see com.fasterxml.jackson.databind.ser.std.StdSerializer + */ +public class DateTimeStampSerializer extends StdSerializer { + + /** + * + */ + private static final long serialVersionUID = -4753139740916300831L; + + public DateTimeStampSerializer() { + this(null); + } + + public DateTimeStampSerializer(Class t) { + super(t); + } + + @Override + public void serialize(Date date, JsonGenerator generator, SerializerProvider provider) throws IOException { + generator.writeNumber(date.getTime()); + } +} diff --git a/src/main/java/net/locusworks/common/utils/FileReader.java b/src/main/java/net/locusworks/common/utils/FileReader.java new file mode 100644 index 0000000..34fa588 --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/FileReader.java @@ -0,0 +1,202 @@ +package net.locusworks.common.utils; + +import static net.locusworks.common.Charsets.UTF_8; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import net.locusworks.common.interfaces.AutoCloseableIterator; + +/** + * Class to read in a file that can be used in the try-with-resource block + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + */ +public class FileReader implements AutoCloseableIterator, Iterable { + + private BufferedReader reader; + private LineInfo info; + private Integer lineNumber; + + /** + * Constructor + * @param fileName Name of the file to read + */ + public FileReader(String fileName) { + init(fileName); + } + + /** + * Constructor + * @param file File to read + */ + public FileReader(File file) { + init(file); + } + + /** + * Constructor + * @param reader Buffered reader to read data from + */ + public FileReader(BufferedReader reader) { + init(reader); + } + + /** + * Initialization helper + * @param fileName Name of the file to load + * This will look into the resources directory if it cannot + * find the file directly. + */ + private void init(String fileName) { + //check to see if the file exists + File f = new File(fileName); + if (f.exists()) { + init(f); //If it does. load through the file initializer + return; + } + + //Check to see if the file is in the resources directory + InputStream is = this.getClass().getResourceAsStream(fileName); + if (is == null) { + is = this.getClass().getClassLoader().getResourceAsStream(fileName); + } + //If it cant be found, throw a runtime exception + if (is == null) { + throw new IllegalArgumentException("Unable to find resource with name of" + fileName); + } + + //Call the buffered reader initializer once the file is found + init(new BufferedReader(new InputStreamReader(is, UTF_8))); + } + + /** + * Initializer helper to load file + * @param file File to load + */ + private void init(File file) { + if (file == null) throw new IllegalArgumentException("File cannot be null"); + if (!file.exists()) throw new IllegalArgumentException("File " + file + " does not exist"); + if (!file.isFile()) throw new IllegalArgumentException("File " + file + " is not a file"); + try { + init(new BufferedReader(new InputStreamReader(new FileInputStream(file), UTF_8))); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + /** + * Initializer helper for buffered reader + * This is ultimately where all initializers end as a buffered reader + * @param reader buffered reader to load + */ + private void init(BufferedReader reader) { + this.reader = reader; + this.lineNumber = 0; + } + + @Override + public boolean hasNext() { + try { + String line = this.reader.readLine(); + if (line == null) { + this.close(); + this.info = null; + return false; + } + this.lineNumber++; + this.info = new LineInfo(this.lineNumber, line); + return true; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Override + public LineInfo next() { + if (this.info == null) { + throw new NoSuchElementException("Call to next was initiated but there are no more elements to read"); + } + return this.info; + } + + @Override + public Iterator iterator() { + return this; + } + + @Override + public void close() { + if (this.reader != null) { + try { + this.reader.close(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + } + + public static class LineInfo { + private Integer lineNumber; + private Integer lineLength; + private String line; + + /** + * @param lineNumber the current line number in the file + * @param line the line information from the file + */ + public LineInfo(Integer lineNumber, String line) { + this.lineNumber = lineNumber; + this.line = line; + this.lineLength = line.length(); + } + + /** + * @return the lineNumber + */ + public Integer getLineNumber() { + return lineNumber; + } + + /** + * @param lineNumber the lineNumber to set + */ + public void setLineNumber(Integer lineNumber) { + this.lineNumber = lineNumber; + } + + /** + * @return the lineLength + */ + public Integer getLineLength() { + return lineLength; + } + + /** + * @param lineLength the lineLength to set + */ + public void setLineLength(Integer lineLength) { + this.lineLength = lineLength; + } + + /** + * @return the line + */ + public String getLine() { + return line; + } + + /** + * @param line the line to set + */ + public void setLine(String line) { + this.line = line; + } + } +} diff --git a/src/main/java/net/locusworks/common/utils/HashUtils.java b/src/main/java/net/locusworks/common/utils/HashUtils.java new file mode 100644 index 0000000..ead20e9 --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/HashUtils.java @@ -0,0 +1,179 @@ +package net.locusworks.common.utils; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +/** + * Wrapper class that leverages java's MessageDigest to hash files + * @author Isaac Parenteau + * + */ +public class HashUtils { + + private static final Charset UTF_8 = StandardCharsets.UTF_8; + + /** + * Used to build output as Hex + */ + private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + /** + * Used to build output as Hex + */ + private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + /** + * Size of the streaming buffer + */ + public static final Integer STREAM_BUFFER_LENGTH = 1024; + + /** + * Hash a string literal + * @param hashType Hash types supported by MessageDigest (i.e MD5, SHA-1, SHA-512) + * @param data String to hash + * @return hash value of the string literal + */ + public static String hash(String hashType, String data) { + return hash(hashType, data, true); + } + + /** + * Hash a string literal + * @param hashType Hash types supported by MessageDigest (i.e MD5, SHA-1, SHA-512) + * @param data String to hash + * @param toLower True to output the hash in lower case. False to output in upper case + * @return hash value of the string literal + */ + public static String hash(String hashType, String data, boolean toLower) { + byte[] stringData = data.getBytes(UTF_8); + return hash(hashType, stringData, toLower); + } + + /** + * Hash a file + * @param hashType Hash types supported by MessageDigest (i.e MD5, SHA-1, SHA-512) + * @param data File to hash + * @return hash value of the file + */ + public static String hash(String hashType, File data) { + return hash(hashType, data, true); + } + + /** + * Hash a file + * @param hashType Hash types supported by MessageDigest (i.e MD5, SHA-1, SHA-512) + * @param data File to hash + * @param toLower True to output the hash in lower case. False to output in upper case + * @return hash value of the file + */ + public static String hash(String hashType, File data, boolean toLower) { + InputStream stream; + try { + stream = new FileInputStream(data); + } catch (IOException ex) { + throw new IllegalArgumentException(ex.getMessage()); + } + return hash(stream, hashType, toLower); + } + + /** + * Hash a byte array + * @param hashType Hash types supported by MessageDigest (i.e MD5, SHA-1, SHA-512) + * @param data data to hash + * @return hash value of the data + */ + public static String hash(String hashType, byte[] data) { + return hash(hashType, data, true); + } + + /** + * Hash a byte array + * @param hashType Hash types supported by MessageDigest (i.e MD5, SHA-1, SHA-512) + * @param data data to hash + * @param toLower True to output the hash in lower case. False to output in upper case + * @return hash value of the data + */ + public static String hash(String hashType, byte[] data, boolean toLower) { + return hash(new BufferedInputStream(new ByteArrayInputStream(data)), hashType, toLower); + } + + /** + * Hash an input stream + * @param stream Stream with the data to hash + * @param hashType Hash types supported by MessageDigest (i.e MD5, SHA-1, SHA-512) + * @return Hash value of the input stream + */ + public static String hash(InputStream stream, String hashType) { + return hash(stream, hashType, true); + } + + /** + * Hash an input stream + * @param stream Stream with the data to hash + * @param hashType Hash types supported by MessageDigest (i.e MD5, SHA-1, SHA-512) + * @param toLower True to output the hash in lower case. False to output in upper case + * @return Hash value of the input stream + */ + public static String hash(InputStream stream, String hashType, boolean toLower) { + MessageDigest digest = null; + try(InputStream is = stream) { + digest = MessageDigest.getInstance(hashType); + + byte[] buffer = new byte[STREAM_BUFFER_LENGTH]; + int read = is.read(buffer, 0, STREAM_BUFFER_LENGTH); + + while (read > -1) { + digest.update(buffer, 0, read); + read = is.read(buffer, 0, STREAM_BUFFER_LENGTH); + } + + return encodeHexString(digest.digest(), toLower); + } catch (Exception ex) { + throw new IllegalArgumentException(ex.getMessage()); + } + } + + /** + * Encode the hash data back to a string + * @param data Data to encode + * @param toLower output to lower case + * @return + */ + private static String encodeHexString(byte[] data, boolean toLower) { + return new String(encodeHex(data, toLower)); + } + + /** + * Encode the hash data to a character array + * @param data Data to encode + * @param toLower output to lower case + * @return + */ + private static char[] encodeHex(byte[] data, boolean toLower) { + return encodeHex(data, toLower ? DIGITS_LOWER : DIGITS_UPPER); + } + + /** + * Encode the hex to a character array + * @param data Data to encode + * @param toDigits digits to use + * @return + */ + private static char[] encodeHex(byte[] data, char[] toDigits) { + int l = data.length; + char[] out = new char[l << 1]; + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; + out[j++] = toDigits[0x0F & data[i]]; + } + return out; + } +} diff --git a/src/main/java/net/locusworks/common/utils/RandomString.java b/src/main/java/net/locusworks/common/utils/RandomString.java new file mode 100644 index 0000000..00def2a --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/RandomString.java @@ -0,0 +1,79 @@ +package net.locusworks.common.utils; + +import java.security.SecureRandom; +import java.util.Objects; +import java.util.Random; + +import static net.locusworks.common.Charsets.UTF_8; + +public class RandomString { + + public static final String LOWER = "abcdefghijklmnopqrstuvwxyz"; + public static final String UPPER = LOWER.toUpperCase(); + public static final String DIGITS = "0123456789"; + + public static final String ALPHA_NUMERIC = LOWER + UPPER + DIGITS; + + private Random random; + + private char[] symbols; + private int length; + + private static RandomString instance; + + private RandomString(Integer length) { + this(length, new SecureRandom()); + } + + private RandomString(Integer length, Random random) { + this(length, random, ALPHA_NUMERIC); + } + + private RandomString(Integer length, Random random, String symbols) { + if (length < 1) throw new IllegalArgumentException("Length has to be greater than 1"); + if (symbols.length() < 2) throw new IllegalArgumentException("Symbols need to be greater than 2"); + this.random = Objects.requireNonNull(random); + this.symbols = symbols.toCharArray(); + } + + private synchronized final void setRandom(Random random) { + this.random = random; + } + + private synchronized final void setLength(int length) { + this.length = length; + } + + public String nextString() { + char[] buffer = new char[length]; + for (int index = 0; index < buffer.length; index++) { + buffer[index] = symbols[random.nextInt(symbols.length)]; + } + return new String(buffer); + } + + public static String getString(Integer length) { + if (instance == null) { + instance = new RandomString(length); + } + instance.setLength(length); + return instance.nextString(); + } + + public static String getString(Integer length, Random random) { + if (instance == null) { + instance = new RandomString(length); + } + instance.setLength(length); + instance.setRandom(random); + return instance.nextString(); + } + + public static byte[] getBytes(Integer length) { + return getString(length).getBytes(UTF_8); + } + + public static byte[] getBytes(Integer length, Random random) { + return getString(length, random).getBytes(UTF_8); + } +} diff --git a/src/main/java/net/locusworks/common/utils/Splitter.java b/src/main/java/net/locusworks/common/utils/Splitter.java new file mode 100644 index 0000000..a29e914 --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/Splitter.java @@ -0,0 +1,130 @@ +package net.locusworks.common.utils; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static net.locusworks.common.utils.Checks.checkArguments; +import static net.locusworks.common.utils.Checks.checkNotNull; + +public class Splitter { + + private String splitSeq; + private boolean omitEmptyStrings = false; + private int partition; + private int limit; + + private static Splitter splitter; + + private Splitter(String seq) { + this.splitSeq = seq; + } + + private Splitter(int partition) { + this.partition = partition; + } + + public Splitter omitEmptyStrings() { + this.omitEmptyStrings = true; + return this; + } + + public Splitter withLimit(int limit) { + this.limit = limit; + return this; + } + + public MapSplitter withKeyValueSeparator(String separator) { + checkArguments(!Utils.isEmptyString(separator), "Key value separator cannot be empty or null"); + return new MapSplitter(this, separator); + } + + public String[] splitToArray(String sentence) { + List list = split(sentence); + return list.toArray(new String[list.size()]); + } + + public List split(String sentence) { + checkArguments(!Utils.isEmptyString(sentence), "provided value is null or empty"); + List list = new ArrayList<>(); + + if (!Utils.isEmptyString(splitSeq)) + populateForTrimmer(sentence, list); + else + populateForFixedWidth(sentence, list); + + return limit > 0 ? list.subList(0, limit) : list; + } + + private void populateForFixedWidth(String sentence, List list) { + int strLength = sentence.length(); + for (int i = 0; i < strLength; i += partition) { + list.add(sentence.substring(i, Math.min(strLength, i + partition))); + } + } + + private void populateForTrimmer(String sentence, List list) { + for (String s : sentence.split(splitSeq)) { + if (s == null || (omitEmptyStrings && s.trim().isEmpty())) continue; + list.add(s.trim()); + } + } + + public static Splitter fixedLengthSplit(int partition) { + checkArguments(partition > 0, "Partition has to be greater than 0"); + splitter = new Splitter(partition); + return splitter; + } + + public static Splitter on(String split) { + checkNotNull(split, "Split value provided was null"); + splitter = new Splitter(split); + return splitter; + } + + public static Splitter onNewLine() { + return on("\\r?\\n"); + } + + public static Splitter onSpace() { + return on(" "); + } + + public static class MapSplitter { + + private Splitter splitter; + private String separator; + private boolean skipInvalid = false; + + private MapSplitter(Splitter splitter, String separator) { + checkNotNull(splitter, "Splitter cannot be null"); + checkArguments(!Utils.isEmptyString(separator), "Key value separator cannot be empty or null"); + this.splitter = splitter; + this.separator = separator; + } + + public MapSplitter skipInvalidKeyValues() { + this.skipInvalid = true; + return this; + } + + public Map split(String sentence) { + checkArguments(!Utils.isEmptyString(sentence), "provided value is null or empty"); + Map map = new LinkedHashMap<>(); + + for (String s : splitter.split(sentence)) { + String[] keyValue = s.split(separator); + try { + checkArguments(keyValue.length == 2, "invalid length found for key value mapping"); + } catch (IllegalArgumentException ex) { + if (!skipInvalid) throw ex; + continue; + } + map.put(keyValue[0], keyValue[1]); + } + + return map; + } + } +} diff --git a/src/main/java/net/locusworks/common/utils/StreamUtils.java b/src/main/java/net/locusworks/common/utils/StreamUtils.java new file mode 100644 index 0000000..9cadb6e --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/StreamUtils.java @@ -0,0 +1,66 @@ +package net.locusworks.common.utils; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Utility class to make iterators streamable + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + */ +public class StreamUtils { + + /** + * Convert a iterator to a stream + * @param iterator the iterator to convert + * @param the class type + * @return stream of the iterator + */ + public static Stream asStream(Iterator iterator) { + return asStream(iterator, false); + } + + public static Stream asStream(Iterable iterable) { + return asStream(iterable, false); + } + + /** + * Converts an array to a stream + * @param items the items to convert + * @param the class type + * @return stream of the array + */ + public static Stream asStream(T[] items) { + return asStream(Arrays.asList(items).iterator(), false); + } + + /** + * Converts an array to a stream + * @param items the items to convert + * @param parallel make the stream parallel if set to true + * @param the class type + * @return stream of the array + */ + public static Stream asStream(T[] items, boolean parallel) { + return asStream(Arrays.asList(items).iterator(), parallel); + } + + public static Stream asStream(Iterable iterable, boolean parallel) { + return StreamSupport.stream(iterable.spliterator(), parallel); + } + + /** + * Convert an iterator to a stream + * @param iterator iterator to convert + * @param parallel make the stream parallel if set to true. + * @param the class type + * @return stream of the iterator + */ + public static Stream asStream(Iterator iterator, boolean parallel) { + Iterable iterable = () -> iterator; + return StreamSupport.stream(iterable.spliterator(), parallel); + } +} diff --git a/src/main/java/net/locusworks/common/utils/Success.java b/src/main/java/net/locusworks/common/utils/Success.java new file mode 100644 index 0000000..0d6a2db --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/Success.java @@ -0,0 +1,41 @@ +package net.locusworks.common.utils; + +/*** + * Success class to return data back to the client + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + */ +public class Success { + private boolean success = true; + private Object body; + + protected Success() { + this(true, true); + } + + protected Success(boolean success) { + this(success, success); + } + + public Success(boolean success, Object body) { + this.success = success; + this.body = body; + } + + public Object getBody() { + return body; + } + + public boolean getSuccess() { + return success; + } + + public static Success success() { + return new Success(); + } + + public static Success fail() { + return new Success(false); + } +} diff --git a/src/main/java/net/locusworks/common/utils/Utils.java b/src/main/java/net/locusworks/common/utils/Utils.java new file mode 100644 index 0000000..9cc887e --- /dev/null +++ b/src/main/java/net/locusworks/common/utils/Utils.java @@ -0,0 +1,1125 @@ +package net.locusworks.common.utils; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import net.locusworks.common.annotations.MapValue; +import net.locusworks.common.interfaces.ThrowingConsumer; + +import java.util.Set; + +/*** + * Utils class with generic methods + * @author Isaac Parenteau + * @version 1.0.0 + * @date 02/15/2018 + */ +public class Utils { + + + /** + * Finds if all the values are equal + * @param or first value is equal to at least one other value + * @param values the values to compare + * @return true if the first value is equal to at least one other value; false otherwise + */ + @SafeVarargs + public static boolean areEqual(Boolean or, E... values) { + if (values.length < 2) { + throw new IllegalArgumentException("Not enough values to compare"); + } + + E firstVal = values[0]; + boolean equal = true; + + for (int i = 1; i < values.length; i++) { + if (or) + equal |= firstVal.equals(values[i]); + else + equal &= firstVal.equals(values[i]); + } + + return equal; + } + + /** + * Checks to see if values entered are all equal + * @param the type parameter + * @param values the values + * + * @return boolean + */ + @SafeVarargs + public static boolean areEqual(E... values) { + return areEqual(false, values); + } + + /** + * Checks to see if a group of values are not valid. All values have to be invalid to return true + * One valid value will return false + * @param values Values to check + * @param the expected class type of the objects passed in. All objects need to be of same type + * @return true if all values are invalid, false otherwise + */ + @SuppressWarnings("unchecked") + public static boolean areNotValid(V... values) { + return !areValid(values); + } + + /** + * Checks to see if a group of values are valid. All values have to be valid to return true + * One invalid value will return false + * @param the expected class type of the objects passed in. All objects need to be of same type + * @param values the values to check + * @return true if the values are valid, false otherwise + */ + @SuppressWarnings("unchecked") + public static boolean areValid(V... values) { + return validateValues(values); + } + + /** + * Build a map class + * + * @param the type parameter + * @param the type parameter + * @param mapClass The map class to map to i.e HashMap, TreeMap etc + * @param mapKey the map key + * @param mapValue the map value + * @param data The data to place in the map + * + * @return map The map with the objects + */ + @SuppressWarnings("unchecked") + public static Map buildMap(Class mapClass, Class mapKey, Class mapValue, Object... data) { + Map results = new LinkedHashMap(); + try { + results = (Map) mapClass.newInstance(); + } catch (Exception ex) { + throw new IllegalArgumentException("Unable to create instance of " + mapClass.getSimpleName()); + } + + if (data.length % 2 != 0) { + throw new IllegalArgumentException("Odd number of arguments provided"); + } + + Object key = null; + Integer step = -1; + + for (Object value : data) { + switch(++step % 2) { + case 0: + if (mapKey == null) { + throw new IllegalArgumentException("Null key value"); + } + if (value instanceof Class) { + try { + value = ((Class)value).newInstance(); + } catch (Exception ex) { + throw new IllegalArgumentException("Unable to create new instance of " + value); + } + } + + if (!mapKey.isInstance(value)) { + throw new IllegalArgumentException("Key is not the correct instance. Expecting " + mapKey.getName() + " Received " + value.getClass().getName()); + } + + key = value; + continue; + case 1: + if (!mapValue.isInstance(value)) { + throw new IllegalArgumentException("Value is not the correct instance. Expecting " + mapValue.getName() + " Received " + value.getClass().getName()); + } + results.put((K) key, (V) value); + break; + default: + throw new IllegalAccessError("Caculation for step was not a multiple of two. This shouldn't have happened"); + } + } + return results; + } + + /** + * Builds a set of objects + * @param setClass Class type of the set to create + * @param setValue Class type of the objects being placed in the set + * @param Class type of the return object (should be the same as setValue) + * @param data Data to place inside the set + * @return Set filled with the data + */ + @SuppressWarnings("unchecked") + public static Set buildSet(Class setClass, Class setValue, Object... data) { + Set results = null; + + try { + results = (Set) setClass.newInstance(); + } catch (Exception ex) { + throw new IllegalArgumentException("Unable to create instance of " + setClass.getSimpleName()); + } + + for (Object value : data) { + if (!setValue.isInstance(value)) { + throw new IllegalArgumentException("Value is not the correct instance. Expecting " + setValue.getName() + " Received " + value.getClass().getName()); + } + results.add((V) value); + } + + return results; + } + + /** + * Public method of type HashMap called "buildStringHashMap". + * The method takes String parameters and a data parameter. + * The method returns a new HashMap that uses String types as key / value pairs. + * @param data - The data + * @return - Returns a HashMap using the data. + */ + public static HashMap buildStringHashMap(String... data) { + return new HashMap(buildMap(HashMap.class, String.class, String.class, (Object[]) data)); + } + + /** + * Clone list list. + * + * @param the type parameter + * @param list the list + * + * @return the list + * @throws IllegalAccessException throw when an object in the list cannot be accessed through reflection + * @throws InstantiationException thrown when an boject in the list cannot be instantiated through reflection + */ + @SuppressWarnings("unchecked") + public static List cloneList(List list) throws InstantiationException, IllegalAccessException { + List clone = new ArrayList<>(); + for (E item : list) { + clone.add((E) cloneObject(item)); + } + return clone; + } + + /** + * Clone object object. + * @param obj the obj + * @return the object + * @throws IllegalAccessException thrown when the filed cannot be access + * @throws InstantiationException Thrown when the object cannot be instantiated + */ + public static Object cloneObject(Object obj) throws InstantiationException, IllegalAccessException { + Object clone = obj.getClass().newInstance(); + for (Field field : obj.getClass().getDeclaredFields()) { + try { + field.setAccessible(true); + if (field.get(obj) == null || Modifier.isFinal(field.getModifiers())){ + continue; + } + if (field.getType().isPrimitive() || field.getType().equals(String.class) + || field.getType().getSuperclass().equals(Number.class) + || field.getType().equals(Boolean.class)){ + field.set(clone, field.get(obj)); + } else { + Object childObj = field.get(obj); + if (childObj == obj) { + field.set(clone, clone); + } else { + field.set(clone, cloneObject(field.get(obj))); + } + } + } catch (NullPointerException ex) { + continue; + } + } + return clone; + } + + /*** + * Convert an object to a map. Checks to see if there is a MapValue annotation + * and uses the values found to convert the fields to the key and the field value as the value + * @param obj Object to convert + * @return A map representation of the object + * @throws Exception exception + */ + public static Map convertToMap(Object obj) throws Exception { + Class clazz = obj.getClass(); + Map map = new LinkedHashMap<>(); + + boolean hasAnnotations = clazz.isAnnotationPresent(MapValue.class); + + for (Field field : clazz.getDeclaredFields()) { + field.setAccessible(true); + String key = field.getName(); + Object value = field.get(obj); + if (value == null) continue; + + if (hasAnnotations) { + if (!field.isAnnotationPresent(MapValue.class)) { + continue; + } + MapValue annotation = field.getDeclaredAnnotation(MapValue.class); + if (annotation.ignore()) { + continue; + } + + if (!isEmptyString(annotation.value())) { + key = annotation.value(); + } + } + map.put(key, value); + } + + return map; + } + + /** + * Convert a generic map to Stringed map where the key and value are both strings + * @param map map to convert + * @param The expected class of the key + * @param The expected class of the value + * @return convertedMap + */ + public static Map convertToStringMap(Map map) { + Map convert = new LinkedHashMap<>(); + for (Entry item : map.entrySet()) { + convert.put(String.valueOf(item.getKey()), String.valueOf(item.getValue())); + } + + return convert; + } + + public static Map convertToStringMap(Object obj) throws Exception { + return convertToStringMap(convertToMap(obj)); + } + + /*** + * Takes an input list and converts it to another list denoted by what is grabbed from the fieldName + * @param fieldName Name of the field in the object to retrieve + * @param list the list to retrieve values from + * @param The expected class of the List + * @param The expected class of the Iterable list + * @return The converted list + */ + public static List extractFieldToList(String fieldName, Iterable list) { + return setToList(extractFieldToSet(fieldName, list)); + } + + /** + * Extract a field from an iterable object and place them in a set + * @param fieldName field to check + * @param list list to iterate + * @param The expected class of the Set + * @param The Expected class of the iterable list + * @return set with the desired items + */ + @SuppressWarnings("unchecked") + public static Set extractFieldToSet(String fieldName, Iterable list) { + Set newSet = new LinkedHashSet<>(); + Field field = null; + for (K item : Utils.safeList(list)) { + try { + field = getField(item.getClass(), fieldName); + + if (field == null) { + throw new IllegalArgumentException("Unable to find field with name " + fieldName); + } + + E val = (E)field.get(item); + newSet.add(val); + + } catch (Exception ex) { + throw new IllegalArgumentException(ex); + } + } + return newSet; + } + + /** + * Filters out items in a list with the given parameters + * @param fieldName the field name to inspect for the filter value + * @param filter the filter value to retrieve + * @param list the list to inspect + * @param the expected class of the iterable list + * @return List of items that met the criteria of the filter + */ + @SuppressWarnings("unchecked") + public static List filterList(String fieldName, Object filter, Iterable list) { + List newList = new ArrayList(); + + Field field = null; + + for (E item : safeList(list)) { + try { + field = getField(item.getClass(), fieldName); + + if (field == null) { + throw new IllegalArgumentException("Unable to find field with name " + fieldName); + } + + E val = (E)field.get(item); + if (val.equals(filter)) { + newList.add(item); + } + } catch (Exception ex) { + throw new IllegalArgumentException(ex); + } + } + + return newList; + } + + /** + * Find a value within the list. + * + * Public method of type E called "findValue". + * The method takes a parameter of type E called "itemToFind" and a String parameter called "fieldName" and a parameter of type List called "list". + * The method loops through each item in the list and tries to get the class name and return it as an item. + * The method the takes each item and searches for it in the list. + * @param itemToFind - The Item to find + * @param fieldName - The field name where the item is in + * @param list - The list to search + * @param - The expected class of item to be found + * @param - The expected class of the Iterable list + * @return - Returns returns the value of the specified field or null if there is no value. + * + */ + @SuppressWarnings({ "unchecked", "unlikely-arg-type" }) + public static E findValue(E itemToFind, String fieldName, List list) { + for (K item : Utils.safeList(list)) { + try { + if (isPrimitiveOrWrapper(item.getClass())) { + if (itemToFind.equals(item)) { + return (E) item; + } + } else { + Field field = getField(item.getClass(), fieldName); + E value = (E) field.get(item); + if (itemToFind.equals(value)) { + return value; + } + } + } catch(IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException(String.format("Unable to find field %s. -> %s", fieldName, e.getMessage())); + } + } + + return null; + } + + /** + * Find values list. + * + * Public method of type List called "findValues". + * The method takes a E type parameter called "valueToFind" and a String parameter called "fieldName" and a List parameter called "list". + * The method creates a new array list called "tmpList". + * The method then iterates through the list and gets all of the classes as items. + * If it can't find the fields, it will throw an illegal argument exception. + * @param valueToFind - The value to find + * @param fieldName - The field name + * @param list - The list + * @param - The expected class of item to be found + * @param - The expected class of the Iterable list + * @return - Returns the list. + */ + @SuppressWarnings({ "unchecked", "unlikely-arg-type" }) + public static List findValues(E valueToFind, String fieldName, List list) { + List tmpList = new ArrayList<>(); + + for (K item : Utils.safeList(list)) { + try { + if (isPrimitiveOrWrapper(item.getClass())) { + if (valueToFind.equals(item)) { + tmpList.add((E)item); + } + } else { + Field field = getField(item.getClass(), fieldName); + E value = (E) field.get(item); + if (valueToFind.equals(value)) { + tmpList.add(value); + } + } + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException(String.format("Unable to find field %s. -> %s", fieldName, e.getMessage())); + } + } + + return tmpList; + } + + /** + * A replacement for String.format. Allows for to many parameters or to few + * Replaces {} in the string in order. + * + * @param message the message + * @param args the args + * + * @return string + */ + public static String formatString(String message, Object... args) { + String tmp = message; + + for(Object arg : args) { + tmp = tmp.replaceFirst("\\{\\}", String.valueOf(arg)); + } + + return tmp; + } + + /** + * Get a class name + * + * @param the type parameter + * @param obj the obj + * + * @return class name + */ + public static String getClassName(V obj) { + return getClassName(obj, false); + } + + /** + * Get a class name + * + * @param the type parameter + * @param obj the obj + * @param verbose the verbose + * + * @return class name + */ + public static String getClassName(V obj, boolean verbose) { + return obj == null ? "Unknown" : (verbose ? obj.getClass().getName() : obj.getClass().getSimpleName()); + } + + /** + * Use reflection to get the field values + * @param clazz + * @param fieldName + * @return field + */ + private static Field getField(Class clazz, String fieldName) { + Field field = null; + while(clazz != null) { + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + break; + } catch(Exception ex) { + clazz = clazz.getSuperclass(); + } + } + + return field; + } + + /** + * Get a value from a map + * + * @param the type parameter + * @param the type parameter + * @param map the map with values + * @param key the key to find + * + * @return map value + */ + public static T getMapValue(Map map, K key) { + return getMapValue(map, key, null); + } + + /** + * Get a value from a map + * + * @param the type parameter + * @param the type parameter + * @param map the map with the values + * @param key the key to find + * @param defaultValue the default value if the key is not found + * + * @return map value + */ + public static T getMapValue(Map map, K key, T defaultValue) { + return map.containsKey(key) ? map.get(key) : defaultValue; + } + + /** + * Checks to see if the proved string is empty + * new String(null) = true + * new String("") = true + * new String(" ") = true + * new String("foo") = false + * new String(" bar ") = false + * @param string String to check + * @return true if the string is empty false otherwise + */ + public static boolean isEmptyString(String string) { + return string == null || string.trim().isEmpty(); + } + + /** + * Checks to see if a passed in value is not a valid value + * For Strings checks to see if they are empty or not null + * For collections checks to see if they are null or empty + * for all others just checks if they are null + * @param the expected class type of the object + * @param value The value to check + * @return true if the value is not valid, false otherwise + */ + @SafeVarargs + public static boolean isNotValid(V... values) { + return !validateValues(values); + } + + /** + * Checks to see if a passed in value is a valid value + * For Strings checks to see if they are not empty or not null + * For collections checks to see if they are not null and not empty + * for all others just checks if they are not null + * @param the expected class type of the object + * @param value The value to check + * @return true if the value is valid, false otherwise + */ + @SafeVarargs + public static boolean isValid(V... values) { + return validateValues(values); + } + + /** + * Is primitive or wrapper boolean. + * + * @param clazz the clazz + * + * @return the boolean + */ + public static boolean isPrimitiveOrWrapper(Class clazz) { + return clazz.isPrimitive() || getWrapperTypes().contains(clazz); + } + + /** + * Converts a list of items into a map + * + * @param the type parameter + * @param the type parameter + * @param keyFieldName the name of the field that will be used as the key + * @param list the list to convert + * + * @return a map + */ + @SuppressWarnings("unchecked") + public static Map listToMap(String keyFieldName, List list) { + Map map = new HashMap<>(); + + for(V value : Utils.safeList(list)) { + try { + Field field = getField(value.getClass(), keyFieldName); + K key = (K) field.get(value); + map.put(key, value); + } catch(IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException(String.format("Unable to find field %s. -> %s", keyFieldName, e.getMessage())); + } + } + + return map; + } + + /** + * List to set set. + * + * @param the type parameter + * @param valueSet the value set + * + * @return the set + */ + public static Set listToSet(Collection valueSet) { + return toSet(valueSet); + } + + /** + * Creates an array from a collection + * + * Public method of type array called "makeArray". + * The method takes a Collection object parameter called "collection" and a Class object parameter called "clazz". + * The method first checks to see if the Collection object is null. If so, it returns null. + * The method then creates an array called "results" and populates it with a new instance of "clazz" and the size of the collection. + * The method then creates a Int variable called "index" and sets the value to zero. + * The method then iterates through the collection and adds all of the items to the results array. + * The method then returns the results array. + * @param - The type parameter + * @param collection - The collection + * @param clazz - The clazz + * @return e[] - Returns the array of results. + */ + @SuppressWarnings("unchecked") + public static E[] makeArray(Collection collection, Class clazz) { + if (collection == null) { + return null; + } + + E[] results = (E[])Array.newInstance(clazz, collection.size()); + + int index = 0; + for (E item : collection) { + results[index++] = item; + } + + return results; + } + + /** + * Creates a list from a collection. + * Public method of type List called "makeList". + * The method takes a Collection object parameter called "collection". + * The method checks to see if the Collection Object is null. If so, it returns null. + * Otherwise, it creates an ArrayList object called "list". + * It then iterates through the Collection Object and adds all of the collection items to that list. + * The method then returns the list. + * @param - The type parameter + * @param collection - The collection + * @return - Returns the list. + */ + public static List makeList(Collection collection) { + if (collection == null) { + return null; + } + List list = new ArrayList<>(); + for (E item : collection) { + list.add(item); + } + return list; + } + + /** + * Public method of type List called "makeList". + * The method takes a parameter of type E array called "array". + * The method checks to see if the array is null. If so, it returns null. + * Otherwise, it creates an ArrayList object called "newList". + * It then iterates through the array and adds all the items to the new list. + * The method then returns the list. + * @param - The type parameter + * @param array - The array + * @return - Returns the list. + */ + public static List makeList(E[] array) { + if (array == null) { + return null; + } + + List newList = new ArrayList<>(); + + for (E item : array) { + newList.add(item); + } + + return newList; + } + + /** + * Creates a list from an iterable object + * Public method of type List called "makeList". + * The method takes an Iterable object parameter called "iter". + * The method checks to see if the object is null. If so, it returns null. + * Otherwise, it creates a new ArrayList object called "list". + * It then iterates through the Iterable object and adds items to it. + * After that it returns the list. + * @param iter - The object to iterate over. + * @param - The expected class of the Iterable object + * @return - Returns the list. + */ + public static List makeList(Iterable iter) { + if (iter == null) { + return null; + } + List list = new ArrayList<>(); + for (E item : iter) { + list.add(item); + } + return list; + } + + /** + * Public method of type Set called "makeSet". + * The method takes an Iterable object parameter called "collection". + * The method first creates a new HashSet called "set". + * The method then iterates through the collection and adds all of the items from it to the HashSet. + * The method then returns the HashSet. + * @param - The type parameter + * @param collection - The iterable object name. + * @return - Returns the HashSet with all of the items of the collection inside of it. + */ + public static Set makeSet(Iterable collection) { + Set set = new HashSet<>(); + for (E item : safeList(collection)) { + set.add(item); + } + return set; + } + + /** + * Map to list list. + * + * @param the type parameter + * @param the type parameter + * @param map the map + * + * @return the list + */ + public static List mapToList(Map map) { + List list = new ArrayList<>(); + + for (V key : map.values()) { + list.add(key); + } + + return list; + } + + /** + * Map to set set. + * + * @param the type parameter + * @param the type parameter + * @param map the map + * + * @return the set + */ + public static Set mapToSet(Map map) { + Set list = new HashSet<>(); + + for (V key : map.values()) { + list.add(key); + } + + return list; + } + + /** + * Reverse a map switching key value pairs + * + * @param the type parameter + * @param the type parameter + * @param map the map + * + * @return map with reverse key value pairs + */ + public static Map reverseMap(Map map) { + Map reverseMap = new HashMap<>(); + + for(Map.Entry entry : map.entrySet()) { + reverseMap.put(entry.getValue(), entry.getKey()); + } + + return reverseMap; + } + + /** + * Creates a safe list + * + * @param the type parameter + * @param list The list to check + * + * @return an empty list if list is invalid or the list if its valid + */ + public static Collection safeList(Collection list) { + if (!validateValue(list)) { + return new ArrayList(); + } + return list; + } + + /** + * Safe list e [ ]. + * + * @param the type parameter + * @param list the list + * + * @return the e [ ] + */ + @SuppressWarnings("unchecked") + public static E[] safeList(E[] list) { + if (!validateValue(list)) { + return (E[]) new Object[0]; + } + return list; + } + + /** + * Creates a safe list + * + * @param the type parameter + * @param list The list to check + * + * @return an empty list if list is invalid or the list if its valid + */ + public static Iterable safeList(Iterable list) { + if (!validateValue(list)) { + return new ArrayList(); + } + return list; + } + + /** + * Creates a safe list + * + * @param the type parameter + * @param list The list to check + * + * @return an empty list if list is invalid or the list if its valid + */ + public static List safeList(List list) { + if (!validateValue(list)) { + return new ArrayList(); + } + return list; + } + + /** + * Creates a safe set + * @param the type parameter + * @param set the set to check + * @return an empty set if the set is null otherwise the set + */ + public static Set safeSet(Set set) { + if (set == null) { + return new HashSet(); + } + return set; + } + + /** + * Checks to see if a string is not blank or null. + * if its not blank it will return the string + * otherwise it will return an empty string + * @param string The string to check + * @return the passed in string if its not null either empty string + */ + public static String safeString(String string) { + return isEmptyString(string) ? "" : string; + } + + /** + * Sets to list. + * + * @param the type parameter + * @param valueSet the value set + * + * @return the to list + */ + public static List setToList(Set valueSet) { + List list = new ArrayList<>(); + for (E value : valueSet) { + list.add(value); + } + return list; + } + + /** + * Converts a String into an integer without exception + * @param value The string value to convert + * @param defaultValue The default value to return if the string cant be converted + * @return the integer representation of the passed in string or the default value if an exception occured + */ + public static Integer toInteger(String value, Integer defaultValue) { + try { + return Integer.parseInt(value); + } catch (Exception ex) { + return defaultValue; + } + } + + /** + * Create a list from given values + * + * @param the type parameter + * @param values the values to add the list + * + * @return List containing said values + */ + @SafeVarargs + public static List toList(E... values) { + return Arrays.asList(values); + } + + public static List toList(Iterable iterable) { + List list = new ArrayList(); + for (E item : iterable) { + list.add(item); + } + return list; + } + + /** + * Create a set from given values + * + * @param the type parameter + * @param values the values to add the set + * + * @return Set containing said values + */ + @SafeVarargs + public static Set toSet(E... values) { + Set set = new LinkedHashSet<>(); + for (E value : values) { + set.add(value); + } + + return set; + } + + public static List toByteList(byte[] bytes) { + return IntStream.range(0, bytes.length) + .mapToObj(index -> new Byte(bytes[index])) + .collect(Collectors.toList()); + } + + /** + * Converts an iterable object to a hash set + * @param iterable The Iterable object to convert + * @param the class type of the set + * @return set with the iterable items inside + */ + public static Set toSet(Iterable iterable) { + Set set = new HashSet<>(); + for (E value : iterable) { + set.add(value); + } + return set; + } + + public static long size(Iterable iterable) { + long count = 0; + for (Iterator iter = iterable.iterator(); iter.hasNext(); iter.next()) { count++; } + return count; + } + + public static E get(Iterable iterable, int index) { + AtomicInteger count = new AtomicInteger(0); + Optional test = StreamUtils.asStream(iterable) + .filter(item -> count.getAndIncrement() == index) + .findFirst(); + return test.isPresent() ? test.get() : null; + } + + public static Consumer handleExceptionWrapper(ThrowingConsumer consumer) { + return i -> { + try { + consumer.accept(i); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + }; + } + + /** + * Public method of type Boolean called "validateValue". + * The method takes an Object parameter called "value". + * The method loops through and checks to see if value is empty. + * If so, it will return that the value is not null and that it is not empty. + * Otherwise it will check to see the value is an instance of Map. + * If so, it will return that the value is not null and that it is not empty. + * Otherwise it will check to see if value is an instance of a String. + * If so, it will return that the value is not null and that it is not empty. + * Otherwise it will check to see if value is an instance of a Collection Object. + * If so, it will return that the value is not null and if the value.size is greater than zero. + * Otherwise it will just return that the value is not null and not empty. + * @param - The type parameter + * @param value - Value to validate + * @return - Returns true if it is valid; false otherwise + */ + @SuppressWarnings("rawtypes") + public static boolean validateValue(V value) { + if (value == null) { + return false; + } + if (value instanceof Collection) { + return !((Collection)value).isEmpty(); + } else if (value instanceof Map){ + return !((Map)value).isEmpty(); + } else if (value instanceof Boolean) { + return ((Boolean)value) == true; + } + else if (!(value instanceof String)) { + return value != null; + } + + return !value.toString().trim().isEmpty(); + } + + /** + * Validates all given values. + * + * Public method of type Boolean called "validateValues". + * The method takes multiple V type objects called "objectToValidate". + * The method creates a boolean type variable called "valid" and sets it to true. + * The method then loops through all the objects and validates each value in the object. + * The method sets valid equal to the result of true and false. + * If the method cannot validate the object values, it will throw an exception. If so, it returns false. + * Otherwise, the method will return the variable called "valid'. + * @param - The type parameter + * @param objectsToValidate - Objects to validate + * @return - Returns true if all objects are valid false otherwise. + */ + @SafeVarargs + public static boolean validateValues(V... objectsToValidate) { + return validateValues(false, objectsToValidate); + } + + /** + * Validates all given values to + * @param or Set the or flag to check for at least one validated value + * @param objectsToValidate objects to validate. + * @param the expected class type of the objects passed in. All objects need to be of same type + * @return true if the values are valid, false otherwise + */ + @SafeVarargs + public static boolean validateValues(boolean or, V... objectsToValidate) { + boolean valid = true; + try { + for (Object obj : objectsToValidate) { + if (or && validateValue(obj)) { + return true; + } + + valid &= validateValue(obj); + } + } catch (Exception ex) { + return false; + } + + return valid; + } + + public static boolean isJUnitRunning() { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + Optional junit = StreamUtils.asStream(stackTrace) + .map(element -> element.getClassName()) + .filter(className -> className.startsWith("org.junit.")) + .findFirst(); + return junit.isPresent(); + } + + private static Set> getWrapperTypes() { + Set> ret = new HashSet>(); + ret.add(Boolean.class); + ret.add(Character.class); + ret.add(Byte.class); + ret.add(Short.class); + ret.add(Integer.class); + ret.add(Long.class); + ret.add(Float.class); + ret.add(Double.class); + ret.add(String.class); + return ret; + } +} \ No newline at end of file diff --git a/src/main/resources/log4j.xml b/src/main/resources/log4j.xml new file mode 100644 index 0000000..f381635 --- /dev/null +++ b/src/main/resources/log4j.xml @@ -0,0 +1,24 @@ + + + + + + + + + %d{dd-MMM-yyyy HH:mm:ss.SSS} %m%n + + + + + + + + + + + + + + diff --git a/src/test/java/net/locusworks/test/AESEncryptionTest.java b/src/test/java/net/locusworks/test/AESEncryptionTest.java new file mode 100644 index 0000000..794a91d --- /dev/null +++ b/src/test/java/net/locusworks/test/AESEncryptionTest.java @@ -0,0 +1,41 @@ +package net.locusworks.test; + +import org.junit.Assert; +import org.junit.Test; + +import net.locusworks.common.crypto.AES; +import net.locusworks.common.utils.Utils; + +public class AESEncryptionTest { + + @Test + public void testEncryption() { + try { + String encrypted = AES.createInstance().encrypt("hello world"); + Assert.assertTrue(String.format("Encrypted String is not blank? :%s", encrypted), !Utils.isEmptyString(encrypted)); + } catch (Exception ex) { + ex.printStackTrace(System.err); + Assert.fail(); + } + } + + @Test + public void testDecryption() { + String testString ="hello world"; + try { + AES aes = AES.createInstance(); + String encrypted = aes.encrypt(testString); + Assert.assertTrue(String.format("Encrypted String is not blank? :%s", encrypted), !Utils.isEmptyString(encrypted)); + + String decrypted = aes.decrypt(encrypted); + Assert.assertTrue(String.format("Decrypted String is not blank? :%s", decrypted), !Utils.isEmptyString(encrypted)); + + Assert.assertTrue("Test String and Original String the same? :%s", testString.equals(decrypted)); + + } catch (Exception ex) { + ex.printStackTrace(System.err); + Assert.fail(); + } + } + +} diff --git a/src/test/java/net/locusworks/test/AllTests.java b/src/test/java/net/locusworks/test/AllTests.java new file mode 100644 index 0000000..7d9e8e9 --- /dev/null +++ b/src/test/java/net/locusworks/test/AllTests.java @@ -0,0 +1,11 @@ +package net.locusworks.test; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({ AESEncryptionTest.class, FileReaderTest.class, HashSaltTest.class, RandomStringTest.class }) +public class AllTests { + +} diff --git a/src/test/java/net/locusworks/test/FileReaderTest.java b/src/test/java/net/locusworks/test/FileReaderTest.java new file mode 100644 index 0000000..7453ba3 --- /dev/null +++ b/src/test/java/net/locusworks/test/FileReaderTest.java @@ -0,0 +1,92 @@ +package net.locusworks.test; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import net.locusworks.common.interfaces.AutoCloseableIterator; +import net.locusworks.common.utils.FileReader; +import net.locusworks.common.utils.FileReader.LineInfo; +import net.locusworks.common.utils.RandomString; + +public class FileReaderTest { + + private static final String TEST_FILE = "test_file.txt"; + private static Map numLines = new LinkedHashMap<>(); + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + File testFile = new File(TEST_FILE); + FileOutputStream fos = new FileOutputStream(testFile); + + Integer count = ThreadLocalRandom.current().nextInt(100); + + for (int i = 1; i <= count; i++) { + String randomString = RandomString.getString(ThreadLocalRandom.current().nextInt(5, 100)) + "\n"; + numLines.put(i, randomString.length()); + fos.write(randomString.getBytes()); + } + + fos.close(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + File file = new File(TEST_FILE); + file.delete(); + } + + @Test + public void testForLoop() { + Integer lineCount = 0; + for(LineInfo s : new FileReader(new File(TEST_FILE))) { + lineCount++; + Integer lineNumber = s.getLineNumber(); + Integer lineLength = s.getLine().length(); + Integer mapLineLength = numLines.get(lineNumber); + assertTrue(lineLength == (mapLineLength-1)); + } + assertTrue(lineCount == numLines.size()); + } + + @Test + public void testIterator() { + Integer lineCount = 0; + + try(AutoCloseableIterator iter = new FileReader(new File(TEST_FILE))) { + while(iter.hasNext()) { + lineCount++; + LineInfo s = iter.next(); + Integer lineNumber = s.getLineNumber(); + Integer lineLength = s.getLine().length(); + Integer mapLineLength = numLines.get(lineNumber); + assertTrue(lineLength == (mapLineLength-1)); + } + } + assertTrue(lineCount == numLines.size()); + } + + @Test + public void testForIterator() { + Integer lineCount = 0; + for(Iterator iter = new FileReader(new File(TEST_FILE)); iter.hasNext();) { + lineCount++; + LineInfo s = iter.next(); + Integer lineNumber = s.getLineNumber(); + Integer lineLength = s.getLine().length(); + Integer mapLineLength = numLines.get(lineNumber); + assertTrue(lineLength == (mapLineLength-1)); + } + assertTrue(lineCount == numLines.size()); + } + +} diff --git a/src/test/java/net/locusworks/test/HashSaltTest.java b/src/test/java/net/locusworks/test/HashSaltTest.java new file mode 100644 index 0000000..8851ac5 --- /dev/null +++ b/src/test/java/net/locusworks/test/HashSaltTest.java @@ -0,0 +1,34 @@ +package net.locusworks.test; + +import org.junit.Assert; +import org.junit.Test; + +import net.locusworks.common.crypto.HashSalt; +import net.locusworks.common.utils.Utils; + +public class HashSaltTest { + + private static String samplePassword="Hello World"; + + @Test + public void testEncryption() { + try { + String hashSalt = HashSalt.createHash(samplePassword); + Assert.assertTrue(String.format("Encrypted String is not blank? :%s", hashSalt), !Utils.isEmptyString(hashSalt)); + } catch(Exception ex) { + Assert.fail(); + } + } + + @Test + public void testDecryption() { + try { + String hashSalt = HashSalt.createHash(samplePassword); + boolean decrypted = HashSalt.validatePassword(samplePassword, hashSalt); + Assert.assertTrue("Test String and Original String the same? :%s", decrypted); + } catch(Exception ex) { + Assert.fail(); + } + } + +} diff --git a/src/test/java/net/locusworks/test/HashUtilsTest.java b/src/test/java/net/locusworks/test/HashUtilsTest.java new file mode 100644 index 0000000..1eaad9b --- /dev/null +++ b/src/test/java/net/locusworks/test/HashUtilsTest.java @@ -0,0 +1,38 @@ +package net.locusworks.test; + +import static org.junit.Assert.*; + +import org.apache.commons.codec.digest.DigestUtils; +import org.junit.Test; + +import net.locusworks.common.utils.HashUtils; + +public class HashUtilsTest { + + private static final String TEST_STRING = "Hello World"; + + @Test + public void testMD5() throws Exception { + String digestUtilsMD5 = DigestUtils.md5Hex(TEST_STRING.getBytes()); + String hashUtilsMD5 = HashUtils.hash("MD5", TEST_STRING); + + assertTrue(digestUtilsMD5.equals(hashUtilsMD5)); + } + + @Test + public void testSHA1() throws Exception { + String digestUtilsMD5 = DigestUtils.sha1Hex(TEST_STRING.getBytes()); + String hashUtilsMD5 = HashUtils.hash("SHA-1", TEST_STRING); + + assertTrue(digestUtilsMD5.equals(hashUtilsMD5)); + } + + @Test + public void testSHA512() throws Exception { + String digestUtilsMD5 = DigestUtils.sha512Hex(TEST_STRING.getBytes()); + String hashUtilsMD5 = HashUtils.hash("SHA-512", TEST_STRING); + + assertTrue(digestUtilsMD5.equals(hashUtilsMD5)); + } + +} diff --git a/src/test/java/net/locusworks/test/ImmutablesTest.java b/src/test/java/net/locusworks/test/ImmutablesTest.java new file mode 100644 index 0000000..d8992a6 --- /dev/null +++ b/src/test/java/net/locusworks/test/ImmutablesTest.java @@ -0,0 +1,45 @@ +package net.locusworks.test; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import net.locusworks.common.immutables.Pair; +import net.locusworks.common.immutables.Triplet; +import net.locusworks.common.immutables.Unit; + + +public class ImmutablesTest { + + @Test + public void testUnit() { + Unit unit = new Unit<>("Hello World"); + assertTrue(unit.getValue1().equals("Hello World")); + Unit unit2 = new Unit<>(2); + assertTrue(unit2.getValue1().equals(2)); + } + + @Test + public void testPair() { + Pair pair1 = new Pair<>("Hello", "World"); + assertTrue(pair1.getValue1().equals("Hello")); + assertTrue(pair1.getValue2().equals("World")); + + Pair pair2 = new Pair<>("Foo", 25); + assertTrue(pair2.getValue1().equals("Foo")); + assertTrue(pair2.getValue2().equals(25)); + + Pair pair3 = new Pair<>(1, 23); + assertTrue(pair3.getValue1().equals(1)); + assertTrue(pair3.getValue2().equals(23)); + } + + @Test + public void testTriplet() { + Triplet triplet1 = new Triplet<>("Hello", 24, "World"); + assertTrue(triplet1.getValue1().equals("Hello")); + assertTrue(triplet1.getValue2().equals(24)); + assertTrue(triplet1.getValue3().equals("World")); + } + +} diff --git a/src/test/java/net/locusworks/test/ObjectMapperHelperTest.java b/src/test/java/net/locusworks/test/ObjectMapperHelperTest.java new file mode 100644 index 0000000..e27b861 --- /dev/null +++ b/src/test/java/net/locusworks/test/ObjectMapperHelperTest.java @@ -0,0 +1,73 @@ +/** + * + */ +package net.locusworks.test; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import net.locusworks.common.immutables.Triplet; +import net.locusworks.common.objectmapper.ObjectMapperHelper; +import net.locusworks.common.utils.Utils; + +import static net.locusworks.common.utils.Constants.JUNIT_TEST_CHECK; +import static net.locusworks.common.utils.Constants.LOG4J_CONFIG_PROPERTY; + +/** + * Test cases to test ObjectMapperHelper.class + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * + */ +public class ObjectMapperHelperTest { + + private static Triplet test; + + /** + * @throws java.lang.Exception exception + */ + @BeforeClass + public static void setUpBeforeClass() throws Exception { + System.setProperty(LOG4J_CONFIG_PROPERTY, "log4j2-test.xml"); + System.setProperty(JUNIT_TEST_CHECK, "true"); + test = new Triplet("Hello", 24, "World"); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + System.clearProperty(LOG4J_CONFIG_PROPERTY); + System.clearProperty(JUNIT_TEST_CHECK); + } + + @Test + public void testWrite() { + String value = ObjectMapperHelper.writeValue(test).getResults(); + assertTrue(value != null && !value.trim().isEmpty()); + } + + @Test + public void testRead() { + String value = ObjectMapperHelper.writeValue(test).getResults(); + assertTrue(value != null && !value.trim().isEmpty()); + + Triplet tmp = ObjectMapperHelper.readValue(value, Triplet.class).getResults(); + assertTrue(tmp != null); + assertTrue(tmp.equals(test)); + } + + @SuppressWarnings("rawtypes") + @Test + public void testListWriteRead() { + List> htrList = Utils.toList(test); + String value = ObjectMapperHelper.writeValue(htrList).getResults(); + assertTrue(value != null && !value.trim().isEmpty()); + List tmpList = ObjectMapperHelper.readListValue(value, Triplet.class).getResults(); + assertTrue(tmpList != null && tmpList.size() > 0); + } + +} diff --git a/src/test/java/net/locusworks/test/PropertiesManagerTest.java b/src/test/java/net/locusworks/test/PropertiesManagerTest.java new file mode 100644 index 0000000..7ab0684 --- /dev/null +++ b/src/test/java/net/locusworks/test/PropertiesManagerTest.java @@ -0,0 +1,122 @@ +package net.locusworks.test; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +import org.junit.AfterClass; +import org.junit.Test; + +import net.locusworks.common.configuration.PropertiesManager; + +/** + * Test cases for the properties manager class + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * + */ +public class PropertiesManagerTest { + + private static final String PROPERTIES_FILE = "test.properties"; + private static final String TMP_PROPS = "temp.properties"; + private static final int ENTRY_SIZE = 4; + + public static enum Configuration { + DB_HOST("dbHost"), + DB_PORT("dbPort"), + USER_EXPIRATION_DAYS("userExpirationDays"), + LOG_LEVEL("logLevel"); + + private String value; + + private Configuration(String value) { + this.value = value; + } + + /** + * Get the current value of the enumeration + * @return value + */ + public String getValue() { + return this.value; + } + + @Override + public String toString() { + return this.value; + } + } + + @AfterClass + public static void removeSavedProps() { + File tmp = new File(TMP_PROPS); + if (tmp.exists()) { + tmp.delete(); + } + } + + @Test + public void testPropertiesLoad() { + try { + Properties props = PropertiesManager.loadConfiguration(this.getClass(), PROPERTIES_FILE); + assertTrue(props != null); + assertTrue(props.containsKey(Configuration.USER_EXPIRATION_DAYS.toString())); + assertTrue(props.containsKey(Configuration.DB_HOST.toString())); + assertTrue(props.containsKey(Configuration.DB_PORT.toString())); + assertTrue(props.containsKey(Configuration.LOG_LEVEL.toString())); + } catch (IOException e) { + fail(e.getMessage()); + } + } + + @Test + public void testAddConfiguration() { + try { + Properties props = PropertiesManager.loadConfiguration(this.getClass(), PROPERTIES_FILE); + Properties tmp = new Properties(); + assertTrue(tmp.keySet().size() == 0); + PropertiesManager.addConfiguration(tmp, props); + assertTrue(tmp.keySet().size() == ENTRY_SIZE); + assertTrue(tmp.containsKey(Configuration.USER_EXPIRATION_DAYS.toString())); + assertTrue(tmp.containsKey(Configuration.DB_HOST.toString())); + assertTrue(tmp.containsKey(Configuration.DB_PORT.toString())); + assertTrue(tmp.containsKey(Configuration.LOG_LEVEL.toString())); + } catch (IOException e) { + fail(e.getMessage()); + } + } + + @Test + public void testRemoveConfiguration() { + try { + Properties props = PropertiesManager.loadConfiguration(this.getClass(), PROPERTIES_FILE); + Properties tmp = new Properties(); + assertTrue(props.keySet().size() == ENTRY_SIZE); + assertTrue(tmp.keySet().size() == 0); + PropertiesManager.removeConfiguration(props, tmp); + assertTrue(props.keySet().size() == 0); + assertTrue(tmp.keySet().size() == 0); + } catch (IOException e) { + fail(e.getMessage()); + } + } + + @Test + public void testSaveConfiguration() { + try { + Properties props = PropertiesManager.loadConfiguration(this.getClass(), PROPERTIES_FILE); + File tmpFile = new File(TMP_PROPS); + PropertiesManager.saveConfiguration(props, tmpFile, "test propertis"); + Properties tmp = PropertiesManager.loadConfiguration(tmpFile); + assertTrue(tmp.keySet().size() == ENTRY_SIZE); + assertTrue(tmp.containsKey(Configuration.USER_EXPIRATION_DAYS.toString())); + assertTrue(tmp.containsKey(Configuration.DB_HOST.toString())); + assertTrue(tmp.containsKey(Configuration.DB_PORT.toString())); + assertTrue(tmp.containsKey(Configuration.LOG_LEVEL.toString())); + } catch (IOException e) { + fail(e.getMessage()); + } + } +} diff --git a/src/test/java/net/locusworks/test/RandomStringTest.java b/src/test/java/net/locusworks/test/RandomStringTest.java new file mode 100644 index 0000000..8e411b0 --- /dev/null +++ b/src/test/java/net/locusworks/test/RandomStringTest.java @@ -0,0 +1,26 @@ +package net.locusworks.test; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import net.locusworks.common.utils.RandomString; + +public class RandomStringTest { + + @Test + public void testStaticBytes() { + for (Integer length = 3; length < 50; length++) { + assertTrue(RandomString.getBytes(length).length == length); + } + } + + @Test + public void testStaticString() { + for (Integer length = 3; length < 50; length++) { + String random = RandomString.getString(length); + assertTrue(random.length() == length); + } + } + +} diff --git a/src/test/java/net/locusworks/test/UtilsTest.java b/src/test/java/net/locusworks/test/UtilsTest.java new file mode 100644 index 0000000..e16587d --- /dev/null +++ b/src/test/java/net/locusworks/test/UtilsTest.java @@ -0,0 +1,39 @@ +package net.locusworks.test; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import net.locusworks.common.utils.Utils; + +/** + * Test cases for the Utils class + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * + */ +public class UtilsTest { + + @Test + public void testSafeString() { + assertNotNull(Utils.safeString(null)); + assertTrue(Utils.safeString(null).isEmpty()); + assertFalse(Utils.safeString("hello world").isEmpty()); + } + + @Test + public void testEmptyString() { + assertTrue(Utils.isEmptyString(null)); + assertTrue(Utils.isEmptyString("")); + assertTrue(Utils.isEmptyString(" ")); + assertFalse(Utils.isEmptyString("foo")); + assertFalse(Utils.isEmptyString(" bar ")); + } + + @Test + public void testToInteger() { + assertTrue(Utils.toInteger("Hello word", 2) == 2); + assertTrue(Utils.toInteger("23", 5023) == 23); + } + +} diff --git a/src/test/resources/log4j2-test.xml b/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000..a2c3fe0 --- /dev/null +++ b/src/test/resources/log4j2-test.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties new file mode 100644 index 0000000..292c3da --- /dev/null +++ b/src/test/resources/test.properties @@ -0,0 +1,4 @@ +userExpirationDays=3650 +dbHost=localhost +dbPort=3306 +logLevel=INFO diff --git a/temp.properties b/temp.properties new file mode 100644 index 0000000..15169ba --- /dev/null +++ b/temp.properties @@ -0,0 +1,6 @@ +#test propertis +#Fri Feb 16 09:42:40 EST 2018 +logLevel=INFO +dbPort=3306 +userExpirationDays=3650 +dbHost=localhost