How do I extract the value of a route variable from the URL in a Scala-Play app? - scala

I am writing a module for the Play Framework. In part of my module I have the following code
abstract class SecurityFiltering extends GlobalSettings{
override def onRequestReceived(request: RequestHeader) = {
play.Logger.debug("onRequestReceived: " + request)
super.onRequestReceived(request)
}
override def doFilter(next: RequestHeader => Handler): (RequestHeader => Handler) = {
request => {
play.Logger.debug("doFilter: " + request)
super.doFilter(next)(request)
}
}
override def onRouteRequest(request: RequestHeader): Option[Handler] = {
play.Logger.debug("onRouteRequest: " + request)
super.onRouteRequest(request)
}
}
In the doFilter method I am able to determine the following useful information
ROUTE_PATTERN = /x/$name<[^/]+>/$age<[^/]+>
ROUTE_CONTROLLER = controllers.Application
ROUTE_ACTION_METHOD = tester
ROUTE_VERB = GET
path = /x/hello
What I need in addition to this is the values for the named parts of the URL before the QueryString. So given the following route in my test application I need to retrieve Name=Pete and Age=41
localhost:9000/x/Pete/41
There is surely some code in the Play Framework which already does this but I am unable to find it. Can someone suggest how I achieve this goal, or point me at which Play class extracts these values?

package models.com.encentral.tattara.web.util;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RouteExtractor {
//something like "/foo/$id<[^/]+>/edit/$james<[^/]+>"
private String routePattern;
private String path;
//something like /$id<[^/]+>
private static final String INDEX_PATTERN = "\\$(.+?)\\<\\[\\^\\/\\]\\+\\>";
public RouteExtractor(String routePattern, String path) {
this.routePattern = routePattern;
this.path = path;
}
private Map<Integer, String> extractPositions() {
Pattern pattern = Pattern.compile(INDEX_PATTERN);
Matcher matcher = pattern.matcher(this.routePattern);
Map<Integer, String> results = new HashMap<>();
int index = 0;
while (matcher.find()) {
results.put(index++, matcher.group(1));
}
return results;
}
private String replaceRoutePatternWithGroup() {
Pattern pattern = Pattern.compile(INDEX_PATTERN);
Matcher matcher = pattern.matcher(this.routePattern);
return matcher.replaceAll("([^/]+)");
}
public Map<String, String> extract() {
Pattern pattern = Pattern.compile(this.replaceRoutePatternWithGroup());
Matcher matcher = pattern.matcher(this.path);
final Map<String, String> results = new HashMap<>();
if (matcher.find()) {
this.extractPositions().entrySet().stream().forEach(s -> {
results.put(s.getValue(), matcher.group(s.getKey() + 1));
});
}
return results;
}
}

As per this GitHub issue response via JRoper
onRequestReceived is the thing that does the routing and tags the request, so of course it's not going to have any of the routing information when it's first invoked, only after it's invoked.
val p = """\$([^<]+)<([^>]+)>""".r
override def onRequestReceived(request: RequestHeader) = {
val (taggedRequest, handler) = super.onRequestReceived(request)
val pattern = taggedRequest.tags("ROUTE_PATTERN")
val paramNames = p.findAllMatchIn(pattern).map(m => m.group(1)).toList
val pathRegex = ("^" + p.replaceAllIn(pattern, m => "(" + m.group(2) + ")") + "$").r
val paramValues = pathRegex.findFirstMatchIn(request.path).get.subgroups
val params: Map[String, String] = paramNames.zip(paramValues).toMap
// ^ your params map, will be Map("name" -> "Pete", "age" -> "41")
(taggedRequest, handler)
}
That said, there are usually better, more typesafe ways to achieve whatever you're trying to achieve. If you depend on there being specific parameters in the URL, then a filter is not the right thing, because filters apply to all requests, whether they have those parameters or not. Rather, you should probably be using action composition or a custom action builder, like so:
case class MyAction(name: String, age: Int) extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
// Do your filtering here, you have access to both the name and age above
block(request)
}
}
def foo(name: String, age: Int) = MyAction(name, age) { request =>
Ok("Hello world")
}
def bar(name: String, age: Int) = MyAction(name, age).async { request =>
Future.successful(Ok("Hello world"))
}

Related

Akka streams change return type of 3rd party flow/stage

