[java-opensaml] 01/01: Initial refactor to make storage format pluggable.

Scott Cantor cantor.2 at osu.edu
Thu Apr 2 15:35:47 EDT 2020


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch OSJ-246
in repository java-opensaml.

View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=bb210d7f48579f2db647e62b80899d78410877db

commit bb210d7f48579f2db647e62b80899d78410877db
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Apr 2 15:33:58 2020 -0400

    Initial refactor to make storage format pluggable.
---
 .../client/AbstractClientStorageServiceStore.java  | 114 +++++++++++
 .../storage/impl/client/ClientStorageService.java  | 218 ++-------------------
 .../impl/client/ClientStorageServiceStore.java     |  76 +++++++
 .../impl/client/JSONClientStorageServiceStore.java | 185 +++++++++++++++++
 4 files changed, 389 insertions(+), 204 deletions(-)

diff --git a/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/AbstractClientStorageServiceStore.java b/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/AbstractClientStorageServiceStore.java
new file mode 100644
index 0000000..6ef7d8b
--- /dev/null
+++ b/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/AbstractClientStorageServiceStore.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.storage.impl.client;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.storage.MutableStorageRecord;
+import org.opensaml.storage.impl.client.ClientStorageService.ClientStorageSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Base class for the storage and reconstitution of data for a {@link ClientStorageService}.
+ */
+public abstract class AbstractClientStorageServiceStore implements ClientStorageServiceStore {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractClientStorageServiceStore.class);
+
+    /** The underlying map of data records. */
+    @Nonnull @NonnullElements private final Map<String, Map<String, MutableStorageRecord<?>>> contextMap;
+    
+    /** Data source. */
+    @Nonnull private final ClientStorageSource source;
+    
+    /** Dirty bit. */
+    private boolean dirty;
+
+    /**
+     * Reconstitute stored data.
+     * 
+     * <p>The dirty bit is set based on the result. If successful, the bit is cleared,
+     * but if an error occurs, it will be set.</p>
+     * 
+     * @param raw serialized data to load
+     * @param src data source
+     */
+    AbstractClientStorageServiceStore(@Nullable @NotEmpty final String raw, @Nonnull final ClientStorageSource src) {
+        source = Constraint.isNotNull(src, "ClientStorageSource cannot be null");
+        contextMap = new HashMap<>();
+        if (raw != null) {
+            try {
+                doLoad(raw);
+            } catch (final IOException e) {
+                contextMap.clear();
+                // Setting this should force corrupt data in the client to be overwritten.
+                setDirty(true);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Nonnull public ClientStorageSource getSource() {
+        return source;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isDirty() {
+        return dirty;
+    }
+    
+    /**
+     * Set the dirty bit for the current data.
+     * 
+     * @param flag  dirty bit to set
+     */
+    public void setDirty(final boolean flag) {
+        dirty = flag;
+    }
+
+    /** {@inheritDoc} */
+    @Nonnull @NonnullElements @Live public Map<String,Map<String,MutableStorageRecord<?>>> getContextMap() {
+        return contextMap;
+    }
+    
+    /**
+     * Reconstitute stored data.
+     * 
+     * @param raw serialized data to load
+     * 
+     * @throws IOException if an error occurs
+     */
+    public abstract void doLoad(@Nullable @NotEmpty final String raw) throws IOException;
+    
+    /** {@inheritDoc} */
+    @Nullable public abstract ClientStorageServiceOperation save(@Nonnull final ClientStorageService storageService)
+            throws IOException;
+    
+}
\ No newline at end of file
diff --git a/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/ClientStorageService.java b/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/ClientStorageService.java
index c243f61..14f93ef 100644
--- a/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/ClientStorageService.java
+++ b/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/ClientStorageService.java
@@ -18,11 +18,8 @@
 package org.opensaml.storage.impl.client;
 
 import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
 import java.security.KeyException;
 import java.time.Duration;
-import java.time.Instant;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.TimerTask;
@@ -32,13 +29,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import javax.json.Json;
-import javax.json.JsonException;
-import javax.json.JsonObject;
-import javax.json.JsonReader;
-import javax.json.JsonStructure;
-import javax.json.JsonValue;
-import javax.json.stream.JsonGenerator;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -155,15 +145,12 @@ public class ClientStorageService extends AbstractMapBackedStorageService implem
         }
     }
     
-    /** {@inheritDoc} */
     // Checkstyle: CyclomaticComplexity ON
     
     /** {@inheritDoc} */
     public boolean isServerSide() {
         return false;
     }
-    
-    /** {@inheritDoc} */
 
     /** {@inheritDoc} */
     public boolean isClustered() {
@@ -223,6 +210,15 @@ public class ClientStorageService extends AbstractMapBackedStorageService implem
     }
     
     /**
+     * Get the {@link DataSealer} to use for data security.
+     * 
+     * @return {@link DataSealer} to use for data security
+     */
+    @NonnullAfterInit public DataSealer getDataSealer() {
+        return dataSealer;
+    }
+    
+    /**
      * Set the {@link DataSealer} to use for data security.
      * 
      * @param sealer {@link DataSealer} to use for data security
@@ -281,8 +277,6 @@ public class ClientStorageService extends AbstractMapBackedStorageService implem
     public long getValueSize() {
         return getContextSize();
     }
-
-    // Checkstyle: CyclomaticComplexity ON
     
     /** {@inheritDoc} */
     @Override
@@ -431,7 +425,7 @@ public class ClientStorageService extends AbstractMapBackedStorageService implem
                 
                 log.trace("{} Data after decryption: {}", getLogPrefix(), decrypted);
                 
-                storageObject = new ClientStorageServiceStore(decrypted, source);
+                storageObject = new JSONClientStorageServiceStore(decrypted, source);
                 
                 if (keyStrategy != null) {
                     try {
@@ -447,16 +441,16 @@ public class ClientStorageService extends AbstractMapBackedStorageService implem
                 log.debug("{} Successfully decrypted and loaded storage state from client", getLogPrefix());
             } catch (final DataExpiredException e) {
                 log.debug("{} Secured data or key has expired", getLogPrefix());
-                storageObject = new ClientStorageServiceStore(null, source);
+                storageObject = new JSONClientStorageServiceStore(null, source);
                 storageObject.setDirty(true);
             } catch (final DataSealerException e) {
                 log.error("{} Exception unwrapping secured data", getLogPrefix(), e);
-                storageObject = new ClientStorageServiceStore(null, source);
+                storageObject = new JSONClientStorageServiceStore(null, source);
                 storageObject.setDirty(true);
             }
         } else {
             log.trace("{} Initializing empty storage state into session", getLogPrefix());
-            storageObject = new ClientStorageServiceStore(null, source);
+            storageObject = new JSONClientStorageServiceStore(null, source);
         }
         
         // The object should be loaded, and marked "clean", or in the event of just about any failure
@@ -500,7 +494,7 @@ public class ClientStorageService extends AbstractMapBackedStorageService implem
             }
 
             try {
-                return ((ClientStorageServiceStore) object).save();
+                return ((ClientStorageServiceStore) object).save(this);
             } catch (final IOException e) {
                 log.error("{} Error while serializing storage data", getLogPrefix(), e);
                 return null;
@@ -518,189 +512,5 @@ public class ClientStorageService extends AbstractMapBackedStorageService implem
     @Nonnull @NotEmpty private String getLogPrefix() {
         return "StorageService " + getId() + ":";
     }
-    
-    /**
-     * Implements a session-bound backing store and locking mechanism for the {@link ClientStorageService}.
-     */
-    public class ClientStorageServiceStore {
-        
-        /** The underlying map of data records. */
-        @Nonnull @NonnullElements private final Map<String, Map<String, MutableStorageRecord<?>>> contextMap;
-        
-        /** Data source. */
-        @Nonnull private final ClientStorageSource source; 
         
-        /** Dirty bit. */
-        private boolean dirty;
-        
-        /**
-         * Reconstitute stored data.
-         * 
-         * <p>The dirty bit is set based on the result. If successful, the bit is cleared,
-         * but if an error occurs, it will be set.</p>
-         * 
-         * @param raw serialized data to load
-         * @param src data source
-         */
-        ClientStorageServiceStore(@Nullable @NotEmpty final String raw, @Nonnull final ClientStorageSource src) {
-            contextMap = new HashMap<>();
-            source = Constraint.isNotNull(src, "Data source cannot be null");
-            
-            if (raw == null) {
-                return;
-            }
-            
-            try {
-                final JsonReader reader = Json.createReader(new StringReader(raw));
-                final JsonStructure st = reader.read();
-                if (!(st instanceof JsonObject)) {
-                    throw new JsonException("Found invalid data structure while parsing context map");
-                }
-                final JsonObject obj = (JsonObject) st;
-                
-                for (final Map.Entry<String,JsonValue> context : obj.entrySet()) {
-                    if (context.getValue().getValueType() != JsonValue.ValueType.OBJECT) {
-                        throw new JsonException("Found invalid data structure while parsing context map");
-                    }
-                    
-                    // Create new context if necessary.
-                    Map<String,MutableStorageRecord<?>> dataMap = contextMap.get(context.getKey());
-                    if (dataMap == null) {
-                        dataMap = new HashMap<>();
-                        contextMap.put(context.getKey(), dataMap);
-                    }
-                    
-                    final JsonObject contextRecords = (JsonObject) context.getValue();
-                    for (final Map.Entry<String,JsonValue> record : contextRecords.entrySet()) {
-                    
-                        final JsonObject fields = (JsonObject) record.getValue();
-                        Long exp = null;
-                        if (fields.containsKey("x")) {
-                            exp = fields.getJsonNumber("x").longValueExact();
-                        }
-                        
-                        dataMap.put(record.getKey(), new MutableStorageRecord<>(fields.getString("v"), exp));
-                    }
-                }
-                setDirty(false);
-            } catch (final NullPointerException | ClassCastException | ArithmeticException | JsonException e) {
-                contextMap.clear();
-                // Setting this should force corrupt data in the client to be overwritten.
-                setDirty(true);
-                log.error("{} Found invalid data structure while parsing context map", getLogPrefix(), e);
-            }
-        }
-
-        /**
-         * Get the map of contexts to manipulate during operations.
-         * 
-         * @return map of contexts to manipulate
-         */
-        @Nonnull @NonnullElements @Live Map<String, Map<String, MutableStorageRecord<?>>> getContextMap() {
-            return contextMap;
-        }
-        
-        /**
-         * Get the data source.
-         * 
-         * @return data source
-         */
-        @Nonnull public ClientStorageSource getSource() {
-            return source;
-        }
-
-        /**
-         * Get the dirty bit for the current data.
-         * 
-         * @return  status of dirty bit
-         */
-        boolean isDirty() {
-            return dirty;
-        }
-        
-        /**
-         * Set the dirty bit for the current data.
-         * 
-         * @param flag  dirty bit to set
-         */
-        void setDirty(final boolean flag) {
-            dirty = flag;
-        }
-
-// Checkstyle: CyclomaticComplexity OFF        
-        /**
-         * Serialize current state of stored data into a storage operation.
-         * 
-         * @return the operation, or a null if the data has not been modified since loading or saving
-         * 
-         * @throws IOException if an error occurs
-         */
-        @Nullable ClientStorageServiceOperation save() throws IOException {
-            
-            if (!isDirty()) {
-                log.trace("{} Storage state has not been modified, save operation skipped", getLogPrefix());
-                return null;
-            }
-            
-            if (contextMap.isEmpty()) {
-                log.trace("{} Data is empty", getLogPrefix());
-                return new ClientStorageServiceOperation(getId(), getStorageName(), null, source);
-            }
-
-            long exp = 0L;
-            final long now = System.currentTimeMillis();
-            boolean empty = true;
-
-            try {
-                final StringWriter sink = new StringWriter(128);
-                final JsonGenerator gen = Json.createGenerator(sink);
-                
-                gen.writeStartObject();
-                for (final Map.Entry<String,Map<String, MutableStorageRecord<?>>> context : contextMap.entrySet()) {
-                    if (!context.getValue().isEmpty()) {
-                        gen.writeStartObject(context.getKey());
-                        for (final Map.Entry<String,MutableStorageRecord<?>> entry : context.getValue().entrySet()) {
-                            final MutableStorageRecord<?> record = entry.getValue();
-                            final Long recexp = record.getExpiration();
-                            if (recexp == null || recexp > now) {
-                                empty = false;
-                                gen.writeStartObject(entry.getKey())
-                                    .write("v", record.getValue());
-                                if (recexp != null) {
-                                    gen.write("x", record.getExpiration());
-                                    exp = Math.max(exp, recexp);
-                                }
-                                gen.writeEnd();
-                            }
-                        }
-                        gen.writeEnd();
-                    }
-                }
-                gen.writeEnd().close();
-
-                if (empty) {
-                    log.trace("{} Data is empty", getLogPrefix());
-                    return new ClientStorageServiceOperation(getId(), getStorageName(), null, source);
-                }
-                
-                final String raw = sink.toString();
-                
-                log.trace("{} Size of data before encryption is {}", getLogPrefix(), raw.length());
-                log.trace("{} Data before encryption is {}", getLogPrefix(), raw);
-                try {
-                    final String wrapped = dataSealer.wrap(raw,
-                            exp > 0 ? Instant.ofEpochMilli(exp) : Instant.now().plus(Duration.ofDays(1)));
-                    log.trace("{} Size of data after encryption is {}", getLogPrefix(), wrapped.length());
-                    setDirty(false);
-                    return new ClientStorageServiceOperation(getId(), getStorageName(), wrapped, source);
-                } catch (final DataSealerException e) {
-                    throw new IOException(e);
-                }
-            } catch (final JsonException e) {
-                throw new IOException(e);
-            }
-        }
-    }
-// Checkstyle: CyclomaticComplexity ON
-    
 }
