/** * Copyright (c) 2008-2010 Mark Logic Corporation. All rights reserved. * * 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. * * The use of the Apache License does not indicate that this project is * affiliated with the Apache Software Foundation. */ package com.marklogic.recordloader.svn; import java.io.ByteArrayOutputStream; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import org.tmatesoft.svn.core.SVNDirEntry; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.SVNRepositoryFactory; import org.tmatesoft.svn.core.wc.SVNWCUtil; import com.marklogic.recordloader.AbstractInputHandler; import com.marklogic.recordloader.Configuration; import com.marklogic.recordloader.FatalException; import com.marklogic.recordloader.LoaderException; import com.marklogic.recordloader.LoaderInterface; /** * @author Michael Blakeley, michael.blakeley@marklogic.com * */ public class SvnInputHandler extends AbstractInputHandler { private long revision; /* * (non-Javadoc) * * @see com.marklogic.recordloader.AbstractInputHandler#run() */ @Override public void run() throws LoaderException { getFactory(); // alternatively, treat first arg as repository url? if (0 < inputs.length) { logger.warning("ignoring inputs: " + Arrays.deepToString(inputs)); } String url = config.getInputPath(); if (null == url) { throw new FatalException("missing required property: " + Configuration.INPUT_PATH_KEY + " must be an svn repository url"); } logger.info("Reading from svn repository " + url); // TODO add svnkit.jar to ant files DAVRepositoryFactory.setup(); SVNRepository repository = null; try { repository = SVNRepositoryFactory.create(SVNURL .parseURIEncoded(url)); ISVNAuthenticationManager am = SVNWCUtil .createDefaultAuthenticationManager(); repository.setAuthenticationManager(am); // TODO support configurable revisions revision = repository.getLatestRevision(); logger.info("root = " + repository.getRepositoryRoot(true)); logger.info("UUID = " + repository.getRepositoryUUID(true)); logger.info("revision = " + revision); SVNNodeKind nodeKind = repository.checkPath("", revision); if (nodeKind == SVNNodeKind.NONE) { throw new FatalException("no entry at '" + url + "'."); } else if (nodeKind == SVNNodeKind.FILE) { throw new FatalException(url + " is a file"); } listEntries(repository, ""); } catch (SVNException e) { e.printStackTrace(); } finally { if (null != repository) { try { repository.closeSession(); } catch (SVNException e) { logger.logException("couldn't close repository session", e); } } } } @SuppressWarnings("unchecked") public void listEntries(SVNRepository repository, String path) throws LoaderException, SVNException { Collection<SVNDirEntry> entries = repository.getDir(path, revision, null, (Collection<?>) null); Iterator<SVNDirEntry> iterator = entries.iterator(); while (iterator.hasNext()) { SVNDirEntry entry = iterator.next(); String name = entry.getRelativePath(); String fullPath = path + ("".equals(path) ? "" : "/") + name; String rootedPath = "/" + fullPath; SVNNodeKind kind = entry.getKind(); logger.finer(rootedPath + " is a " + kind); if (SVNNodeKind.DIR == kind) { listEntries(repository, fullPath); continue; } try { // seems to be important to *not* use rootedPath submit(repository, fullPath, rootedPath); } catch (SVNException e) { if (config.isFatalErrors()) { throw e; } logger.logException(e); continue; } } } /** * @param repository * @param path * @param rootedPath * @throws SVNException * @throws LoaderException */ private void submit(SVNRepository repository, String path, String rootedPath) throws SVNException, LoaderException { // hack to skip large mp3 files // TODO implement something configurable if (path.endsWith(".mp3")) { logger.warning("skipping " + path); return; } // queue the file for loading ByteArrayOutputStream baos = new ByteArrayOutputStream(); repository.getFile(path, revision, null, baos); if (null == baos) { throw new NullPointerException(path + " is empty"); } LoaderInterface loader = factory.newLoader(baos.toByteArray()); loader.setRecordPath(rootedPath); // TODO implement path-based type lookup - move to a content factory? /* * if (path.matches("^.+\\.(xml|xsd)")) { logger.finer(path + * " is xml"); loader.setFormat(DocumentFormat.XML); } else if (path * .matches("^.+\\.(css|html|incl|js|log|sh|tmpl|txt|xqy)$")) { * logger.finer(path + " is text"); * loader.setFormat(DocumentFormat.TEXT); } else { logger.finer(path + * " is binary"); loader.setFormat(DocumentFormat.BINARY); } */ pool.submit(loader); } }