I have a graph that reads from sqs, writes to another system and then deletes from sqs. In order to delete from sqs i need a receipt handle on the SqsMessage object
In the case of Http flows the signature of the flow allows me to say which type gets emitted downstream from the flow,
Flow[(HttpRequest, T), (Try[HttpResponse], T), HostConnectionPool]
In this case i can set T to SqsMessage and i still have all the data i need.
However some connectors e.g google cloud pub sub emits a completely useless (to me) pub sub id.
Downstream of the pub sub flow I need to be able to access the sqs message id which i had prior to the pub sub flow.
What is the best way to work around this without rewriting the pub sub connector
I conceptually want something a bit like this:
Flow[SqsMessage] //i have my data at this point
within(
.map(toPubSubMessage)
.via(pubSub))
... from here i have the same type i had before within however it still behaves like a linear graph with back pressure etc
You can use PassThrough integration pattern.
As example of usage look on akka-streams-kafka -> Class akka.kafka.scaladsl.Producer -> Mehtod def flow[K, V, PassThrough]
So just implement your own Stage with PassThrough element, example inakka.kafka.internal.ProducerStage[K, V, PassThrough]
package my.package
import java.util.concurrent.atomic.AtomicInteger
import scala.concurrent.Future
import scala.util.{Failure, Success, Try}
import akka.stream._
import akka.stream.ActorAttributes.SupervisionStrategy
import akka.stream.stage._
final case class Message[V, PassThrough](record: V, passThrough: PassThrough)
final case class Result[R, PassThrough](result: R, message: PassThrough)
class PathThroughStage[R, V, PassThrough]
extends GraphStage[FlowShape[Message[V, PassThrough], Future[Result[R, PassThrough]]]] {
private val in = Inlet[Message[V, PassThrough]]("messages")
private val out = Outlet[Result[R, PassThrough]]("result")
override val shape = FlowShape(in, out)
override protected def createLogic(inheritedAttributes: Attributes) = {
val logic = new GraphStageLogic(shape) with StageLogging {
lazy val decider = inheritedAttributes.get[SupervisionStrategy]
.map(_.decider)
.getOrElse(Supervision.stoppingDecider)
val awaitingConfirmation = new AtomicInteger(0)
#volatile var inIsClosed = false
var completionState: Option[Try[Unit]] = None
override protected def logSource: Class[_] = classOf[PathThroughStage[R, V, PassThrough]]
def checkForCompletion() = {
if (isClosed(in) && awaitingConfirmation.get == 0) {
completionState match {
case Some(Success(_)) => completeStage()
case Some(Failure(ex)) => failStage(ex)
case None => failStage(new IllegalStateException("Stage completed, but there is no info about status"))
}
}
}
val checkForCompletionCB = getAsyncCallback[Unit] { _ =>
checkForCompletion()
}
val failStageCb = getAsyncCallback[Throwable] { ex =>
failStage(ex)
}
setHandler(out, new OutHandler {
override def onPull() = {
tryPull(in)
}
})
setHandler(in, new InHandler {
override def onPush() = {
val msg = grab(in)
val f = Future[Result[R, PassThrough]] {
try {
Result(// TODO YOUR logic
msg.record,
msg.passThrough)
} catch {
case exception: Exception =>
decider(exception) match {
case Supervision.Stop =>
failStageCb.invoke(exception)
case _ =>
Result(exception, msg.passThrough)
}
}
if (awaitingConfirmation.decrementAndGet() == 0 && inIsClosed) checkForCompletionCB.invoke(())
}
awaitingConfirmation.incrementAndGet()
push(out, f)
}
override def onUpstreamFinish() = {
inIsClosed = true
completionState = Some(Success(()))
checkForCompletion()
}
override def onUpstreamFailure(ex: Throwable) = {
inIsClosed = true
completionState = Some(Failure(ex))
checkForCompletion()
}
})
override def postStop() = {
log.debug("Stage completed")
super.postStop()
}
}
logic
}
}

First render in Play 2.3.x does not use correct language

