Commit 2d82cbed authored by Ate Douma's avatar Ate Douma

REPO-1920 [UpdateExecutor] Provide support for custom node selection/navigation

parent 639467f9
/*
* Copyright 2012-2013 Hippo B.V. (http://www.onehippo.com)
* Copyright 2012-2018 Hippo B.V. (http://www.onehippo.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -17,6 +17,7 @@ package org.onehippo.repository.update;
import java.util.Map;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
......@@ -43,10 +44,47 @@ public abstract class BaseNodeUpdateVisitor implements NodeUpdateVisitor {
this.visitorContext = visitorContext;
}
/**
* Overridable boolean function to indicate if skipped node paths should be logged (default true)
* @return true if skipped node paths should be logged
*/
public boolean logSkippedNodePaths() {
return true;
}
/**
* Overridable boolean function to indicate if node checkout can be skipped (default false)
* @return true if node checkout can be skipped (e.g. for readonly visitors and/or updates unrelated to versioned content)
*/
public boolean skipCheckoutNodes() {
return false;
}
@Override
public void initialize(Session session) throws RepositoryException {
}
/**
* Initiates the retrieval of the nodes when using custom, instead of path or xpath (query) based, node
* selection/navigation, returning the first node to visit. Intended to be overridden, default implementation returns null.
* @param session
* @return first node to visit, or null if none found
* @throws RepositoryException
*/
public Node firstNode(final Session session) throws RepositoryException {
return null;
}
/**
* Return a following node, when using custom, instead of path or xpath (query) based, node selection/navigation.
* Intended to be overridden, default implementation returns null.
* @return next node to visit, or null if none left
* @throws RepositoryException
*/
public Node nextNode() throws RepositoryException {
return null;
}
@Override
public void destroy() {
}
......
/*
* Copyright 2012-2015 Hippo B.V. (http://www.onehippo.com)
* Copyright 2012-2018 Hippo B.V. (http://www.onehippo.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -157,8 +157,10 @@ class UpdaterExecutionReport {
return skippedFile;
}
void skipped(String path) {
skippedStream.println(path);
void skipped(String path, boolean report) {
if (report) {
skippedStream.println(path);
}
skippedCount++;
}
......
/*
* Copyright 2012-2013 Hippo B.V. (http://www.onehippo.com)
* Copyright 2012-2018 Hippo B.V. (http://www.onehippo.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -97,7 +97,7 @@ public class UpdaterExecutor implements EventListener {
logEvent(updaterInfo.getMethod(), updaterInfo.getStartedBy(), message);
final NodeUpdateVisitor updater = updaterInfo.getUpdater();
try {
if (updater instanceof BaseNodeUpdateVisitor) {
if (updaterInfo.isBaseNodeUpdateVisitor()) {
((BaseNodeUpdateVisitor) updater).setLogger(getLogger());
((BaseNodeUpdateVisitor) updater).setParametersMap(jsonToParamsMap(updaterInfo.getParameters()));
((BaseNodeUpdateVisitor) updater).setVisitorContext(new BaseNodeUpdateVisitorContext());
......@@ -107,8 +107,13 @@ public class UpdaterExecutor implements EventListener {
if (updaterInfo.isRevert()) {
runRevertVisitor();
} else {
runPathVisitor();
runQueryVisitor();
if (updaterInfo.getPath() != null) {
runPathVisitor();
} else if (updaterInfo.getQuery() != null) {
runQueryVisitor();
} else {
runCustomVisitor();
}
}
} catch (RepositoryException e) {
error("Unexpected exception while executing updater", e);
......@@ -171,6 +176,34 @@ public class UpdaterExecutor implements EventListener {
}
}
private void runCustomVisitor() throws RepositoryException {
final BaseNodeUpdateVisitor customVisitor = (BaseNodeUpdateVisitor) updaterInfo.getUpdater();
int count = 0;
try {
Node node = customVisitor.firstNode(session);
while (node != null) {
if (cancelled) {
info("Update cancelled");
break;
}
executeUpdater(node);
commitBatchIfNeeded();
if (++count % PROGRESS_REPORT_INTERVAL == 0) {
info("Processed " + count + " nodes");
}
node = customVisitor.nextNode();
}
} catch (UnsupportedOperationException e) {
warn("Cannot run updater: not implemented: " + e.getMessage());
} catch (RepositoryException e) {
error("Unexpected exception while running updater", e);
} finally {
if (count % PROGRESS_REPORT_INTERVAL != 0) {
info("Processed " + count + " nodes");
}
}
}
private void runPathVisitor() throws RepositoryException {
final Node startNode = getStartNode();
if (startNode != null) {
......@@ -287,7 +320,7 @@ public class UpdaterExecutor implements EventListener {
if (updated) {
report.updated(path);
} else if (!failed) {
report.skipped(path);
report.skipped(path, updaterInfo.logSkippedNodePaths());
}
}
......@@ -498,7 +531,7 @@ public class UpdaterExecutor implements EventListener {
* would fail
*/
private void ensureIsCheckedOut(Node node) throws RepositoryException {
if (!node.isCheckedOut()) {
if (!updaterInfo.skipCheckoutNodes() && !node.isCheckedOut()) {
log.debug("Checking out node {}" + node.getPath());
JcrUtils.ensureIsCheckedOut(background.getNodeByIdentifier(node.getIdentifier()));
}
......@@ -608,7 +641,7 @@ public class UpdaterExecutor implements EventListener {
@Override
public void reportSkipped(String path) {
report.skipped(path);
report.skipped(path, updaterInfo.logSkippedNodePaths());
}
@Override
......
/*
* Copyright 2012-2013 Hippo B.V. (http://www.onehippo.com)
* Copyright 2012-2018 Hippo B.V. (http://www.onehippo.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -52,6 +52,7 @@ class UpdaterInfo {
private final String identifier;
private final String name;
private final String description;
private final boolean baseNodeUpdateVisitor;
private final String path;
private final String query;
private final String language;
......@@ -70,6 +71,8 @@ class UpdaterInfo {
private final Binary updatedNodes;
private final String nodeType;
private final Class<? extends NodeUpdateVisitor> updaterClass;
private final boolean logSkippedNodePaths;
private final boolean skipCheckoutNodes;
/**
* @param node a node of type <code>hipposys:updaterinfo</code> carrying the meta data of the {@link NodeUpdateVisitor}
......@@ -88,6 +91,25 @@ class UpdaterInfo {
identifier = node.getIdentifier();
name = node.getName();
description = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_DESCRIPTION, null);
parameters = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_PARAMETERS, null);
final String script = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_SCRIPT, null);
final String klass = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_CLASS, null);
if ((script == null || script.isEmpty()) && (klass == null || klass.isEmpty())) {
throw new IllegalArgumentException("Either script or class property must be present");
}
if (klass != null && !klass.isEmpty()) {
updaterClass = (Class<? extends NodeUpdateVisitor>) Class.forName(klass);
} else {
final GroovyClassLoader gcl = GroovyUpdaterClassLoader.createClassLoader();
final GroovyCodeSource gcs = new GroovyCodeSource(script, "updater", "/hippo/updaters");
updaterClass = gcl.parseClass(gcs, false);
}
if (!NodeUpdateVisitor.class.isAssignableFrom(updaterClass)) {
throw new IllegalArgumentException("Class must implement " + NodeUpdateVisitor.class.getName());
}
baseNodeUpdateVisitor = (BaseNodeUpdateVisitor.class.isAssignableFrom(updaterClass));
path = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_PATH, null);
String queryString = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_QUERY, null);
if (!Strings.isNullOrEmpty(queryString)) {
......@@ -95,11 +117,9 @@ class UpdaterInfo {
}
query = queryString;
language = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_LANGUAGE, DEFAULT_QUERY_LANGUAGE);
parameters = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_PARAMETERS, null);
boolean hasPath = path != null && !path.isEmpty();
boolean hasQuery = query != null && !query.isEmpty();
if (!hasPath && !hasQuery) {
if (!hasPath && !hasQuery && !baseNodeUpdateVisitor) {
throw new IllegalArgumentException("Either path or query property must be present, you specified neither");
}
if (hasPath && hasQuery) {
......@@ -110,24 +130,11 @@ class UpdaterInfo {
batchSize = JcrUtils.getLongProperty(node, HippoNodeType.HIPPOSYS_BATCHSIZE, DEFAULT_BATCH_SIZE);
dryRun = JcrUtils.getBooleanProperty(node, HippoNodeType.HIPPOSYS_DRYRUN, false);
startedBy = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_STARTEDBY, null);
final String script = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_SCRIPT, null);
final String klass = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_CLASS, null);
if ((script == null || script.isEmpty()) && (klass == null || klass.isEmpty())) {
throw new IllegalArgumentException("Either script or class property must be present");
}
if (klass != null && !klass.isEmpty()) {
updaterClass = (Class<? extends NodeUpdateVisitor>) Class.forName(klass);
} else {
final GroovyClassLoader gcl = GroovyUpdaterClassLoader.createClassLoader();
final GroovyCodeSource gcs = new GroovyCodeSource(script, "updater", "/hippo/updaters");
updaterClass = gcl.parseClass(gcs, false);
}
if (!NodeUpdateVisitor.class.isAssignableFrom(updaterClass)) {
throw new IllegalArgumentException("Class must implement " + NodeUpdateVisitor.class.getName());
}
final Object o = updaterClass.newInstance();
updater = (NodeUpdateVisitor) o;
logSkippedNodePaths = baseNodeUpdateVisitor ? ((BaseNodeUpdateVisitor)updater).logSkippedNodePaths() : false;
skipCheckoutNodes = baseNodeUpdateVisitor ? ((BaseNodeUpdateVisitor)updater).skipCheckoutNodes() : true;
updatedNodes = JcrUtils.getBinaryProperty(node, HippoNodeType.HIPPOSYS_UPDATED, null);
nodeType = JcrUtils.getStringProperty(node, HippoNodeType.HIPPOSYS_NODETYPE, null);
}
......@@ -150,7 +157,11 @@ class UpdaterInfo {
* The description of this updater
*/
String getDescription() {
return name;
return description;
}
boolean isBaseNodeUpdateVisitor() {
return baseNodeUpdateVisitor;
}
/**
......@@ -242,6 +253,14 @@ class UpdaterInfo {
return updaterClass;
}
boolean logSkippedNodePaths() {
return logSkippedNodePaths;
}
boolean skipCheckoutNodes() {
return skipCheckoutNodes;
}
Iterator<String> getUpdatedNodes() {
if (updatedNodes != null) {
try {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment