1. Audience

This Project aims at Developers who wants to enable their SpringBoot Application to be authenticated against an Owncloud Instance and use the Owncloud Provisioning API.

It’s planned to implement the following additional API’s of Owncloud:

2. Changelog

2.1. 1.4.0

  • BUGFIX: Thread-Deadlock on calling close() twice on PipedInputStream or PipedOutputStream

  • UPDATE: updated to Version 2.0.3.RELEASE of Spring Boot and other Dependency updates

3. Quick Start

3.1. Maven Dependency

Add the following Maven Dependency:

pom.xml
<dependency>
  <groupId>software.coolstuff</groupId>
  <artifactId>owncloud-spring-boot-starter</artifactId>
  <version>1.5.0</version>
</dependency>

3.2. Application Properties

Add the following Property:

application.properties
owncloud.location=https://www.example.com/owncloud

3.3. Test your Application

When you now run your Spring-Boot Application the following Login-Screen appears:

Login Screen

Provide your Owncloud User Credentials and you are now able to work with your Application.

4. What’s included

This Spring-Boot Starter provides the following Functionalities:

  • simple Auto-Configuration

    • add it as a Maven Dependency

    • configure the Server-Address as a Spring-Boot Application-Property

  • Authenticate against one Owncloud Instance by using the Spring Security Authentication Mechanism

  • Get Information about the authenticated User

  • Modify the granted Authorities during the Authentication Process

  • Get User Information (Display-Name, eMail) of the authenticated User

  • Modify the User Information (Display-Name, eMail) of the authenticated User

  • Get the assigned Groups of the authenticated User

  • additional Enhancements as an Administrator:

    • Query all Users of the Owncloud Instance (optional with Filter by DisplayName)

    • Query all Groups of the Owncloud Instance (optional with Filter by Group Name)

    • Get detailed Information about one User by its Username

    • Modify the User Information of any User

    • Create new Users

    • Delete Users

    • Get the Group Memberships of any User

    • Modify the Group Memberships of any User

    • Get the User Memberships of any Group

    • get File Quota Information about any User

  • InMemory Owncloud Instance

    • Read Data as XML from

      • a Classpath Resource (read only) or

      • an external File Resource (read write)

    • Useful for local Development

    • Useful for Unit Tests / Integration Tests

  • save any type of File on the Owncloud

  • delete existing Files on the Owncloud

  • create/modify/delete Subdirectories

  • get File Quota Information about the authenticated User

5. Prerequisites

The following Prerequisites must be met in Order to work with this Spring-Boot Starter:

  • Owncloud Instance must be installed, configured and running.

  • The Owncloud Instance must have at least Version 8

  • You must be able to connect to this Owncloud Instance (i.E. Firewall-Settings, Network, …​)

  • The Provisioning API must be enabled

6. Configuration

All Services of this Spring-Boot Starter will be managed by the following Configuration Parameters.

Name available for mandatory Data Type Default Description

owncloud.location

-

true

String

-

The Location of the Owncloud Instance

owncloud.user-service.enable-modifications

-

false

boolean

true

Modifications through OwncloudUserService and OwncloudGroupService are allowed/disallowed

owncloud.resource-service.add-relative-down-path

-

true

boolean

true

add .. to the List of available Owncloud-Resource within a Directory

owncloud.resource-service.piped-stream-buffer-size

-

true

Integer

8192

Buffer Size (in Bytes) for Content-Streaming (InputStream/OutputStream)

owncloud.resource-service.piped-stream-uncaught-exception-log-level

-

true

LogLevel

error

Log Level for any uncaught Exceptions while Content-Streaming

owncloud.resource-service.sardine-cache.concurrency-level

REST

false

Integer

-

Concurrency Level for the Sardine Cache (look at Guava CacheBuilder concurrencyLevel)

owncloud.resource-service.sardine-cache.expire-after-access

REST

false

Long

-

Duration of Availability of the cached Sardine-Implementation after the last Access (see Guava CacheBuilder expireAfterAccess)

owncloud.resource-service.sardine-cache.expire-after-access-time-unit

REST

false

java.util.concurrent.TimeUnit

TimeUnit.SECONDS

Timeunit for owncloud.resource-service.sardine-cache.expire-after-access

owncloud.resource-service.sardine-cache.expire-after-write

REST