I'm using simple middleware to update the Result
object WithLanguage extends ActionFunction[User, User] {
def invokeBlock[A](request: User[A], block: (User[A]) => Future[Result]): Future[Result] = {
if(request.cookies.get(Play.langCookieName).isEmpty){
val lang: Lang = Lang.get(request.user.language).getOrElse(play.api.i18n.Lang.preferred(request.acceptLanguages))
block(request).withLang(lang))
} else {
block(request)
}
}
}
But the first response is not rendered with the appropriate language - on refresh yes.
I can get it to work by performing a circular redirect in the case of setting a new cookie Future(Redirect(request.uri).withLang(...) but I wonder if there is a cleaner way.
This works ... It's possible to modify the request headers imperatively, but writing this way feels wrong.
object WithLanguage extends ActionFunction[User, User] {
def invokeBlock[A](request: User[A], block: (User[A]) => Future[Result]): Future[Result] = {
if(request.cookies.get(Play.langCookieName).isEmpty){
val cookies = Cookies(request.headers.get(COOKIE)).cookies + (Play.langCookieName -> Cookie(Play.langCookieName, request.user.language.code))
val updatedHeaders = new Headers {
val data: Seq[(String, Seq[String])] = (request.headers.toMap + (COOKIE -> Seq(Cookies.encode(cookies.values.toSeq)))).toSeq
}
val modifiedRequest = User(
user = request.user,
request = Request(request.copy(headers = updatedHeaders), request.body)
)
block(modifiedRequest).map(_.withLang(request.user.language)) // redundant cookie set on result
} else {
block(request)
}
}
}

Neo4j OGM example with Scala

I tried the example mentioned in Luanne's article The essence of Spring Data Neo4j 4 in Scala. The code can be found in the neo4j-ogm-scala repository.
package neo4j.ogm.scala.domain
import org.neo4j.ogm.annotation.GraphId;
import scala.beans.BeanProperty
import org.neo4j.ogm.annotation.NodeEntity
import org.neo4j.ogm.annotation.Relationship
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
abstract class Entity {
#GraphId
#BeanProperty
var id: Long = _
override def equals(o: Any): Boolean = o match {
case other: Entity => other.id.equals(this.id)
case _ => false
}
override def hashCode: Int = id.hashCode()
}
#NodeEntity
class Category extends Entity {
var name: String = _
def this(name: String) {
this()
this.name = name
}
}
#NodeEntity
class Ingredient extends Entity {
var name: String = _
#Relationship(`type` = "HAS_CATEGORY", direction = "OUTGOING")
var category: Category = _
#Relationship(`type` = "PAIRS_WITH", direction = "UNDIRECTED")
var pairings: Set[Pairing] = Set()
def addPairing(pairing: Pairing): Unit = {
pairing.first.pairings +(pairing)
pairing.second.pairings +(pairing)
}
def this(name: String, category: Category) {
this()
this.name = name
this.category = category
}
}
#RelationshipEntity(`type` = "PAIRS_WITH")
class Pairing extends Entity {
#StartNode
var first: Ingredient = _
#EndNode
var second: Ingredient = _
def this(first: Ingredient, second: Ingredient) {
this()
this.first = first
this.second = second
}
}
object Neo4jSessionFactory {
val sessionFactory = new SessionFactory("neo4j.ogm.scala.domain")
def getNeo4jSession(): Session = {
System.setProperty("username", "neo4j")
System.setProperty("password", "neo4j")
sessionFactory.openSession("http://localhost:7474")
}
}
object Main extends App {
val spices = new Category("Spices")
val turmeric = new Ingredient("Turmeric", spices)
val cumin = new Ingredient("Cumin", spices)
val pairing = new Pairing(turmeric, cumin)
cumin.addPairing(pairing)
val session = Neo4jSessionFactory.getNeo4jSession()
val tx: Transaction = session.beginTransaction()
try {
session.save(spices)
session.save(turmeric)
session.save(cumin)
session.save(pairing)
tx.commit()
} catch {
case e: Exception => // tx.rollback()
} finally {
// tx.commit()
}
}
The problem is that nothing gets saved to Neo4j. Can you please point out the problem in my code?
Thanks,
Manoj.
Scala’s Long is an instance of a Value class. Value classes were explicitly introduced to avoid allocating runtime objects. At the JVM level therefore Scala's Long is equivalent to Java’s primitive long which is why it has the primitive type signature J. It cannot be therefore be null, and should not be used as a graphId. Although Scala mostly will do auto-boxing between its own Long and Java’s Long class, this doesn’t apply to declarations, only to operations on those objects.
The #GraphId isn't being picked up on your entities. I have zero knowledge of Scala but it looks like the scala long isn't liked much by the OGM; var id: java.lang.Long = _ works fine.

Custom json serialization of structured scala case classes

I have some working jackson scala module code for roundtripping scala case classes. Jackson worked great for flat case classes but when I made one which contains a list of other case classes the amount of code I seemed to need was a lot. Consider:
abstract class Message
case class CardDrawn(player: Long, card: Int, mType: String = "CardDrawn") extends Message
case class CardSet(cards: List[CardDrawn], mType: String = "CardSet") extends Message
To get the CardSet to roundtrip to/from json with jackson scala module I used a custom serializer/deserializer written in java:
object ScrumGameMashaller {
val mapper = new ObjectMapper()
val module = new SimpleModule("CustomSerializer")
module.addSerializer(classOf[CardSet], new CardSetSerializer)
module.addDeserializer(classOf[CardSet], new CardSetDeserializer)
val scalaModule = DefaultScalaModule
mapper.registerModule(scalaModule)
mapper.registerModule(module)
def jsonFrom(value: Any): String = {
import java.io.StringWriter
val writer = new StringWriter()
mapper.writeValue(writer, value)
writer.toString
}
private[this] def objectFrom[T: Manifest](value: String): T =
mapper.readValue(value, typeReference[T])
private[this] def typeReference[T: Manifest] = new TypeReference[T] {
override def getType = typeFromManifest(manifest[T])
}
private[this] def typeFromManifest(m: Manifest[_]): Type = {
if (m.typeArguments.isEmpty) { m.runtimeClass }
else new ParameterizedType {
def getRawType = m.runtimeClass
def getActualTypeArguments = m.typeArguments.map(typeFromManifest).toArray
def getOwnerType = null
}
}
with serializer:
public class CardSetSerializer extends JsonSerializer<CardSet> {
#Override
public void serialize(CardSet cardSet, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeArrayFieldStart("cards");
List<CardDrawn> cardsDrawn = cardSet.cards();
scala.collection.Iterator<CardDrawn> iter = cardsDrawn.iterator();
while(iter.hasNext()){
CardDrawn cd = iter.next();
cdSerialize(jgen,cd);
}
jgen.writeEndArray();
jgen.writeStringField("mType", "CardSet");
jgen.writeEndObject();
}
private void cdSerialize(JsonGenerator jgen, CardDrawn cd) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("player", cd.player());
jgen.writeNumberField("card", cd.card());
jgen.writeEndObject();
}
}
and matching deserializer:
public class CardSetDeserializer extends JsonDeserializer<CardSet> {
private static class CardDrawnTuple {
Long player;
Integer card;
}
#Override
public CardSet deserialize(JsonParser jsonParser, DeserializationContext cxt) throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode root = oc.readTree(jsonParser);
JsonNode cards = root.get("cards");
Iterator<JsonNode> i = cards.elements();
List<CardDrawn> cardObjects = new ArrayList<>();
while( i.hasNext() ){
CardDrawnTuple t = new CardDrawnTuple();
ObjectNode c = (ObjectNode) i.next();
Iterator<Entry<String, JsonNode>> fields = c.fields();
while( fields.hasNext() ){
Entry<String,JsonNode> f = fields.next();
if( f.getKey().equals("player")) {
t.player = f.getValue().asLong();
} else if( f.getKey().equals("card")){
t.card = f.getValue().asInt();
} else {
System.err.println(CardSetDeserializer.class.getCanonicalName()+ " : unknown field " + f.getKey());
}
}
CardDrawn cd = new CardDrawn(t.player, t.card, "CardDrawn");
cardObjects.add(cd);
}
return new CardSet(JavaConversions.asScalaBuffer(cardObjects).toList(), "CardSet");
}
}
This seems like a lot code to deal with something fairly vanilla in the scala. Can this code be improved (what did I miss that jackson has to make this easy)? Else is there a library which will do structured case classes automatically? The jerkson examples looked easy but that seems to have been abandoned.
Argonaut does a great job. Mark Hibbard helped me out with getting the example below working. All that is needed is to create a codec for the types and it will implicitly add an asJson to your objects to turn them into strings. It will also add a decodeOption[YourClass] to strings to extract an object. The following:
package argonaut.example
import argonaut._, Argonaut._
abstract class Message
case class CardDrawn(player: Long, card: Int, mType: String = "CardDrawn") extends Message
case class CardSet(cards: List[CardDrawn], mType: String = "CardSet") extends Message
object CardSetExample {
implicit lazy val CodecCardSet: CodecJson[CardSet] = casecodec2(CardSet.apply, CardSet.unapply)("cards","mType")
implicit lazy val CodecCardDrawn: CodecJson[CardDrawn] = casecodec3(CardDrawn.apply, CardDrawn.unapply)("player", "card", "mType")
def main(args: Array[String]): Unit = {
val value = CardSet(List(CardDrawn(1L,2),CardDrawn(3L,4)))
println(s"Got some good json ${value.asJson}")
val jstring =
"""{
| "cards":[
| {"player":"1","card":2,"mType":"CardDrawn"},
| {"player":"3","card":4,"mType":"CardDrawn"}
| ],
| "mType":"CardSet"
| }""".stripMargin
val parsed: Option[CardSet] =
jstring.decodeOption[CardSet]
println(s"Got a good object ${parsed.get}")
}
}
outputs:
Got some good json {"cards":[{"player":"1","card":2,"mType":"CardDrawn"},{"player":"3","card":4,"mType":"CardDrawn"}],"mType":"CardSet"}
Got a good object CardSet(List(CardDrawn(1,2,CardDrawn), CardDrawn(3,4,CardDrawn)),CardSet)
The question is old but maybe someone could still find it helpful. Apart from Argonaut, Scala has several Json libraries. Here you can find a list of them updated to the beginning of 2016 (and it still gives you a good overall picture).
Most of them (probably all) should allow you to come up with a drier version of your custom serializer/deserailizer. My preference goes to json4s which aims to provide a single AST across multiple libraries including Jackson (a bit like slf4j does for logging libraries). In this post you can find a working example of a Json custom serializer/deserializer using Json4s and Akka Http.

