diff --git a/changelog.md b/changelog.md index 9c2acbc74f..eb72baef18 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,9 @@ Codion Change Log ### is.codion.common.db - AbstractDatabase, transaction isolation now a final field, instead of using the configuration value directly. - AbstractDatabase, login timeout now set in a static initializer block, instead of during connection creation. +### is.codion.common.rmi +- SerializationWhitelist.writeToFile() improved a bit, javadocs fixed. +- SerializationWhitelist no longer public, refactored. ### is.codion.swing.common.ui - KeyboardShortcuts.copy() added. - CalendarPanel.Builder added, replaces factory methods diff --git a/common/rmi/src/main/java/is/codion/common/rmi/server/AbstractServer.java b/common/rmi/src/main/java/is/codion/common/rmi/server/AbstractServer.java index 6f793ab6a8..b61aaf000b 100644 --- a/common/rmi/src/main/java/is/codion/common/rmi/server/AbstractServer.java +++ b/common/rmi/src/main/java/is/codion/common/rmi/server/AbstractServer.java @@ -29,6 +29,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ObjectInputFilter; import java.rmi.NoSuchObjectException; import java.rmi.Remote; import java.rmi.RemoteException; @@ -51,8 +52,6 @@ import java.util.function.Predicate; import static is.codion.common.rmi.server.RemoteClient.remoteClient; -import static is.codion.common.rmi.server.SerializationWhitelist.serializationDryRun; -import static is.codion.common.rmi.server.SerializationWhitelist.writeDryRunWhitelist; import static java.util.Collections.unmodifiableCollection; import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newSingleThreadExecutor; @@ -244,8 +243,9 @@ public final void shutdown() { sharedAuthenticators.forEach(AbstractServer::closeAuthenticator); authenticators.values().forEach(AbstractServer::closeAuthenticator); auxiliaryServers.forEach(AbstractServer::stopAuxiliaryServer); - if (serializationDryRun()) { - writeDryRunWhitelist(); + ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter(); + if (serialFilter instanceof SerializationWhitelist.DryRun) { + ((SerializationWhitelist.DryRun) serialFilter).writeToFile(configuration.serializationFilterWhitelist()); } shutdownEvent.run(); } @@ -463,11 +463,19 @@ private static RemoteClient clearPasswords(RemoteClient remoteClient) { } private static void configureSerializationWhitelist(ServerConfiguration configuration) { - if (configuration.serializationFilterDryRun()) { - SerializationWhitelist.configureDryRun(configuration.serializationFilterWhitelist()); + String whitelistFile = configuration.serializationFilterWhitelist(); + if (nullOrEmpty(whitelistFile)) { + LOG.info("No serialization whitelist file specified"); } else { - SerializationWhitelist.configure(configuration.serializationFilterWhitelist()); + if (configuration.serializationFilterDryRun()) { + ObjectInputFilter.Config.setSerialFilter(SerializationWhitelist.whitelistDryRun()); + LOG.info("Serialization filter dry-run enabled"); + } + else { + ObjectInputFilter.Config.setSerialFilter(SerializationWhitelist.whitelistFilter(whitelistFile)); + LOG.info("Serialization filter whitelist set: " + whitelistFile); + } } } diff --git a/common/rmi/src/main/java/is/codion/common/rmi/server/SerializationWhitelist.java b/common/rmi/src/main/java/is/codion/common/rmi/server/SerializationWhitelist.java index cd0c7ebd23..449bbf4a19 100644 --- a/common/rmi/src/main/java/is/codion/common/rmi/server/SerializationWhitelist.java +++ b/common/rmi/src/main/java/is/codion/common/rmi/server/SerializationWhitelist.java @@ -22,7 +22,6 @@ import org.slf4j.LoggerFactory; import java.io.BufferedReader; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -30,6 +29,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -37,7 +37,6 @@ import java.util.Set; import java.util.stream.Stream; -import static is.codion.common.NullOrEmpty.nullOrEmpty; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; @@ -45,7 +44,7 @@ /** * Implements a serialization whitelist */ -public final class SerializationWhitelist { +final class SerializationWhitelist { private static final Logger LOG = LoggerFactory.getLogger(SerializationWhitelist.class); @@ -54,64 +53,35 @@ public final class SerializationWhitelist { private SerializationWhitelist() {} /** - * Configures a serialization whitelist, does nothing if {@code whitelist} is null or empty. + * Creates a serialization filter based on a whitelist. * Supports 'classpath:' prefix for a whitelist in the classpath root. * @param whitelistFile the path to the file containing the whitelisted class names */ - public static void configure(String whitelistFile) { - if (!nullOrEmpty(whitelistFile)) { - ObjectInputFilter.Config.setSerialFilter(new SerializationFilter(whitelistFile)); - LOG.info("Serialization filter whitelist set: " + whitelistFile); - } - } - - /** - * Configures a serialization whitelist for a dry run, does nothing if {@code dryRunFile} is null or empty. - * Note that this will append to an existing file. - * @param dryRunFile the dry-run results file to write to, appended to if it exists - * @throws IllegalArgumentException in case of a classpath dry run file - */ - public static void configureDryRun(String dryRunFile) { - if (!nullOrEmpty(dryRunFile)) { - ObjectInputFilter.Config.setSerialFilter(new SerializationFilterDryRun(dryRunFile)); - LOG.info("Serialization filter whitelist set for dry-run: " + dryRunFile); - } + static WhitelistFilter whitelistFilter(String whitelistFile) { + return new WhitelistFilter(whitelistFile); } /** - * Returns true if a serialization dry-run is active. - * @return true if a dry-run is active. + * Creates a serialization filter based on a whitelist. + * @param classnames the whitelisted class names */ - public static boolean serializationDryRun() { - return ObjectInputFilter.Config.getSerialFilter() instanceof SerializationFilterDryRun; + static WhitelistFilter whitelistFilter(Collection classnames) { + return new WhitelistFilter(classnames); } /** - * Writes the class names collected during a dry-run to file. - * If dry-run was not active this method has no effect. + * Creates a serialization filter for a whitelist dry run. + * @param whitelistFile the file to write the dry-run results to + * @throws IllegalArgumentException in case of a classpath dry run file */ - public static void writeDryRunWhitelist() { - ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter(); - if (serialFilter instanceof SerializationFilterDryRun) { - ((SerializationFilterDryRun) serialFilter).writeToFile(); - } + static DryRun whitelistDryRun() { + return new DryRun(); } - private static final class SerializationFilterDryRun implements ObjectInputFilter { + static final class DryRun implements ObjectInputFilter { - private final String whitelistFile; private final Set> deserializedClasses = new HashSet<>(); - private SerializationFilterDryRun(String whitelistFile) { - if (requireNonNull(whitelistFile).toLowerCase().startsWith(CLASSPATH_PREFIX)) { - throw new IllegalArgumentException("Filter dry run can not be performed with a classpath whitelist: " + whitelistFile); - } - if (new File(whitelistFile).exists()) { - throw new IllegalArgumentException("Whitelist file to write to after dry-run already exists"); - } - this.whitelistFile = requireNonNull(whitelistFile, "whitelistFile"); - } - @Override public Status checkInput(FilterInfo filterInfo) { Class clazz = filterInfo.serialClass(); @@ -122,16 +92,19 @@ public Status checkInput(FilterInfo filterInfo) { return Status.ALLOWED; } - private void writeToFile() { + /** + * Writes all classnames found during the dry-run to the specified file. + * @param whitelistFile the file to write to + */ + synchronized void writeToFile(String whitelistFile) { + if (requireNonNull(whitelistFile).toLowerCase().startsWith(CLASSPATH_PREFIX)) { + throw new IllegalArgumentException("Filter dry run can not be performed with a classpath whitelist: " + whitelistFile); + } try { - File file = new File(whitelistFile); - if (!file.createNewFile()) { - throw new IOException("Whitelist file already exists: " + whitelistFile); - } - Files.write(file.toPath(), deserializedClasses.stream() + Files.write(Paths.get(whitelistFile), deserializedClasses.stream() .map(Class::getName) .sorted() - .collect(toList())); + .collect(toList()), StandardOpenOption.CREATE); LOG.debug("Serialization whitelist written: " + whitelistFile); } catch (Exception e) { @@ -140,7 +113,7 @@ private void writeToFile() { } } - static final class SerializationFilter implements ObjectInputFilter { + static final class WhitelistFilter implements ObjectInputFilter { private static final String COMMENT = "#"; private static final String WILDCARD = "*"; @@ -148,11 +121,11 @@ static final class SerializationFilter implements ObjectInputFilter { private final Set allowedClassnames = new HashSet<>(); private final List allowedWildcardClassnames = new ArrayList<>(); - SerializationFilter(String whitelistFile) { + private WhitelistFilter(String whitelistFile) { this(readWhitelistItems(whitelistFile)); } - SerializationFilter(Collection whitelistItems) { + private WhitelistFilter(Collection whitelistItems) { addWhitelistItems(whitelistItems); } diff --git a/common/rmi/src/test/java/is/codion/common/rmi/server/SerializationWhitelistTest.java b/common/rmi/src/test/java/is/codion/common/rmi/server/SerializationWhitelistTest.java index 8ad3ad11a8..f18c4f96fa 100644 --- a/common/rmi/src/test/java/is/codion/common/rmi/server/SerializationWhitelistTest.java +++ b/common/rmi/src/test/java/is/codion/common/rmi/server/SerializationWhitelistTest.java @@ -19,7 +19,7 @@ package is.codion.common.rmi.server; import is.codion.common.Serializer; -import is.codion.common.rmi.server.SerializationWhitelist.SerializationFilter; +import is.codion.common.rmi.server.SerializationWhitelist.WhitelistFilter; import org.junit.jupiter.api.Test; @@ -31,28 +31,41 @@ import java.util.List; import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; public final class SerializationWhitelistTest { @Test void dryRun() throws IOException, ClassNotFoundException { - assertThrows(IllegalArgumentException.class, () -> SerializationWhitelist.configureDryRun("classpath:dryrun")); + assertThrows(IllegalArgumentException.class, () -> SerializationWhitelist.whitelistDryRun().writeToFile("classpath:dryrun")); File tempFile = File.createTempFile("serialization_dry_run_test", "txt"); - assertThrows(IllegalArgumentException.class, () -> SerializationWhitelist.configureDryRun(tempFile.getAbsolutePath())); - tempFile.delete(); - SerializationWhitelist.configureDryRun(tempFile.getAbsolutePath()); + + SerializationWhitelist.DryRun serialFilter = SerializationWhitelist.whitelistDryRun(); + ObjectInputFilter.Config.setSerialFilter(serialFilter); + Serializer.deserialize(Serializer.serialize(Integer.valueOf(42))); - Serializer.deserialize(Serializer.serialize(Double.valueOf(42))); Serializer.deserialize(Serializer.serialize(Long.valueOf(42))); - SerializationWhitelist.writeDryRunWhitelist(); + serialFilter.writeToFile(tempFile.getAbsolutePath()); + List classNames = Files.readAllLines(tempFile.toPath(), StandardCharsets.UTF_8); + + assertEquals(3, classNames.size()); + assertEquals(Integer.class.getName(), classNames.get(0)); + assertEquals(Long.class.getName(), classNames.get(1)); + assertEquals(Number.class.getName(), classNames.get(2)); + + Serializer.deserialize(Serializer.serialize(Double.valueOf(42))); + serialFilter.writeToFile(tempFile.getAbsolutePath()); + + classNames = Files.readAllLines(tempFile.toPath(), StandardCharsets.UTF_8); + assertEquals(4, classNames.size()); assertEquals(Double.class.getName(), classNames.get(0)); assertEquals(Integer.class.getName(), classNames.get(1)); assertEquals(Long.class.getName(), classNames.get(2)); assertEquals(Number.class.getName(), classNames.get(3)); - assertTrue(tempFile.exists()); + tempFile.delete(); } @@ -64,7 +77,7 @@ void testNoWildcards() { "is.codion.common.state.State", "is.codion.common.state.StateObserver" ); - SerializationFilter filter = new SerializationFilter(whitelistItems); + WhitelistFilter filter = SerializationWhitelist.whitelistFilter(whitelistItems); assertEquals(filter.checkInput("is.codion.common.value.Value"), ObjectInputFilter.Status.ALLOWED); assertEquals(filter.checkInput("is.codion.common.state.State"), ObjectInputFilter.Status.ALLOWED); assertEquals(filter.checkInput("is.codion.common.state.States"), ObjectInputFilter.Status.REJECTED); @@ -75,18 +88,18 @@ void testNoWildcards() { @Test void file() { - assertThrows(RuntimeException.class, () -> new SerializationFilter("src/test/resources/whitelist_test_non_existing.txt")); - testFilter(new SerializationFilter("src/test/resources/whitelist_test.txt")); + assertThrows(RuntimeException.class, () -> SerializationWhitelist.whitelistFilter("src/test/resources/whitelist_test_non_existing.txt")); + testFilter(SerializationWhitelist.whitelistFilter("src/test/resources/whitelist_test.txt")); } @Test void classpath() { - assertThrows(IllegalArgumentException.class, () -> new SerializationFilter("classpath:src/test/resources/whitelist_test.txt")); - testFilter(new SerializationFilter("classpath:whitelist_test.txt")); - testFilter(new SerializationFilter("classpath:/whitelist_test.txt")); + assertThrows(IllegalArgumentException.class, () -> SerializationWhitelist.whitelistFilter("classpath:src/test/resources/whitelist_test.txt")); + testFilter(SerializationWhitelist.whitelistFilter("classpath:whitelist_test.txt")); + testFilter(SerializationWhitelist.whitelistFilter("classpath:/whitelist_test.txt")); } - private static void testFilter(SerializationFilter filter) { + private static void testFilter(WhitelistFilter filter) { assertEquals(filter.checkInput("is.codion.common.value.Value"), ObjectInputFilter.Status.ALLOWED); assertEquals(filter.checkInput("is.codion.common.state.State"), ObjectInputFilter.Status.ALLOWED); assertEquals(filter.checkInput("is.codion.common.state.States"), ObjectInputFilter.Status.ALLOWED); diff --git a/demos/manual/src/main/java/is/codion/framework/demos/manual/notes/NotesDemo.java b/demos/manual/src/main/java/is/codion/framework/demos/manual/notes/NotesDemo.java index 2c8d6f742b..d9995d4cad 100644 --- a/demos/manual/src/main/java/is/codion/framework/demos/manual/notes/NotesDemo.java +++ b/demos/manual/src/main/java/is/codion/framework/demos/manual/notes/NotesDemo.java @@ -48,6 +48,7 @@ import com.formdev.flatlaf.intellijthemes.FlatAllIJThemes; import com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialDarkerIJTheme; +import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JPanel; import javax.swing.JTable; @@ -243,6 +244,7 @@ public NotesApplicationPanel(NotesApplicationModel applicationModel) { notePanel.initialize();// Lazy initialization of UI components applicationPanel.setLayout(Layouts.borderLayout()); applicationPanel.add(notePanel, BorderLayout.CENTER); + applicationPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); }); } diff --git a/documentation/src/docs/asciidoc/technical/server.adoc b/documentation/src/docs/asciidoc/technical/server.adoc index 2c6bd351d1..ccc27c111f 100644 --- a/documentation/src/docs/asciidoc/technical/server.adoc +++ b/documentation/src/docs/asciidoc/technical/server.adoc @@ -56,12 +56,15 @@ No dynamic class loading is required. === Serialization whitelist -A {url-javadoc}{common-rmi}/is/codion/common/rmi/server/SerializationWhitelist.html[serialization whitelist] can be used by setting the following system property. +A serialization whitelist can be used by setting the following system property. [source] ---- codion.server.serializationFilterWhitelist=config/whitelist.txt ---- +---- +codion.server.serializationFilterWhitelist=classpath:whitelist.txt +---- A whitelist can be created during a server dry-run by adding the following system property. The whitelist containing all classes deserialized during the run is written to disk on server shutdown. diff --git a/readme.adoc b/readme.adoc index 2bb4413777..86f9970af2 100644 --- a/readme.adoc +++ b/readme.adoc @@ -383,7 +383,7 @@ public class StoreDemo { @Override protected void initializeUI() { - setInitialFocusAttribute(Customer.FIRST_NAME); + initialFocusAttribute().set(Customer.FIRST_NAME); createTextField(Customer.FIRST_NAME); createTextField(Customer.LAST_NAME); createTextField(Customer.EMAIL); @@ -404,7 +404,7 @@ public class StoreDemo { @Override protected void initializeUI() { - setInitialFocusAttribute(Address.STREET); + initialFocusAttribute().set(Address.STREET); createForeignKeyComboBox(Address.CUSTOMER_FK); createTextField(Address.STREET); createTextField(Address.CITY);