false

Long

-

Duration of Availability of the cached Sardine-Implementation after Write (see Guava CacheBuilder expireAfterWrite)

owncloud.resource-service.sardine-cache.expire-after-write-time-unit

REST

false

java.util.concurrent.TimeUnit

TimeUnit.SECONDS

Timeunit for owncloud.resource-service.sardine-cache.expire-after-write

owncloud.resource-service.sardine-cache.initial-capacity

REST

false

Integer

-

Initial Capacity of the Sardine Cache (see Guava CacheBuilder initialCapacity)

owncloud.resource-service.sardine-cache.maximum-size

REST

false

Long

-

Maximum Size of the Sardine Cache (see Guava CacheBuilder maximumSize)

owncloud.resource-service.sardine-cache.maximum-weight

REST

false

Long

-

Maximum Weight of the Entries within the Sardine Cache (see Guava CacheBuilder maximumWeight)

owncloud.resource-service.sardine-cache.refresh-after-write

REST

false

Long

-

Duration when the Entries of the Sardine-Cache should be refreshed after Write (see Guava CacheBuilder refreshAfterWrite)

owncloud.resource-service.sardine-cache.refresh-after-write-time-unit

REST

false

java.util.concurrent.TimeUnit

TimeUnit.SECONDS

Timeunit for owncloud.resource-service.sardine-cache.refresh-after-write

owncloud.resource-service.message-digest-algorithm

LOCAL

true

OwncloudLocalProperties.ResourceServiceProperties.MessageDigestAlgorithm

MessageDigestAlgorithm.MD5

Message Digest Algorithm for the Checksum Service

owncloud.resource-service.location

LOCAL

true

java.nio.file.Path

-

Root-Path of the local Files to be served by the OwncloudResourceService

owncloud.resource-service.piped-stream-temporary-file-prefix

LOCAL

true

String

owncloud

File Prefix used for temporary Files by PipedInputStream and PipedOutputStream

6.1. owncloud.location

The Configuration Parameter owncloud.location defines, which Backend will be used.

It can have one of the following Values:

starts with modifiable Description

http://
https://

true

The Authentication uses the Owncloud of this Web-Address
i.E.: https://www.example.com/owncloud

classpath:

false

Get Data of an In-Memory Owncloud Instance from a Classpath Resource

file:

true

Get Data of an In-Memory Owncloud Instance from an external File Resource

Normally you would provide the Web-Address of the Owncloud-Instance. But for local Development or Tests it could be useful, to have an Owncloud Instance which always behaves the same (returns the same Users, the same Groups, …​).

There you can define a XML-Resource with either classpath: or file:.
This XML-Resource should have the following Format:

owncloud.xml
<owncloud>
  <users> (1)
    <user> (2)
      <username>user1</username> (3)
      <password>s3cr3t</password> (4)
      <enabled>true</enabled>

      <displayname>Mr. User 1</displayname> (5)
      <email>user1@example.com</email>

      <groups> (6)
        <group>group1</group>
        <group>group2</group>
      </groups>

      <quota>10240</quota> (7)
    </user>
    <user>
      <username>user2</username>
      <password>s3cr3t</password>
      <enabled>false</enabled>
      <displayName>Mrs. User 2</displayName>
      <email>user2@example.com</email>
    </user>
  </users>

  <groups> (8)
    <group>group1</group>
    <group>group2</group>
    <group>group3</group>
  </groups>
</owncloud>
  1. List of all existing Users

  2. The Definition of a single User

  3. Username, Password and Availability-Stats (<enabled>) are mandatory.

  4. unencrypted Password (because you’re in local Development or Test Environment)

  5. optional Parameters

  6. Group Memberships of the User.

  7. File Quota of the User (in Bytes). If omitted the User has unlimited Quota on the File-System.

  8. All available Groups of the InMemory Owncloud Instance

Note
All Groups, which are referenced as a User-Membership will be checked when the Service starts.
If there are any Groups, which are not defined at the <groups> Section the Service will fail on Startup with an IllegalStateException.

So if you define the Configuration Parameter owncloud.location either as

  • classpath:/path/to/owncloud.xml or

  • file:/path/to/owncloud.xml

the Data of the provided XML-File will be read on Application Startup and resist as a InMemory Representation used by the Services of this Spring-Boot Starter (Authentication, UserQuery, UserModification, …​).

