Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow loading migrations from a jar file #9

Open
fkoehler opened this issue Apr 14, 2014 · 7 comments
Open

Allow loading migrations from a jar file #9

fkoehler opened this issue Apr 14, 2014 · 7 comments

Comments

@fkoehler
Copy link
Contributor

We are using Pillar inside a Play2 application where it is deployed as a jar. One can not read migrations from a directory inside a jar. We worked around this with something like the following. I am not creating a pull request as I am not sure on how to integrate this and if it's desired behaviour nor do I know how to correctly test this. sorry.

  private val registry = Registry(loadMigrationsFromJarOrFilesystem())
  private val migrator = Migrator(registry, new LoggerReporter)

  private def loadMigrationsFromJarOrFilesystem() = {
    val migrationsDir = "migrations/"
    val migrationNames = JarUtils.getResourceListing(getClass, migrationsDir).toList.filter(_.nonEmpty)
    val parser = Parser()

    migrationNames.map(name => getClass.getClassLoader.getResourceAsStream(migrationsDir + name)).map {
      stream =>
        try {
          parser.parse(stream)
        } finally {
          stream.close()
        }
    }.toList
  }

where JarUtils.getResourceListing is taken from the top answer here: http://stackoverflow.com/questions/6247144/how-to-load-a-folder-from-a-jar

Hope that helps

@magro
Copy link
Contributor

magro commented Jun 8, 2014

@pvenable WDYT?

@pvenable
Copy link
Collaborator

I don't have this use case, but it seems reasonable to support it. :)

@PeterLappo
Copy link

Hi
Have implemented some code that loads from a class path and directories as I have a need for this.
ClassPathUtil.java and CPRegistry.scala. You might not like the logger or the package names. Sorry no unit tests but works fine in my project.

package util;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ClassPathUtil
 *  
 * @author Peter Lappo
 *
 */

public class ClassPathUtil {

  private static Logger log = LoggerFactory.getLogger(ClassPathUtil.class);

  /**
   * List directory contents from the classpath. Not recursive. This is basically a brute-force implementation. Works
   * for regular files and also JARs.
   * 
   * For example:
   * ClassPathUtil.getResourceListing(this.getClass(), "cql/migrations/");
   * 
   * @param clazz
   *          Any java class that lives in the same place as the resources you want.
   * @param path
   *          Must end with "/" otherwise path is not formed properly. 
   *          Should not start with "/" as we search from the root directory anyway.
   * @return List of URLs for each resource found in a resource directory or jar.
   * 
   * @throws URISyntaxException
   * @throws IOException
   * 
   * @author Peter Lappo
   */
  public static List<URL> getResourceListing(Class<?> clazz, String path) throws URISyntaxException, IOException {
    Enumeration<URL> dirURLs = clazz.getClassLoader().getResources(path);
    List<URL> result = new ArrayList<URL>();

    while (dirURLs.hasMoreElements()) {
      URL url = dirURLs.nextElement();
      log.info("Checking url: " + url);

      if (url.getProtocol().equals("file")) {
        File file = new File(url.getFile());
        if (file.isDirectory()) {
          // enumerate files in directory
          for (String entrystr :file.list()) {
            result.add(new URL(url.toExternalForm() + entrystr));
          }
        } else {
          result.add(url);
        }
      }

      if (url.getProtocol().equals("jar")) {
        if (url.toExternalForm().contains("sources")) {
          log.warn("Ignoring sources url: " + url);
          continue; // ignore sources
        }
        /* A JAR path */
        String jarPath = url.getPath().substring(5, url.getPath().indexOf("!")); // strip out only the JAR file
        JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
        Enumeration<JarEntry> entries = jar.entries(); // gives ALL entries in jar
        while (entries.hasMoreElements()) {
          JarEntry entry = entries.nextElement();
          String name = entry.getName();
          if (name.startsWith(path)) { // filter according to the path
            if (entry.isDirectory()) {
              log.warn("Ignoring subdirectory url: " + entry);
              continue; // ignore subdirectories
            }
            String entrystr = name.substring(path.length());
            result.add(new URL(url.toExternalForm() + entrystr));
          }
        }
        jar.close();
      }
    }

    return result;
  }

}
package util

import java.io.BufferedInputStream
import java.io.File
import java.net.URL
import com.chrisomeara.pillar.Migration
import com.chrisomeara.pillar.Parser
import com.chrisomeara.pillar.Registry
import com.chrisomeara.pillar.Reporter
import com.chrisomeara.pillar.ReportingMigration
import java.io.InputStream

/**
 * Pillar Registry factory the loads migration definitions from the classpath.
 * This will load definitions from files and jar resources.
 * 
 * @author Peter Lappo
 */

object CPRegistry {
  def apply(migrations: Seq[Migration]): Registry = {
    new Registry(migrations)
  }

  def fromClassPath(clazz: Class[_], path: String, reporter: Reporter): Registry = {
    new Registry(parseMigrationsInClassPath(path).map(new ReportingMigration(reporter, _)))
  }

  def fromClassPath(clazz: Class[_], path: String): Registry = {
    new Registry(parseMigrationsInClassPath(path))
  }

  private def parseMigrationsInClassPath(path: String): Seq[Migration] = {
    import scala.collection.JavaConversions.asScalaBuffer
    val paths = ClassPathUtil.getResourceListing(this.getClass(), path)
    val parser = Parser()
    paths.map {
      path =>
        val stream:InputStream = new BufferedInputStream(path.openStream())
        try {
          parser.parse(stream)
        } finally {
          stream.close()
        }
    }.toList
  }

}

