Skip to content

Commit

Permalink
Issue 2 and 3: Add documentation to Deposit and DepositProperties (#22)
Browse files Browse the repository at this point in the history
* Add javadoc style documentation to public methods

* Follow an intelliJ suggestion

* Rename 'empty' to 'from';Complete the docs.

* Add param description

* More explicit doc on createFromData

* review of #22 and adding of extra test

* remove Option's from 'DepositProperties.load'
  • Loading branch information
lindareijnhoudt authored and rvanheest committed Aug 7, 2018
1 parent 52e7a2a commit 73cdce6
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 48 deletions.
68 changes: 60 additions & 8 deletions src/main/scala/nl/knaw/dans/bag/Deposit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,15 @@ class Deposit private(val baseDir: File,
withDepositProperties(newProperties)
}

/**
* Saves the in-memory deposit to the file system. It saves all the bag-it files by calling
* `DansBag.save()` and the deposit.properties by calling `DepositProperties.save()`.
* As most methods in this library only manipulate the bag-it files and Deposit in memory, a call
* to `save()` is necessary to serialize the `nl.knaw.dans.bag.Deposit`.
*
* @return `scala.util.Success` if the save was performed successfully,
* `scala.util.Failure` otherwise
*/
def save(): Try[Unit] = {
for {
_ <- bag.save()
Expand All @@ -351,22 +360,52 @@ object Deposit {

val depositPropertiesName = "deposit.properties"

def empty(baseDir: File,
algorithms: Set[ChecksumAlgorithm] = Set(ChecksumAlgorithm.SHA1),
bagInfo: Map[String, Seq[String]] = Map.empty,
state: State,
depositor: Depositor,
bagStore: BagStore): Try[Deposit] = {
/**
* Creates a new `nl.knaw.dans.bag.Deposit` with an empty `nl.knaw.dans.bag.DansBag` inside.
*
* @param baseDir the directory for the new Deposit
* @param algorithms the algorithms with which the checksums for the (payload/tag) files are
* calculated. If none are provided, SHA1 is used.
* @param bagInfo the entries to be added to `bag-info.txt`
* @param state the state to be set in the `deposit.properties`' state.label
* @param depositor the depositor to be set in the `deposit.properties`' `depositor.userId`
* @param bagStore the bagId for the `bag-dir` in this `nl.knaw.dans.bag.Deposit`
* @return if successful, returns a `nl.knaw.dans.bag.Deposit` object representing the deposit
* located at `baseDir`, else returns an exception
*/
def from(baseDir: File,
algorithms: Set[ChecksumAlgorithm] = Set(ChecksumAlgorithm.SHA1),
bagInfo: Map[String, Seq[String]] = Map.empty,
state: State,
depositor: Depositor,
bagStore: BagStore): Try[Deposit] = {
if (baseDir.exists)
Failure(new FileAlreadyExistsException(baseDir.toString))
else
for {
bag <- DansBag.empty(baseDir / bagStore.bagId.toString, algorithms, bagInfo)
properties = DepositProperties.empty(state, depositor, bagStore)
properties = DepositProperties.from(state, depositor, bagStore)
_ <- properties.save(depositProperties(baseDir))
} yield new Deposit(baseDir, bag, properties)
}

/**
* Creates a new Deposit, as a parent-directory to the `payloadDir`. A new `DansBag` will be
* created in the `Deposit`, with the bag-it files, and the data files in the `data/` directory.
* However, no `metadata/` files will be created. These have to be added separately.
*
* @param payloadDir the directory containing the payload (data) files for the bag. The `Deposit`
* will be created here, and the payload files will be moved to the `data/`
* directory in the new `DansBag`
* @param algorithms the algorithms with which the checksums for the (payload/tag) files are
* calculated. If none provided SHA1 is used.
* @param bagInfo the entries to be added to `bag-info.txt`
* @param state the state to be set in the deposit.properties' state.label
* @param depositor the depositor to be set in the deposit.properties' depositor.userId
* @param bagStore the bagId for the bag-dir in this Deposit
* @return if successful, returns a `nl.knaw.dans.bag.Deposit` object representing the deposit
* located at `payloadDir` else returns an exception
*/
def createFromData(payloadDir: File,
algorithms: Set[ChecksumAlgorithm] = Set(ChecksumAlgorithm.SHA1),
bagInfo: Map[String, Seq[String]] = Map.empty,
Expand All @@ -379,12 +418,19 @@ object Deposit {
for {
bagDir <- moveBag(payloadDir, bagStore.bagId)
bag <- DansBag.createFromData(bagDir, algorithms, bagInfo)
properties = DepositProperties.empty(state, depositor, bagStore)
properties = DepositProperties.from(state, depositor, bagStore)
_ <- properties.save(depositProperties(payloadDir))
} yield new Deposit(payloadDir, bag, properties)
}
}

/**
* Reads the `baseDir` as a Deposit.
*
* @param baseDir the directory containing the deposit
* @return if successful, returns a `nl.knaw.dans.bag.Deposit` object representing the deposit
* located at `baseDir` else returns an exception
*/
def read(baseDir: File): Try[Deposit] = {
for {
bagDir <- findBagDir(baseDir)
Expand All @@ -393,6 +439,12 @@ object Deposit {
} yield new Deposit(baseDir, bag, properties)
}

/**
* Returns the `baseDir` of the Deposit object
*
* @param deposit the deposit to extract the `baseDir` from
* @return returns the `baseDir` of the Deposit object
*/
implicit def depositAsFile(deposit: Deposit): File = deposit.baseDir

private def moveBag(payloadDir: File, bagId: UUID): Try[File] = Try {
Expand Down
98 changes: 70 additions & 28 deletions src/main/scala/nl/knaw/dans/bag/DepositProperties.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import java.nio.file.NoSuchFileException
import java.util.UUID

import better.files.File
import nl.knaw.dans.bag.DepositProperties._
import nl.knaw.dans.bag.DepositProperties.{ stateDescription, _ }
import nl.knaw.dans.bag.SpringfieldPlayMode.SpringfieldPlayMode
import nl.knaw.dans.bag.StageState.StageState
import nl.knaw.dans.bag.StateLabel.StateLabel
Expand All @@ -39,6 +39,13 @@ case class DepositProperties(creation: Creation = Creation(),
springfield: Springfield = Springfield(),
staged: Staged = Staged()) {

/**
* Writes the `DepositProperties` to `file` on the filesystem.
*
* @param file the file location to serialize the properties to
* @return `scala.util.Success` if the save was performed successfully,
* `scala.util.Failure` otherwise
*/
def save(file: File): Try[Unit] = Try {
new PropertiesConfiguration {
setDelimiterParsingDisabled(true)
Expand Down Expand Up @@ -93,14 +100,29 @@ object DepositProperties {
val stagedState = "staged.state"
// @formatter:on

def empty(state: State, depositor: Depositor, bagStore: BagStore): DepositProperties = {
/**
* Creates a `DepositProperties` object, with only the mandatory properties set.
*
* @param state the `State` to be set
* @param depositor the accountname of the depositor
* @param bagStore the bagId to be used for this deposit
* @return a new `DepositProperties`
*/
def from(state: State, depositor: Depositor, bagStore: BagStore): DepositProperties = {
DepositProperties(
state = state,
depositor = depositor,
bagStore = bagStore
)
}

/**
* Reads a `File` as a `deposit.properties` file.
*
* @param propertiesFile the file to be converted to a `DepositProperties`
* @return if successful the `DepositProperties` representing the `propertiesFile`,
* else a Failure with a NoSuchFileException
*/
def read(propertiesFile: File): Try[DepositProperties] = {
if (propertiesFile.exists && propertiesFile.isRegularFile)
Try {
Expand All @@ -113,31 +135,51 @@ object DepositProperties {
Failure(new NoSuchFileException(s"$propertiesFile does not exist or isn't a file"))
}

/**
* Loads a new `DepositProperties` object with the corresponding elements from the
* `PropertiesConfiguration`. `properties` should at least contain all mandatory properties.
*
* @param properties the `PropertiesConfiguration` containing at least all mandatory deposit properties
* @return if successful a new `DepositProperties` representing the provided `properties`
* else a `Failure` with a `NoSuchElementException` if not all deposit properties were present
*/
def load(properties: PropertiesConfiguration): Try[DepositProperties] = Try {
val creationTimestampValue = properties.getString(creationTimestamp)
val stateLabelValue = properties.getString(stateLabel)
val stateDescriptionValue = properties.getString(stateDescription)
val depositorUserIdValue = properties.getString(depositorUserId)
val bagStoreBagIdValue = properties.getString(bagStoreBagId)

require(creationTimestampValue != null, s"could not find mandatory field '$creationTimestamp'")
require(stateLabelValue != null, s"could not find mandatory field '$stateLabel'")
require(stateDescriptionValue != null, s"could not find mandatory field '$stateDescription'")
require(depositorUserIdValue != null, s"could not find mandatory field '$depositorUserId'")
require(bagStoreBagIdValue != null, s"could not find mandatory field '$bagStoreBagId'")

DepositProperties(
creation = new Creation(
timestamp = properties.getString(creationTimestamp)
timestamp = creationTimestampValue
),
state = new State(
label = properties.getString(stateLabel),
description = properties.getString(stateDescription)
label = stateLabelValue,
description = stateDescriptionValue
),
depositor = Depositor(
userId = properties.getString(depositorUserId)
userId = depositorUserIdValue
),
bagStore = new BagStore(
bagId = properties.getString(bagStoreBagId),
bagId = bagStoreBagIdValue,
archived = properties.getString(bagStoreArchived)
),
identifier = Identifier(
doi = Option(properties.getString(doiIdentifier))
identifier = new Identifier(
doi = properties.getString(doiIdentifier)
),
curation = new Curation(
userId = Option(properties.getString(dataManagerUserId)),
email = Option(properties.getString(datamanagerEmail)),
isNewVersion = Option(properties.getString(isNewVersion)),
required = Option(properties.getString(curationRequired)),
performed = Option(properties.getString(curationPerformed))
userId = properties.getString(dataManagerUserId),
email = properties.getString(datamanagerEmail),
isNewVersion = properties.getString(isNewVersion),
required = properties.getString(curationRequired),
performed = properties.getString(curationPerformed)
),
springfield = new Springfield(
domain = properties.getString(springfieldDomain),
Expand Down Expand Up @@ -181,14 +223,14 @@ case class State(label: StateLabel, description: String) {

case class Depositor(userId: String)

case class Identifier(doi: Option[String] = None)
case class Identifier(doi: Option[String] = None) {
def this(doi: String) = {
this(Option(doi))
}
}

case class BagStore(bagId: UUID,
archived: Option[Boolean] = None) {
def this(bagId: UUID, archived: Boolean) = {
this(bagId, Option(archived))
}

def this(bagId: String, archived: String) = {
this(UUID.fromString(bagId), Option(archived).map(BooleanUtils.toBoolean))
}
Expand All @@ -205,15 +247,15 @@ case class Curation(dataManager: DataManager = DataManager(),
isNewVersion: Option[Boolean] = Option.empty,
required: Option[Boolean] = Option.empty,
performed: Option[Boolean] = Option.empty) {
def this(userId: Option[String],
email: Option[String],
isNewVersion: Option[String],
required: Option[String],
performed: Option[String]) = {
this(DataManager(userId, email),
isNewVersion.map(BooleanUtils.toBoolean),
required.map(BooleanUtils.toBoolean),
performed.map(BooleanUtils.toBoolean),
def this(userId: String,
email: String,
isNewVersion: String,
required: String,
performed: String) = {
this(DataManager(Option(userId), Option(email)),
Option(isNewVersion).map(BooleanUtils.toBoolean),
Option(required).map(BooleanUtils.toBoolean),
Option(performed).map(BooleanUtils.toBoolean),
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/nl/knaw/dans/bag/v0/DansV0Bag.scala
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ class DansV0Bag private(private[v0] val locBag: LocBag) extends DansBag {

if (destinationPath.exists)
throw new FileAlreadyExistsException(destinationPath.toString(), null, "already exists in payload")
if (fetchFiles.find(_.file == destinationPath).isDefined)
if (fetchFiles.exists(_.file == destinationPath))
throw new FileAlreadyExistsException(destinationPath.toString(), null, "already exists in fetch.txt")
if (!destinationPath.isChildOf(data))
throw new IllegalArgumentException(s"a fetch file can only point to a location inside the bag/data directory; $destinationPath is outside the data directory")
Expand Down
25 changes: 20 additions & 5 deletions src/test/scala/nl/knaw/dans/bag/DepositPropertiesSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ import scala.util.Failure

class DepositPropertiesSpec extends TestSupportFixture with FileSystemSupport with TestDeposits with FixDateTimeNow {

"empty" should "create a deposit.properties object containing only the minimal required properties" in {
"from" should "create a deposit.properties object containing only the minimal required properties" in {
val state = State(StateLabel.DRAFT, "this deposit is still in draft")
val depositor = Depositor("myuser")
val bagId = UUID.fromString("1c2f78a1-26b8-4a40-a873-1073b9f3a56a")
val bagStore = new BagStore(bagId, archived = false)
val props = DepositProperties.empty(state, depositor, bagStore)
val bagStore = BagStore(bagId, archived = Option(false))
val props = DepositProperties.from(state, depositor, bagStore)

props.creation.timestamp.toString(ISODateTimeFormat.dateTime()) shouldBe fixedDateTimeNow.toString(ISODateTimeFormat.dateTime())

Expand Down Expand Up @@ -119,22 +119,37 @@ class DepositPropertiesSpec extends TestSupportFixture with FileSystemSupport wi
props.staged.state shouldBe empty
}

it should "fail if the file does not exist" in {
it should "fail when the file does not exist" in {
val file = simpleDepositDirV0 / "non-existing-deposit.properties"
inside(DepositProperties.read(file)) {
case Failure(e: NoSuchFileException) =>
e should have message s"$file does not exist or isn't a file"
}
}

it should "fail if the given better.files.File is not a regular file" in {
it should "fail when the given better.files.File is not a regular file" in {
val file = simpleDepositDirV0
inside(DepositProperties.read(file)) {
case Failure(e: NoSuchFileException) =>
e should have message s"$file does not exist or isn't a file"
}
}

it should "fail when deposit.properties does not contain the minimal required properties" in {
// state.description and state.label are missing
val file = (testDir / "deposit.properties").write(
"""creation.timestamp=2018-05-25T20:08:56.210+02:00
|depositor.userId=myuser
|bag-store.bag-id=1c2f78a1-26b8-4a40-a873-1073b9f3a56a
""".stripMargin
)

inside(DepositProperties.read(file)) {
case Failure(e: IllegalArgumentException) =>
e should have message "requirement failed: could not find mandatory field 'state.label'"
}
}

"copy" should "change properties" in {
val props = simpleDepositPropertiesV0

Expand Down
12 changes: 6 additions & 6 deletions src/test/scala/nl/knaw/dans/bag/DepositSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class DepositSpec extends TestSupportFixture
baseDir.toJava shouldNot exist
bagDir.toJava shouldNot exist

inside(Deposit.empty(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
inside(Deposit.from(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
case Success(deposit) =>
baseDir.toJava should exist
bagDir.toJava should exist
Expand All @@ -66,7 +66,7 @@ class DepositSpec extends TestSupportFixture
val bagId = UUID.randomUUID()
val bagStore = BagStore(bagId)

inside(Deposit.empty(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
inside(Deposit.from(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
case Success(deposit) =>
deposit.bag.baseDir.listRecursively.toList should contain only(
deposit.bag.baseDir / "data",
Expand All @@ -87,7 +87,7 @@ class DepositSpec extends TestSupportFixture
val bagId = UUID.randomUUID()
val bagStore = BagStore(bagId)

inside(Deposit.empty(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
inside(Deposit.from(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
case Success(deposit) =>
(deposit.baseDir / "deposit.properties").toJava should exist
}
Expand All @@ -102,7 +102,7 @@ class DepositSpec extends TestSupportFixture
val bagId = UUID.randomUUID()
val bagStore = BagStore(bagId)

inside(Deposit.empty(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
inside(Deposit.from(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
case Success(deposit) =>
val props = new PropertiesConfiguration(deposit.baseDir / "deposit.properties" toJava)
props.getKeys.asScala.toList should contain only(
Expand Down Expand Up @@ -131,7 +131,7 @@ class DepositSpec extends TestSupportFixture
val bagId = UUID.randomUUID()
val bagStore = BagStore(bagId)

inside(Deposit.empty(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
inside(Deposit.from(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
case Success(deposit) =>
deposit.creationTimestamp shouldBe fixedDateTimeNow
deposit.stateLabel shouldBe state.label
Expand Down Expand Up @@ -174,7 +174,7 @@ class DepositSpec extends TestSupportFixture
baseDir.toJava should exist
baseDir.isRegularFile shouldBe true

inside(Deposit.empty(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
inside(Deposit.from(baseDir, algorithms, bagInfo, state, depositor, bagStore)) {
case Failure(e: FileAlreadyExistsException) =>
e should have message baseDir.toString()
}
Expand Down

0 comments on commit 73cdce6

Please sign in to comment.