When you use a file: Resource the changed Data will be rewritten to this Resource on a normal Shutdown of the Application. This is useful for incremental Integration Tests.

When you use a classpath: Resource the changed Data will not be written. Therefor this type should be used for local Development and/or Unit Tests.

7. Authentication

The Authentication has been implemented as a Spring Security AutheticationProvider.
Due to the AutoConfiguration Feature this AuthenticationProvider will be automatically loaded and configured when the Configuration Parameter owncloud.location has been set.

7.1. Configuration

By including owncloud-spring-boot-starter into the Classpath this Authentication-Provider is autoconfigured unless you decide to Overwrite it (by instantiating your own Implementation as a Subclass of OwncloudAuthenticationProvider).

If this is the only Authentication-Provider existing then Spring-Boot will use this Authentication-Provider.

When you have several Authentication-Provider instantiated then you have to configure its Usage by a WebSecurityConfigurerAdapter

WebSecurityConfigurerAdapter
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

  @Autowired
  @Qualifier("owncloudAuthenticationProvider")
  private AuthenticationProvider authenticationProvider;

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authenticationProvider);
  }

}

By the Qualifier owncloudAuthenticationProvider you can inject the Owncloud Authentication Provider directly into the WebSecurityConfigurerAdapter and use this AuthenticationProvider for Authentication.

7.2. RememberMe Services

Unfortunatly RememberMe Service is not available for Owncloud Authentication. Like Microsoft ActiveDirectory the Owncloud Instance doesn’t provide the Password of a User which is necessary for InMemory RememberMe Services.
Also the RememberMe-Services based on Persistent Tokens will not work because you need the Password of the Owncloud User for all Requests to the Owncloud. This could only be provided, if the original Session is available (and not reauthenticated by the RememberMe Service).

Maybe one time Owncloud will provide an Application Token or work as a OAuth Provider. But this will be future work.

7.3. Groups vs. Authorities

A Group in Owncloud is mostly a Summary of Privileges the User has been granted to (Shares, Calendars, Addressbooks, …​)

An Authority is, simple said, a Set of Privileges, which you can test upon the Call of a Method.

A Owncloud Group wont represent a simple Authority instead a Group of Authorities.

Therefor you can map the Groups of the Owncloud User to Authorities needed by your Application in 2 Ways.

7.3.1. GrantedAuthoritiesMapper

With the help of a GrantedAuthoritiesMapper you can map the Owncloud Groups of the User to Authorities you can use within your Application. The simplest of them is the SimpleAuthorityMapper which prepends a Prefix ROLE_ to the Owncloud Group Name.

You simply add the GrantedAuthoritiesMapper as a Spring Bean:

@Configuration
public class MyConfiguration {

  @Bean
  public GrantedAuthoritiesMapper grantedAuthoritiesMapper() {
    return new SimpleGrantedAuthoritiesMapper();
  }

}

Up now all Groups of the User will be added with the Prefix ROLE_ and added as an Authority to the List of Authorities of the Authentication Object.

7.3.2. OwncloudGrantedAuthoritiesMapper

In Spring Security there exists a good Database Schema for the Relationship between Users, Groups and Authorities:

diagram classes

Following this Schema there are 2 Kinds of Classes involved

  1. User, Group & UserGroup are managed by Owncloud

  2. Authority, UserAuthority & GroupAuthority are managed by your Application

A Spring Bean of OwncloudGrantedAuthoritiesMapper matches your Authorities to the authenticated User during the Authentication Process.

MyOwncloudGrantedAuthoritiesMapper.java
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;

import software.coolstuff.springframework.owncloud.service.api.OwncloudGrantedAuthoritiesMapper;

@Service
public class MyOwncloudGrantedAuthoritiesMapper implements OwncloudGrantedAuthoritiesMapper {

  private final static String ROLE_PREFIX = "ROLE_";

  @Autowired
  private UserAuthorityRepository userAuthorityRepository;

  @Autowired
  private GroupAuthorityRepository groupAuthorityRepository;