@magro
Copy link
Contributor

magro commented Nov 2, 2014

@PeterLappo Why does fromClassPath require a clazz? It's not used AFAICS.

@comeara Would you merge a pull request with this?

@PeterLappo
Copy link

Enumeration dirURLs = clazz.getClassLoader().getResources(path);

The JVM can have more than one classloader to avoid conflicts in class versions (think webserver frameworks) and is used to manage security so you need to load the resources for your context. The chances are it would work fine without one.

Peter

www.smr.co.uk
+44 7767 784452

On 2 Nov 2014, at 18:59, Martin Grotzke notifications@github.com wrote:

@PeterLappo Why does fromClassPath require a clazz? It's not used AFAICS.

@comeara Would you merge a pull request with this?


Reply to this email directly or view it on GitHub.

@magro
Copy link
Contributor

magro commented Nov 2, 2014

Sure, but shouldn't fromClassPath pass the clazz to
parseMigrationsInClassPath then? Alternatively it could accept a
classloader.

Cheers,
Martin
Am 02.11.2014 18:54 schrieb "Peter" notifications@github.com:

Hi
Have implemented some code that loads from a class path and directories as
I have a need for this.
ClassPathUtil.java and CPRegistry.scala. You might not like the logger or
the package names. Sorry no unit tests but works fine in my project.

package util;
import java.io.File;import java.io.IOException;import java.net.URISyntaxException;import java.net.URL;import java.net.URLDecoder;import java.util.ArrayList;import java.util.Enumeration;import java.util.List;import java.util.jar.JarEntry;import java.util.jar.JarFile;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
/** * ClassPathUtil * * @author Peter Lappo * */
public class ClassPathUtil {

private static Logger log = LoggerFactory.getLogger(ClassPathUtil.class);

/** * List directory contents from the classpath. Not recursive. This is basically a brute-force implementation. Works * for regular files and also JARs. * * For example: * ClassPathUtil.getResourceListing(this.getClass(), "cql/migrations/"); * * @param clazz * Any java class that lives in the same place as the resources you want. * @param path * Must end with "/" otherwise path is not formed properly. * Should not start with "/" as we search from the root directory anyway. * @return List of URLs for each resource found in a resource directory or jar. * * @throws URISyntaxException * @throws IOException * * @author Peter Lappo */
public static List getResourceListing(Class<?> clazz, String path) throws URISyntaxException, IOException {
Enumeration dirURLs = clazz.getClassLoader().getResources(path);
List result = new ArrayList();

while (dirURLs.hasMoreElements()) {
  URL url = dirURLs.nextElement();
  log.info("Checking url: " + url);

  if (url.getProtocol().equals("file")) {
    File file = new File(url.getFile());
    if (file.isDirectory()) {
      // enumerate files in directory
      for (String entrystr :file.list()) {
        result.add(new URL(url.toExternalForm() + entrystr));
      }
    } else {
      result.add(url);
    }
  }

  if (url.getProtocol().equals("jar")) {
    if (url.toExternalForm().contains("sources")) {
      log.warn("Ignoring sources url: " + url);
      continue; // ignore sources
    }
    /* A JAR path */
    String jarPath = url.getPath().substring(5, url.getPath().indexOf("!")); // strip out only the JAR file
    JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
    Enumeration<JarEntry> entries = jar.entries(); // gives ALL entries in jar
    while (entries.hasMoreElements()) {
      JarEntry entry = entries.nextElement();
      String name = entry.getName();
      if (name.startsWith(path)) { // filter according to the path
        if (entry.isDirectory()) {
          log.warn("Ignoring subdirectory url: " + entry);
          continue; // ignore subdirectories
        }
        String entrystr = name.substring(path.length());
        result.add(new URL(url.toExternalForm() + entrystr));
      }
    }
    jar.close();
  }
}

return result;

}
}

package util
import java.io.BufferedInputStreamimport java.io.Fileimport java.net.URLimport com.chrisomeara.pillar.Migrationimport com.chrisomeara.pillar.Parserimport com.chrisomeara.pillar.Registryimport com.chrisomeara.pillar.Reporterimport com.chrisomeara.pillar.ReportingMigrationimport java.io.InputStream
/** * Pillar Registry factory the loads migration definitions from the classpath. * This will load definitions from files and jar resources. * * @author Peter Lappo */
object CPRegistry {
def apply(migrations: Seq[Migration]): Registry = {
new Registry(migrations)
}

def fromClassPath(clazz: Class[_], path: String, reporter: Reporter): Registry = {
new Registry(parseMigrationsInClassPath(path).map(new ReportingMigration(reporter, _)))
}

def fromClassPath(clazz: Class[_], path: String): Registry = {
new Registry(parseMigrationsInClassPath(path))
}

private def parseMigrationsInClassPath(path: String): Seq[Migration] = {
import scala.collection.JavaConversions.asScalaBuffer
val paths = ClassPathUtil.getResourceListing(this.getClass(), path)
val parser = Parser()
paths.map {
path =>
val stream:InputStream = new BufferedInputStream(path.openStream())
try {
parser.parse(stream)
} finally {
stream.close()
}
}.toList
}
}


Reply to this email directly or view it on GitHub
#9 (comment).

@ches
Copy link

ches commented Jun 15, 2017

I agree it would be nice to be able to use migrations from resources on the classpath.

This alternative that functions as a library allows you to deploy your usual artifact instead of needing to somehow also deploy source (or a standalone repo of conf/) + the pillar executable into a server environment where it can reach the production Cassandra cluster. Support for this would pave the way for more easily using Pillar as a library for the same sort of use case when required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants