/*
* Copyright 2012-2014 Brian Matthews
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.btmatthews.leabharlann.service.impl;
import com.btmatthews.atlas.jcr.JCRAccessor;
import com.btmatthews.atlas.jcr.NodeCallback;
import com.btmatthews.atlas.jcr.SessionCallback;
import com.btmatthews.leabharlann.domain.File;
import com.btmatthews.leabharlann.domain.FileContent;
import com.btmatthews.leabharlann.domain.Folder;
import com.btmatthews.leabharlann.domain.Workspace;
import com.btmatthews.leabharlann.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.jcr.*;
import javax.jcr.nodetype.NodeType;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/**
* An implementation of the {@link LibraryService} that accesses and manipulates the contents of a Java Content
* Repository using a {@link JCRAccessor} API object.
*
* @author <a href="mailto:brian@btmatthews.com">Brian Matthews</a>
* @since 1.0.0
*/
@Service
public class LibraryServiceImpl implements LibraryService {
/**
* The callback used to transform repository nodes into {@link Folder} descriptors.
*/
private FolderNodeCallback folderNodeCallback = new FolderNodeCallback();
/**
* The callback used to transform repository nodes into {@link File} descriptors.
*/
private FileNodeCallback fileNodeCallback = new FileNodeCallback();
/**
* The {@link JCRAccessor} API object used to access the Java Content Repository.
*/
private final JCRAccessor jcrAccessor;
/**
* Used to determine the file's content (MIME) type.
*/
private final TypeDetector typeDetector;
/**
* Used to determine the file's content encoding.
*/
private final EncodingDetector encodingDetector;
/**
* Inject the {@link JCRAccessor} API object used to access the Java Content repository.
*
* @param jcrAccessor The {@link JCRAccessor} API object.
* @param typeDetector Used to determine the file's content (MIME) type.
* @param encodingDetector Used to determine the file's content encoding.
*/
@Autowired
public LibraryServiceImpl(final JCRAccessor jcrAccessor,
final TypeDetector typeDetector,
final EncodingDetector encodingDetector) {
this.jcrAccessor = jcrAccessor;
this.typeDetector = typeDetector;
this.encodingDetector = encodingDetector;
}
/**
* Get the {@link Workspace} descriptor for the workspace named {@code workspaceName}.
*
* @param workspaceName The workspace name.
* @return The {@link Workspace} descriptor.
*/
public Workspace getWorkspace(final String workspaceName) {
return new WorkspaceImpl(workspaceName);
}
/**
* Get a list of {@link Workspace} descriptors for all the workspaces in the Java Content Repository.
*
* @return A list of {@link Workspace} descriptors.
*/
public List<Workspace> getWorkspaces() {
return jcrAccessor.withSession("default", new SessionCallback<List<Workspace>>() {
@Override
public List<Workspace> doInSession(Session session) throws RepositoryException {
final List<Workspace> workspaces = new ArrayList<Workspace>();
final javax.jcr.Workspace workspace = session.getWorkspace();
final String[] workspaceNames = workspace.getAccessibleWorkspaceNames();
for (final String workspaceName : workspaceNames) {
final Workspace workspaceFolder = getWorkspace(workspaceName);
workspaces.add(workspaceFolder);
}
return workspaces;
}
});
}
/**
* Get the {@link Folder} descriptor for the root folder of the workspace described by {@code workspace}.
*
* @param workspace The {@link Workspace} descriptor.
* @return The {@link Folder} descriptor.
*/
public Folder getRoot(final Workspace workspace) {
return jcrAccessor.withRoot(workspace.getName(), folderNodeCallback);
}
/**
* Create a new folder named {@code name} in the workspace described by {@code workspace} under the folder
* identified by {@code parentId}.
*
* @param workspace The {@link Workspace} descriptor.
* @param parentId The node identifier of the parent folder.
* @param name The name for the new child folder.
* @return The {@link Folder} descriptor for the newly created folder.
*/
public Folder createFolder(final Workspace workspace,
final String parentId,
final String name) {
return jcrAccessor.withNodeId(workspace.getName(), parentId, new NodeCallback<Folder>() {
@Override
public Folder doInSessionWithNode(Session session, Node node) throws RepositoryException {
final Node folder = node.addNode(name, NodeType.NT_FOLDER);
session.save();
return folderNodeCallback.doInSessionWithNode(session, folder);
}
});
}
/**
* Get the folder identified by {@code id} in the workspace described by {@code workspace}.
*
* @param workspace The {@link Workspace} descriptor.
* @param id The node identifier of the folder.
* @return The {@link Folder} descriptor.
*/
public Folder getFolder(final Workspace workspace,
final String id) {
return jcrAccessor.withNodeId(workspace.getName(), id, folderNodeCallback);
}
/**
* Get the file identified by {@code id} in the workspace described by {@code workspace}.
*
* @param workspace The {@link Workspace} descriptor.
* @param id The node identifier of the file.
* @return The {@link File} descriptor.
*/
public File getFile(final Workspace workspace,
final String id) {
return jcrAccessor.withNodeId(workspace.getName(), id, fileNodeCallback);
}
/**
* Get a list of {@link Folder} descriptors for the sub-folders of the folder described by {@code parent}.
*
* @param workspace The {@link Workspace} descriptor.
* @param parent The {@link Folder} descriptor of the parent folder.
* @return A list containing a {@link Folder} descriptor for each sub-folder.
*/
public List<Folder> getFolders(final Workspace workspace,
final Folder parent) {
final List<Folder> folders = new ArrayList<Folder>();
jcrAccessor.withNodeId(workspace.getName(), parent.getId(), new NodeCallback() {
@Override
public Object doInSessionWithNode(final Session session, final Node node) throws RepositoryException {
final NodeIterator nodes = node.getNodes();
while (nodes.hasNext()) {
final Node child = nodes.nextNode();
if (child.isNodeType(NodeType.NT_FOLDER)) {
final Folder folder = folderNodeCallback.doInSessionWithNode(session, child);
folders.add(folder);
}
}
return null;
}
});
return folders;
}
/**
* Get a list of {@link File} descriptors for the file in the folder described by {@code parent}.
*
* @param workspace The {@link Workspace} descriptor.
* @param parent The {@link Folder} descriptor of the parent folder.
* @return A list containing a {@link File} descriptor for each file.
*/
public List<File> getFiles(final Workspace workspace,
final Folder parent) {
final List<File> files = new ArrayList<File>();
jcrAccessor.withNodeId(workspace.getName(), parent.getId(), new NodeCallback() {
@Override
public Object doInSessionWithNode(final Session session, final Node node) throws RepositoryException {
final NodeIterator nodes = node.getNodes();
while (nodes.hasNext()) {
final Node child = nodes.nextNode();
if (node.isNodeType(NodeType.NT_FILE)) {
final File file = fileNodeCallback.doInSessionWithNode(session, child);
files.add(file);
}
}
return null;
}
});
return files;
}
/**
* Get a {@link FileContent} descriptor for the file described by {@code file}.
*
* @param workspace The {@link Workspace} descriptor.
* @param file The {@link File} descriptor.
* @return The {@link FileContent} descriptor.
*/
public FileContent getFileContent(final Workspace workspace,
final File file) {
return new FileContentImpl(workspace.getName(), file.getId());
}
/**
* Import contents into the repository.
*
* @param workspace The target workspace in the repository.
* @param parent The target directory in the repository.
* @param source The import source.
*/
public void importContents(final Workspace workspace,
final Folder parent,
final ImportSource source) {
jcrAccessor.withNodeId(
workspace.getName(),
parent.getId(),
new ImportContentsCallback(source));
}
/**
* Callback that creates a {@link Folder} descriptor for matching repository nodes.
*/
private static class FolderNodeCallback implements NodeCallback<Folder> {
/**
* Called when a repository node is matched.
*
* @param session The repository session.
* @param node The matching folder node.
* @return A {@link Folder} file descriptor.
* @throws RepositoryException If there was an error accessing the node properties.
*/
@Override
public Folder doInSessionWithNode(final Session session,
final Node node)
throws RepositoryException {
return new FolderImpl(node.getIdentifier(), node.getName(),
node.getPath());
}
}
private class ImportContentsCallback implements NodeCallback {
private final ImportSource source;
ImportContentsCallback(final ImportSource source) {
this.source = source;
}
@Override
public Object doInSessionWithNode(final Session session,
final Node node)
throws Exception {
source.process(new ImportContentsSourceCallback(session, node));
session.save();
return null;
}
}
private class ImportContentsSourceCallback implements ImportCallback {
private final Session session;
private final Node node;
ImportContentsSourceCallback(final Session session,
final Node node) {
this.session = session;
this.node = node;
}
@Override
public void directory(final String path) throws Exception {
if (!"/".equals(path)) {
final String[] pathElements = StringUtils.split(path, "/");
Node currentNode = node;
for (final String pathElement : pathElements) {
if (currentNode.hasNode(pathElement)) {
currentNode = currentNode.getNode(pathElement);
} else {
currentNode = currentNode.addNode(pathElement, NodeType.NT_FOLDER);
}
}
}
}
@Override
public void file(final String path,
final long lastModified,
final byte[] contents)
throws Exception {
final String[] pathElements = StringUtils.split(path, "/");
Node currentNode = node;
for (int i = 0; i < pathElements.length - 1; i++) {
if (currentNode.hasNode(pathElements[i])) {
currentNode = currentNode.getNode(pathElements[i]);
} else {
currentNode = currentNode.addNode(pathElements[i], NodeType.NT_FOLDER);
}
}
final String filename = pathElements[pathElements.length - 1];
currentNode = currentNode.addNode(filename, NodeType.NT_FILE);
currentNode = currentNode.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE);
final String encoding = encodingDetector.detect(filename, contents);
if (encoding != null) {
currentNode.setProperty(Property.JCR_ENCODING, encoding);
}
final String mimeType = typeDetector.detect(filename, contents);
if (mimeType != null) {
currentNode.setProperty(Property.JCR_MIMETYPE, mimeType);
}
final Calendar lastModifiedDate = Calendar.getInstance();
lastModifiedDate.setTimeInMillis(lastModified);
currentNode.setProperty(Property.JCR_LAST_MODIFIED, lastModifiedDate);
final ValueFactory valueFactory = session.getValueFactory();
final Binary data = valueFactory.createBinary(new ByteArrayInputStream(contents));
currentNode.setProperty(Property.JCR_DATA, data);
}
}
/**
* Callback that creates {@link File} descriptor for matching repository nodes.
*/
private class FileNodeCallback implements NodeCallback<File> {
/**
* Called when a repository node is matched.
*
* @param session The repository session.
* @param node The matching file node.
* @return A {@link File} file descriptor.
* @throws RepositoryException If there was an error accessing the node properties.
*/
@Override
public File doInSessionWithNode(final Session session,
final Node node)
throws RepositoryException {
final Node resourceNode = node.getNode(Node.JCR_CONTENT);
final String mimeType = jcrAccessor.getStringProperty(resourceNode,
Property.JCR_MIMETYPE);
final String encoding = jcrAccessor.getStringProperty(resourceNode,
Property.JCR_ENCODING);
final Calendar lastModified = jcrAccessor.getCalendarProperty(resourceNode,
Property.JCR_LAST_MODIFIED);
final Binary data = jcrAccessor.getBinaryProperty(resourceNode, Property.JCR_DATA);
return new FileImpl(node.getIdentifier(), node.getName(),
node.getPath(), mimeType, encoding, data.getSize(), lastModified);
}
}
}