  @Override
  public Collection<? extends GrantedAuthority> mapAuthorities(
      String username,
      Collection<? extends GrantedAuthority> grantedAuthorities) {
    Set<GrantedAuthority> authorities = new HashSet<>();

    addAllAuthorities(userAuthorityRepository.getAuthorities(username), authorities);

    if (CollectionUtils.isNotEmpty(grantedAuthorities)) {
      for (GrantedAuthority grantedAuthority : grantedAuthorities) {
        List<ApplicationAuthority> groupAuthorities =
          groupAuthorityRepository.getAuthorities(grantedAuthority.getAuthority());
        addAllAuthorities(groupAuthorities, authorities);
      }
    }

    return authorities;
  }

  private void addAllAuthorities(
      Collection<ApplicationAuthority> applicationAuthorities,
      Set<GrantedAuthority> springSecurityAuthorities) {
    if (CollectionUtils.isEmpty(applicationAuthorities)) {
      return;
    }

    for (ApplicationAuthority applicationAuthority : applicationAuthorities) {
      GrantedAuthority springSecurityAuthority =
        new SimpleGrantedAuthority(applicationAuthority.getName());
      if (!StringUtils.startsWith(applicationAuthority.getName(), ROLE_PREFIX)) {
        springSecurityAuthority =
          new SimpleGrantedAuthority(ROLE_PREFIX + applicationAuthority.getName());
      }
      springSecurityAuthorities.add(springSecurityAuthority);
    }
  }

}

By the assumption that

  • Class ApplicationAuthority simply returns the Authority by Method getName()

  • Class UserAuthorityRepository returns a List of ApplicationAuthority by Method getAuthorities(String username)

  • Class GroupAuthorityRepository returns a List of ApplicationAuthority by Method getAuthorities(String groupname)

this Class returns all Authorities for the Owncloud User username and its associated Groups (authorities).

8. UserDetails Service

Like the Authentication Provider the OwncloudUserDetailsService will be autoconfigured by including owncloud-spring-boot-starter into your Classpath.

The OwncloudUserDetailsService will be used by OwncloudAuthenticationProvider to load the UserDetails of the authenticated User. This is necessary to identify the Enabled/Disabled Status of the authenticated User.

Therefor the OwncloudUserDetails Object will be included into the OwncloudAuthentication Object and will be returned by calling getPrincipal().

Because Owncloud only serves the Enabled/Disabled Status of the User the following Method will always return true:

  • isAccountNonExpired()

  • isAccountNonLocked()

  • isCredentialsNonExpired()

Beside the normal Attributes of the Spring Security UserDetails Object the OwncloudUserDetails Object provides the following, additional Fields:

  • getDisplayName() …​ The full Name of the User

  • getEmail() …​ The Email of the User

  • getGroups() …​ A List of Groups the User is a Member of

9. OwncloudUserService

When you are authenticated by the Owncloud AuthenticationProvider you can use this Service simply by @Autowire it to your Bean.

For more Details about the Owncloud REST-Calls please look at the Owncloud User provisioning API.

The following Methods will be available:

Method Owncloud REST Call Description

List<String> findAll()

GET /ocs/v1.php/cloud/users

All Users

List<String> findAll(String filter)

GET /ocs/v1.php/cloud/users?search={filter}

All Users whose Display Name matches the Search criteria

Optional<OwncloudUserDetails> findOne(String username)

GET /ocs/v1.php/cloud/users/{username}
GET /ocs/v1.php/cloud/users/{username}/groups

Information about one User

  • Full Name

  • Email

  • enabled / disabled

  • all Groups associated to the User

OwncloudUserDetails save(OwncloudModificationUser user)

additional (when creating a new User):
POST /ocs/v1.php/users -d userid="{username}" -d password="{password}"

modifying User:
PUT /ocs/v1.php/users -d key="display" -d value="{displayName}"

PUT /ocs/v1.php/users -d key="email" -d value="{email}"

when Groups will be modified:
POST /ocs/v1.php/users/{username}/groups -d groupid="{groupname}"

DELETE /ocs/v1.php/users/{username}/groups -d groupid="{groupname}"

  • creates a new User

  • modifies the Information of an existing User

Note
due to Security Reasons the Password of a User will not be modified nor will it be returned.

void delete(String username)

DELETE /ocs/v1.php/users/{username}

Removes a User from Owncloud

Note
all Objects of this User (Files, Calendars, Addressbooks, …​) will also be removed.

9.1. creating a new User

If you want to create a new User you create a new Instance of OwncloudModificationUser

