/* * 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}... */ 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(); } } }