Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
net.adamcin.recap.impl.RecapSessionImpl Maven / Gradle / Ivy
/*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to
*/
package net.adamcin.recap.impl;
import net.adamcin.commons.jcr.batch.*;
import net.adamcin.recap.api.*;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.util.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import javax.jcr.*;
import javax.jcr.nodetype.NodeType;
import java.util.*;
/**
* Implementation of {@link RecapSession} using a
* {@link net.adamcin.commons.jcr.batch.BatchSession} to manage auto-saves
*/
public final class RecapSessionImpl implements RecapSession {
private static final Logger LOGGER = LoggerFactory.getLogger(RecapSessionImpl.class);
private static final int DEFAULT_BATCH_SIZE = 1024;
private static final long DEFAULT_THROTTLE = 0L;
private static final RecapFilter DEFAULT_FILTER = new RecapFilter() {
public boolean includesPath(String path) {
return true;
}
};
private final RecapSessionInterrupter interrupter;
private final RecapAddress address;
private final RecapOptions options;
private final Session localSession;
private final Session remoteSession;
private RecapProgressListener progressListener;
private final BatchSession targetSession;
private String lastSuccessfulPath;
private int totalSyncPaths = 0;
private int totalNodes = 0;
private long totalSize = 0L;
private long currentSize = 0L;
private long start = 0L;
private long end = 0L;
private Map prefixMapping = new HashMap();
private boolean allowLastModifiedProperty = false;
private boolean interrupted = false;
private boolean finished = false;
public RecapSessionImpl(RecapSessionInterrupter interrupter,
RecapAddress address,
RecapOptions options,
Session localSession,
Session remoteSession) {
if (interrupter == null) {
throw new NullPointerException("interrupter");
}
if (address == null) {
throw new NullPointerException("address");
}
if (options == null) {
throw new NullPointerException("options");
}
if (localSession == null) {
throw new NullPointerException("localSession");
}
if (remoteSession == null) {
throw new NullPointerException("remoteSession");
}
if (localSession.getRepository().equals(remoteSession.getRepository())
&& localSession.getWorkspace().equals(remoteSession.getWorkspace())) {
throw new IllegalArgumentException("localSession and remoteSession must not be on the same repository and workspace");
}
this.interrupter = interrupter;
this.address = address;
this.options = new OptionsShield(options);
this.localSession = localSession;
this.remoteSession = remoteSession;
Session dstSession = getDestinationSession();
targetSession = new DefaultBatchSession(dstSession);
targetSession.addListener(new SyncSaveListener());
allowLastModifiedProperty = isValidNameForSession(this.options.getLastModifiedProperty());
}
private Session getSourceSession() {
if (this.options.isReverse()) {
return this.localSession;
} else {
return this.remoteSession;
}
}
private Session getDestinationSession() {
if (this.options.isReverse()) {
return this.remoteSession;
} else {
return this.localSession;
}
}
public BatchSession getTargetSession() {
return targetSession;
}
private void start() {
if (this.start == 0L) {
this.start = System.currentTimeMillis();
}
}
class SyncSaveListener extends DefaultBatchSessionListener {
@Override
public void onSave(BatchSaveInfo info) {
super.onSave(info);
long afterSave = System.currentTimeMillis();
trackMessage("Saved %d nodes (%d kB) in %d ms.",
info.getCount(), currentSize / 1000L, info.getTime());
trackMessage("Total time: %d ms, total nodes %d, %d kB",
afterSave - start, totalNodes, totalSize / 1024L);
currentSize = 0L;
if (options.getThrottle() > 0L && !interrupted && !finished) {
trackMessage("Throttling enabled. Waiting %ds...", options.getThrottle());
try {
Thread.sleep(options.getThrottle() * 1000L);
} catch (InterruptedException e) {
LOGGER.debug("[onSave] thread interrupted.");
}
}
}
@Override
public void onRemove(BatchRemoveInfo info) {
super.onRemove(info);
totalNodes++;
trackPath(RecapProgressListener.PathAction.DELETE, info.getPath());
}
}
public void checkPermissions(String path) throws RecapSessionException {
String parentPath = Text.getRelativeParent(path, 1);
try {
getSourceSession().checkPermission(path, Session.ACTION_READ);
} catch (RepositoryException e) {
trackError(path, e);
}
try {
getTargetSession().checkPermission(parentPath, Session.ACTION_READ);
getTargetSession().checkPermission(parentPath, Session.ACTION_ADD_NODE);
} catch (RepositoryException e) {
trackError(parentPath, e);
}
try {
getTargetSession().checkPermission(path, Session.ACTION_READ);
getTargetSession().checkPermission(path, Session.ACTION_ADD_NODE);
getTargetSession().checkPermission(path, Session.ACTION_SET_PROPERTY);
if (!options.isNoDelete()) {
getTargetSession().checkPermission(path, Session.ACTION_REMOVE);
}
} catch (RepositoryException e) {
trackError(path, e);
}
}
public Node getOrCreateTargetNode(Node sourceNode) throws RepositoryException {
if (sourceNode.getDepth() == 0) {
return getTargetSession().getRootNode();
} else if (getTargetSession().getRootNode().hasNode(sourceNode.getPath().substring(1))) {
return getTargetSession().getRootNode().getNode(sourceNode.getPath().substring(1));
} else {
Node sourceParent = sourceNode.getParent();
Node targetParent = getOrCreateTargetNode(sourceParent);
Node targetNode;
if (!targetParent.hasNode(sourceNode.getName())) {
targetNode = targetParent.addNode(sourceNode.getName(), sourceNode.getPrimaryNodeType().getName());
trackPath(RecapProgressListener.PathAction.ADD, targetNode.getPath());
} else {
targetNode = targetParent.getNode(sourceNode.getName());
}
return targetNode;
}
}
public void sync(String path) throws RecapSessionException {
if (this.finished) {
throw new RecapSessionException("RecapSession already finished.");
}
trackMessage("Sync %s %s http://%s:%d/", path, this.options.isReverse() ? "to" : "from", this.address.getHostname(), this.address.getPort());
try {
start();
Node srcNode = getSourceSession().getNode(path);
Node srcParent = srcNode.getParent();
Node dstParent = getOrCreateTargetNode(srcParent);
String dstName = srcNode.getName();
this.copy(srcNode, dstParent, dstName, !this.options.isNoRecurse());
this.lastSuccessfulPath = path;
this.totalSyncPaths++;
} catch (PathNotFoundException e) {
LOGGER.debug("PathNotFoundException while preparing path: {}. Message: {}", path, e.getMessage());
trackError(path, e);
} catch (RepositoryException e) {
LOGGER.error("RepositoryException while copying path: {}. Message: {}", path, e.getMessage());
trackFailure(path, e);
this.interrupted = true;
this.finish();
throw new RecapSessionException("RepositoryException while preparing path: " + path, e);
}
}
public RecapAddress getAddress() {
return address;
}
public RecapOptions getOptions() {
return options;
}
public boolean isFinished() {
return finished;
}
public void logout() {
if (this.remoteSession != null) {
this.remoteSession.logout();
}
}
public RecapProgressListener getProgressListener() {
return progressListener;
}
public void setProgressListener(RecapProgressListener progressListener) {
this.progressListener = progressListener;
}
private void trackPath(RecapProgressListener.PathAction action, String path) {
if (this.getProgressListener() != null) {
this.getProgressListener().onPath(action, this.totalNodes, path);
}
}
private void trackError(String path, Exception exception) {
if (this.getProgressListener() != null) {
this.getProgressListener().onError(path, exception);
}
}
private void trackFailure(String path, Exception exception) {
if (this.getProgressListener() != null) {
this.getProgressListener().onFailure(path, exception);
}
}
private void trackMessage(String fmt, Object... args) {
if (this.getProgressListener() != null) {
this.getProgressListener().onMessage(fmt, args);
}
}
private void sanityCheck() throws RecapSessionException {
if (this.finished) {
throw new RecapSessionException("RecapSession already finished.");
}
}
public void syncContent(String path) throws RecapSessionException {
sanityCheck();
trackMessage("Sync %s %s http://%s:%d/", path, this.options.isReverse() ? "to" : "from", this.address.getHostname(), this.address.getPort());
try {
if (this.start == 0L) {
this.start = System.currentTimeMillis();
}
Node srcNode = getSourceSession().getNode(path);
Node srcParent = srcNode.getParent();
Node dstParent = getOrCreateTargetNode(srcParent);
String dstName = srcNode.getName();
this.copy(srcNode, dstParent, dstName, false);
if (srcNode.hasNode(JcrConstants.JCR_CONTENT)) {
Node srcContentNode = srcNode.getNode(JcrConstants.JCR_CONTENT);
this.copy(srcContentNode, dstParent.getNode(dstName), JcrConstants.JCR_CONTENT,
!this.options.isNoRecurse());
}
this.lastSuccessfulPath = path;
this.totalSyncPaths++;
} catch (PathNotFoundException e) {
LOGGER.debug("PathNotFoundException while preparing path: {}. Message: {}", path, e.getMessage());
trackError(path, e);
} catch (RepositoryException e) {
LOGGER.error("RepositoryException while copying path: {}. Message: {}", path, e.getMessage());
trackFailure(path, e);
this.interrupted = true;
this.finish();
throw new RecapSessionException("RepositoryException while preparing path: " + path, e);
}
}
public void delete(String path) throws RecapSessionException {
sanityCheck();
if (this.options.isReverse()) {
trackMessage("Deleting %s from http://%s:%d/", path,
this.address.getHostname(), this.address.getPort());
} else {
trackMessage("Deleting %s from local repository", path);
}
try {
getTargetSession().removeItem(path);
} catch (PathNotFoundException e) {
LOGGER.debug("PathNotFoundException while removing path: {}. Message: {}", path, e.getMessage());
trackError(path, e);
} catch (RepositoryException e) {
LOGGER.error("RepositoryException while removing path: {}. Message: {}", path, e.getMessage());
trackFailure(path, e);
this.interrupted = true;
this.finish();
throw new RecapSessionException("RepositoryException while removing path: " + path, e);
}
}
public void finish() throws RecapSessionException {
if (!this.finished) {
this.finished = true;
RecapSessionException exception = null;
if (!this.interrupted) {
try {
getTargetSession().commit();
trackMessage("Done.");
} catch (RepositoryException e) {
LOGGER.error("[finish] Failed to save remaining changes.", e);
trackMessage("Failed to save remaining changes. %s", e.getMessage());
this.interrupted = true;
exception = new RecapSessionException("Failed to save remaining changes.", e);
}
}
this.end = System.currentTimeMillis();
this.logout();
if (this.getTotalNodes() > 0) {
trackMessage("Copy %s. %d nodes in %dms. %d bytes",
(this.interrupted ? "interrupted" : "completed"),
this.getTotalNodes(), this.getTotalTimeMillis(), this.getTotalSize());
trackMessage("%d root paths added or updated successfully. Last successful path: %s",
this.getTotalSyncPaths(), this.getLastSuccessfulSyncPath());
}
if (exception != null) {
throw exception;
}
}
}
/**
* The recursive copy function
* @param src existing source node
* @param dstParent existing destination parent node
* @param dstName name of destination node
* @param recursive
* @throws RecapSessionException
* @throws RepositoryException
*/
private void copy(Node src, Node dstParent, String dstName, boolean recursive)
throws RecapSessionException, RepositoryException {
if (interrupter.isInterrupted()) {
throw new RecapSessionException("RecapSession interrupted.");
}
String path = src.getPath();
String dstPath = dstParent.getPath() + "/" + dstName;
boolean useSysView = src.getDefinition().isProtected();
boolean isNew = false;
boolean overwrite = this.options.isUpdate();
boolean included = this.options.getFilter().includesPath(path);
getTargetSession().disableAutoSave();
++totalNodes;
Node dst;
if (dstParent.hasNode(dstName)) {
dst = dstParent.getNode(dstName);
if (!included) {
trackPath(RecapProgressListener.PathAction.IGNORE, dstPath);
} else if (overwrite) {
if ((this.options.isOnlyNewer()) && (dstName.equals(JcrConstants.JCR_CONTENT))) {
if (isNewer(src, dst)) {
trackPath(RecapProgressListener.PathAction.UPDATE, dstPath);
} else {
overwrite = false;
recursive = false;
trackPath(RecapProgressListener.PathAction.NO_ACTION, dstPath);
}
} else {
trackPath(RecapProgressListener.PathAction.UPDATE, dstPath);
}
if (useSysView) {
dst = sysCopy(src, dstParent, dstName);
}
} else {
trackPath(RecapProgressListener.PathAction.NO_ACTION, dstPath);
}
} else {
try {
if (included && useSysView) {
dst = sysCopy(src, dstParent, dstName);
} else {
dst = dstParent.addNode(dstName, src.getPrimaryNodeType().getName());
}
trackPath(RecapProgressListener.PathAction.ADD, dstPath);
isNew = true;
} catch (RepositoryException e) {
LOGGER.warn("Error while adding node {} (ignored): {}", dstPath, e.toString());
trackError(dstPath, e);
return;
}
}
if (included && useSysView) {
trackTree(dst, isNew);
} else {
Set names = new HashSet();
if (included && ((overwrite) || (isNew))) {
if (!isNew) {
for (NodeType nt : dst.getMixinNodeTypes()) {
names.add(nt.getName());
}
for (NodeType nt : src.getMixinNodeTypes()) {
String mixName = checkNameSpace(nt.getName());
if (!names.remove(mixName)) {
dst.addMixin(nt.getName());
}
}
for (String mix : names) {
dst.removeMixin(mix);
}
} else {
for (NodeType nt : src.getMixinNodeTypes()) {
dst.addMixin(checkNameSpace(nt.getName()));
}
}
names.clear();
if (!isNew) {
PropertyIterator iter = dst.getProperties();
while (iter.hasNext()) {
names.add(checkNameSpace(iter.nextProperty().getName()));
}
}
PropertyIterator iter = src.getProperties();
while (iter.hasNext()) {
Property p = iter.nextProperty();
String pName = checkNameSpace(p.getName());
names.remove(pName);
if (p.getDefinition().isProtected()) {
continue;
}
if (dst.hasProperty(pName)) {
dst.getProperty(pName).remove();
}
if (p.getDefinition().isMultiple()) {
Value[] vs = p.getValues();
dst.setProperty(pName, vs);
for (long s : p.getLengths()) {
this.totalSize += s;
this.currentSize += s;
}
} else {
Value v = p.getValue();
dst.setProperty(pName, v);
long s = p.getLength();
this.totalSize += s;
this.currentSize += s;
}
}
for (String pName : names) {
try {
dst.getProperty(pName).remove();
} catch (RepositoryException e) {
LOGGER.warn("[copy] failed to remove property {} from node {}", pName, path);
}
}
}
// re-enable auto-save before recursion
getTargetSession().enableAutoSave();
if (recursive) {
names.clear();
if ((overwrite) && (!isNew)) {
NodeIterator niter = dst.getNodes();
while (niter.hasNext()) {
names.add(checkNameSpace(niter.nextNode().getName()));
}
}
NodeIterator niter = src.getNodes();
while (niter.hasNext()) {
Node child = niter.nextNode();
String cName = checkNameSpace(child.getName());
names.remove(cName);
copy(child, dst, cName, true);
}
if (!options.isNoDelete()) {
for (String name : names) {
try {
Node cNode = dst.getNode(name);
cNode.remove();
} catch (RepositoryException e) {
LOGGER.warn("[copy] failed to delete existing node in dst that does not exist in src", e);
}
}
}
}
}
}
private Node sysCopy(Node src, Node dstParent, String dstName)
throws RepositoryException {
try {
ContentHandler handler = dstParent.getSession().getImportContentHandler(dstParent.getPath(), 0);
src.getSession().exportSystemView(src.getPath(), handler, true, false);
return dstParent.getNode(dstName);
} catch (SAXException e) {
throw new RepositoryException("Unable to perform sysview copy", e);
}
}
private void trackTree(Node node, boolean isNew) throws RepositoryException {
NodeIterator iter = node.getNodes();
while (iter.hasNext()) {
Node child = iter.nextNode();
if (isNew) {
trackPath(RecapProgressListener.PathAction.ADD, child.getPath());
} else {
trackPath(RecapProgressListener.PathAction.UPDATE, child.getPath());
}
trackTree(child, isNew);
}
}
private boolean isNewer(Node src, Node dst) {
try {
Calendar srcDate = null;
Calendar dstDate = null;
if ((this.allowLastModifiedProperty) && (src.hasProperty(this.options.getLastModifiedProperty())) && (dst.hasProperty(this.options.getLastModifiedProperty()))) {
srcDate = src.getProperty(this.options.getLastModifiedProperty()).getDate();
dstDate = dst.getProperty(this.options.getLastModifiedProperty()).getDate();
} else if ((src.hasProperty(JcrConstants.JCR_LASTMODIFIED)) && (dst.hasProperty(JcrConstants.JCR_LASTMODIFIED))) {
srcDate = src.getProperty(JcrConstants.JCR_LASTMODIFIED).getDate();
dstDate = dst.getProperty(JcrConstants.JCR_LASTMODIFIED).getDate();
}
return (srcDate == null) || (dstDate == null) || (srcDate.after(dstDate));
} catch (RepositoryException e) {
LOGGER.error("Unable to compare dates: {}", e.toString());
}
return true;
}
private boolean isValidNameForSession(String name) {
if (StringUtils.isEmpty(name)) {
return false;
}
try {
mapPrefixedName(name);
return true;
} catch (RepositoryException e) {
LOGGER.error("Error processing namespace for {}: {}", name, e.toString());
return false;
}
}
private String mapPrefixedName(String name) throws RepositoryException {
int idx = name.indexOf(':');
if (idx > 0) {
String prefix = name.substring(0, idx);
String mapped = this.prefixMapping.get(prefix);
if (mapped == null) {
String uri = getSourceSession().getNamespaceURI(prefix);
int i = -1;
try {
mapped = getTargetSession().getNamespacePrefix(uri);
} catch (NamespaceException e) {
mapped = prefix;
i = 0;
}
while (i >= 0) {
try {
getTargetSession().getWorkspace().getNamespaceRegistry().registerNamespace(mapped, uri);
i = -1;
} catch (NamespaceException e1) {
mapped = prefix + i++;
}
}
this.prefixMapping.put(prefix, mapped);
}
if (mapped.equals(prefix)) {
return name;
}
return mapped + prefix.substring(idx);
}
return name;
}
private String checkNameSpace(String name) {
try {
name = mapPrefixedName(name);
} catch (RepositoryException e) {
LOGGER.error("Error processing namespace for {}: {}", name, e.toString());
}
return name;
}
public Session getRemoteSession() {
return this.remoteSession;
}
public Session getLocalSession() {
return this.localSession;
}
public int getTotalSyncPaths() {
return this.totalSyncPaths;
}
public String getLastSuccessfulSyncPath() {
return this.lastSuccessfulPath;
}
public int getTotalNodes() {
return this.totalNodes;
}
public long getTotalSize() {
return this.totalSize;
}
public long getTotalTimeMillis() {
return this.end - this.start;
}
static class OptionsShield implements RecapOptions {
final RecapOptions unsafe;
OptionsShield(RecapOptions unsafe) {
if (unsafe == null) {
throw new NullPointerException("unsafe");
}
this.unsafe = unsafe;
}
public Integer getBatchSize() {
Integer unsafeBatchSize = unsafe.getBatchSize();
if (unsafeBatchSize == null) {
LOGGER.debug("[OptionsShield#getBatchSize] null value for batchSize; using hard coded default of {}",
DEFAULT_BATCH_SIZE);
return DEFAULT_BATCH_SIZE;
} else {
return unsafeBatchSize;
}
}
public Long getThrottle() {
Long unsafeThrottle = unsafe.getThrottle();
if (unsafeThrottle == null) {
LOGGER.debug("[OptionsShield#getBatchSize] null value for throttle; using hard coded default of {}",
DEFAULT_THROTTLE);
return DEFAULT_THROTTLE;
} else {
return unsafeThrottle;
}
}
public RecapFilter getFilter() {
RecapFilter unsafeFilter = unsafe.getFilter();
if (unsafeFilter == null) {
LOGGER.debug("[OptionsShield#getFilter] null value for filter; using hard coded default filter");
return DEFAULT_FILTER;
} else {
return unsafeFilter;
}
}
public String getLastModifiedProperty() { return unsafe.getLastModifiedProperty(); }
public RequestDepthConfig getRequestDepthConfig() { return unsafe.getRequestDepthConfig(); }
public boolean isOnlyNewer() { return unsafe.isOnlyNewer(); }
public boolean isUpdate() { return unsafe.isUpdate(); }
public boolean isReverse() { return unsafe.isReverse(); }
public boolean isNoRecurse() { return unsafe.isNoRecurse(); }
public boolean isNoDelete() { return unsafe.isNoDelete(); }
@Override public String toString() { return unsafe.toString(); }
}
}