\ No newline at end of file
diff --git a/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/ClientStorageServiceStore.java b/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/ClientStorageServiceStore.java
new file mode 100644
index 0000000..764e982
--- /dev/null
+++ b/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/ClientStorageServiceStore.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.storage.impl.client;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.storage.MutableStorageRecord;
+import org.opensaml.storage.impl.client.ClientStorageService.ClientStorageSource;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+
+/**
+ * Abstraction for the storage and reconstitution of data for a {@link ClientStorageService}.
+ */
+public interface ClientStorageServiceStore {
+
+    /**
+     * Get the data source.
+     * 
+     * @return data source
+     */
+    @Nonnull ClientStorageSource getSource();
+
+    /**
+     * Get the dirty bit for the current data.
+     * 
+     * @return  status of dirty bit
+     */
+    boolean isDirty();
+    
+    /**
+     * Set the dirty bit for the current data.
+     * 
+     * @param flag  dirty bit to set
+     */
+    void setDirty(final boolean flag);
+
+    /**
+     * Get the map of contexts to manipulate during operations.
+     * 
+     * @return map of contexts to manipulate
+     */
+    @Nonnull @NonnullElements @Live Map<String,Map<String,MutableStorageRecord<?>>> getContextMap();
+    
+    /**
+     * Serialize current state of stored data into a storage operation.
+     * 
+     * @param storageService storage service
+     * 
+     * @return the operation, or a null if the data has not been modified since loading or saving
+     * 
+     * @throws IOException if an error occurs
+     */
+    @Nullable ClientStorageServiceOperation save(@Nonnull final ClientStorageService storageService) throws IOException;
+    
+}
\ No newline at end of file
diff --git a/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/JSONClientStorageServiceStore.java b/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/JSONClientStorageServiceStore.java
new file mode 100644
index 0000000..da9074a
--- /dev/null
+++ b/opensaml-storage-impl/src/main/java/org/opensaml/storage/impl/client/JSONClientStorageServiceStore.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.storage.impl.client;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.json.Json;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+import javax.json.stream.JsonGenerator;
+
+import org.opensaml.storage.MutableStorageRecord;
+import org.opensaml.storage.impl.client.ClientStorageService.ClientStorageSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.security.DataSealerException;
+
+/**
+ * JSON-based storage for {@link ClientStorageService}.
+ */
+public class JSONClientStorageServiceStore extends AbstractClientStorageServiceStore {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(JSONClientStorageServiceStore.class);
+
+    /**
+     * Reconstitute stored data.
+     * 
+     * <p>The dirty bit is set based on the result. If successful, the bit is cleared,
+     * but if an error occurs, it will be set.</p>
+     * 
+     * @param raw serialized data to load
+     * @param src data source
+     */
+    public JSONClientStorageServiceStore(@Nullable @NotEmpty final String raw,
+            @Nonnull final ClientStorageSource src) {
+        super(raw, src);
+    }
+    
+    /** {@inheritDoc} */
+    public void doLoad(@Nullable @NotEmpty final String raw) throws IOException {
+        try {
+            final JsonReader reader = Json.createReader(new StringReader(raw));
+            final JsonStructure st = reader.read();
+            if (!(st instanceof JsonObject)) {
+                throw new JsonException("Found invalid data structure while parsing context map");
+            }
+            final JsonObject obj = (JsonObject) st;
+            
+            for (final Map.Entry<String,JsonValue> context : obj.entrySet()) {
+                if (context.getValue().getValueType() != JsonValue.ValueType.OBJECT) {
+                    throw new JsonException("Found invalid data structure while parsing context map");
+                }
+                
+                // Create new context if necessary.
+                Map<String,MutableStorageRecord<?>> dataMap = getContextMap().get(context.getKey());
+                if (dataMap == null) {
+                    dataMap = new HashMap<>();
+                    getContextMap().put(context.getKey(), dataMap);
+                }
+                
+                final JsonObject contextRecords = (JsonObject) context.getValue();
+                for (final Map.Entry<String,JsonValue> record : contextRecords.entrySet()) {
+                
+                    final JsonObject fields = (JsonObject) record.getValue();
+                    Long exp = null;
+                    if (fields.containsKey("x")) {
+                        exp = fields.getJsonNumber("x").longValueExact();
+                    }
+                    
+                    dataMap.put(record.getKey(), new MutableStorageRecord<>(fields.getString("v"), exp));
+                }
+            }
+            setDirty(false);
+        } catch (final NullPointerException | ClassCastException | ArithmeticException | JsonException e) {
+            getContextMap().clear();
+            // Setting this should force corrupt data in the client to be overwritten.
+            setDirty(true);
+            log.error("Found invalid data structure while parsing context map", e);
+        }
+    }
+
+//Checkstyle: CyclomaticComplexity OFF        
+    /** {@inheritDoc} */
+    @Nullable public ClientStorageServiceOperation save(@Nonnull final ClientStorageService storageService)
+            throws IOException {
+        
+        if (!isDirty()) {
+            log.trace("Storage state has not been modified, save operation skipped");
+            return null;
+        }
+        
+        if (getContextMap().isEmpty()) {
+            log.trace("Data is empty");
+            return new ClientStorageServiceOperation(storageService.getId(), storageService.getStorageName(), null,
+                    getSource());
+        }
+
+        long exp = 0L;
+        final long now = System.currentTimeMillis();
+        boolean empty = true;
+
+        try {
+            final StringWriter sink = new StringWriter(128);
+            final JsonGenerator gen = Json.createGenerator(sink);
+            
+            gen.writeStartObject();
+            for (final Map.Entry<String,Map<String, MutableStorageRecord<?>>> context
+                    : getContextMap().entrySet()) {
+                if (!context.getValue().isEmpty()) {
+                    gen.writeStartObject(context.getKey());
+                    for (final Map.Entry<String,MutableStorageRecord<?>> entry : context.getValue().entrySet()) {
+                        final MutableStorageRecord<?> record = entry.getValue();
+                        final Long recexp = record.getExpiration();
+                        if (recexp == null || recexp > now) {
+                            empty = false;
+                            gen.writeStartObject(entry.getKey())
+                                .write("v", record.getValue());
+                            if (recexp != null) {
+                                gen.write("x", record.getExpiration());
+                                exp = Math.max(exp, recexp);
+                            }
+                            gen.writeEnd();
+                        }
+                    }
+                    gen.writeEnd();
+                }
+            }
+            gen.writeEnd().close();
+
+            if (empty) {
+                log.trace(" Data is empty");
+                return new ClientStorageServiceOperation(storageService.getId(), storageService.getStorageName(), null,
+                        getSource());
+            }
+            
+            final String raw = sink.toString();
+            
+            log.trace("Size of data before encryption is {}", raw.length());
+            log.trace("Data before encryption is {}", raw);
+            try {
+                final String wrapped = storageService.getDataSealer().wrap(raw,
+                        exp > 0 ? Instant.ofEpochMilli(exp) : Instant.now().plus(Duration.ofDays(1)));
+                log.trace("Size of data after encryption is {}", wrapped.length());
+                setDirty(false);
+                return new ClientStorageServiceOperation(storageService.getId(), storageService.getStorageName(),
+                        wrapped, getSource());
+            } catch (final DataSealerException e) {
+                throw new IOException(e);
+            }
+        } catch (final JsonException e) {
+            throw new IOException(e);
+        }
+    }
+//Checkstyle: CyclomaticComplexity ON
+    
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list