Make sure you have completed prerequisites (see Prerequisites) and created a starter project (see Create a starting point Spring project).
-
Add the following dependency in the Maven project file (i.e.
pom.xml):
<dependencies> <!-- Alfresco Java SDK 6 Java ReST API wrapper Spring Boot Starter --> <dependency> <groupId>org.alfresco</groupId> <artifactId>alfresco-acs-java-rest-api-spring-boot-starter</artifactId> <version>6.2.0</version> </dependency> </dependencies> - Remove the default Spring Boot starter dependency (i.e. <artifactId>spring-boot-starter</artifactId>).
-
Modify the contents of the Spring Boot application class
(org/alfresco/tutorial/sdk5demo/Sdk5DemoApplication.java)
by adding the following
com.fasterxml.jackson.databind.ObjectMapper. This is
required for deserializing dates:
package org.alfresco.tutorial.sdk5demo; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import javax.annotation.PostConstruct; @SpringBootApplication public class Sdk5DemoApplication { @Autowired private ObjectMapper objectMapper; @PostConstruct public void setUp() { objectMapper.registerModule(new JavaTimeModule()); } public static void main(String[] args) { SpringApplication.run(Sdk5DemoApplication.class, args); } } -
Test it:
$ mvn clean package -Dlicense.skip=true [INFO] Scanning for projects... ... $ java -jar target/rest-api-0.0.1-SNAPSHOT.jar . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.4.2) 2021-04-07 14:31:35.599 INFO 53273 --- [ main] o.a.tutorial.restapi.RestApiApplication : Starting RestApiApplication v0.0.1-SNAPSHOT using Java 11.0.2 on MBP512-MBERGLJUNG-0917 with PID 53273 (/Users/mbergljung/IDEAProjects/docs-new/sdk5/sdk5-rest-api-java-wrapper-sample/target/rest-api-0.0.1-SNAPSHOT.jar started by mbergljung in /Users/mbergljung/IDEAProjects/docs-new/sdk5/sdk5-rest-api-java-wrapper-sample) 2021-04-07 14:31:35.605 INFO 53273 --- [ main] o.a.tutorial.restapi.RestApiApplication : No active profile set, falling back to default profiles: default 2021-04-07 14:31:36.832 INFO 53273 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=55661aff-d1dc-3db8-94e2-cf0514d3118c 2021-04-07 14:31:37.443 INFO 53273 --- [ main] o.a.tutorial.restapi.RestApiApplication : Started RestApiApplication in 2.832 seconds (JVM running for 3.563)
Looks ready for some ReST API code.
-
Now, start adding your ReST API code, let’s create a command line client that
can be used to create sites, create folders, create files, and to search. First
update the Spring Boot application class to look like follows, making use of the
org.springframework.boot.CommandLineRunner:
package org.alfresco.tutorial.restapi; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class RestApiApplication implements CommandLineRunner { private static final Logger LOGGER = LoggerFactory.getLogger(RestApiApplication.class); @Autowired CreateSiteCmd createSiteCmd; @Autowired CreateFolderCmd createFolderCmd; @Autowired CreateFileCmd createFileCmd; @Autowired SearchCmd searchCmd; public static void main(String[] args) { SpringApplication.run(RestApiApplication.class, args); } public void run(String... args) throws Exception { for (int i = 0; i < args.length; ++i) { LOGGER.info("args[{}]: {}", i, args[i]); } String command = args[0]; switch (command) { case "create-site": createSiteCmd.execute(args[1]); break; case "create-folder": // siteId, folderName createFolderCmd.execute(args[1], args[2]); break; case "create-file": // parentFolderNodeId, filename createFileCmd.execute(args[1], args[2]); break; case "search": // siteId, term searchCmd.execute(args[1], args[2]); break; default: LOGGER.error("Command {} is not available", command); } } }This command line runner uses a number of beans to support creating different things in the Alfresco Repository, such as sites and folders.
-
Start by creating the CreateSiteCmd bean that will facilitate
creating sites via the ReST API Java wrapper, in the same package as the Spring
Boot application class create the following class:
package org.alfresco.tutorial.restapi; import org.alfresco.core.handler.SitesApi; import org.alfresco.core.model.Site; import org.alfresco.core.model.SiteBodyCreate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Objects; @Component public class CreateSiteCmd { static final Logger LOGGER = LoggerFactory.getLogger(CreateSiteCmd.class); @Autowired SitesApi sitesApi; public void execute(String siteId) throws IOException { Site site = Objects.requireNonNull(sitesApi.createSite( new SiteBodyCreate() .id(siteId) .title("title-" + siteId) .description("description-" + siteId) .visibility(SiteBodyCreate.VisibilityEnum.PUBLIC), null, null, null).getBody()).getEntry(); LOGGER.info("Created site: {}", site); } } - To use one of the ReST API Java wrapper services, such as SitesApi , auto wire it into the component as in the above class. Creating stuff in the repository usually mean making a HTTP POST in the background. In these cases there is always a body class that we can use to fill in POST data, such as SiteBody in this case. A successful API call will return a populated result object called Site.
-
In a similar way we add the other three command beans in the same directory as
follows, starting with the CreateFolderCmd:
package org.alfresco.tutorial.restapi; import org.alfresco.core.handler.NodesApi; import org.alfresco.core.handler.SitesApi; import org.alfresco.core.model.Node; import org.alfresco.core.model.NodeBodyCreate; import org.alfresco.core.model.SiteContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Objects; @Component public class CreateFolderCmd { static final Logger LOGGER = LoggerFactory.getLogger(CreateFolderCmd.class); @Autowired SitesApi sitesApi; @Autowired NodesApi nodesApi; public void execute(String siteId, String folderName) throws IOException { SiteContainer docLibContainer = Objects.requireNonNull(sitesApi.getSiteContainer(siteId, "documentLibrary", null).getBody()).getEntry(); LOGGER.info("Creating folder in site DocumentLibrary folder Node ID: {}", docLibContainer.getId()); Node folderNode = Objects.requireNonNull(nodesApi.createNode(docLibContainer.getId(), new NodeBodyCreate() .nodeType("cm:folder") .name(folderName), null, null, null, null, null).getBody()).getEntry(); LOGGER.info("Created folder: {}", folderNode.toString()); } }The NodesApi is one of the main APIs that we will use a lot to manipulate folders and files. We use it here to create a folder node in the site’s document library.
-
Next we create the CreateFileCmd as follows in the same
directory:
package org.alfresco.tutorial.restapi; import org.alfresco.core.handler.NodesApi; import org.alfresco.core.model.Node; import org.alfresco.core.model.NodeBodyCreate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Objects; @Component public class CreateFileCmd { static final Logger LOGGER = LoggerFactory.getLogger(CreateFileCmd.class); @Autowired NodesApi nodesApi; public void execute(String parentFolderId, String fileName) throws IOException { // Get the parent folder where file should be stored Node parentFolderNode = Objects.requireNonNull(nodesApi.getNode(parentFolderId, null, null, null).getBody()).getEntry(); LOGGER.info("Got parent folder node: {}", parentFolderNode.toString()); // Create the file node metadata Node fileNode = Objects.requireNonNull(nodesApi.createNode(parentFolderNode.getId(), new NodeBodyCreate().nodeType("cm:content").name(fileName), null, null, null, null, null).getBody()).getEntry(); // Add the file node content Node updatedFileNode = Objects.requireNonNull(nodesApi.updateNodeContent(fileNode.getId(), "Some text for this file...".getBytes(), true, null, null, null, null).getBody()).getEntry(); LOGGER.info("Created file with content: {}", updatedFileNode.toString()); } }You might notice that it requires two calls to create a file with content. The ReST API does provide a way to do this with one call as can be seen in the Upload a file section in the Develop chapter of the Alfresco Content Services documentation. However, the generated Java wrapping classes does not yet provide functionality for this (it is scheduled to be supported in a future version of the SDK).
-
Add also the final SearchCmd class as follows:
package org.alfresco.tutorial.restapi; import org.alfresco.search.handler.SearchApi; import org.alfresco.search.model.RequestQuery; import org.alfresco.search.model.ResultSetPaging; import org.alfresco.search.model.SearchRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class SearchCmd { static final Logger LOGGER = LoggerFactory.getLogger(SearchCmd.class); @Autowired SearchApi searchApi; public void execute(String siteId, String term) throws IOException { ResponseEntity<ResultSetPaging> result = searchApi.search(new SearchRequest() .query(new RequestQuery() .language(RequestQuery.LanguageEnum.AFTS) .query("(SITE:\"" + siteId + "\" AND TEXT:\"" + term + "\" )"))); LOGGER.info("Search result: {}", result.getBody().getList().getEntries()); } } -
Now, stop and build it again:
$ ^C ... $ mvn clean package -Dlicense.skip=true ...
-
Create an Alfresco Share site with id test as follows:
$ java -jar target/rest-api-0.0.1-SNAPSHOT.jar create-site test ... 2021-04-08 13:16:49.239 INFO 62074 --- [ main] o.a.tutorial.restapi.RestApiApplication : args[0]: create-site 2021-04-08 13:16:49.241 INFO 62074 --- [ main] o.a.tutorial.restapi.RestApiApplication : args[1]: test 2021-04-08 13:16:52.989 INFO 62074 --- [ main] o.a.tutorial.restapi.CreateSiteCmd : Created site: class Site { id: test guid: 59dc57a1-ad07-4715-8844-005cc7fc59d7 title: title-test description: description-test visibility: PUBLIC preset: site-dashboard role: SiteManager } -
Then create a folder called folder1 in the site with id
test:
$ java -jar target/rest-api-0.0.1-SNAPSHOT.jar create-folder test folder1 ... 2021-04-08 13:19:23.264 INFO 62106 --- [ main] o.a.tutorial.restapi.RestApiApplication : args[0]: create-folder 2021-04-08 13:19:23.266 INFO 62106 --- [ main] o.a.tutorial.restapi.RestApiApplication : args[1]: test 2021-04-08 13:19:23.267 INFO 62106 --- [ main] o.a.tutorial.restapi.RestApiApplication : args[2]: folder1 2021-04-08 13:19:23.560 INFO 62106 --- [ main] o.a.tutorial.restapi.CreateFolderCmd : Creating folder in site DocumentLibrary folder Node ID: aa02f5eb-f45d-4ab4-bf21-9eeb8c243d51 2021-04-08 13:19:24.166 INFO 62106 --- [ main] o.a.tutorial.restapi.CreateFolderCmd : Created folder: class Node { id: 3e16d079-2fdc-4d64-ad76-c65c233165f4 name: folder1 nodeType: cm:folder isFolder: true isFile: false isLocked: false modifiedAt: 2021-04-08T12:19:23.876Z modifiedByUser: class UserInfo { displayName: Administrator id: admin } createdAt: 2021-04-08T12:19:23.876Z createdByUser: class UserInfo { displayName: Administrator id: admin } parentId: aa02f5eb-f45d-4ab4-bf21-9eeb8c243d51 isLink: null isFavorite: null content: null aspectNames: [cm:auditable] properties: null allowableOperations: null path: null permissions: null definition: null } -
Create a file called somefile.txt in the folder called
folder1 (3e16d079-2fdc-4d64-ad76-c65c233165f4):
$ java -jar target/rest-api-0.0.1-SNAPSHOT.jar create-file 3e16d079-2fdc-4d64-ad76-c65c233165f4 somefile.txt ... 2021-04-08 13:21:55.972 INFO 62152 --- [ main] o.a.tutorial.restapi.RestApiApplication : args[0]: create-file 2021-04-08 13:21:55.973 INFO 62152 --- [ main] o.a.tutorial.restapi.RestApiApplication : args[1]: 3e16d079-2fdc-4d64-ad76-c65c233165f4 2021-04-08 13:21:55.973 INFO 62152 --- [ main] o.a.tutorial.restapi.RestApiApplication : args[2]: somefile.txt 2021-04-08 13:21:56.211 INFO 62152 --- [ main] o.a.tutorial.restapi.CreateFileCmd : Got parent folder node: class Node { id: 3e16d079-2fdc-4d64-ad76-c65c233165f4 name: folder1 nodeType: cm:folder isFolder: true isFile: false isLocked: false modifiedAt: 2021-04-08T12:19:23.876Z modifiedByUser: class UserInfo { displayName: Administrator id: admin } createdAt: 2021-04-08T12:19:23.876Z createdByUser: class UserInfo { displayName: Administrator id: admin } parentId: aa02f5eb-f45d-4ab4-bf21-9eeb8c243d51 isLink: null isFavorite: null content: null aspectNames: [cm:auditable] properties: null allowableOperations: null path: null permissions: null definition: null } 2021-04-08 13:21:56.896 INFO 62152 --- [ main] o.a.tutorial.restapi.CreateFileCmd : Created file with content: class Node { id: 1187b449-258e-4843-997f-991b7995b665 name: somefile.txt nodeType: cm:content isFolder: false isFile: true isLocked: false modifiedAt: 2021-04-08T12:21:56.697Z modifiedByUser: class UserInfo { displayName: Administrator id: admin } createdAt: 2021-04-08T12:21:56.265Z createdByUser: class UserInfo { displayName: Administrator id: admin } parentId: 3e16d079-2fdc-4d64-ad76-c65c233165f4 isLink: null isFavorite: null content: class ContentInfo { mimeType: text/plain mimeTypeName: Plain Text sizeInBytes: 26 encoding: ISO-8859-1 } aspectNames: [cm:versionable, cm:auditable] properties: {cm:versionLabel=1.0, cm:versionType=MAJOR} allowableOperations: null path: null permissions: null definition: null } -
Finally, search for content matching text file in site with id
test:
$ java -jar target/rest-api-0.0.1-SNAPSHOT.jar search test file ... 2021-04-08 14:40:51.379 INFO 63261 --- [ main] o.a.tutorial.restapi.RestApiApplication : args[0]: search 2021-04-08 14:40:51.381 INFO 63261 --- [ main] o.a.tutorial.restapi.RestApiApplication : args[1]: test 2021-04-08 14:40:51.381 INFO 63261 --- [ main] o.a.tutorial.restapi.RestApiApplication : args[2]: file 2021-04-08 14:40:52.493 INFO 63261 --- [ main] org.alfresco.tutorial.restapi.SearchCmd : Search result: [class ResultSetRowEntry { entry: class ResultNode { id: 1187b449-258e-4843-997f-991b7995b665 name: somefile.txt nodeType: cm:content isFolder: false isFile: true isLocked: false modifiedAt: 2021-04-08T12:21:59.077Z modifiedByUser: class UserInfo { displayName: Administrator id: admin } createdAt: 2021-04-08T12:21:56.265Z createdByUser: class UserInfo { displayName: Administrator id: admin } parentId: 3e16d079-2fdc-4d64-ad76-c65c233165f4 isLink: null content: class ContentInfo { mimeType: text/plain mimeTypeName: Plain Text sizeInBytes: 26 encoding: ISO-8859-1 mimeTypeGroup: null } aspectNames: null properties: null allowableOperations: null path: null search: class SearchEntry { score: 1.0 highlight: null } archivedByUser: null archivedAt: null versionLabel: null versionComment: null } }]This sample has shown us that it’s easy to interact with the Alfresco Repository from a Java client with the help of SDK Java ReST API services.
For more information see the ReST API Java wrapper extension point section in the Alfresco Content Services documentation.