© 2008-2017 The original authors.
Note
|
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. |
Preface
Project metadata
-
Version control - http://github.com/paulcwarren/spring-content/
-
Bugtracker - http://github.com/paulcwarren/spring-content/issues
-
Release repository - https://repo1.maven.org/maven2/
-
Snapshots repository - https://oss.sonatype.org/content/repositories/snapshots
1. Working with Spring Stores
The goal of the Spring Content is to make it easy to create applications that manage content such as documents, images and video by significantly reducing the amount of boilerplate code that the Developer must create for themselves. Instead, the Developer provides interfaces only that declare the intent for the content-related functionality. Based on these, and on class-path dependencies, Spring Content is then able to inject storage-specific implementations.
Important
|
This chapter explains the core concepts and interfaces for Spring Content. The examples in this chapter use Java configuration and code samples for the Spring Content S3 module. Adapt the Java configuration and the types to be extended to the equivalents for the particular Spring Content module that you are using. |
1.1. Core concepts
The central interfaces in the Spring Content are Store
, AssociativeStore
and ContentStore
. These interfaces
provide access to content streams through the standard Spring Resource API either directly or through association with
Spring Data entities.
1.1.1. Store
The simplest interface is the Store
interface. Essentially, it is a Spring ResourceLoader
that returns instances Spring Resource
. It is also generic allowing the Resource’s ID (or location) to be specified. All other Store interfaces extend from Store
.
public interface Store<SID extends Serializable> {
Resource getResource(SID id); (1)
}
-
Returns a Resource handle for the specified
id
For example, given a PictureStore
that extends Store
it is possible to store (retrieve and delete) pictures.
1.1.2. AssociativeStore
AssociativeStore
extends from Store
allowing Spring Resource’s to be associated with JPA Entities.
public interface AssociativeStore<SID extends Serializable> {
Resource getResource(SID id); (1)
void associate(S entity, PropertyPath path, SID id); (2)
void unassociate(S entity, PropertyPath path); (3)
Resource getResource(S entity, PropertyPath path); (4)
}
-
Returns a Resource handle for the specified
id
-
Associates the Resource
id
with the Entityentity
at the PropertyPathpath
-
Unassociates the Resource at the PropertyPath
path
from the entity -
Returns a handle for the associated Resource at PropertyPath
path
For example, given an Entity User
with Spring Content annotations, a UserRepository
and the PictureStore
this time extending AssociativeStore
it is possible to store and associate a profile picture for each user.
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String username;
@ContentId
private String profilePictureId;
@ContentLength
private Long profilePictureLength
}
public interface UserRepository extends JpaRepository<User, Long> {
}
public interface PictureStore extends AssociativeStore<User, String> {
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner demo(UserRespository repo, PictureStore store) {
return (args) -> {
// create a new user
User jbauer = new User("jbauer");
// store a picture
WritableResource r = (WritableResource)store.getResource("/some/picture.jpeg");
try (InputStream is = new FileInputStream("/tmp/jbauer.jpg")) {
try (OutputStream os = ((WritableResource)r).getOutputStream()) {
IOUtils.copyLarge(is, os);
}
}
// associate the Resource with the Entity
store.associate(jbauer, PropertyPath("profilePicture"), "/some/picture.jpeg");
// save the user
repository.save(jbauer);
};
}
}
1.2. ContentStore
ContentStore
extends AssociativeStore and provides a more convenient API for managing associated content based on java Stream
, rather than Resource
.
public interface ContentStore<E, CID extends Serializable> {
void setContent(E entity, InputStream content); (1)
InputStream getContent(E entity); (2)
void unsetContent(E entity); (3)
}
-
Stores content and associates it with
entity
-
Returns the content associated with
entity
-
Deletes content and unassociates it from
entity
The example above can be refactored as follows:
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String username;
@ContentId
private String profilePictureId;
@ContentLength
private Long profilePictureLength
}
public interface UserRepository extends JpaRepository<User, Long> {
}
public interface ProfilePictureStore extends ContentStore<User, String> {
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner demo(UserRepository repository, ProfilePictureStore store) {
return (args) -> {
// create a new user
User jbauer = new User("jbauer");
// store profile picture
store.setContent(jbauer, PropertyPath.from("profilePicture"), new FileInputStream("/tmp/jbauer.jpg"));
// save the user
repository.save(jbauer);
};
}
}
1.3. ReactiveContentStore
ReactiveContentStore
is an experimental Store that provides a reactive API for managing associated content based on
Mono and Flux reactive API.
public interface ReactiveContentStore<E, CID extends Serializable> {
Mono<S> setContent(S entity, PropertyPath path, long contentLen, Flux<ByteBuffer> buffer); (1)
Flux<ByteBuffer> getContentAsFlux(S entity, PropertyPath path); (2)
Mono<E> unsetContent(E entity); (3)
}
-
Stores content and associates it with
entity
-
Returns the content associated with
entity
-
Deletes content and unassociates it from
entity
The example above can be refactored as follows:
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String username;
@ContentId
private String profilePictureId;
@ContentLength
private Long profilePictureLength
}
public interface UserRepository extends JpaRepository<User, Long> {
}
public interface ProfilePictureStore extends ReactiveContentStore<User, String> {
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner demo(UserRepository repository, ProfilePictureStore store) {
return (args) -> {
// create a new user
User jbauer = new User("jbauer");
// store profile picture
FileInputStream fis = new FileInputStream("/tmp/jbauer.jpg");
int len = fis.available();
ByteBuffer byteBuffer = ByteBuffer.allocate(len);
Channels.newChannel(fis).read(byteBuffer);
store.setContent(jbauer, PropertyPath.from("profilePicture"), len, Flux.just(byteBuffer)))
.doOnSuccess(updatedJbauer -> {
// save the user
repository.save(updatedJbauer).block(Duration.ofSeconds(10));
}).block(Duration.ofSeconds(10));
};
}
}
Currently, S3 is the only storage module that supports this experimental API.
1.3.1. Content Properties
As we can see above content is "associated" by adding additional metadata about the content to the Entity. This additional metadata is annotated with Spring Content annotations. There are several. The only mandatory annotation is @ContentId
. Other optional annotations include @ContentLength
, @MimeType
and @OriginalFileName
. These may be added to your entities when you need to capture this additional infomation about your associated content.
When adding these optional annotations it is highly recommended that you correlate the field’s name creating a "content property". This allows for multiple pieces of content to be associated with the same entity, as shown in the following example. When associating a single piece of content this is not necessary but still recommended.
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String username;
@ContentId
private String profilePictureId; (1)
@ContentLength
private Long profilePictureLength
@MimeType
private String profilePictureType;
@OriginalFileName
private String profilePictureName;
@ContentId
private String avatarId; (2)
@ContentLength
private Long avatarLength
@MimeType
private String avatarType;
}
-
Content property "profilePicture" with id, length, type and original filename
-
Content property "avatar" with id, length and type
When modeled thus these can then be managed as follows:
InputStream profilePicture = store.getContent(user, PropertyPath.from("profilePicture"));
store.setContent(user, PropertyPath.from("avatar"), avatarStream);
1.3.2. Nested Content Properties
If desired content properties can also be nested, as the following JPA example shows:
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String username;
private @Embedded Images images = new Images();
}
@Embeddable
public class Images {
@ContentId
private String profilePictureId;
@ContentLength
private Long profilePictureLength
@MimeType
private String profilePictureType;
@OriginalFileName
private String profileName;
@ContentId
private String avatarId;
@ContentLength
private Long avatarLength
@MimeType
private String avatarType;
}
These can then be managed with forward slash (/
) separated property paths:
InputStream profilePicture = store.getContent(user, PropertyPath.from("images/profilePicture"));
store.setContent(user, PropertyPath.from("images/avatar"), avatarStream);
1.3.3. Using Stores with Multiple Spring Content Storage Modules
Using a single Spring Content storage module in your application keeps things simple because all Storage beans will use to that one Spring Content storage module as their implementation. Sometimes, applications require more than one Spring Content storage module. In such cases, a store definition must distinguish between storage technologies by extending one of the module-specific signature Store interfaces.
See Signature Types for the signature types for the storage modules you are using.
Manual Storage Override
Because Spring Content provides an abstraction over storage it is also common to use one storage module for testing but another
for production. For these cases it is possible to again include multiple Spring Content storage modules,
but use generic Store interfaces, rather than signature types, and instead specify the spring.content.storage.type.default=<storage_module_id>
property to manually set the storage implementation to be injected into your Storage beans.
1.3.4. Events
Spring Content emits twelve events. Roughly speaking one for each Store method. They are:
-
BeforeGetResourceEvent
-
AfterGetResourceEvent
-
BeforeAssociateEvent
-
AfterAssociateEvent
-
BeforeUnassociateEvent
-
AfterUnassociateEvent
-
BeforeSetContent
-
AfterSetContent
-
BeforeGetContent
-
AfterGetContent
-
BeforeUnsetContent
-
AfterUnsetContent
Writing an ApplicationListener
If you wish to extend Spring Content’s functionality you can subclass the abstract class AbstractStoreEventListener
and
override the methods that you are interested in. When these events occur your handlers will be called.
There are two variants of each event handler. The first takes the entity with with the content is associated and is the source of the event. The second takes the event object. The latter can be useful, especially for events related to Store methods that return results to the caller.
public class ExampleEventListener extends AbstractContentRepositoryEventListener {
@Override
public void onAfterSetContent(Object entity) {
...logic to inspect and handle the entity and it's content after it is stored
}
@Override
public void onBeforeGetContent(BeforeGetContentEvent event) {
...logic to inspect and handle the entity and it's content before it is fetched
}
}
The down-side of this approach is that it does not filter events based on Entity.
Writing an Annotated StoreEventHandler
Another approach is to use an annotated handler, which does filter events based on Entity.
To declare a handler, create a POJO and annotate it as @StoreEventHandler
. This tells
Spring Content that this class needs to be inspected for handler methods. It
iterates over the class’s methods and looks for annotations that correspond to the
event. There are twelve handler annotations:
-
HandleBeforeGetResource
-
HandleAfterGetResource
-
HandleBeforeAssociate
-
HandleAfterAssociate
-
HandleBeforeUnassociate
-
HandleAfterUnassociate
-
HandleBeforeSetContent
-
HandleAfterSetContent
-
HandleBeforeGetContent
-
HandleAfterGetContent
-
HandleBeforeUnsetContent
-
HandleAfterUnsetContent
@StoreEventHandler
public class ExampleAnnotatedEventListener {
@HandleAfterSetContent
public void handleAfterSetContent(SopDocument doc) {
...type-safe handling logic for SopDocument's and their content after it is stored
}
@HandleBeforeGetContent
public void onBeforeGetContent(Product product) {
...type-safe handling logic for Product's and their content before it is fetched
}
}
These handlers will be called only when the event originates from a matching entity.
As with the ApplicationListener event handler in some cases it is useful to handle the event. For example, when Store methods returns results to the caller.
@StoreEventHandler
public class ExampleAnnotatedEventListener {
@HandleAfterSetContent
public void handleAfterGetResource(AfterGetResourceEvent event) {
SopDocument doc = event.getSource();
Resource resourceToBeReturned = event.getResult();
...code that manipulates the resource being returned...
}
}
To register your event handler, either mark the class with one of Spring’s @Component stereotypes so it can be picked up by @SpringBootApplication or @ComponentScan. Or declare an instance of your annotated bean in your ApplicationContext.
@Configuration
public class ContentStoreConfiguration {
@Bean
ExampeAnnotatedEventHandler exampleEventHandler() {
return new ExampeAnnotatedEventHandler();
}
}
1.3.5. Searchable Stores
Applications that handle documents and other media usually have search capabilities allowing relevant content to be found by looking inside of it for keywords or phrases, so called full-text search.
Spring Content is able to support this capability with it’s Searchable<CID>
interface.
public interface Searchable<CID> {
Iterable<T> search(String queryString);
}
Any Store interface can be made to extend Searchable<CID>
in order to extend its capabilities to include the
search(String queryString)
method. For example:
public interface DocumentContentStore extends ContentStore<Document, UUID>, Searchable<UUID> {
}
...
@Autowired
private DocumentContentStore store;
Iterable<UUID> = store.search("to be or not to be");
For search
to return actual results full-text indexing must be enabled. See Fulltext Indexing and Searching
for more information on how to do this.
1.3.6. Renderable Stores
Applications that handle files and other media usually also have rendition capabilities allowing content to be transformed from one format to another.
Content stores can therefore optionally also be given rendition capabilities by extending the Renderable<E>
interface.
public interface Renderable<E> {
InputStream getRendition(E entity, String mimeType);
}
Returns a mimeType
rendition of the content associated with entity
.
Renditions must be enabled and renderers provided. See Renditions for more information on how to do this.
1.4. Creating Content Store Instances
To use these core concepts:
-
Define a Spring Data entity and give it’s instances the ability to be associated with content by adding
@ContentId
and@ContentLength
annotations@Entity public class SopDocument { private @Id @GeneratedValue Long id; private String title; private String[] authors, keywords; // Spring Content managed attribute private @ContentId UUID contentId; private @ContentLength Long contentLen; }
-
Define an interface extending Spring Data’s
CrudRepository
and type it to the domain and ID classes.public interface SopDocumentRepository extends CrudRepository<SopDocument, Long> { }
-
Define another interface extending
ContentStore
and type it to the domain and@ContentId
class.public interface SopDocumentContentStore extends ContentStore<SopDocument, UUID> { }
-
Optionally, make it extend
Searchable
public interface SopDocumentContentStore extends ContentStore<SopDocument, UUID>, Searchable<UUID> { }
-
Optionally, make it extend
Renderable
public interface SopDocumentContentStore extends ContentStore<SopDocument, UUID>, Renderable<SopDocument> { }
-
Set up Spring to create proxy instances for these two interfaces using JavaConfig:
@EnableJpaRepositories @EnableS3Stores class Config {}
NoteThe JPA and S3 namespaces are used in this example. If you are using the repository and content store abstractions for other databases and stores, you need to change this to the appropriate namespace declaration for your store module. -
Inject the repositories and use them
@Component public class SomeClass { @Autowired private SopDocumentRepository repo; @Autowired private SopDocumentContentStore contentStore; public void doSomething() { SopDocument doc = new SopDocument(); doc.setTitle("example"); contentStore.setContent(doc, new ByteArrayInputStream("some interesting content".getBytes())); # (1) doc.save(); ... InputStream content = contentStore.getContent(sopDocument); ... List<SopDocument> docs = doc.findAllByContentId(contentStore.findKeyword("interesting")); ... } }
-
Spring Content will update the
@ContentId
and@ContentLength
fields
-
1.5. Patterns of Content Association
Content can be associated with a Spring Data Entity in several ways.
1.5.1. Entity Association
The simplest, allowing you to associate one Entity with one Resource, is to decorate your Spring Data Entity with the Spring Content attributes.
The following example shows a Resource associated with an Entity Dvd
.
@Entity
public class Dvd {
private @Id @GeneratedValue Long id;
private String title;
// Spring Content managed attributes
private @ContentId UUID contentId;
private @ContentLength Long contentLen;
...
}
public interface DvdRepository extends CrudRepository<Dvd, Long> {}
public interface DvdStore extends ContentStore<Dvd, UUID> {}
1.5.2. Property Association
Sometimes you might want to associate multiple different Resources with an Entity. To do this it is also possible to associate Resources with one or more Entity properties.
The following example shows two Resources associated with a Dvd
entity. The first Resource is the Dvd’s cover Image and the second is the Dvd’s Stream.
@Entity
public class Dvd {
private @Id @GeneratedValue Long id;
private String title;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "image_id")
private Image image;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "stream_id")
private Stream stream;
...
}
@Entity
public class Image {
// Spring Data managed attribute
private @Id @GeneratedValue Long id;
@OneToOne
private Dvd dvd;
// Spring Content managed attributes
private @ContentId UUID contentId;
private @ContentLength Long contentLen;
}
@Entity
public class Stream {
// Spring Data managed attribute
private @Id @GeneratedValue Long id;
@OneToOne
private Dvd dvd;
// Spring Content managed attributes
private @ContentId UUID contentId;
private @ContentLength Long contentLen;
}
public interface DvdRepository extends CrudRepository<Dvd, Long> {}
public interface ImageStore extends ContentStore<Image, UUID> {}
public interface StreamStore extends ContentStore<Stream, UUID> {}
Note how the Content attributes are placed on each property object of on the Entity itself.
When using JPA with a relational database these are typically (but not always) also Entity associations. However when using NoSQL databases like MongoDB that are capable of storing hierarchical data they are true property associations.
Property Collection Associations
In addition to associating many different types of Resource with a single Entity. It is also possible to associate one Entity with many Resources using a java.util.Collection
property, as the following example shows.
@Entity
public class Dvd {
private @Id @GeneratedValue Long id;
private String title;
@OneToMany
@JoinColumn(name = "chapter_id")
private List<Chapter> chapters;
...
}
@Entity
public class Chapter {
// Spring Data managed attribute
private @Id @GeneratedValue Long id;
// Spring Content managed attributes
private @ContentId UUID contentId;
private @ContentLength Long contentLen;
}
public interface DvdRepository extends CrudRepository<Dvd, Long> {}
public interface ChapterStore extends ContentStore<Chapter, UUID> {}
2. CMIS Integration
2.1. Maven Central Coordinates
The maven coordinates for this Spring Content library are as follows:
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-cmis</artifactId>
</dependency>
As it is usual to use several Spring Content libraries together importing the bom is recommended:
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-bom</artifactId>
<version>${spring-content-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Spring Content CMIS supports the CMIS browser (JSON) bindings.
These bindings can be used as an alternative to the endpoints exported by Spring Content REST. This may be desirable for document management use cases or for migration away from traditional document management systems.
Spring Content CMIS provides a set of annotations that can be placed on an existing Spring Content domain model to map its classes onto the CMIS domain model in order to export its entities via the CMIS browser bindings.
2.2. CMIS Domain Model Mapping
2.2.1. CmisRepository
At the root of the CMIS model and services is a repository, which is an instance of the content management system and its store of metadata, content, and indexes.
The repository is the end point to which all requests are directed and is the root path of the resources being
addressed in CMIS. With Spring Content CMIS, the repository is defined using the @EnableCmis
annotation. Each Spring
Content CMIS application therefore exports, at most, one CMIS Repository.
2.2.2. CmisObject
The Spring Data/Content domain model can be mapped to the CMIS domain model using two annotations; @CmisDocument
and
@CmisFolder
. These annotations should be placed on Spring Data entity types with each being placed on a single entity
type.
Both @CmisDocument
and @CmisFolder
inherit a common set of automatically mapped properties from a logical
CmisObject
, as follows:
Field | CMIS field |
---|---|
|
|
|
|
|
|
|
|
|
|
In addition, the @CmisName
annotation must be present to identify the cmis:name
field and the @CmisDescription
annotation may be present to identify a cmis:description
field.
2.2.3. CmisDocument
Entity types mapped to @CmisDocument
that also have a ContentStore
will also have additional content stream
properties and service for accessing the binary information that is the document.
Field | CMIS field |
---|---|
|
|
|
|
|
|
Entity types mapped to @CmisDocument
that are managed with a Repository
that extends LockingAndVersioningRepository
have additional versioning properties and service.
Field | CMIS field |
---|---|
|
|
|
|
|
|
|
|
Other version-related cmis properties, like cmis:isLatestVersion
for example, are computed at runtime. Private working
copies (CMIS 1.1) are supported by default.
Currently, renditions and fulltext indexing/query are not supported.
2.2.4. CmisFolder
With a CMIS repository, Document objects can live in one, or more, folders. A folder can also exist in other folders creating a hierarchy.
Spring Content CMIS supports folder hierarchy by mapping an entity type to @CmisFolder
and by mapping the parent/child
relationship using the @CmisReference
annotation. However, Spring Content CMIS support single-filing of documents and
folders only. In JPA terms, it supports a bi-directional @OneToMany
relationship between the folder and the document
entity.
2.2.5. CmisNavigationService
These two annotations are sufficient to achieve a working folder hierarchy. However, it is also possible to provide a
CmisNavigationService
bean through configuration in order to provide a more efficient implementation for
navigation. This is recommended.
2.3. CMIS Workbench
A good option for testing is to use the CMIS Workbench. If you wish to use this client application in your Spring application ensure you disable Spring’s hidden method filter otherwise it attempts to parse POSTed requests from the CMIS Workbench client causing the request to fail.
Put this line in your application.properties
:
spring.mvc.hiddenmethod.filter.enabled=false
2.4. Using Spring Content CMIS
2.4.1. Enabling the CMIS Integration
To enable CMIS, place a @EnableCmis
annotation on a suitable @Configuration
class.
@Configuration
@EnableCmis(basePackages = "examples.acme", (1)
id = "1", (2)
name = "Example CMIS Integration",
description = "Spring Content CMIS Integration Example",
vendorName = "Spring Content",
productName = "Spring Content Integration CMIS",
productVersion = "1.0.0")
public static class ApplicationConfig {
...beans...
}
-
The packages to search for Spring Data Repositories and Spring Content Stores with entities that carry
@CmisDocument
and@CmisFolder
annotations -
The id and other attribute of the Cmis Repository to export
2.4.2. Domain Model Mapping
CmisObject
In this example we have a superclass entity for the common properties shared between the @CmisDocument
and
@CmisFolder
.
@Entity
@EntityListeners(AuditingEntityListener.class)
@Inheritance(strategy = InheritanceType.JOINED)
@NoArgsConstructor
@Getter
@Setter
public class BaseObject {
@javax.persistence.Id (1)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
@CmisName (2)
private String name;
@CmisDescription (3)
private String description;
@CreatedBy (4)
private String createdBy;
@CreatedDate
private Long createdDate;
@LastModifiedBy
private String lastModifiedBy;
@LastModifiedDate
private Long lastModifiedDate;
@Version
private Long vstamp;
@CmisReference(type = CmisReferenceType.Parent) (5)
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Folder parent;
public BaseObject(String name) {
this.name = name;
}
}
-
Mapped to
cmis:objectId
-
Mapped to
cmis:name
-
Mapped to
cmis:description
-
Mapped to
cmis:createdBy
,cmis:creationDate
,cmis:lastModifiedBy
andcmis:lastModificationDate
-
Maps the child end of a bi-directional one to many parent/child relationship
CmisFolder
Folder
extends BaseObject
and maps the only remaining folder-related attribute, the child end of the parent/child
relationship.
@Entity
@NoArgsConstructor
@Getter
@Setter
@CmisFolder (1)
public class Folder extends BaseObject {
@CmisReference(type= CmisReferenceType.Child) (2)
@OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", cascade = CascadeType.ALL)
private Collection<BaseObject> children;
public Folder(String name) {
super(name);
}
}
public interface FolderRepository extends JpaRepository<Folder, Long> {
List<Folder> findAllByParent(Folder parent);
}
-
@CmisFolder
indicating this should be exported as a cmis:folder type -
Maps the parent end of a bi-directional one to many parent/child relationship
CmisDocument
Document
also extends BaseObject
and also maps the content stream and version attributes.
@Entity
@NoArgsConstructor
@Getter
@Setter
@CmisDocument (1)
public class Document extends BaseObject {
@ContentId (2)
private UUID contentId;
@ContentLength (3)
private Long contentLen;
@MimeType (4)
private String mimeType;
@LockOwner (5)
private String lockOwner;
@AncestorId
private Long ancestorId;
@AncestorRootId (6)
private Long ancestralRootId;
@SuccessorId
private Long successorId;
@VersionNumber
private String versionNumber = "0.0"; (7)
@VersionLabel
private String versionLabel; (8)
public Document(String name) {
super(name);
}
public Document(Document doc) {
this.setName(doc.getName());
this.setDescription(doc.getDescription());
this.setParent(doc.getParent());
}
}
public interface DocumentRepository extends JpaRepository<Document, Long>, LockingAndVersioningRepository<Document, Long> {
List<Document> findAllByParent(Folder parent);
}
public interface DocumentStorage extends ContentStore<Document, UUID> {
//
}
-
@CmisDocument
indicating this should be exported as a cmis:document type -
Mapped to
cmis:contentStreamId
-
Mapped to
cmis:contentStreamLength
-
Mapped to
cmis:contentStreamMimeType
-
Mapped to
cmis:versionSeriesCheckedOutBy
-
Mapped to
cmis:versionSeriesId
-
Mapped to
cmis:versionLabel
-
Mapped to
cmis:checkinComment
CmisNavigationService
Optionally, you may also configure a CmisNavigationService
bean in order to provide a more efficient implementation
for navigation.
@Bean
public CmisNavigationService cmisNavigationService(FolderRepository folders, DocumentRepository docs) {
return new CmisNavigationService<Folder>() {
@Override
public List getChildren(Folder parent) {
List<Object> children = new ArrayList<>();
List<Folder> folderChildern = folders.findAllByParent(parent);
List<Document> documentChildren = docs.findAllByParent(parent);
children.addAll(folderChildern);
children.addAll(documentChildren);
return children;
}
};
}
For more information you can refer to our github example project here.