Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: sbabcoc/Java-Utils
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: java-utils-1.3.3
Choose a base ref
...
head repository: sbabcoc/Java-Utils
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref

Commits on Dec 5, 2017

  1. Copy the full SHA
    ab2a6b7 View commit details
  2. Update README.md

    sbabcoc authored Dec 5, 2017
    Copy the full SHA
    2483629 View commit details
  3. Update PathUtils.java

    sbabcoc authored Dec 5, 2017
    Copy the full SHA
    5184186 View commit details

Commits on Feb 16, 2018

  1. Copy the full SHA
    4d7b823 View commit details
  2. Copy the full SHA
    ffdd5da View commit details
  3. Copy the full SHA
    6c7c462 View commit details
  4. Document type parameter

    sbabcoc committed Feb 16, 2018
    Copy the full SHA
    257fd56 View commit details
  5. Fix a few Sonar issues

    sbabcoc committed Feb 16, 2018
    Copy the full SHA
    34b4417 View commit details

Commits on Feb 18, 2018

  1. Add DatabaseUtils unit tests

    sbabcoc committed Feb 18, 2018
    Copy the full SHA
    a4780c2 View commit details
  2. Copy the full SHA
    a7f50f0 View commit details
  3. Copy the full SHA
    751637d View commit details
  4. Copy the full SHA
    2f3eccc View commit details

Commits on Feb 19, 2018

  1. Copy the full SHA
    51780dd View commit details

Commits on Feb 21, 2018

  1. Finish VolumeInfo class

    sbabcoc committed Feb 21, 2018
    Copy the full SHA
    7abd671 View commit details
  2. Copy the full SHA
    9b209f5 View commit details
  3. Copy the full SHA
    d9df9e6 View commit details
  4. Tweak implementation

    sbabcoc committed Feb 21, 2018
    Copy the full SHA
    1ecdf62 View commit details
  5. Merge pull request #2 from Nordstrom/pr/add-os-type-check

    Pr/add os type check
    sbabcoc authored Feb 21, 2018
    Copy the full SHA
    9b05863 View commit details
  6. Copy the full SHA
    d3f5651 View commit details
  7. Copy the full SHA
    8f5d325 View commit details
  8. Commit POM change

    sbabcoc committed Feb 21, 2018
    Copy the full SHA
    dde0ccd View commit details
  9. Copy the full SHA
    077e3ed View commit details
  10. Copy the full SHA
    c371642 View commit details
  11. Another try at POM

    sbabcoc committed Feb 21, 2018
    Copy the full SHA
    38e63e7 View commit details
  12. Copy the full SHA
    e7a58cc View commit details
  13. Copy the full SHA
    7e55d9e View commit details

Commits on Feb 28, 2018

  1. Copy the full SHA
    f7eb1c7 View commit details

Commits on Mar 1, 2018

  1. Copy the full SHA
    915daed View commit details
  2. Copy the full SHA
    bfaec2d View commit details
  3. Copy the full SHA
    494d4da View commit details
  4. Copy the full SHA
    c46214a View commit details
  5. Copy the full SHA
    a3ee8ed View commit details
  6. Copy the full SHA
    f972db0 View commit details
  7. Copy the full SHA
    997d7be View commit details
  8. Copy the full SHA
    d74dded View commit details
  9. Test

    sbabcoc committed Mar 1, 2018
    Copy the full SHA
    35b1651 View commit details
  10. Untest

    sbabcoc authored Mar 1, 2018
    Copy the full SHA
    dfdfd49 View commit details
  11. Copy the full SHA
    08e805c View commit details
  12. Copy the full SHA
    1bc0e73 View commit details
  13. Copy the full SHA
    7bde69d View commit details
  14. Copy the full SHA
    a4e1dbc View commit details
  15. Copy the full SHA
    3bc2a64 View commit details
  16. Copy the full SHA
    7da09fc View commit details
  17. Copy the full SHA
    ddbaa42 View commit details
  18. Copy the full SHA
    4cff925 View commit details
  19. Copy the full SHA
    f302c9c View commit details
  20. So much for that

    sbabcoc committed Mar 1, 2018
    Copy the full SHA
    0ac5ed6 View commit details
  21. Set SNAPSHOT version

    sbabcoc committed Mar 1, 2018
    Copy the full SHA
    1e6de15 View commit details
  22. Copy the full SHA
    3506f1f View commit details
  23. Copy the full SHA
    109c640 View commit details
Showing with 3,551 additions and 544 deletions.
  1. +15 −14 .gitignore
  2. +241 −21 README.md
  3. +248 −188 pom.xml
  4. +57 −0 src/main/java/com/nordstrom/common/base/ExceptionUnwrapper.java
  5. +50 −0 src/main/java/com/nordstrom/common/base/StackTrace.java
  6. +1 −1 src/main/java/com/nordstrom/common/base/UncheckedThrow.java
  7. +37 −0 src/main/java/com/nordstrom/common/clazz/CustomClassLoader.java
  8. +166 −0 src/main/java/com/nordstrom/common/file/OSInfo.java
  9. +297 −72 src/main/java/com/nordstrom/common/file/PathUtils.java
  10. +120 −0 src/main/java/com/nordstrom/common/file/VolumeInfo.java
  11. +210 −0 src/main/java/com/nordstrom/common/jar/JarUtils.java
  12. +669 −235 src/main/java/com/nordstrom/common/jdbc/DatabaseUtils.java
  13. +533 −0 src/main/java/com/nordstrom/common/jdbc/Param.java
  14. +88 −0 src/main/java/com/nordstrom/common/params/Params.java
  15. +63 −0 src/main/java/com/nordstrom/common/uri/UriUtils.java
  16. +35 −0 src/test/java/com/nordstrom/common/base/UncheckedThrowTest.java
  17. +74 −0 src/test/java/com/nordstrom/common/file/OSInfoTest.java
  18. +67 −13 src/test/java/com/nordstrom/common/file/PathUtilsTest.java
  19. +25 −0 src/test/java/com/nordstrom/common/file/VolumeInfoTest.java
  20. +25 −0 src/test/java/com/nordstrom/common/jar/JarUtilsTest.java
  21. +298 −0 src/test/java/com/nordstrom/common/jdbc/DatabaseUtilsTest.java
  22. +60 −0 src/test/java/com/nordstrom/common/jdbc/StoredProcedure.java
  23. +84 −0 src/test/java/com/nordstrom/common/params/ParamTest.java
  24. +87 −0 src/test/java/com/nordstrom/common/uri/UriUtilsTest.java
  25. +1 −0 src/test/resources/META-INF/services/java.sql.Driver
29 changes: 15 additions & 14 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
.settings
target
.classpath
.project
*~
*.tmproj
*.iml
.idea
.scala_dependencies
integration/bundle/
integration/felix-cache/
runner
.DS_Store
test-output
.settings
target
.classpath
.project
*~
*.tmproj
*.iml
.idea
.scala_dependencies
integration/bundle/
integration/felix-cache/
runner
.DS_Store
test-output
derby.log
262 changes: 241 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,97 @@
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.nordstrom.tools/java-utils/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.nordstrom.tools/java-utils)
[![Maven Central](https://img.shields.io/maven-central/v/com.nordstrom.tools/java-utils.svg)](https://central.sonatype.com/search?q=com.nordstrom.tools+java-utils&core=gav)

# NORDSTROM JAVA UTILS

**Nordstrom Java Utils** is a small collection of general-purpose utility classes with wide applicability.

## What You'll Find Here

* [StackTrace](#stacktrace) provides a facility to capture the flow of execution that led to interesting system state transitions.
* [ExceptionUnwrapper](#exceptionunwrapper) provides methods for extracting the contents of "wrapped" exceptions.
* [UncheckedThrow](#uncheckedthrow) provides a method that uses type erasure to enable you to throw checked exception as unchecked.
* [DatabaseUtils](#databaseutils) provides facilities that enable you to define collections of database queries and stored procedures in an easy-to-execute format.
* [Query Collections](#query-collections) are defined as Java enumerations that implement the `QueryAPI` interface
* [Stored Procedure Collections](#stored-procedure-collections) are defined as Java enumerations that implement the `SProcAPI` interface
* [Recommended Implementation Strategies](#recommended-implementation-strategies) to maximize usability and configurability
* [Query Collection Example](#query-collection-example)
* [Registering JDBC Providers](#registering-jdbc-providers) with the **ServiceLoader** facility of **DatabaseUtils**
* [OSInfo](#osinfo) provides utility methods and abstractions for host operating system features.
* [VolumeInfo](#volumeinfo) provides methods that parse the output of the 'mount' utility into a mapped collection of volume property records.
* [PathUtils](#pathutils) provides a method to acquire the next file path in sequence for the specified base name and extension in the indicated target folder.
* [Params Interface](#params-interface) defines concise methods for the creation of named parameters and parameter maps.
* [JarUtils](#jarutils) provides methods related to Java JAR files:
* [Assembling a Classpath String](#assembling-a-classpath-string)
* [Finding a JAR File Path](#finding-a-jar-file-path)
* [Extracting the `Premain-Class` Attribute](#extracting-the-premain-class-attribute)
* [UriUtils](#uriutils) provides convenience methods for assembling **URI** objects without having to deal with **URISyntaxException**.

## StackTrace

The **StackTrace** class extends **Throwable** and it's intended to facilitate capture of the flow of execution that triggered system state changes that may lead to future operation errors. For example, an object that's no longer valid might get used subsequent to the event that caused it to become invalid, or a long-lived object may get discarded without corresponding resources being cleaned up.

For more details, check out the [blog post](https://blog.vanillajava.blog/2021/12/unusual-java-stacktrace-extends.html) that provided the implementation for **StackTrace**.

## ExceptionUnwrapper

The **ExceptionUnwrapper** class provides methods for extracting the contents of "wrapped" exceptions.

## UncheckedThrow

The **UncheckedThrow** class uses type erasure to enable client code to throw checked exceptions as unchecked. This allows methods to throw checked exceptions without requiring clients to handle or declare them. It should be used judiciously, as this exempts client code from handling or declaring exceptions created by their own actions. The target use case for this facility is to throw exceptions that were serialized in responses from a remote system. Although the compiler won't require clients of methods using this technique to handle or declare the suppressed exception, the JavaDoc for such methods should include a `@throws` declaration for implementers who might want to handle or declare it voluntarily.


```java
...

String value;
try {
value = URLDecoder.decode(keyVal[1], "UTF-8");
} catch (UnsupportedEncodingException e) {
       throw UncheckedThrow.throwUnchecked(e);
throw UncheckedThrow.throwUnchecked(e);
}

...
```

## DatabaseUtils

The **DatabaseUtils** class provides facilities that enable you to define collections of Oracle database queries and execute them easily. Query collections are defined as Java enumerations that implement the `QueryAPI` interface:
**DatabaseUtils** provides facilities that enable you to define collections of database queries and stored procedures in an easy-to-execute format.

### Query Collections

Query collections are defined as Java enumerations that implement the `QueryAPI` interface:
* `getQueryStr` - Get the query string for this constant. This is the actual query that's sent to the database.
* `getArgNames` - Get the names of the arguments for this query. This provides diagnostic information if the incorrect number of arguments is specified by the client.
* `getArgCount` - Get the number of arguments required by this query. This enables **DatabaseUtils** to verify that the correct number of arguments has been specified by the client.
* `getConnection` - Get the connection string associated with this query. This eliminates the need for the client to provide this information.
* `getEnum` - Get the enumeration to which this query belongs. This enables **DatabaseUtils** to retrieve the name of the query's enumerated constant for diagnostic messages.

To maximize usability and configurability, we recommend the following implementation strategy for your query collections:
* Define your query collection as an enumeration that implements `QueryAPI`.
* Define each query constant with a property name and a name for each argument (if any).
* To assist users of your queries, preface their names with a type indicator (**GET** or **UPDATE**).
* Back the query collection with a configuration that implements the `Settings API`:
* groupId: com.nordstrom.tools
* `getEnum` - Get the enumeration to which this query belongs. This enables `executeQuery(Class, QueryAPI, Object[])` to retrieve the name of the query's enumerated constant for diagnostic messages.
* ... see the _JavaDoc_ for the `QueryAPI` interface for additional information.

### Stored Procedure Collections

Store procedure collections are defined as Java enumerations that implement the `SProcAPI` interface:
* `getSignature` - Get the signature for this stored procedure object. This defines the name of the stored procedure and the modes of its arguments. If the stored procedure accepts `varargs`, this will also be indicated (see _JavaDoc_ for details).
* `getArgTypes` - Get the argument types for this stored procedure object.
* `getConnection` - Get the connection string associated with this stored procedure. This eliminates the need for the client to provide this information.
* `getEnum` - Get the enumeration to which this stored procedure belongs. This enables `executeStoredProcedure(Class, SProcAPI, Object[])` to retrieve the name of the stored procedured's enumerated constant for diagnostic messages.
* ... see the _JavaDoc_ for the `SProcAPI` interface for additional information.

### Recommended Implementation Strategy

To maximize usability and configurability, we recommend the following implementation strategy:
* Define your collection as an enumeration:
* Query collections implement the `QueryAPI` interface.
* Stored procedure collections implement the `SProcAPI` interface.
* Define each constant:
* (query) Specify a property name and a name for each argument (if any).
* (sproc) Declare the signature and the type for each argument (if any).
* To assist users of your queries, preface their names with a type indicator (<b>GET</b> or <b>UPDATE</b>).
* Back query collections with configurations that implement the **`Settings API`**:
* groupId: com.nordstrom.test-automation.tools
* artifactId: settings
* className: com.nordstrom.automation.settings.SettingsCore
* To support execution on multiple endpoints, implement `getConnection` with sub-configurations or other dynamic data sources (e.g. - web service).

##### Query Collection Example
* To support execution on multiple endpoints, implement `QueryAPI.getConnection()` or `SProcAPI.getConnection()` with sub-configurations or other dynamic data sources (e.g. - web service).

#### Query Collection Example

```java
public class OpctConfig extends SettingsCore<OpctConfig.OpctValues> {
@@ -105,11 +155,6 @@ public class OpctConfig extends SettingsCore<OpctConfig.OpctValues> {
return args;
}

@Override
public int getArgCount() {
return args.length;
}

@Override
public String getConnection() {
if (rmsQueries.contains(this)) {
@@ -162,12 +207,93 @@ public class OpctConfig extends SettingsCore<OpctConfig.OpctValues> {
public static OpctConfig getConfig() {
return OpctValues.config;
}

public enum SProcValues implements SProcAPI {
/** args: [ ] */
SHOW_SUPPLIERS("SHOW_SUPPLIERS()"),
/** args: [ coffee_name, supplier_name ] */
GET_SUPPLIER_OF_COFFEE("GET_SUPPLIER_OF_COFFEE(>, <)", Types.VARCHAR, Types.VARCHAR),
/** args: [ coffee_name, max_percent, new_price ] */
RAISE_PRICE("RAISE_PRICE(>, >, =)", Types.VARCHAR, Types.REAL, Types.NUMERIC),
/** args: [ str, val... ] */
IN_VARARGS("IN_VARARGS(<, >:)", Types.VARCHAR, Types.INTEGER),
/** args: [ val, str... ] */
OUT_VARARGS("OUT_VARARGS(>, <:)", Types.INTEGER, Types.VARCHAR);

private int[] argTypes;
private String signature;

SProcValues(String signature, int... argTypes) {
this.signature = signature;
this.argTypes = argTypes;
}

@Override
public String getSignature() {
return signature;
}

@Override
public int[] getArgTypes () {
return argTypes;
}

@Override
public String getConnection() {
return OpctValues.getRmsConnect();
}

@Override
public Enum<SProcValues> getEnum() {
return this;
}
}
}
```

### Registering JDBC Drivers

To provide maximum flexibility, JDBC interacts with database instances through a defined interface (**java.sql.Driver**). Implementations of this interface translate its methods into their vendor-specific protocol, in classes called **drivers**. For example, [OracleDriver](https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/jdbc/OracleDriver.html) enables JDBC to interact with Oracle database products.

In JDBC connection URLs, the vendor and driver are specified as suffixes to the `jdbc` protocol. For the Oracle "thin" driver, this is `jdbc:oracle:thin`. This protocol/vendor/driver combination is handled by **OracleDriver**, and JDBC needs this class to be registered to handle this vendor-specific protocol.

To simplify the process of registering vendor-specific JDBC drivers, **DatabaseUtils** loads these for you through the Java **ServiceLoader** facility. Declare the driver(s) you need in a **ServiceLoader** provider configuration file at **_META-INF/services/java.sql.Driver_**:

```
oracle.jdbc.OracleDriver
```

This sample provider configuration file will cause **DatabaseUtils** to load the JDBC driver class for Oracle database products. The JAR that declares this class needs to be on the class path for this to work. For Maven projects, you just need to add the correct dependency:

```xml
[pom.xml]
<project ...>
[...]

<dependencies>
[...]
<dependency>
<groupId>com.oracle.jdbc</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4.0</version>
</dependency>
</dependencies>

[...]
</project>
```

## OSInfo

The **OSInfo** class provides utility methods and abstractions for host operating system features.

## VolumeInfo

The **VolumeInfo** class provides methods that parse the output of the 'mount' utility into a mapped collection of volume property records.

## PathUtils

The **PathUtils** `getNextPath` method provides a method to acquire the next file path in sequence for the specified base name and extension in the indicated target folder. If the target folder already contains at least one file that matches the specified base name and extension, the algorithm used to select the next path will always return a path whose index is one more than the highest index that currently exists. (If a single file with no index is found, its implied index is 1.)
The **PathUtils** class provides a method to acquire the next file path in sequence for the specified base name and extension in the indicated target folder. If the target folder already contains at least one file that matches the specified base name and extension, the algorithm used to select the next path will always return a path whose index is one more than the highest index that currently exists. (If a single file with no index is found, its implied index is 0.)

##### Example usage of `getNextPath`

@@ -196,3 +322,97 @@ The **PathUtils** `getNextPath` method provides a method to acquire the next fil

...
```

## Params Interface

The **Params** interface defines concise methods for the creation of named parameters and parameter maps. This facility can make your code much easier to read and maintain. The following example, which is extracted from the **Params** unit test class, demonstrates a few basic features.

#### Params Example
```
package com.nordstrom.example;
import static com.nordstrom.common.params.Params.param;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.util.Optional;
import org.testng.annotations.Test;
import com.nordstrom.common.params.Params;
public class ParamTest implements Params {
@Test
public void testDefault() {
assertFalse(Params.super.getParameters().isPresent());
}
@Test
public void testParam() {
Param param = param("boolean", true);
assertEquals(param.getKey(), "boolean");
verifyBoolean(param.getVal());
}
@Test
public void testParams() {
Optional<Map<String, Object>> optParameters = getParameters();
assertTrue(optParameters.isPresent());
Map<String, Object> parameters = optParameters.get();
assertFalse(parameters.isEmpty());
assertTrue(parameters.containsKey("boolean"));
verifyBoolean(parameters.get("boolean"));
}
private void verifyBoolean(Object value) {
assertTrue(value instanceof Boolean);
assertTrue((Boolean) value);
}
@Override
public Optional<Map<String, Object>> getParameters() {
return Params.mapOf(param("boolean", true), param("int", 1), param("String", "one"),
param("Map", Params.mapOf(param("key", "value"))));
}
}
```

This code uses a static import to eliminate redundant references to the **Params** interface. It also shows the unrestricted data types of parameter values. The use of **Optional** objects enables you to provide an indication that no value was returned without the risks associated with `null`.

## JarUtils

The **JarUtils** class provides methods related to Java JAR files.

* `getClasspath` assembles a classpath string from the specified array of dependencies.
* `findJarPathFor` finds the path to the JAR file from which the named class was loaded.
* `getJarPremainClass` gets the 'Premain-Class' attribute from the indicated JAR file.

The methods of this class provide critical services for the `Local Grid` feature of [**Selenium Foundation**](https://github.com/sbabcoc/Selenium-Foundation), handling the task of locating the JAR files that declare the classes required by the Java-based servers it launches.

### Assembling a Classpath String

The **`getClasspath`** method assembles a classpath string from the specified array of dependency contexts. This is useful for launching a Java sub-process, as it greatly simplifies the task of collecting the paths of JAR files that declare the classes required by your process. If any of the specified dependency contexts names the `premain` class of a Java agent, the string returned by this method will contain two records delimited by a `newline` character:

* `0` - assembled classpath string
* `1` - tab-delimited list of Java agent paths

### Finding a JAR File Path

The **`findJarPathFor`** method will find the absolute path to the JAR file from which the named class was loaded, provided the class has been loaded from a JAR file on the local file system.

### Extracting the `Premain-Class` Attribute

The **`getJarPremainClass`** method will extract the `Premain-Class` attribute from the manifest of the indicated JAR file. The value of this attribute specifies the name of a `Java agent` class declared by the JAR.

## UriUtils

As of Java 20, all of the constructors of the **URL** class have been deprecated. The recommended replacement is the **`toURL()`** method of the **URI** class, but all of the constructors of _this_ class throw **URISyntaxException**. While this isn't a huge ordeal, having to handle this exception in contexts where the constructor arguments have already been validated can degrade code readability with no benefit to code safety. The **UriUtils** class provides two convenience methods that employ the same strategy used by the **`create()`** method of the **URI** class - wrapping the **URISyntaxException** in an **IllegalArgumentException**:

* `makeBasicURI` assembles a basic URI from the specified components - scheme, host, port, and path.
* `uriForPath` assembles a URI for the specified path under the provided context.

Note that the **`toURL()`** method of the **URI** class throws the **MalformedURLException**, but this exception is a subclass of **IOException**, which affected code is almost certain to be handling already.

> Written with [StackEdit](https://stackedit.io/).
436 changes: 248 additions & 188 deletions pom.xml

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions src/main/java/com/nordstrom/common/base/ExceptionUnwrapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.nordstrom.common.base;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
* This utility class provides methods for extracting the contents of "wrapped" exceptions.
*/
public class ExceptionUnwrapper {

private static final Set<Class<? extends Exception>> unwrappable =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(RuntimeException.class, InvocationTargetException.class)));

private ExceptionUnwrapper() {
throw new AssertionError("ExceptionUnwrapper is a static utility class that cannot be instantiated.");
}

/**
* Unwrap the specified exception.
* <p>
* <b>NOTE</b>: This method unwraps the exception chain until it encounters either a non-wrapper exception or an
* exception with no specified cause. The unwrapped exception is returned.
*
* @param throwable exception to be unwrapped
* @return unwrapped exception
*/
public static Throwable unwrap(Throwable throwable) {
return unwrap(throwable, null);
}

/**
* Unwrap the specified exception, optionally retaining wrapper messages.
* <p>
* <b>NOTE</b>: This method unwraps the exception chain until it encounters either a non-wrapper exception or an
* exception with no specified cause. The unwrapped exception is returned.
*
* @param throwable exception to be unwrapped
* @param builder to compile wrapper messages (may be 'null')
* @return unwrapped exception
*/
public static Throwable unwrap(Throwable throwable, StringBuilder builder) {
Throwable thrown = throwable;
if (thrown != null) {
while (unwrappable.contains(thrown.getClass())) {
Throwable unwrapped = thrown.getCause();
if (unwrapped == null) break;
if (builder != null) builder.append(thrown.getMessage()).append(" -> ");
thrown = unwrapped;
}
}
return thrown;
}

}
50 changes: 50 additions & 0 deletions src/main/java/com/nordstrom/common/base/StackTrace.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.nordstrom.common.base;

/**
* Throwable created purely for the purposes of reporting a stack trace.
*
* This is not an Error or an Exception and is not expected to be thrown or caught.
* This <a href='https://blog.vanillajava.blog/2021/12/unusual-java-stacktrace-extends.html'>blog post</a> provided the
* original implementation.
* @author <a href='https://github.com/peter-lawrey'>Peter K Lawrey</a>
*/

public class StackTrace extends Throwable {

private static final long serialVersionUID = -3623586250962214453L;

public StackTrace() {
this("stack trace");
}

public StackTrace(String message) {
this(message, null);
}

public StackTrace(String message, Throwable cause) {
super(message + " on " + Thread.currentThread().getName(), cause);
}

public static StackTrace forThread(Thread t) {
if (t == null) return null;

StackTrace st = new StackTrace(t.toString());
StackTraceElement[] stackTrace = t.getStackTrace();
int start = 0;

if (stackTrace.length > 2) {
if (stackTrace[0].isNativeMethod()) {
start++;
}
}

if (start > 0) {
StackTraceElement[] ste2 = new StackTraceElement[stackTrace.length - start];
System.arraycopy(stackTrace, start, ste2, 0, ste2.length);
stackTrace = ste2;
}

st.setStackTrace(stackTrace);
return st;
}
}
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ public static RuntimeException throwUnchecked(final Throwable thrown) {
* @throws T dummy declaration to satisfy the compiler
*/
@SuppressWarnings("unchecked")
private static <T extends Exception> void propagate(Throwable thrown) throws T {
private static <T extends Throwable> void propagate(Throwable thrown) throws T {
// Due to generic type erasure, this cast only serves to satisfy the compiler
// that the requirement to declare the thrown exception has been met.
throw (T) thrown;
37 changes: 37 additions & 0 deletions src/main/java/com/nordstrom/common/clazz/CustomClassLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.nordstrom.common.clazz;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.util.Arrays;

/**
* This class implements a custom class loader initialized with a standard class path specification.
*/
public class CustomClassLoader extends ClassLoader {

private final URL[] pathUrls;

public CustomClassLoader(String classpath) {
pathUrls = Arrays.stream(classpath.split(File.pathSeparator)).map(entry -> {
try {
return Paths.get(entry).toUri().toURL();
} catch (MalformedURLException e) {
throw new UncheckedIOException("Invalid classpath entry: " + entry, e);
}
}).toArray(URL[]::new);
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try (URLClassLoader classLoader = new URLClassLoader(pathUrls)) {
return classLoader.loadClass(name);
} catch (IOException e) {
throw new ClassNotFoundException("Failed loading class: " + name, e);
}
}
}
166 changes: 166 additions & 0 deletions src/main/java/com/nordstrom/common/file/OSInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package com.nordstrom.common.file;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;

/**
* This class provides utility methods and abstractions for host operating system features.
*
* @param <T> an operating system mapping enumeration that implements the {@link OSProps} interface
*/
public class OSInfo<T extends Enum<T> & OSInfo.OSProps> {

private static final String osName = System.getProperty("os.name");
private static final String version = System.getProperty("os.version");
private static final String arch = System.getProperty("os.arch");

private final Map<T, String> typeMap = new LinkedHashMap<>();

/**
* Get an object that supports the set of operating systems defined in the {@link OSType} enumeration.
*
* @return OSUtils object that supports the operating systems defined in {@link OSType}
*/
public static OSInfo<OSType> getDefault() {
return new OSInfo<>(OSType.class);
}

/**
* Create an object that supports the mappings defined by the specified enumeration.
*
* @param enumClass operating system mapping enumeration
*/
public OSInfo(Class<T> enumClass) {
putAll(enumClass);
}

/**
* Get the enumerated type constant for the active operating system.
*
* @return OS type constant; if no match, returns 'null'
*/
public T getType() {
// populate a linked list with the entries of the linked type map
List<Entry<T, String>> entryList = new LinkedList<>(typeMap.entrySet());
// get a list iterator, setting the cursor at the tail end
ListIterator<Entry<T, String>> iterator = entryList.listIterator(entryList.size());
// iterate from last to first
while (iterator.hasPrevious()) {
Entry<T, String> thisEntry = iterator.previous();
if (osName.matches(thisEntry.getValue())) {
return thisEntry.getKey();
}
}
return null;
}

/**
* Add the specified mapping to the collection.
* <p>
* <b>NOTE</b>: If a mapping for the specified constant already exists, this mapping will be replaced.
*
* @param <U> an operating system mapping enumeration that implements the {@link OSProps} interface
* @param typeConst OS type constant
* @return value of previous mapping; 'null' if no mapping existed
*/
@SuppressWarnings("unchecked")
public <U extends Enum<U> & OSProps> String put(U typeConst) {
return typeMap.put((T) typeConst, typeConst.pattern());
}

/**
* Add the specified mapping to the collection.
* <p>
* <b>NOTE</b>: If a mapping for the specified constant already exists, this mapping will be replaced.
*
* @param <U> an operating system mapping enumeration that implements the {@link OSProps} interface
* @param typeConst OS type constant
* @param pattern OS name match pattern
* @return value of previous mapping; 'null' if no mapping existed
*/
@SuppressWarnings("unchecked")
public <U extends Enum<U> & OSProps> String put(U typeConst, String pattern) {
return typeMap.put((T) typeConst, pattern);
}

/**
* Add the mappings defined by the specified enumeration to the collection.
* <p>
* <b>NOTE</b>: If any of the specified mappings already exist, the previous mappings will be replaced.
*
* @param <U> an operating system mapping enumeration that implements the {@link OSProps} interface
* @param enumClass operating system mapping enumeration
*/
public <U extends Enum<U> & OSProps> void putAll(Class<U> enumClass) {
for (U typeConst : enumClass.getEnumConstants()) {
put(typeConst);
}
}

/**
* Get the name of the active operating system.
*
* @return name of the active operating system
*/
public static String osName() {
return osName;
}

/**
* Get the version of the existing operating system.
*
* @return version of the existing operating system
*/
public static String version() {
return version;
}

/**
* Get the architecture of the active operating system.
*
* @return architecture of the active operating system
*/
public static String arch() {
return arch;
}

/**
* This enumeration defines the default set of operating system mappings.
*/
public enum OSType implements OSProps {
WINDOWS("(?i).*win.*"),
MACINTOSH("(?i).*mac.*"),
UNIX("(?i).*(?:nix|nux|aix).*"),
SOLARIS("(?i).*sunos.*");

OSType(String pattern) {
this.pattern = pattern;
}

private String pattern;

@Override
public String pattern() {
return pattern;
}
}

/**
* This interface defines the required contract for operating system mapping enumerations.
*/
public interface OSProps {

/**
* Get the OS name match pattern for this mapping.
*
* @return OS name match pattern
*/
String pattern();

}

}
369 changes: 297 additions & 72 deletions src/main/java/com/nordstrom/common/file/PathUtils.java

Large diffs are not rendered by default.

120 changes: 120 additions & 0 deletions src/main/java/com/nordstrom/common/file/VolumeInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.nordstrom.common.file;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.nordstrom.common.file.OSInfo.OSType;

/**
* This utility class provides methods that parse the output of the 'mount' utility into a mapped collection of
* volume property records.
*/
public class VolumeInfo {

static final boolean IS_WINDOWS = (OSInfo.getDefault().getType() == OSType.WINDOWS);

private VolumeInfo() {
throw new AssertionError("VolumeInfo is a static utility class that cannot be instantiated");
}

/**
* Invoke the 'mount' utility and return its output as a mapped collection of volume property records.
*
* @return map of {@link VolumeProps} objects
* @throws IOException if an I/O error occurs
*/
public static Map<String, VolumeProps> getVolumeProps() throws IOException {
Process mountProcess;
if (IS_WINDOWS) {
String[] cmd = {"sh", "-c", "mount | grep noumount"};
mountProcess = Runtime.getRuntime().exec(cmd);
} else {
mountProcess = Runtime.getRuntime().exec("mount");
}
return getVolumeProps(mountProcess.getInputStream());
}

/**
* Parse the content of the provided input stream into a mapped collection of volume property records.
* <p>
* <b>NOTE</b>: This method assumes that the provided content was produced by the 'mount' utility.
*
* @param is {@link InputStream} emitted by the 'mount' utility
* @return map of {@link VolumeProps} objects
* @throws IOException if an I/O error occurs
*/
public static Map<String, VolumeProps> getVolumeProps(InputStream is) throws IOException {
Map<String, VolumeProps> propsList = new HashMap<>();
Pattern template = Pattern.compile("(.+) on (.+) type (.+) \\((.+)\\)");

InputStreamReader isr = new InputStreamReader(is);
try(BufferedReader mountOutput = new BufferedReader(isr)) {
String line;
while(null != (line = mountOutput.readLine())) {
Matcher matcher = template.matcher(line);
if (matcher.matches()) {
String spec = matcher.group(1);
String file = matcher.group(2);
String type = matcher.group(3);
String[] opts = matcher.group(4).split(",");
VolumeProps props = new VolumeProps(spec, file, type, opts);
if (props.size > 0L) {
propsList.put(spec, props);
}
}
}
}
return propsList;
}

public static class VolumeProps {

String file;
String type;
String[] opts;

private final long size;
private long free;

VolumeProps(String spec, String file, String type, String... opts) {
if (IS_WINDOWS) {
this.file = spec;
} else {
this.file = file;
}

this.type = type;
this.opts = opts;

File f = new File(this.file);
this.size = f.getTotalSpace();
this.free = f.getFreeSpace();
}

public String getFile() {
return file;
}

public String getType() {
return type;
}

public String[] getOpts() {
return opts;
}

public long getSize() {
return size;
}
public long getFree() {
return free;
}
}
}
210 changes: 210 additions & 0 deletions src/main/java/com/nordstrom/common/jar/JarUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package com.nordstrom.common.jar;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;

import com.nordstrom.common.base.UncheckedThrow;

/**
* This utility class provides methods related to Java JAR files:
*
* <ul>
* <li>{@link #getClasspath} assemble a classpath string from the specified array of dependencies.</li>
* <li>{@link #findJarPathFor} find the path to the JAR file from which the named class was loaded.</li>
* <li>{@link #getJarPremainClass} gets the 'Premain-Class' attribute from the indicated JAR file.</li>
* </ul>
*
* <b>NOTE</b>: The core implementation for the {@link #findJarPathFor(String)} method was shared on
* <a href="https://stackoverflow.com">Stack Overflow</a> by <a href="https://stackoverflow.com/users/125844/notnoop">
* notnoop</a> in their <a href="https://stackoverflow.com/a/1983875">answer</a> to a question regarding
* how to locate the JAR file from which a specified class was loaded. This code was attributed to the
* <a href="https://github.com/rzwitserloot/lombok.patcher">lombok.patcher</a> project, published by
* <a href="https://github.com/rzwitserloot">Reinier Zwitserloot</a>. An updated version of the original
* code can be found <a
* href="https://github.com/rzwitserloot/lombok.patcher/blob/master/src/patcher/lombok/patcher/ClassRootFinder.java">
* here</a>.
*/
public class JarUtils {

private JarUtils() {
throw new AssertionError("JarUtils is a static utility class that cannot be instantiated.");
}

/**
* Assemble a classpath string from the specified array of dependencies.
* <p>
* <b>NOTE</b>: If any of the specified dependency contexts names the {@code premain} class of a Java agent, the
* string returned by this method will contain two records delimited by a {@code newline} character:
*
* <ul>
* <li>0 - assembled classpath string</li>
* <li>1 - tab-delimited list of Java agent paths</li>
* </ul>
*
* @param dependencyContexts array of dependency contexts
* @return assembled classpath string (see <b>NOTE</b>)
*/
public static String getClasspath(final String[] dependencyContexts) {
// get dependency context paths
List<String> contextPaths = getContextPaths(false, dependencyContexts);
// pop classpath from collection
String classPath = contextPaths.remove(0);
// if no agents were found
if (contextPaths.isEmpty()) {
// classpath only
return classPath;
} else {
// classpath plus tab-delimited list of agent paths
return classPath + "\n" + String.join("\t", contextPaths);
}
}

/**
* Assemble a list of context paths from the specified array of dependencies.
* <p>
* <b>NOTE</b>: The first item of the returned list contains a path-delimited string of JAR file paths suitable
* for use with the Java {@code -cp} command line option. If any of the specified dependency contexts names the
* {@code premain} class of a Java agent, subsequent items contain fully-formed {@code -javaagent} specifications.
*
* @param dependencyContexts array of dependency contexts
* @return list of classpath/javaagent specifications (see <b>NOTE</b>)
*/
public static List<String> getContextPaths(final String[] dependencyContexts) {
return getContextPaths(true, dependencyContexts);
}

/**
* Assemble a list of context paths from the specified array of dependencies.
* <p>
* <b>NOTE</b>: The first item of the returned list contains a path-delimited string of JAR file paths suitable
* for use with the Java {@code -cp} command line option. If any of the specified dependency contexts names the
* {@code premain} class of a Java agent, subsequent items contain Java agent JAR paths with optional prefix.
*
* @param prefixAgents {@code true} to request prefixing Java agent paths with {@code -javaagent:}
* @param dependencyContexts array of dependency contexts
* @return list of classpath/javaagent paths (see <b>NOTE</b>)
*/
private static List<String> getContextPaths(final boolean prefixAgents, final String[] dependencyContexts) {
Set<String> pathList = new HashSet<>();
Set<String> agentList = new HashSet<>();
List<String> contextPaths = new ArrayList<>();
final String prefix = prefixAgents ? "-javaagent:" : "";
for (String contextClassName : dependencyContexts) {
// get JAR path for this dependency context
String jarPath = findJarPathFor(contextClassName);
// if this context names the premain class of a Java agent
if (contextClassName.equals(getJarPremainClass(jarPath))) {
// collect agent path
agentList.add(prefix + jarPath);
// otherwise
} else {
// collect class path
pathList.add(jarPath);
}
}
// add assembled classpath string
contextPaths.add(String.join(File.pathSeparator, pathList));
// add Java agent paths
contextPaths.addAll(agentList);
return contextPaths;
}

/**
* If the provided class has been loaded from a JAR file that is on the
* local file system, will find the absolute path to that JAR file.
* <p>
* <b>NOTE</b>: The core implementation of this method was lifted from
* the {@code lombok.patcher} project. See the comments at the head of
* {@link JarUtils this class} for details.
*
* @param contextClassName
* The JAR file that contained the class file that represents
* this class will be found.
* @return absolute path to the JAR file from which the specified class was
* loaded
* @throws IllegalStateException
* If the specified class was loaded from a directory or in some
* other way (such as via HTTP, from a database, or some other
* custom class-loading device).
*/
public static String findJarPathFor(final String contextClassName) {
Class<?> contextClass;

try {
contextClass = Class.forName(contextClassName);
} catch (ClassNotFoundException e) {
throw UncheckedThrow.throwUnchecked(e);
}

String shortName = contextClassName;
int idx = shortName.lastIndexOf('.');
String protocol;

if (idx > -1) {
shortName = shortName.substring(idx + 1);
}

String uri = contextClass.getResource(shortName + ".class").toString();

if (uri.startsWith("file:")) {
protocol = "file:";
String relPath = '/' + contextClassName.replace('.', '/') + ".class";
if (uri.endsWith(relPath)) {
idx = uri.length() - relPath.length();
} else {
throw new IllegalStateException("Unrecognized structure file-protocol path: " + uri);
}
} else if (uri.startsWith("jar:file:")) {
protocol = "jar:file:";
idx = uri.indexOf('!');
if (idx == -1) {
throw new IllegalStateException("No separator found in jar-protocol path: " + uri);
}
} else {
idx = uri.indexOf(':');
protocol = (idx > -1) ? uri.substring(0, idx) : "(unknown)";
throw new IllegalStateException("This class has been loaded remotely via the " + protocol
+ " protocol. Only loading from a jar on the local file system is supported.");
}

try {
String fileName = URLDecoder.decode(uri.substring(protocol.length(), idx),
Charset.defaultCharset().name());
return new File(fileName).getAbsolutePath();
} catch (UnsupportedEncodingException e) {
throw (InternalError) new InternalError(
"Default charset doesn't exist. Your VM is borked.").initCause(e);
}
}

/**
* Extract the 'Premain-Class' attribute from the manifest of the indicated JAR file.
*
* @param jarPath absolute path to the JAR file
* @return value of 'Premain-Class' attribute; {@code null} if unspecified
*/
public static String getJarPremainClass(String jarPath) {
try(InputStream inputStream = new FileInputStream(jarPath);
JarInputStream jarStream = new JarInputStream(inputStream)) {
Manifest manifest = jarStream.getManifest();
if (manifest != null) {
return manifest.getMainAttributes().getValue("Premain-Class");
}
} catch (IOException e) {
// nothing to do here
}
return null;
}

}
904 changes: 669 additions & 235 deletions src/main/java/com/nordstrom/common/jdbc/DatabaseUtils.java

Large diffs are not rendered by default.

533 changes: 533 additions & 0 deletions src/main/java/com/nordstrom/common/jdbc/Param.java

Large diffs are not rendered by default.

88 changes: 88 additions & 0 deletions src/main/java/com/nordstrom/common/params/Params.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.nordstrom.common.params;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
* This interface enables implementers to provide methods to support for concisely-defined parameters.
*/
public interface Params {

/**
* Get the defined parameters.
*
* @return optional map of named parameters
*/
Optional<Map<String, Object>> getParameters();

/**
* This class defines a parameter object.
*/
static class Param {

private final String key;
private final Object val;

/**
* Constructor for parameter object.
*
* @param key parameter key
* @param val parameter value
*/
public Param(String key, Object val) {
if ((key == null) || key.isEmpty()) {
throw new IllegalArgumentException("[key] must be a non-empty string");
}
this.key = key;
this.val = val;
}

/**
* Get key of this parameter.
*
* @return parameter key
*/
public String getKey() {
return key;
}

/**
* Get value of this parameter.
*
* @return parameter value
*/
public Object getVal() {
return val;
}

/**
* Assemble a map of parameters.
*
* @param params array of {@link Param} objects; may be {@code null} or empty
* @return optional map of parameters (may be empty)
*/
public static Optional<Map<String, Object>> mapOf(Param... params) {
if ((params == null) || (params.length == 0)) {
return Optional.empty();
}
Map<String, Object> paramMap = new HashMap<>();
for (Param param : params) {
paramMap.put(param.key, param.val);
}
return Optional.of(Collections.unmodifiableMap(paramMap));
}

/**
* Create a parameter object for the specified key/value pair.
*
* @param key parameter key (name)
* @param val parameter value
* @return parameter object
*/
public static Param param(String key, Object val) {
return new Param(key, val);
}
}
}
63 changes: 63 additions & 0 deletions src/main/java/com/nordstrom/common/uri/UriUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.nordstrom.common.uri;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class UriUtils {

private UriUtils() {
throw new AssertionError("UriUtils is a static utility class that cannot be instantiated.");
}

/**
* Assemble a URI for the specified path under the provided context. <br>
* <b>NOTE</b>: The URI returned by this method uses the scheme, host, and port of the provided context URL
* and specified path string.
*
* @param context context URL
* @param pathAndParams path component and query parameters
* @return URI for the specified path and parameters within the provided context
*/
public static URI uriForPath(final URL context, final String... pathAndParams) {
return makeBasicURI(context.getProtocol(), context.getHost(), context.getPort(), pathAndParams);
}

/**
* Assemble a basic URI from the specified components.
*
* @param scheme scheme name
* @param host host name
* @param port port number
* @param pathAndParams path and query parameters
* @return assembled basic URI
*/
public static URI makeBasicURI(final String scheme, final String host, final int port,
final String... pathAndParams) {
try {
String path = (pathAndParams.length > 0) ? pathAndParams[0] : null;
String query = null;
if (pathAndParams.length > 1) {
query = IntStream.range(1, pathAndParams.length).mapToObj(i -> {
try {
String param = pathAndParams[i];
int index = param.indexOf("=");
if (index == -1) return URLEncoder.encode(param, StandardCharsets.UTF_8.toString());
String key = URLEncoder.encode(param.substring(0, index), StandardCharsets.UTF_8.toString());
String val = URLEncoder.encode(param.substring(index + 1), StandardCharsets.UTF_8.toString());
return key + "=" + val;
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}).collect(Collectors.joining("&"));
}
return new URI(scheme, null, host, port, path, query, null);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
}
35 changes: 35 additions & 0 deletions src/test/java/com/nordstrom/common/base/UncheckedThrowTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.nordstrom.common.base;

import java.io.IOException;

import org.testng.annotations.Test;

public class UncheckedThrowTest {

@Test(expectedExceptions = {IOException.class})
public void testCheckedException() {
try {
throwCheckedException();
} catch (Throwable t) {
throw UncheckedThrow.throwUnchecked(t);
}
}

@Test(expectedExceptions = {AssertionError.class})
public void testUncheckedException() {
try {
throwUncheckedException();
} catch (Throwable t) {
throw UncheckedThrow.throwUnchecked(t);
}
}

private void throwCheckedException() throws IOException {
throw new IOException("This is a checked exception");
}

private void throwUncheckedException() {
throw new AssertionError("This is an unchecked exception");
}

}
74 changes: 74 additions & 0 deletions src/test/java/com/nordstrom/common/file/OSInfoTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.nordstrom.common.file;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;

import org.testng.annotations.Test;

import com.nordstrom.common.file.OSInfo.OSProps;
import com.nordstrom.common.file.OSInfo.OSType;

public class OSInfoTest {

private static final String osName = System.getProperty("os.name").toLowerCase();

@Test
public void testDefaultMapping() {
OSInfo<OSType> osUtils = OSInfo.getDefault();

OSType expected;
if (osName.startsWith("windows")) {
expected = OSType.WINDOWS;
} else if (osName.startsWith("mac")) {
expected = OSType.MACINTOSH;
} else {
expected = OSType.UNIX;
}

assertEquals(osUtils.getType(), expected, "Reported OS type doesn't match expected type");
}

@Test
public void testCustomMapping() {
OSInfo<TestEnum> osUtils = new OSInfo<>(TestEnum.class);
assertEquals(osUtils.getType(), TestEnum.TEST, "Reported OS type doesn't match expected type");
}

@Test
public void testAddOneMapping() {
OSInfo<OSType> osUtils = OSInfo.getDefault();
osUtils.put(TestEnum.TEST);
assertEquals(osUtils.getType(), TestEnum.TEST, "Reported OS type doesn't match expected type");
}

@Test
public void testAddMappingSet() {
OSInfo<OSType> osUtils = OSInfo.getDefault();
osUtils.putAll(TestEnum.class);
assertEquals(osUtils.getType(), TestEnum.TEST, "Reported OS type doesn't match expected type");
}

@Test
public void testOverrideMapping() {
OSInfo<OSType> osUtils = OSInfo.getDefault();
osUtils.put(OSType.SOLARIS, "(?i)" + osName);
assertEquals(osUtils.getType(), OSType.SOLARIS, "Reported OS type doesn't match expected type");
}

@Test
public void testUnsupportedType() {
OSInfo<OSType> osUtils = OSInfo.getDefault();
osUtils.put(osUtils.getType(), "");
assertNull(osUtils.getType(), "Reported OS type doesn't match expected type");
}

public enum TestEnum implements OSProps {
TEST;

@Override
public String pattern() {
return "(?i)" + osName;
}

}
}
80 changes: 67 additions & 13 deletions src/test/java/com/nordstrom/common/file/PathUtilsTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.nordstrom.common.file;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;

import java.io.File;
import java.io.IOException;
@@ -15,9 +17,10 @@
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.annotations.Test;
import org.testng.util.Strings;

public class PathUtilsTest {

@Test
public void testNextPath() throws IOException {
Path outputDir = getOutputPath();
@@ -29,58 +32,89 @@ public void testNextPath() throws IOException {
} else {
Files.createDirectories(targetPath);
}

Path path1 = PathUtils.getNextPath(targetPath, "testNextPath", "txt");
assertEquals(path1.getFileName().toString(), "testNextPath.txt");

path1.toFile().createNewFile();

Path path2 = PathUtils.getNextPath(targetPath, "testNextPath", "txt");
assertEquals(path2.getFileName().toString(), "testNextPath-1.txt");

path2.toFile().createNewFile();
targetPath.resolve("testNextPath-9.txt").toFile().createNewFile();
targetPath.resolve("testNextPath-10.txt").toFile().createNewFile();

Path path3 = PathUtils.getNextPath(targetPath, "testNextPath", "txt");
assertEquals(path3.getFileName().toString(), "testNextPath-11.txt");

Path path4 = PathUtils.getNextPath(targetPath, "test", "txt");
assertEquals(path4.getFileName().toString(), "test.txt");
}

@Test
public void testPrepend() {
String[] actual = PathUtils.prepend("one", "two", "three");
String[] expect = {"one", "two", "three"};
assertEquals(actual, expect);
}

@Test
public void testAppend() {
String[] actual = PathUtils.append("three", "one", "two");
String[] expect = {"one", "two", "three"};
assertEquals(actual, expect);
}

@Test
public void testBaseDir() {
String actual = PathUtils.getBaseDir();
String expect = getBasePath().toString();
assertEquals(actual, expect);
}

@Test
public void testPathForObject() {
Path actual = PathUtils.ReportsDirectory.getPathForObject(this);
Path expect = getBasePath().resolve("target").resolve("surefire-reports");
assertEquals(actual, expect);
}

private Path getOutputPath() {
ITestResult testResult = Reporter.getCurrentTestResult();
ITestContext testContext = testResult.getTestContext();
String outputDirectory = testContext.getOutputDirectory();
Path outputDir = Paths.get(outputDirectory);
return outputDir;
return Paths.get(outputDirectory);
}

private Path getBasePath() {
return Paths.get(System.getProperty("user.dir")).toAbsolutePath();
}

@Test(expectedExceptions = {AssertionError.class},
expectedExceptionsMessageRegExp = "PathUtils is a static utility class that cannot be instantiated")
public void testPrivateConstructor() throws Throwable {

Constructor<?>[] ctors;
ctors = PathUtils.class.getDeclaredConstructors();
assertEquals(ctors.length, 1, "PathUtils must have exactly one constructor");
assertEquals(ctors[0].getModifiers() & Modifier.PRIVATE, Modifier.PRIVATE,
"PathUtils constructor must be private");
assertEquals(ctors[0].getParameterTypes().length, 0, "PathUtils constructor must have no arguments");

try {
ctors[0].setAccessible(true);
ctors[0].newInstance();
} catch (InvocationTargetException e) {
throw e.getCause();
}
}

@Test(expectedExceptions = {NullPointerException.class})
public void testNullPath() throws IOException {
PathUtils.getNextPath(null, "test", "txt");
}

@Test(expectedExceptions = {IllegalArgumentException.class})
public void testNonExistentPath() throws IOException {
PathUtils.getNextPath(Paths.get("foobar"), "test", "txt");
@@ -105,4 +139,24 @@ public void testNullExtenstion() throws IOException {
public void testEmptyExtension() throws IOException {
PathUtils.getNextPath(getOutputPath(), "test", "");
}

@Test
public void testFindExecutableOnSystemPath() {
String path = PathUtils.findExecutableOnSystemPath("java");
assertNotNull(path);
}

@Test
public void testFindExecutableByFullPath() {
String javaPath = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
String path = PathUtils.findExecutableOnSystemPath(javaPath);
assertNotNull(path);
}

@Test
public void testGetSystemPath() {
String systemPath = PathUtils.getSystemPath();
assertFalse(Strings.isNullOrEmpty(systemPath));
}

}
25 changes: 25 additions & 0 deletions src/test/java/com/nordstrom/common/file/VolumeInfoTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.nordstrom.common.file;

import java.io.IOException;
import java.util.Map;

import org.testng.annotations.Test;

import com.nordstrom.common.file.VolumeInfo.VolumeProps;

public class VolumeInfoTest {

@Test
public void test() throws IOException {
Map<String, VolumeProps> propsList = VolumeInfo.getVolumeProps();
for (VolumeProps thisProps : propsList.values()) {
System.out.println("file: " + thisProps.getFile());
System.out.println("type: " + thisProps.getType());
System.out.println("opts: " + String.join(",", thisProps.getOpts()));
System.out.println("size: " + thisProps.getSize());
System.out.println("free: " + thisProps.getFree());
System.out.println("");
}
}

}
25 changes: 25 additions & 0 deletions src/test/java/com/nordstrom/common/jar/JarUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.nordstrom.common.jar;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

import java.io.File;
import org.testng.annotations.Test;

public class JarUtilsTest {

private static final String[] CONTEXTS = { "org.testng.annotations.Test", "com.beust.jcommander.JCommander",
"org.apache.derby.jdbc.EmbeddedDriver", "org.slf4j.Logger" };

@Test
public void testClasspath() {
String result = JarUtils.getClasspath(CONTEXTS);
String[] paths = result.split(File.pathSeparator);
assertEquals(paths.length, CONTEXTS.length, "path entry count mismatch");
for (String thisPath : paths) {
File file = new File(thisPath);
assertTrue(file.exists(), "nonexistent path entry: " + thisPath);
}
}

}
298 changes: 298 additions & 0 deletions src/test/java/com/nordstrom/common/jdbc/DatabaseUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
package com.nordstrom.common.jdbc;

import static org.testng.Assert.assertEquals;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Types;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import com.nordstrom.common.jdbc.DatabaseUtils.QueryAPI;
import com.nordstrom.common.jdbc.DatabaseUtils.ResultPackage;
import com.nordstrom.common.jdbc.DatabaseUtils.SProcAPI;

public class DatabaseUtilsTest {

static {
System.setProperty("derby.system.home", "target");
}

@BeforeClass
public static void startDerby() {
Connection conn = null;
try {
conn = DriverManager.getConnection(TestQuery.connection() + ";create=true");
} catch (SQLException e) {
printSQLException(e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
printSQLException(e);
}
}
}
}

@AfterClass
public static void stopDerby() {
Connection conn = null;
try {
conn = DriverManager.getConnection(TestQuery.connection() + ";shutdown=true");
} catch (SQLException e) {
if ( (e.getErrorCode() == 45000) && ("08006".equals(e.getSQLState())) ) {
// we got the expected exception
System.out.println("Derby shut down normally");
} else {
System.err.println("Derby did not shut down normally");
printSQLException(e);
}
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
printSQLException(e);
}
}
}
}

@Test
public void createTable() {
DatabaseUtils.update(TestQuery.CREATE);
}

@Test(dependsOnMethods= {"createTable"})
public void insertRows() {
DatabaseUtils.update(TestQuery.INSERT, 1956, "Webster St.");
DatabaseUtils.update(TestQuery.INSERT, 1910, "Union St.");
}

@Test(dependsOnMethods={"insertRows"})
public void updateRows() {
DatabaseUtils.update(TestQuery.UPDATE, 180, "Grand Ave.", 1956);
DatabaseUtils.update(TestQuery.UPDATE, 300, "Lakeshore Ave.", 180);
}

@Test(dependsOnMethods={"updateRows"})
public void showAddresses() {
try {
DatabaseUtils.update(TestQuery.SHOW_ADDRESSES);
} catch (Exception ignored) {
}

ResultPackage pkg = DatabaseUtils.getResultPackage(TestSProc.SHOW_ADDRESSES);

int rowCount = 0;
try {
while (pkg.getResultSet().next()) {
rowCount++;
int num = pkg.getResultSet().getInt("num");
String addr = pkg.getResultSet().getString("addr");
System.out.println("addr" + rowCount + ": " + num + " " + addr);
}
} catch (SQLException ignored) {
}
pkg.close();

DatabaseUtils.update(TestQuery.DROP_PROC_SHOW);
assertEquals(rowCount, 2);
}

@Test(dependsOnMethods={"showAddresses"})
public void getInt() {
DatabaseUtils.getInt(TestQuery.GET_NUM);
}

@Test(dependsOnMethods={"getInt"})
public void getString() {
DatabaseUtils.getString(TestQuery.GET_STR);
}

@Test(dependsOnMethods={"getString"})
public void getResultPackage() {
ResultPackage pkg = DatabaseUtils.getResultPackage(TestQuery.GET_RESULT_PACKAGE);
pkg.close();
}

@Test(dependsOnMethods={"getResultPackage"}, alwaysRun=true)
public void dropTable() {
DatabaseUtils.update(TestQuery.DROP);
}

@Test
public void testInVarargs() {
try {
DatabaseUtils.update(TestQuery.IN_VARARGS);
} catch (Exception ignored) {
}

String result = DatabaseUtils.getString(TestSProc.IN_VARARGS, "", 5, 4, 3);
DatabaseUtils.update(TestQuery.DROP_PROC_IN);
assertEquals(result, "RESULT: 5 4 3");
}

@Test()
public void testOutVarargs() throws SQLException {
try {
DatabaseUtils.update(TestQuery.OUT_VARARGS);
} catch (Exception ignored) {
}

ResultPackage pkg = DatabaseUtils.getResultPackage(TestSProc.OUT_VARARGS, 5, 0, 0, 0);

int[] out = new int[3];
out[0] = ((CallableStatement) pkg.getStatement()).getInt(2);
out[1] = ((CallableStatement) pkg.getStatement()).getInt(3);
out[2] = ((CallableStatement) pkg.getStatement()).getInt(4);
pkg.close();

DatabaseUtils.update(TestQuery.DROP_PROC_OUT);

assertEquals(out[0], 5);
assertEquals(out[1], 6);
assertEquals(out[2], 7);
}

@Test
public void testInOutVarargs() throws SQLException {
try {
DatabaseUtils.update(TestQuery.INOUT_VARARGS);
} catch (Exception ignored) {
}

ResultPackage pkg = DatabaseUtils.getResultPackage(TestSProc.INOUT_VARARGS, 5, 3, 10, 100);

int[] out = new int[3];
out[0] = pkg.getCallable().getInt(2);
out[1] = pkg.getCallable().getInt(3);
out[2] = pkg.getCallable().getInt(4);
pkg.close();

DatabaseUtils.update(TestQuery.DROP_PROC_INOUT);

assertEquals(out[0], 8);
assertEquals(out[1], 15);
assertEquals(out[2], 105);
}

enum TestQuery implements QueryAPI {
CREATE("create table location(num int, addr varchar(40))"),
INSERT("insert into location values (?, ?)", "num", "addr"),
UPDATE("update location set num=?, addr=? where num=?", "num", "addr", "whereNum"),
SHOW_ADDRESSES("create procedure SHOW_ADDRESSES() parameter style java "
+ "language java dynamic result sets 1 "
+ "external name 'com.nordstrom.common.jdbc.StoredProcedure.showAddresses'"),
DROP_PROC_SHOW("drop procedure SHOW_ADDRESSES"),
GET_NUM("select num from location where addr='Union St.'"),
GET_STR("select addr from location where num=1910"),
GET_RESULT_PACKAGE("select * from location"),
DROP("drop table location"),
IN_VARARGS("create procedure IN_VARARGS(out result varchar( 32672 ), b int ...) "
+ "language java parameter style derby no sql deterministic "
+ "external name 'com.nordstrom.common.jdbc.StoredProcedure.inVarargs'"),
DROP_PROC_IN("drop procedure IN_VARARGS"),
OUT_VARARGS("create procedure OUT_VARARGS(seed int, out b int ...) "
+ "language java parameter style derby no sql deterministic "
+ "external name 'com.nordstrom.common.jdbc.StoredProcedure.outVarargs'"),
DROP_PROC_OUT("drop procedure OUT_VARARGS"),
INOUT_VARARGS("create procedure INOUT_VARARGS(seed int, inout b int ...) "
+ "language java parameter style derby no sql deterministic "
+ "external name 'com.nordstrom.common.jdbc.StoredProcedure.inoutVarargs'"),
DROP_PROC_INOUT("drop procedure INOUT_VARARGS");

private final String query;
private final String[] args;

TestQuery(String query, String... args) {
this.query = query;
this.args = args;
}

@Override
public String getQueryStr() {
return query;
}

@Override
public String[] getArgNames() {
return args;
}

@Override
public String getConnection() {
return connection();
}

@Override
public Enum<TestQuery> getEnum() {
return this;
}

public static String connection() {
return "jdbc:derby:@TestDB";
}
}

enum TestSProc implements SProcAPI {
SHOW_ADDRESSES("SHOW_ADDRESSES()"),
IN_VARARGS("IN_VARARGS(<, >:)", Types.VARCHAR, Types.INTEGER),
OUT_VARARGS("OUT_VARARGS(>, <:)", Types.INTEGER, Types.INTEGER),
INOUT_VARARGS("INOUT_VARARGS(>, =:)", Types.INTEGER, Types.INTEGER);

private final int[] argTypes;
private final String signature;

TestSProc(String signature, int... argTypes) {
this.signature = signature;
this.argTypes = argTypes;
}

@Override
public String getSignature() {
return signature;
}

@Override
public int[] getArgTypes() {
return argTypes;
}

@Override
public String getConnection() {
return TestQuery.connection();
}

@Override
public Enum<? extends SProcAPI> getEnum() {
return this;
}
}

/**
* Prints details of an SQLException chain to <code>System.err</code>.
* Details included are SQL State, Error code, Exception message.
*
* @param e the SQLException from which to print details.
*/
public static void printSQLException(SQLException e)
{
// Unwraps the entire exception chain to unveil the real cause of the
// Exception.
while (e != null)
{
System.err.println("\n----- SQLException -----");
System.err.println(" SQL State: " + e.getSQLState());
System.err.println(" Error Code: " + e.getErrorCode());
System.err.println(" Message: " + e.getMessage());
e = e.getNextException();
}
}

}
60 changes: 60 additions & 0 deletions src/test/java/com/nordstrom/common/jdbc/StoredProcedure.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.nordstrom.common.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class StoredProcedure {

public static void showAddresses(ResultSet[] rs) throws SQLException {
Connection con = DriverManager.getConnection("jdbc:default:connection");
String query = "select NUM, ADDR from LOCATION";
Statement stmt = con.createStatement();
rs[0] = stmt.executeQuery(query);
}

//////////////////////////
//
// IN, OUT, IN/OUT PARAMETERS
//
//////////////////////////

public static void inVarargs(String[] result, int... values) {
String retval;
if (values == null) {
retval = null;
} else if (values.length == 0) {
retval = null;
} else {
StringBuilder buffer = new StringBuilder();

buffer.append("RESULT: ");

for (int value : values) {
buffer.append(" ").append(value);
}

retval = buffer.toString();
}

result[0] = retval;
}

public static void outVarargs(int seed, int[]... values) throws Exception {
if (values != null) {
for (int i = 0; i < values.length; i++) {
values[i][0] = seed + i;
}
}
}

public static void inoutVarargs(int seed, int[]... values) throws Exception {
if (values != null) {
for (int i = 0; i < values.length; i++) {
values[i][0] += seed;
}
}
}
}
84 changes: 84 additions & 0 deletions src/test/java/com/nordstrom/common/params/ParamTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.nordstrom.common.params;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

import java.util.Map;
import java.util.Optional;

import org.testng.annotations.Test;

public class ParamTest implements Params {

@Test
public void testParam() {
Param param = Param.param("boolean", true);
assertEquals(param.getKey(), "boolean");
verifyBoolean(param.getVal());

param = Param.param("int", 1);
assertEquals(param.getKey(), "int");
verifyInt(param.getVal());

param = Param.param("String", "one");
assertEquals(param.getKey(), "String");
verifyString(param.getVal());

param = Param.param("Map", Param.mapOf(Param.param("key", "value")));
assertEquals(param.getKey(), "Map");
verifyMap(param.getVal());
}

@Test
public void testParams() {
Optional<Map<String, Object>> optParameters = getParameters();
assertTrue(optParameters.isPresent());
Map<String, Object> parameters = optParameters.get();
assertFalse(parameters.isEmpty());

assertTrue(parameters.containsKey("boolean"));
verifyBoolean(parameters.get("boolean"));

assertTrue(parameters.containsKey("int"));
verifyInt(parameters.get("int"));

assertTrue(parameters.containsKey("String"));
verifyString(parameters.get("String"));

assertTrue(parameters.containsKey("Map"));
verifyMap(parameters.get("Map"));
}

private void verifyBoolean(Object value) {
assertTrue(value instanceof Boolean);
assertTrue((Boolean) value);
}

private void verifyInt(Object value) {
assertTrue(value instanceof Integer);
assertEquals(value, 1);
}

private void verifyString(Object value) {
assertTrue(value instanceof String);
assertEquals(value, "one");
}

public void verifyMap(Object value) {
assertTrue(value instanceof Optional);
Optional<?> optObj = (Optional<?>) value;
assertTrue(optObj.isPresent());
Object obj = optObj.get();
assertTrue(obj instanceof Map);
Map<?, ?> map = (Map<?, ?>) obj;
assertTrue(map.containsKey("key"));
assertEquals(map.get("key"), "value");
}

@Override
public Optional<Map<String, Object>> getParameters() {
return Param.mapOf(Param.param("boolean", true), Param.param("int", 1), Param.param("String", "one"),
Param.param("Map", Param.mapOf(Param.param("key", "value"))));
}
}
87 changes: 87 additions & 0 deletions src/test/java/com/nordstrom/common/uri/UriUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.nordstrom.common.uri;

import static org.testng.Assert.assertEquals;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;

import org.testng.annotations.Test;

public class UriUtilsTest {

@Test
public void testUriWithoutPath() throws MalformedURLException {
URL context = URI.create("http://user:pswd@host.com:80/context/path?id=123#frag").toURL();
URI pathURI = UriUtils.uriForPath(context);
assertEquals(pathURI.getScheme(), "http", "Scheme mismatch");
assertEquals(pathURI.getUserInfo(), null, "User info mismatch");
assertEquals(pathURI.getHost(), "host.com", "Host mismatch");
assertEquals(pathURI.getPort(), 80, "Post mismatch");
assertEquals(pathURI.getPath(), "", "Path mismatch");
assertEquals(pathURI.getQuery(), null, "Query mismatch");
assertEquals(pathURI.getFragment(), null, "Fragment mismatch");
}

@Test
public void testUriForPath() throws MalformedURLException {
URL context = URI.create("http://user:pswd@host.com:80/context/path?id=123#frag").toURL();
URI pathURI = UriUtils.uriForPath(context, "/target");
assertEquals(pathURI.getScheme(), "http", "Scheme mismatch");
assertEquals(pathURI.getUserInfo(), null, "User info mismatch");
assertEquals(pathURI.getHost(), "host.com", "Host mismatch");
assertEquals(pathURI.getPort(), 80, "Post mismatch");
assertEquals(pathURI.getPath(), "/target", "Path mismatch");
assertEquals(pathURI.getQuery(), null, "Query mismatch");
assertEquals(pathURI.getFragment(), null, "Fragment mismatch");
}

@Test
public void testUriForPathAndParams() throws MalformedURLException {
URL context = URI.create("http://user:pswd@host.com:80/context/path?id=123#frag").toURL();
URI pathURI = UriUtils.uriForPath(context, "/target", "id=321", "q=fresh & clean");
assertEquals(pathURI.getScheme(), "http", "Scheme mismatch");
assertEquals(pathURI.getUserInfo(), null, "User info mismatch");
assertEquals(pathURI.getHost(), "host.com", "Host mismatch");
assertEquals(pathURI.getPort(), 80, "Post mismatch");
assertEquals(pathURI.getPath(), "/target", "Path mismatch");
assertEquals(pathURI.getQuery(), "id=321&q=fresh+%26+clean", "Query mismatch");
assertEquals(pathURI.getFragment(), null, "Fragment mismatch");
}

@Test
public void testMakeBasicURI() {
URI basicURI = UriUtils.makeBasicURI("http", "host.com", 80);
assertEquals(basicURI.getScheme(), "http", "Scheme mismatch");
assertEquals(basicURI.getUserInfo(), null, "User info mismatch");
assertEquals(basicURI.getHost(), "host.com", "Host mismatch");
assertEquals(basicURI.getPort(), 80, "Post mismatch");
assertEquals(basicURI.getPath(), "", "Path mismatch");
assertEquals(basicURI.getQuery(), null, "Query mismatch");
assertEquals(basicURI.getFragment(), null, "Fragment mismatch");
}

@Test
public void testMakeBasicURIWithPath() {
URI basicURI = UriUtils.makeBasicURI("http", "host.com", 80, "/target");
assertEquals(basicURI.getScheme(), "http", "Scheme mismatch");
assertEquals(basicURI.getUserInfo(), null, "User info mismatch");
assertEquals(basicURI.getHost(), "host.com", "Host mismatch");
assertEquals(basicURI.getPort(), 80, "Post mismatch");
assertEquals(basicURI.getPath(), "/target", "Path mismatch");
assertEquals(basicURI.getQuery(), null, "Query mismatch");
assertEquals(basicURI.getFragment(), null, "Fragment mismatch");
}

@Test
public void testMakeBasicURIWithPathAndParams() {
URI basicURI = UriUtils.makeBasicURI("http", "host.com", 80, "/target", "id=123", "q=fresh & clean");
assertEquals(basicURI.getScheme(), "http", "Scheme mismatch");
assertEquals(basicURI.getUserInfo(), null, "User info mismatch");
assertEquals(basicURI.getHost(), "host.com", "Host mismatch");
assertEquals(basicURI.getPort(), 80, "Post mismatch");
assertEquals(basicURI.getPath(), "/target", "Path mismatch");
assertEquals(basicURI.getQuery(), "id=123&q=fresh+%26+clean", "Query mismatch");
assertEquals(basicURI.getFragment(), null, "Fragment mismatch");
}
}
1 change: 1 addition & 0 deletions src/test/resources/META-INF/services/java.sql.Driver
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.apache.derby.jdbc.EmbeddedDriver