summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKeuin <[email protected]>2021-01-12 16:54:03 +0800
committerkeuin <[email protected]>2021-01-12 16:54:03 +0800
commitf4d926c1eb91749a4b0f89c7a35538821ff7e21b (patch)
tree32890a856dfe67f80762000793d6140d43dd1795
parent4a52f5e6ce06cb6717510c6a975d5490be627c98 (diff)
Add serialization and deserialization for ObjectCollection
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollection.java3
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java1
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializer.java30
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectElement.java3
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/ObjectIdentifier.java4
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java21
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java95
-rw-r--r--src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java1
-rw-r--r--src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializerTest.java29
9 files changed, 173 insertions, 14 deletions
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollection.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollection.java
index e0ef4f7..16d95e6 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollection.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollection.java
@@ -1,8 +1,9 @@
package com.keuin.kbackupfabric.util.backup.incremental;
+import java.io.Serializable;
import java.util.*;
-public class ObjectCollection {
+public class ObjectCollection implements Serializable {
private final String name;
private final Map<String, ObjectElement> elements;
private final Map<String, ObjectCollection> subCollections;
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java
index be9e426..ea9edd9 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java
@@ -16,6 +16,7 @@ import java.util.*;
* The identifier should use hashes that are strong enough, to prevent possible collisions.
*/
public class ObjectCollectionFactory <T extends ObjectIdentifier> {
+
private final FileIdentifierFactory<T> identifierFactory;
public ObjectCollectionFactory(FileIdentifierFactory<T> identifierFactory) {
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializer.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializer.java
new file mode 100644
index 0000000..cc77837
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializer.java
@@ -0,0 +1,30 @@
+package com.keuin.kbackupfabric.util.backup.incremental;
+
+import java.io.*;
+import java.util.Objects;
+
+public class ObjectCollectionSerializer {
+ public static ObjectCollection fromFile(File file) throws IOException {
+ Objects.requireNonNull(file);
+ ObjectCollection collection;
+ try (FileInputStream fileInputStream = new FileInputStream(file)) {
+ try (ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
+ collection = (ObjectCollection) objectInputStream.readObject();
+ } catch (ClassNotFoundException ignored) {
+ // this should not happen
+ return null;
+ }
+ }
+ return collection;
+ }
+
+ public static void toFile(ObjectCollection collection, File file) throws IOException {
+ Objects.requireNonNull(collection);
+ Objects.requireNonNull(file);
+ try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
+ try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
+ objectOutputStream.writeObject(collection);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectElement.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectElement.java
index 1232fb9..cbb886e 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectElement.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectElement.java
@@ -2,13 +2,14 @@ package com.keuin.kbackupfabric.util.backup.incremental;
import com.keuin.kbackupfabric.util.backup.incremental.identifier.ObjectIdentifier;
+import java.io.Serializable;
import java.util.Objects;
/**
* Representing a file in a ObjectCollection.
* Immutable.
*/
-public class ObjectElement {
+public class ObjectElement implements Serializable {
private final String name;
private final ObjectIdentifier identifier;
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/ObjectIdentifier.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/ObjectIdentifier.java
index 6744616..aece07d 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/ObjectIdentifier.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/ObjectIdentifier.java
@@ -1,11 +1,13 @@
package com.keuin.kbackupfabric.util.backup.incremental.identifier;
+import java.io.Serializable;
+
/**
* The identifier distinguishing files in the object collection.
* It should be based on cryptographic hash function in order to prevent possible attacks to the backup system.
* All identifiers should be immutable and implement their own equals method.
* Immutable.
*/
-public interface ObjectIdentifier {
+public interface ObjectIdentifier extends Serializable {
String getIdentification();
}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java
index 64716ed..9ecf2d3 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java
@@ -41,27 +41,28 @@ public class Sha256Identifier extends SingleHashIdentifier {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
- FileInputStream inputStream = new FileInputStream(file);
-
- // This does not work. I don't know why
+ try (FileInputStream inputStream = new FileInputStream(file)) {
+ // This does not work. I don't know why
// FileChannel channel = inputStream.getChannel();
// ByteBuffer buffer = ByteBuffer.allocate(128);
// int readLength;
// while ((readLength = channel.read(buffer)) > 0)
// digest.update(buffer);
- // This also works, without warnings
- byte[] readBuffer = new byte[1024 * 1024];
- int readLength;
- while ((readLength = inputStream.read(readBuffer)) > 0)
- digest.update(readBuffer,0, readLength);
+ // This also works, without warnings
+ byte[] readBuffer = new byte[1024 * 1024];
+ int readLength;
+ while ((readLength = inputStream.read(readBuffer)) > 0)
+ digest.update(readBuffer, 0, readLength);
- // The below lines also works, but the IDE will complain about the while loop
+ // The below lines also works, but the IDE will complain about the while loop
// DigestInputStream digestInputStream = new DigestInputStream(inputStream, digest);
// while(digestInputStream.read() > 0)
// ;
- return digest.digest();
+ return digest.digest();
+ }
+
} catch (NoSuchAlgorithmException ignored) {
// this shouldn't happen
return new byte[SHA256_LENGTH];
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java
new file mode 100644
index 0000000..cd15499
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java
@@ -0,0 +1,95 @@
+package com.keuin.kbackupfabric.util.backup.incremental.manager;
+
+import com.keuin.kbackupfabric.util.backup.incremental.ObjectCollection;
+import com.keuin.kbackupfabric.util.backup.incremental.ObjectElement;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Objects;
+
+public class IncrementalBackupStorageManager {
+
+ private final Path backupStorageBase;
+
+ public IncrementalBackupStorageManager(Path backupStorageBase) {
+ this.backupStorageBase = backupStorageBase;
+ }
+
+ /**
+ * Add a object collection to storage base.
+ * @param collection the collection.
+ * @return objects copied to the base.
+ * @throws IOException I/O Error.
+ */
+ public int addObjectCollection(ObjectCollection collection, File collectionBasePath) throws IOException {
+ Objects.requireNonNull(collection);
+ Objects.requireNonNull(collectionBasePath);
+
+ int copyCount = 0;
+
+ // copy sub files
+ for (Map.Entry<String, ObjectElement> entry : collection.getElementMap().entrySet()) {
+ File copyDestination = new File(backupStorageBase.toFile(), entry.getValue().getIdentifier().getIdentification());
+ if (!baseContainsObject(entry.getValue())) {
+ // element does not exist. copy.
+ Files.copy(Paths.get(collectionBasePath.getAbsolutePath(), entry.getKey()), copyDestination.toPath());
+ ++copyCount;
+ }
+ }
+
+ //copy sub dirs recursively
+ for (Map.Entry<String, ObjectCollection> entry : collection.getSubCollectionMap().entrySet()) {
+ File newBase = new File(collectionBasePath, entry.getKey());
+ copyCount += addObjectCollection(entry.getValue(), newBase);
+ }
+
+ return copyCount;
+ }
+
+ /**
+ * Restore an object collection from the storage base. i.e., restore the save from backup storage.
+ * @param collection the collection to be restored.
+ * @param collectionBasePath save path of the collection.
+ * @return objects restored from the base.
+ * @throws IOException I/O Error.
+ */
+ public int restoreObjectCollection(ObjectCollection collection, File collectionBasePath) throws IOException {
+ Objects.requireNonNull(collection);
+ Objects.requireNonNull(collectionBasePath);
+
+ int copyCount = 0;
+
+ // copy sub files
+ for (Map.Entry<String, ObjectElement> entry : collection.getElementMap().entrySet()) {
+ File copySource = new File(backupStorageBase.toFile(), entry.getValue().getIdentifier().getIdentification());
+ if (!baseContainsObject(entry.getValue())) {
+ throw new IOException(String.format("File %s does not exist in the base.", copySource.getName()));
+ }
+ Files.copy(copySource.toPath(), Paths.get(collectionBasePath.getAbsolutePath(), entry.getKey()));
+ ++copyCount;
+ }
+
+ //copy sub dirs recursively
+ for (Map.Entry<String, ObjectCollection> entry : collection.getSubCollectionMap().entrySet()) {
+ File newBase = new File(collectionBasePath, entry.getKey());
+ copyCount += restoreObjectCollection(entry.getValue(), newBase);
+ }
+
+ return copyCount;
+ }
+
+ /**
+ * Check if the backup base contains given element.
+ * @param objectElement the element.
+ * @return true or false.
+ */
+ private boolean baseContainsObject(ObjectElement objectElement) {
+ // This may be extended to use more variants of hash functions and combinations of other attributes (such as file size)
+ return (new File(backupStorageBase.toFile(), objectElement.getIdentifier().getIdentification())).exists();
+ }
+
+}
diff --git a/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java
index 60a42cf..b7e0aad 100644
--- a/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java
+++ b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java
@@ -72,7 +72,6 @@ public class ObjectCollectionFactoryTest {
// check `12`
validate(collection.getSubCollectionMap().get("1").getSubCollectionMap().get("12"), Collections.emptyList(), Collections.emptyMap());
-
} catch (IOException e) {
e.printStackTrace();
fail();
diff --git a/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializerTest.java b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializerTest.java
new file mode 100644
index 0000000..0edfe01
--- /dev/null
+++ b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializerTest.java
@@ -0,0 +1,29 @@
+package com.keuin.kbackupfabric.util.backup.incremental;
+
+import com.keuin.kbackupfabric.util.backup.incremental.identifier.Sha256Identifier;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import static org.junit.Assert.assertEquals;
+
+public class ObjectCollectionSerializerTest {
+ @Test
+ public void testSerializationConsistency() throws IOException {
+ ObjectCollectionFactory<Sha256Identifier> factory =
+ new ObjectCollectionFactory<>(Sha256Identifier.getFactory());
+ ObjectCollection collection =
+ factory.fromDirectory(new File("./testfile/ObjectCollectionFactoryTest"));
+ File file = new File("./testfile/serialized");
+ if (file.exists()) {
+ Files.delete(file.toPath());
+ }
+ ObjectCollectionSerializer.toFile(collection, file);
+ ObjectCollection collection2 = ObjectCollectionSerializer.fromFile(file);
+ Files.delete(file.toPath());
+ assertEquals(collection, collection2);
+ }
+
+} \ No newline at end of file