blob: 13dd101eced00feb4297d8f3572e465030645c06 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.jackrabbit.vault.vlt.actions;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Calendar;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.jackrabbit.vault.fs.api.RepositoryAddress;
import org.apache.jackrabbit.vault.sync.impl.VaultSyncServiceImpl;
import org.apache.jackrabbit.vault.util.Text;
import org.apache.jackrabbit.vault.vlt.VltContext;
import org.apache.jackrabbit.vault.vlt.VltException;
/**
* <code>Checkout</code>...
*/
public class Sync extends AbstractAction {
public enum Command {
STATUS,
ST,
REGISTER,
UNREGISTER,
INIT,
INSTALL
}
private static final String[] INSTALL_ROOT = {"libs", "crx", "vault", "install"};
private static final String CFG_NODE_NAME = VaultSyncServiceImpl.class.getName();
private static final String CFG_NODE_PATH = "/libs/crx/vault/config/" + CFG_NODE_NAME;
private static final String CFG_ROOTS = VaultSyncServiceImpl.SYNC_SPECS;
private static final String CFG_ENABLED = VaultSyncServiceImpl.SYNC_ENABLED;
private RepositoryAddress mountPoint;
private File localDir;
private final Command cmd;
private boolean force;
public Sync(Command cmd, RepositoryAddress mountPoint, File localDir) {
this.cmd = cmd;
this.mountPoint = mountPoint;
this.localDir = localDir;
}
public void setForce(boolean force) {
this.force = force;
}
public void run(VltContext ctx) throws VltException {
if (mountPoint == null) {
mountPoint = ctx.getMountpoint();
}
if (mountPoint == null) {
throw ctx.error(ctx.getCwd().getAbsolutePath(), "No remote specified and not in vlt checkout.");
}
// currently we just read the config node, assuming it's at the correct location
Session s = null;
try {
s = ctx.login(mountPoint);
switch (cmd) {
case STATUS:
case ST:
status(ctx, s);
break;
case REGISTER:
register(ctx, s, null);
break;
case UNREGISTER:
unregister(ctx, s);
break;
case INSTALL:
install(ctx, s);
break;
case INIT:
init(ctx, s);
}
} catch (RepositoryException e) {
throw new VltException("Error while performing command", e);
} finally {
if (s != null) {
s.logout();
}
}
}
private void init(VltContext ctx, Session s) throws VltException, RepositoryException {
// check if in vlt checkout
if (ctx.getExportRoot().isValid()) {
ctx.getStdout().printf("Starting initialization of sync service in existing vlt checkout %s for %s%n",
ctx.getExportRoot().getJcrRoot().getAbsolutePath(),
mountPoint);
// check if config is present, assume installed
Config cfg = new Config(s);
if (!cfg.load(ctx)) {
force = true;
install(ctx, s);
}
register(ctx, s, true);
ctx.getStdout().printf(
"%nThe directory %1$s is now enabled for syncing.%n" +
"You might perform a 'sync-once' by setting the%n" +
"appropriate flag in the %1$s/.vlt-sync-config.properties file.%n%n",
localDir.getAbsolutePath());
} else {
ctx.getStdout().printf("Starting initialization of sync service in a non vlt checkout directory %s for %s%n",
localDir.getAbsolutePath(),
mountPoint);
// check if empty
if (localDir.listFiles().length > 0) {
throw new VltException("Aborting initialization since directory is not empty.");
}
// check if config is present, assume installed
Config cfg = new Config(s);
if (!cfg.load(ctx)) {
force = true;
install(ctx, s);
}
register(ctx, s, true);
ctx.getStdout().printf(
"%nThe directory %1$s is now enabled for syncing.%n" +
"You need to configure the filter %1$s/.vlt-sync-filter.xml to setup the%n" +
"proper paths. You might also perform a 'sync-once' by setting the%n" +
"appropriate flag in the %1$s/.vlt-sync-config.properties file.%n%n",
localDir.getAbsolutePath());
}
}
private void status(VltContext ctx, Session s) throws RepositoryException {
Config cfg = new Config(s);
if (!cfg.load(ctx)) {
ctx.getStdout().println("No sync-service configured at " + CFG_NODE_PATH);
return;
}
ctx.getStdout().println("Listing sync status for " + mountPoint);
ctx.getStdout().println("- Sync service is " + (cfg.enabled ? "enabled." : "disabled."));
if (cfg.roots.isEmpty()) {
ctx.getStdout().println("- No sync directories configured.");
} else {
for (String path : cfg.roots) {
ctx.getStdout().println("- syncing directory: " + path);
}
}
}
private void register(VltContext ctx, Session s, Boolean enable) throws RepositoryException {
Config cfg = new Config(s);
if (!cfg.load(ctx)) {
ctx.getStdout().println("No sync-service configured at " + CFG_NODE_PATH);
return;
}
for (String path: cfg.roots) {
// need to check canonical path
try {
File f = new File(path).getCanonicalFile();
if (f.equals(localDir)) {
ctx.getStdout().println("Directory is already synced: " + localDir.getAbsolutePath());
return;
}
} catch (IOException e) {
// ignore
}
}
cfg.roots.add(localDir.getAbsolutePath());
if (enable != null) {
cfg.enabled = enable;
}
cfg.save(ctx);
ctx.getStdout().println("Added new sync directory: " + localDir.getAbsolutePath());
}
private void unregister(VltContext ctx, Session s) throws RepositoryException {
Config cfg = new Config(s);
if (!cfg.load(ctx)) {
ctx.getStdout().println("No sync-service configured at " + CFG_NODE_PATH);
return;
}
boolean found = false;
for (String path: cfg.roots) {
// need to check canonical path
try {
File f = new File(path).getCanonicalFile();
if (f.equals(localDir)) {
found = true;
break;
}
} catch (IOException e) {
// ignore
}
}
if (!found) {
ctx.getStdout().println("Directory is not registered: " + localDir.getAbsolutePath());
return;
}
cfg.roots.remove(localDir.getAbsolutePath());
cfg.save(ctx);
ctx.getStdout().println("Removed sync directory: " + localDir.getAbsolutePath());
}
private void install(VltContext ctx, Session s) throws RepositoryException, VltException {
// get sync jar
URLClassLoader cl = (URLClassLoader) VaultSyncServiceImpl.class.getClassLoader();
URL resource = null;
for (URL url: cl.getURLs()) {
if (url.getPath().matches(".*/vault-sync-.*\\.jar")) {
resource = url;
break;
}
}
if (resource == null) {
throw new VltException("Unable to find vault-sync.jar library.");
}
String jarName = Text.getName(resource.getPath());
ctx.getStdout().println("Preparing to install " + jarName + "...");
Node root = s.getRootNode();
for (String name: INSTALL_ROOT) {
root = JcrUtils.getOrAddFolder(root, name);
}
// check if already a bundle is installed
for (Node child: JcrUtils.getChildNodes(root)) {
if (child.getName().startsWith("vault-sync-")) {
if (force) {
ctx.getStdout().println("Detected existing bundle: " + child.getName() + ". Updating");
break;
} else {
ctx.getStdout().println("Detected existing bundle: " + child.getName() + ". Aborting installation. Specify --force to update.");
return;
}
}
}
InputStream in = null;
try {
in = resource.openStream();
if (root.hasNode(jarName)) {
root.getNode(jarName).remove();
}
JcrUtils.putFile(root, jarName, "application/octet-stream", in, Calendar.getInstance());
} catch (IOException e) {
throw new VltException("Error while installing bundle", e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
}
ctx.getStdout().println("Updated bundle: " + jarName);
// update config
root = JcrUtils.getOrAddFolder(root.getParent(), "config");
if (!root.hasNode(CFG_NODE_NAME)) {
root.addNode(CFG_NODE_NAME, "sling:OsgiConfig");
Config cfg = new Config(s);
cfg.enabled = true;
cfg.save(ctx);
ctx.getStdout().println("Created new config at " + CFG_NODE_PATH);
}
s.save();
}
private static class Config {
private final Session s;
private boolean enabled = false;
private Set<String> roots = new LinkedHashSet<String>();
private Config(Session s) {
this.s = s;
}
public boolean load(VltContext ctx) throws RepositoryException {
if (!s.nodeExists(CFG_NODE_PATH)) {
return false;
}
Node cfgNode = s.getNode(CFG_NODE_PATH);
if (cfgNode.hasProperty(CFG_ENABLED)) {
enabled = cfgNode.getProperty(CFG_ENABLED).getBoolean();
}
if (cfgNode.hasProperty(CFG_ROOTS)) {
Property roots = cfgNode.getProperty(CFG_ROOTS);
for (Value v : roots.getValues()) {
this.roots.add(v.getString());
}
}
return true;
}
public void save(VltContext ctx) throws RepositoryException {
// assume node exists
Node cfgNode = s.getNode(CFG_NODE_PATH);
cfgNode.setProperty(CFG_ENABLED, enabled);
Value[] vals = new Value[roots.size()];
int i=0;
for (String path: roots) {
vals[i++] = s.getValueFactory().createValue(path);
}
cfgNode.setProperty(CFG_ROOTS, vals);
s.save();
}
}
}