Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion openig-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

Copyright 2010-2011 ApexIdentity Inc.
Portions Copyright 2011-2016 ForgeRock AS.
Portions copyright 2025 3A Systems LLC.
Portions copyright 2026 3A Systems LLC.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
Expand Down Expand Up @@ -161,6 +161,13 @@
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>

<dependency>
<groupId>com.atlassian.oai</groupId>
<artifactId>swagger-request-validator-core</artifactId>
<version>2.46.0</version>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-http-server</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.forgerock.openig.thread.ScheduledExecutorServiceHeaplet;
import org.openidentityplatform.openig.filter.ICAPFilter;
import org.openidentityplatform.openig.filter.JwtBuilderFilter;
import org.openidentityplatform.openig.handler.router.SwaggerRouter;
import org.openidentityplatform.openig.mq.EmbeddedKafka;
import org.openidentityplatform.openig.mq.MQ_IBM;
import org.openidentityplatform.openig.mq.MQ_Kafka;
Expand Down Expand Up @@ -112,6 +113,7 @@ public class CoreClassAliasResolver implements ClassAliasResolver {
ALIASES.put("SqlAttributesFilter", SqlAttributesFilter.class);
ALIASES.put("StaticRequestFilter", StaticRequestFilter.class);
ALIASES.put("StaticResponseHandler", StaticResponseHandler.class);
ALIASES.put("SwaggerRouter", SwaggerRouter.class);
ALIASES.put("SwitchFilter", SwitchFilter.class);
ALIASES.put("TemporaryStorage", TemporaryStorageHeaplet.class);
ALIASES.put("ThrottlingFilter", ThrottlingFilterHeaplet.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2026 3A Systems LLC.
*/

package org.forgerock.openig.handler.router;

import org.forgerock.util.annotations.VisibleForTesting;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static java.util.Arrays.asList;

public abstract class AbstractDirectoryMonitor {
/**
* Monitored directory.
*/
protected final File directory;

/**
* Snapshot of the directory content. It maps the {@link File} to its {@linkplain File#lastModified() last modified}
* value. It represents the currently "managed" files.
*/
protected final Map<File, Long> snapshot;

protected final Lock lock = new ReentrantLock();

/**
* Builds a new monitor watching for changes in the given {@literal directory} that will notify the given listener.
* This constructor is intended for test cases where it's useful to provide an initial state under control.
* @param directory
* a non-{@literal null} directory (it may or may not exist) to monitor
* @param snapshot
* initial state of the snapshot
*/
public AbstractDirectoryMonitor(final File directory, final Map<File, Long> snapshot) {
this.directory = directory;
this.snapshot = snapshot;
}

/**
* Monitor the directory and notify the listener.
* @param listener the listener to notify about the changes
*/
public void monitor(FileChangeListener listener) {
if (lock.tryLock()) {
try {
FileChangeSet fileChangeSet = createFileChangeSet();
if (fileChangeSet.isEmpty()) {
// If there is no change to propagate, simply return
return;
}
// Invoke listeners
listener.onChanges(fileChangeSet);
} finally {
lock.unlock();
}
}
}

/**
* Returns a snapshot of the changes compared to the previous scan.
* @return a snapshot of the changes compared to the previous scan.
*/
@VisibleForTesting
FileChangeSet createFileChangeSet() {
// Take a snapshot of the current directory
List<File> latest = Collections.emptyList();
if (directory.isDirectory()) {
latest = new ArrayList<>(asList(directory.listFiles(getFileFilter())));
}

// Detect added files
// (in latest but not in known)
Set<File> added = new HashSet<>();
for (File candidate : new ArrayList<>(latest)) {
if (!snapshot.containsKey(candidate)) {
added.add(candidate);
latest.remove(candidate);
}
}

// Detect removed files
// (in known but not in latest)
Set<File> removed = new HashSet<>();
for (File candidate : new ArrayList<>(snapshot.keySet())) {
if (!latest.contains(candidate)) {
removed.add(candidate);
snapshot.remove(candidate);
}
}

// Detect modified files
// Now, latest and known list should have the same Files inside
Set<File> modified = new HashSet<>();
for (File candidate : latest) {
long lastModified = snapshot.get(candidate);
if (lastModified < candidate.lastModified()) {
// File has changed since last check
modified.add(candidate);
snapshot.put(candidate, candidate.lastModified());
}
}

// Append the added files to the known list for next processing step
for (File file : added) {
// Store their last modified value
snapshot.put(file, file.lastModified());
}

return new FileChangeSet(directory, added, modified, removed);
}

protected abstract FileFilter getFileFilter();

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,26 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2014-2016 ForgeRock AS.
* Portions copyright 2026 3A Systems LLC
*/

package org.forgerock.openig.handler.router;

import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
import static java.util.Arrays.asList;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.forgerock.http.util.Json;
import org.forgerock.json.JsonValue;
import org.forgerock.util.annotations.VisibleForTesting;

import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.forgerock.http.util.Json;
import org.forgerock.json.JsonValue;
import org.forgerock.util.annotations.VisibleForTesting;

import com.fasterxml.jackson.databind.ObjectMapper;
import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;

/**
* A {@link DirectoryMonitor} monitors a given directory. It watches the direct content (changes inside
Expand All @@ -56,27 +48,14 @@
* @see FileChangeListener
* @since 2.2
*/
class DirectoryMonitor {
public class DirectoryMonitor extends AbstractDirectoryMonitor {

private static final ObjectMapper MAPPER;
static {
MAPPER = new ObjectMapper().registerModules(new Json.JsonValueModule());
MAPPER.configure(INDENT_OUTPUT, true);
}

/**
* Monitored directory.
*/
private final File directory;

/**
* Snapshot of the directory content. It maps the {@link File} to its {@linkplain File#lastModified() last modified}
* value. It represents the currently "managed" files.
*/
private final Map<File, Long> snapshot;

private Lock lock = new ReentrantLock();

/**
* Builds a new monitor watching for changes in the given {@literal directory} that will notify the given listener.
* It starts with an empty snapshot (at first run, all discovered files will be considered as new).
Expand All @@ -85,7 +64,7 @@ class DirectoryMonitor {
* a non-{@literal null} directory (it may or may not exists) to monitor
*/
public DirectoryMonitor(final File directory) {
this(directory, new HashMap<File, Long>());
super(directory, new HashMap<>());
}

/**
Expand All @@ -97,95 +76,29 @@ public DirectoryMonitor(final File directory) {
* initial state of the snapshot
*/
public DirectoryMonitor(final File directory, final Map<File, Long> snapshot) {
this.directory = directory;
this.snapshot = snapshot;
super(directory, snapshot);
}

/**
* Monitor the directory and notify the listener.
* @param listener the listener to notify about the changes
*/
public void monitor(FileChangeListener listener) {
if (lock.tryLock()) {
try {
FileChangeSet fileChangeSet = createFileChangeSet();
if (fileChangeSet.isEmpty()) {
// If there is no change to propagate, simply return
return;
}
// Invoke listeners
listener.onChanges(fileChangeSet);
} finally {
lock.unlock();
}
}
}


/**
* Returns a snapshot of the changes compared to the previous scan.
* @return a snapshot of the changes compared to the previous scan.
*/
@VisibleForTesting
FileChangeSet createFileChangeSet() {
// Take a snapshot of the current directory
List<File> latest = Collections.emptyList();
if (directory.isDirectory()) {
latest = new ArrayList<>(asList(directory.listFiles(jsonFiles())));
}

// Detect added files
// (in latest but not in known)
Set<File> added = new HashSet<>();
for (File candidate : new ArrayList<>(latest)) {
if (!snapshot.containsKey(candidate)) {
added.add(candidate);
latest.remove(candidate);
}
}

// Detect removed files
// (in known but not in latest)
Set<File> removed = new HashSet<>();
for (File candidate : new ArrayList<>(snapshot.keySet())) {
if (!latest.contains(candidate)) {
removed.add(candidate);
snapshot.remove(candidate);
}
}

// Detect modified files
// Now, latest and known list should have the same Files inside
Set<File> modified = new HashSet<>();
for (File candidate : latest) {
long lastModified = snapshot.get(candidate);
if (lastModified < candidate.lastModified()) {
// File has changed since last check
modified.add(candidate);
snapshot.put(candidate, candidate.lastModified());
}
}

// Append the added files to the known list for next processing step
for (File file : added) {
// Store their last modified value
snapshot.put(file, file.lastModified());
}

return new FileChangeSet(directory, added, modified, removed);
return super.createFileChangeSet();
}


/**
* Factory method to be used as a fluent {@link FileFilter} declaration.
*
* @return a filter for {@literal .json} files
*/
private static FileFilter jsonFiles() {
return new FileFilter() {
@Override
public boolean accept(final File path) {
return path.isFile() && path.getName().endsWith(".json");
}
};
@Override
protected FileFilter getFileFilter() {
return path -> path.isFile() && path.getName().endsWith(".json");
}

void store(String routeId, JsonValue routeConfig) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2014 ForgeRock AS.
* Portions copyright 2026 3A Systems LLC.
*/

package org.forgerock.openig.handler.router;
Expand All @@ -21,7 +22,7 @@
*
* @since 2.2
*/
interface FileChangeListener {
public interface FileChangeListener {

/**
* Notify that changes has been detected in the monitored directory.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2014-2016 ForgeRock AS.
* Portions copyright 2026 3A Systems LLC.
*/

package org.forgerock.openig.handler.router;
Expand All @@ -26,7 +27,7 @@
*
* @since 2.2
*/
class FileChangeSet {
public class FileChangeSet {

/**
* Scanned directory.
Expand Down
Loading
Loading