How to use java.nio.file.Files.walkFileTree in Scala

I want to use the new java.nio.file.Files.walkFileTree in Scala. And I was even successful:
class Visitor
extends
java.nio.file.SimpleFileVisitor [java.nio.file.Path]
{
override def visitFile(
File : java.nio.file.Path,
Attrs : java.nio.file.attribute.BasicFileAttributes) : java.nio.file.FileVisitResult =
{
if (! File.toString.contains(".svn"))
{
System.out.println(File);
} // if
java.nio.file.FileVisitResult.CONTINUE;
} // visitFile
} // Visitor
java.nio.file.Files.walkFileTree (Project_Home, new Visitor)
But while this code works fine I feels a bit like carrying Java paradigms into Scala. So a question to the true Scala Gurus: Is there anything I could improve or is this just it?
A Visitor is really a foreach without the benefit of functions, so let's make a foreach. The method is static, but it takes as first argument a Path, so we'll enrich Path with a foreach method, which is done with something like this:
import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes
implicit def fromNioPath(path: Path): TraverseFiles = new TraversePath(path)
And everything else is inside the TraversePath class, which looks somewhat like this:
class TraversePath(path: Path) {
def foreach(f: (Path, BasicFileAttributes) => Unit) {
// ...
}
}
This is enough for you to write this:
ProjectHome foreach ((file, _) => if (!file.toString.contains(".svn")) println(File))
Of course, it won't actually do anything, so let's get it to do something:
class TraversePath(path: Path) {
def foreach(f: (Path, BasicFileAttributes) => Unit) {
class Visitor extends SimpleFileVisitor[Path] {
override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try {
f(file, attrs)
FileVisitResult.CONTINUE
} catch {
case _ => FileVisitResult.TERMINATE
}
}
Files.walkFileTree(path, new Visitor)
}
}
There, now that line will do the same thing as your code did! However, we can improve it further. It happens that foreach is the only method required of Traversable, so we can extend that class, and get all the methods of a Scala collection!
The only problem is that a Traversable.foreach function takes only one argument, and here we are taking two. We can change it into receive a tuple, though. Here's the full code:
import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes
import scala.collection.Traversable
// Make it extend Traversable
class TraversePath(path: Path) extends Traversable[(Path, BasicFileAttributes)] {
// Make foreach receive a function from Tuple2 to Unit
def foreach(f: ((Path, BasicFileAttributes)) => Unit) {
class Visitor extends SimpleFileVisitor[Path] {
override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try {
// Pass a tuple to f
f(file -> attrs)
FileVisitResult.CONTINUE
} catch {
case _ => FileVisitResult.TERMINATE
}
}
Files.walkFileTree(path, new Visitor)
}
}
ProjectHome foreach {
// use case to seamlessly deconstruct the tuple
case (file, _) => if (!file.toString.contains(".svn")) println(File)
}
Disclaimer: I have tested none of this code, because I don't have Java 7 installed. There are probably some bugs.
You could make your code a bit more pretty, but at the end of the day it would still look like the plain old visitor pattern.
Here's Daniel's script made compilable:
import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes
import scala.collection.Traversable
// Make it extend Traversable
class TraversePath(path: Path) extends Traversable[(Path, BasicFileAttributes)] {
// Make foreach receive a function from Tuple2 to Unit
def foreach[U](f: ((Path, BasicFileAttributes)) => U) {
class Visitor extends SimpleFileVisitor[Path] {
override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try {
// Pass a tuple to f
f(file -> attrs)
FileVisitResult.CONTINUE
} catch {
case _ => FileVisitResult.TERMINATE
}
}
Files.walkFileTree(path, new Visitor)
}
}
val projectHome = new TraversePath(Paths.get("."))
projectHome foreach {
// use case to seamlessly deconstruct the tuple
case (file:Path, attr:BasicFileAttributes) => if (!file.toString.contains(".svn")) println(file)
}
Taking Daniel's answer as fundament, I have worked a little to make Path accessible with convenient implicits, as you are used in collections. Notice that not all functions are included.
class TraversePath(path: Path) {
def foreach(f: (Path, BasicFileAttributes) => Unit) {
Files.walkFileTree(path, new SimpleFileVisitor[Path] {
override def visitFile(file: Path, attrs: BasicFileAttributes) = {
f(file, attrs)
FileVisitResult.CONTINUE
}
})
}
/**
* foreach that takes FileVisitResult instead of Unit
*/
def foreach2(f: (Path, BasicFileAttributes) => FileVisitResult) {
Files.walkFileTree(path, new SimpleFileVisitor[Path] {
override def visitFile(file: Path, attrs: BasicFileAttributes) = f(file, attrs)
})
}
def foldLeft[T](t: T)(f: (T, Path) => T) = {
var current = t
foreach((p, _) => current = f(current, p))
current
}
def forall(f: Path => Boolean) = {
var ret = true
foreach2((p, _) =>
if ( !f(path) ) {
ret = false
FileVisitResult.TERMINATE
}
else
FileVisitResult.CONTINUE
)
ret
}
def exists(f: Path => Boolean) = {
var ret = false
foreach2((p, _) =>
if ( f(path) ) {
ret = true
FileVisitResult.TERMINATE
}
else
FileVisitResult.CONTINUE
)
}
/**
* Directly modifies the underlying path.
*/
def mapReal(f: Path => Path) = foreach((p, _) => Files.move(p, f(p)))
/**
* #param f map function
* #return a left-folded list with the map function applied to each element
*/
def map(f: Path => Path) = foldLeft(Nil: List[Path]) {
case (xs, p) => xs ::: f(p) :: Nil
}
def find(f: Path => Boolean) = {
var k = None: Option[Path]
foreach2((p, _) =>
if ( f(p) ) {
k = Some(p)
FileVisitResult.TERMINATE
} else FileVisitResult.CONTINUE
)
k
}
}
implicit def fromNioPath(path: Path) = new TraversePath(path)
The java.nio API is extremely powerful and is, IMHO, very sufficing for use with Scala. With these implicits (and more, if you want to write some functions), it is very simple to accomplish even harder tasks.
You could use this now by writing something like this:
val path1 = Paths.get(sys.props("user.home"), "workspace")
val path2 = Paths.get(sys.props("user.home"), "workspace2")
val list1 = path1.foldLeft(Nil: List[Path]) {
(xs, p) => xs ::: path1.relativize(p) :: Nil
}
val list2 = path2.foldLeft(Nil: List[Path]) {
(xs, p) => xs ::: path2.relativize(p) :: Nil
}
(list1 diff list2) foreach println
Regards,
Danyel
FIles.walkFileTree example to compare two directories / synchronizing two directories
for file difference
private static void compareDirectories(String srcPath, String destPath) throws IOException, InterruptedException {
System.out.println("sync. started....");
final Path mainDir = Paths.get(srcPath);
final Path otherDir = Paths.get(destPath);
// Walk thru mainDir directory
Files.walkFileTree(mainDir, new FileVisitor<Path>() {
#Override
public FileVisitResult preVisitDirectory(Path path,
BasicFileAttributes atts) throws IOException {
return visitFile(path, atts);
}
#Override
public FileVisitResult visitFile(Path path, BasicFileAttributes mainAtts)
throws IOException {
// I've seen two implementations on windows and MacOSX. One has passed the relative path, one the absolute path.
// This works in both cases
Path relativePath = mainDir.relativize(mainDir.resolve(path));
File tmpFile = new File(otherDir+"/"+relativePath);
if(tmpFile.exists()) {
BasicFileAttributes otherAtts = Files.readAttributes(otherDir.resolve(relativePath), BasicFileAttributes.class);
// Do your comparison logic here: we are skipping directories as all directories are traversed automatically
if(!new File(path.toString()).isDirectory()) {
//write your logic for comparing files
compareEntries(mainDir, otherDir, relativePath, mainAtts, otherAtts);
}
else {
File src = new File(path.toString());
//write your logic here for comparing directories
compareDirectories(src,tmpFile.toPath().toString()+"/"+s);
}
}
else {
//this function will copy missing files in destPath from srcPath recursive function till depth of directory structure
copyFolderOrFiles(new File(path.toString()), tmpFile);
}
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult postVisitDirectory(Path path,
IOException exc) throws IOException {
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult visitFileFailed(Path path, IOException exc)
throws IOException {
exc.printStackTrace();
// If the root directory has failed it makes no sense to continue
return (path.equals(mainDir))? FileVisitResult.TERMINATE:FileVisitResult.CONTINUE;
}
});
}
Extending on the ideas of the other posts. I like the solution where we can match over case classes. The following code just returns a collection of strings for the different events that the visitor would have been called on.
FileWalker(java.nio.file.Paths.get(" your dir ")).map({
case PreVisitDirectory(dir, atts) => s"about to visit dir ${dir}"
case PostVisitDirectory(dir, exc) => s"have visited dir ${dir}"
case VisitFile(file, attrs) => s"visiting file ${file}"
case VisitFileFailed(file, exc) => s"failed to visit ${file}"
})
The implementation of FileWalker is:
import java.io.IOException
import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor}
import java.nio.file.attribute.BasicFileAttributes
trait FileVisitEvent
case class PreVisitDirectory(path: Path, atts: BasicFileAttributes) extends FileVisitEvent
case class PostVisitDirectory(dir: Path, exc: IOException) extends FileVisitEvent
case class VisitFile(file: Path, attrs: BasicFileAttributes) extends FileVisitEvent
case class VisitFileFailed(file: Path, exc: IOException) extends FileVisitEvent
/**
* Scala style walker for a directory tree
*
* Is a treversable over the tree which traverses different event types extending {{FileVisitEvent}}
*
* #param from
*/
class FileWalker(from: Path) extends Traversable[FileVisitEvent] {
// just to simplify error handling
def wrapper(x: => Unit): FileVisitResult = try {
x
FileVisitResult.CONTINUE
}
catch {
case _ : Throwable => FileVisitResult.TERMINATE
}
override def foreach[U](f: (FileVisitEvent) => U): Unit = {
Files.walkFileTree(from, new SimpleFileVisitor[Path] {
override def preVisitDirectory(dir: Path, atts: BasicFileAttributes): FileVisitResult =
wrapper( f(PreVisitDirectory(dir, atts)))
override def postVisitDirectory(dir: Path, exc: IOException): FileVisitResult =
wrapper(f(PostVisitDirectory(dir, exc)))
override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult =
wrapper(f(VisitFile( file, attrs ) ))
override def visitFileFailed(file: Path, exc: IOException): FileVisitResult =
wrapper( f(VisitFileFailed( file, exc ) ))
})
}
}
object FileWalker {
def apply( from : Path ) = new FileWalker( from )
}