OwncloudModificationUser newUser = OwncloudModificationUser.builder()
  .username("username")
  .password("s3cr3t")
  .displayName("Display Name of the new User")
  .email("user@example.com")
  .group("group1")
  .group("group2")
  .build();
OwncloudUserDetails createdUser =
  owncloudUserModificationService.saveUser(newUser);

9.2. modifying a User

If you want to modify the Information about a User this User has to be read just before by OwncloudUserService.findOne(String username). With the OwncloudUserDetails Object in Hands you can create a new OwncloudModificationUser.

owncloudUserService.findOne("user1")
  .map(OwncloudModificationUser::of)
  .map(modificationUser -> {
     modificationUser.setDisplayName("new Display Name of the User");
     return modificationUser;
   })
  .ifPresent(owncloudUserService::save);

10. OwncloudGroupService

When you are authenticated by the Owncloud AuthenticationProvider you can use this Service simply by @Autowire it to your Bean.

For more Details about the Owncloud REST-Calls please look at the Owncloud User provisioning API.

The following Methods will be available:

Method Owncloud REST-Call Description

List<String> findAll()

GET /ocs/v1.php/cloud/groups

All Groups

List<String> findAll(String filter)

GET /ocs/v1.php/cloud/groups?search={filter}

All Groups whose Name matches the Search criteria

List<String> findAllUsers(String groupname)

GET /ocs/v1.php/cloud/groups/{groupname}

All Users associated to the Group

List<String> findAllGroups(String username)

GET /ocs/v1.php/cloud/users/{username}/groups

All Groups associated to the User

void create(String groupname)

POST /ocs/v1.php/groups -d groupid="{groupname}"

Create a new Group

void delete(String groupname)`

DELETE /ocs/v1.php/groups/{groupname}`

Removes a Group from Owncloud

Note
all Memberships of this Group will also be removed. Also all Shares (Files, Calendars, Addressbooks, …​)

11. enable/disable User Modifications

Because Modifications by OwncloudUserService (save, delete) or by OwncloudGroupService (create, delete) need administrative Privileges of the User there is the Danger, that the Application will remove Users and/or Groups unintentionally.

To protect the Usage of OwncloudUserService and OwncloudGroupService can be restricted.
This Restriction is disabled by default.
To deny Modifications by OwncloudUserService and OwncloudGroupService you can set the following Property

owncloud.enable-modifications=false

12. Resource Service

One of the main Aspects of Owncloud is the Usage of a centralized WebDAV File Store. By using the OwncloudResourceService you can interact with this WebDAV-based File Store.

Simply by @Autowire the OwncloudResourceService you can use the following Methods:

Method REST-Call Description

List<OwncloudResource> listRoot()

PROPFIND /remote.php/dav/files/{username}/

Get all Information about Files and/or Directories of the currently authenticated Users Root Directory.

List<OwncloudResource> list(URI relativeTo)

PROPFIND /remote.php/dav/files/{username}/{relativeTo}

Get all Information about Files and/or Directories of the given URI.
This URI is relative to the Root Directory of the currently authenticated User

Optional<OwncloudResource> find(URI path)

PROPFIND /remote.php/dav/files/{username}/{path}

Find a File or Directory by its URI relative to the Root Directory of the currently authenticated User.

OwncloudResource createDirectory(URI directory)

MKCOL /remote.php/dav/files/{username}/{directory}

Create a Directory relative to the Root Directory of the currently authenticated User

void delete(OwncloudResource resource)

DELETE /remote.php/dav/files/{username}/{resource.href}

Delete a File or Directory referenced by the OwncloudResource (either returned by listRoot(), list(URI relativeTo) or find(URI resource)).

Note
Deleting a Directory causes that also all Files and Subdirectories will be removed recursively.

InputStream getInputStream(OwncloudFileResource resource)

GET /remote.php/dav/files/{username}/{resource.href}

get an InputStream to read the Content of a File

OutputStream getOutputStream(OwncloudFileResource resource)

PUT /remote.php/dav/files/{username}/{resource.href}

get an OutputStream to overwrite/append the Content of an existing File.

OutputStream getOutputStream(URI path, MediaType mediaType)

PUT /remote.php/dav/files/{username}/{path}

create a new File and get an OutputStream to write its Content.

OwncloudQuota getQuota()

GET /ocs/v1.php/cloud/users/{username}

get the Quota of the actual authenticated User

12.1. OwncloudResource

A OwncloudResource is an abstract Representation of a Resource on the Owncloud. This Resource can either be a File or a Directory.

The following Information is available about this Resource:

Information Datatype

href

java.net.URI

name

java.lang.String

lastModifiedAt

java.time.LocalDateTime

mediaType

org.springframework.http.MediaType

eTag

java.lang.String

In the case of a File Representation a OwncloudFileResource as a Subclass of OwncloudResource will be instantiated. This OwncloudFileResource has the following additional Information:

Information Datatype

contentLength

java.lang.Long

12.2. from OwncloudResource to OwncloudFileResource

The Methods listRoot() and list(URI relativeTo) both return a List of OwncloudResource because the Content of a Directory can either be Files or another Directories (Subdirectories).

In the case of a Directory the MediaType of the OwncloudResource is set to httpd/unix-directory. If you have a File (when MediaType of OwncloudResource is not httpd/unix-directory) you can convert the OwncloudResource to a OwncloudFileResource. Only with the OwncloudFileResource you can read or write the Content of this File.

Optional<OwncloudFileResource> fileResource =
  resourceService.find(uri)
                 .filter(OwncloudUtils::isNotDirectory)
                 .map(OwncloudUtils:toOwncloudFileResource);

12.3. relative Super Directory

When you call list(URI relativeTo) you will get a List of Files/Directories of the given URI relative to the User Root. OwncloudResourceService will give you 2 additional Entries: . (actual Directory) and .. (Parent Directory)

If you don’t want a Reference to the Parent Directory you can set the Configuration Property owncloud.resource-service.add-relative-down-path to false. (Default is: true).
After setting this Configuration to false no additional Entriy .. with a Reference to the Parent Directory will be added to the Result of list(URI relativeTo).

By letting this Configuration Property to its Default Value of true the relative Link to the Super Directory will be added. The only Exception is the Root-Directory of the User because you can’t navigate below to the Root-Directory.

12.4. Local Resource Service

As with the OwncloudUserService and the OwncloudGroupService you can route the OwncloudResource to use a local Storage instead of a remote Owncloud Instance. You can use the local Storage for Unit- and/or Integration-Tests.

Warning
Don’t use the local Storage of the OwncloudResource to build your own WebDAV Storage. This is not the intent of this Project and the Developers will not take any responsibility for lost Data.

You use the local Storage by setting the local Implementation and a Path to the Root Directory for all Users defined by owncloud.xml.

application.yml
owncloud:
  location: classpath:/owncloud.xml
  resource-service:
    location: /path/to/files

A Subdirectory will be created for every User of the owncloud.xml at the first Time he/she uses any Method of OwncloudResourceService.

For Instance: if User jane uses any Method of OwncloudResourceService a Directory /path/to/files/jane will be created.

12.5. eTag

The Owncloud calculates an eTag for every Resource. This eTag will be used by the Owncloud Client to sync changed Files/Directories.

The Local Storage of OwncloudResourceService has a similar eTag Calculation based on a MessageDigest Algorithm. At the Moment only MD5 will be used.

The Checksum of a File will be calculated by its Content. The Checksum of a Directory will be calculated by recursevly concatinating the Checksum of all Files within this Directory and its Subdirectories.

Everytime the Content of a File changes (either by OwncloudResourceService.getOutputStream or when any other Process outside of the Spring-Boot Application changes the Content of the File) the Checksum will be recalculated.

For better Performance the Checksums will be cached. On Application Startup the Checksum of all Files and Directories under the Path referenced by the Property owncloud.resource-service.location will be calculated and written to a Java Map.

This will take some time and the Application Startup has been locked until the Calculation has been finished. To keep Unit-Tests fast keep the Number and Size of Files small.

12.6. piped Streams

The API of OwncloudResourceService is simple. Because of its simplicity in the Background there are some challenges because of deferred Read/Write Operations (when using the REST Backend).

12.6.1. piped OutputStream

So the Write-Process (OwncloudResourceService.getOutputStream()) never will be called directly on the Files. Instead you (Developer who uses the OwncloudResourceService) will get one end of a Pipe, the PipedOutputStream. With this OutputStream you can handle all your Streaming.

In the Background there has been started a new Thread who keeps the other end of the Pipe, the PipedInputStream. All Data written to the PipedOutputStream will be read by the PipedInputStream. This Background Process handles the Communication with the deferred Owncloud System. If there are some Errors during the I/O Process (i.E. the Owncloud will be shutdown or network problems) the Background Process cancels the Transfer and throws an OwncloudException (either when writing or on close of the PipedOutputStream).

Also the local Implementation of OwncloudResourceService.getOutputStream() uses this Background Process to first write to a temporary File. Only on close() the temporary File will be moved to the real Position. The temporary File will be created on the temporary Path-Location (via Files.createTempFile()). The Prefix of this temporary File can be customized via the Configuration Property owncloud.resource-service.piped-stream-temporary-file-prefix. The Default is: owncloud

application.yml
owncloud:
  location: classpath:/owncloud.xml
  resource-service:
    location: /path/to/files
    piped-stream-temporary-file-prefix: owncloud

12.6.2. piped InputStream

Also the REST-Backend of OwncloudResourceService.getInputStream() uses the piped Streams to handle the deferred Communication to the Owncloud.

So if you call OwncloudResourceService.getInputStream() then you will get a PipedInputStream. This Pipe is connected to a Background Thread which keeps the PipedOutputStream. If there are any Errors during the deferred Read then an OwncloudException will be thrown.

The local Implementation of OwncloudResourceService.getInputStream() on the other Hand doesn’t use this Synchronization Mechanism. Instead you will get a FileInputStream with which you can read the Data directly. Please keep this in mind:

Note
the REST-Implementation of OwncloudResourceService.getInputStream() uses the piped Stream Synchronization Mechanism to handle deferred Exceptions. The local Implementation returns a FileInputStream to the File without the synchronization Mechanism.

12.6.3. Synchronization Buffer

By the Configuration Parameter owncloud.resource-service.piped-stream-buffer-size you can manage the Bytes which will be read/write by the piped Stream. The Value is in Bytes. The Default is 8K (8.192) Bytes.

application.yml
owncloud:
  location: classpath:/owncloud.xml
  resource-service:
    location: /path/to/files
    piped-stream-buffer-size: 8192

12.6.4. Exceptions during Background Synchronization

The Background-Thread will be automatically created (and also destroyed) by the owncloud-spring-boot-starter. It will be created if you call OwncloudResourceService.getInputStream() (only REST-Backend) or OwncloudResourceService.getOutputStream() (REST- and local Backend). It will be closed if you call close() on either the InputStream or the OutputStream Object.

On any Error during the Background Communication the Background-Thread throws an Instance of OwncloudException and logs the Exception to SLF4J. The Log-Level of this uncaught Exception can be handled by the Configuration Parameter owncloud.resource-service.piped-stream-uncaught-exception-log-level Any valid SLF4J LogLevel can be served. The Default is LogLevel.ERROR.

12.7. Sardine Cache (only REST Backend)

Because the WebDAV Protocol enhances the HTTP Protocol by some Methods (PROPFIND, MKCOL, …​) these Methods are not implemented by the Spring RestTemplate. But Sardine is an excellent WebDAV Implementation for Java. Therefor we use Sardine for these HTTP Enhancements.

To keep the Time as short as possible for consecutive WebDAV Operations the Sardine Session of the authenticated User will be cached and reused as long as the User keep the WebDAV Requests active.

This happens by using a Google Guava Cache. The Properties for this Cache can be maintained by Configuration Properties:

application.yml
owncloud:
  location: classpath:/owncloud.xml
  resource-service:
    sardine-cache:
      concurrency-level: 6
      expire-after-access: 5
      expire-after-access-time-unit: MINUTES

For a full List of the Configuration Properties look at Configuration (available for: REST)

13. Logging

This Project uses SLF4J to produce the Logs.

You can disable the Logging by setting the Log-Level of the Package software.coolstuff.springframework.owncloud to off.

logging.level.software.coolstuff.springframework.owncloud=off

As an alternative you can set the Log-Level to trace if you would like to get all Rest-Requests:

logging.level.software.coolstuff.springframework.owncloud=trace

14. Contributions

You can fork this Project on Github and create a Pull Request.

15. License

This Project is licensed under the GPL 3.0 License.

Fork me on GitHub