From 6c676a867b4dcbe4d8afa1ff4284f7efe2f75e5e Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 14 Jun 2016 06:15:31 -0700 Subject: [PATCH 01/47] Initial commit --- .gitignore | 8 ++ LICENSE | 8 ++ README.md | 3 + app/controllers/FormData.java | 17 +++ app/controllers/HomeController.java | 53 ++++++++ app/controllers/MyFileMultipartFormData.java | 68 ++++++++++ .../MyMultipartFormDataBodyParser.java | 127 ++++++++++++++++++ app/views/index.scala.html | 9 ++ app/views/main.scala.html | 23 ++++ build.sbt | 7 + conf/application.conf | 1 + conf/logback.xml | 41 ++++++ conf/routes | 11 ++ project/build.properties | 4 + project/plugins.sbt | 8 ++ public/images/favicon.png | Bin 0 -> 687 bytes public/javascripts/hello.js | 3 + public/stylesheets/main.css | 0 18 files changed, 391 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/controllers/FormData.java create mode 100644 app/controllers/HomeController.java create mode 100644 app/controllers/MyFileMultipartFormData.java create mode 100644 app/controllers/MyMultipartFormDataBodyParser.java create mode 100644 app/views/index.scala.html create mode 100644 app/views/main.scala.html create mode 100644 build.sbt create mode 100644 conf/application.conf create mode 100644 conf/logback.xml create mode 100644 conf/routes create mode 100644 project/build.properties create mode 100644 project/plugins.sbt create mode 100644 public/images/favicon.png create mode 100644 public/javascripts/hello.js create mode 100644 public/stylesheets/main.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..eb372fc71 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +logs +target +/.idea +/.idea_modules +/.classpath +/.project +/.settings +/RUNNING_PID diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..4ca0c6488 --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +This software is licensed under the Apache 2 license, quoted below. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project 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. diff --git a/README.md b/README.md new file mode 100644 index 000000000..853d6f702 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +## Play File Upload using a custom BodyParser + +This is a sample project that shows how to upload a file through Akka Streams using a custom BodyParser using Akka Streams using the Java API. diff --git a/app/controllers/FormData.java b/app/controllers/FormData.java new file mode 100644 index 000000000..3dd6f04b3 --- /dev/null +++ b/app/controllers/FormData.java @@ -0,0 +1,17 @@ +package controllers; + +public class FormData { + private String name; + + public FormData() {} + + public FormData(String name) { + this.name = name; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } +} diff --git a/app/controllers/HomeController.java b/app/controllers/HomeController.java new file mode 100644 index 000000000..1dc04dd19 --- /dev/null +++ b/app/controllers/HomeController.java @@ -0,0 +1,53 @@ +package controllers; + +import play.data.Form; +import play.mvc.BodyParser; +import play.mvc.Controller; +import play.mvc.Http; +import play.mvc.Result; +import views.html.index; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +/** + * This class uses a custom body parser to change the upload type. + */ +@Singleton +public class HomeController extends Controller { + + private final play.data.FormFactory formFactory; + + @Inject + public HomeController(play.data.FormFactory formFactory) { + this.formFactory = formFactory; + } + + public Result index() { + Form form = formFactory.form(FormData.class); + play.mvc.Http.Context context = play.mvc.Http.Context.current(); + return ok(index.render(form, context.messages())); + } + + @BodyParser.Of(MyMultipartFormDataBodyParser.class) + public Result upload() throws IOException { + final Http.Context context = Http.Context.current(); + final Http.Request request = context.request(); + + final Http.MultipartFormData.FilePart filePart = request.body().asMultipartFormData().getFile("name"); + final File file = (File) filePart.getFile(); + final long data = operateOnTempFile(file); + return ok("file size = " + data + ""); + } + + private long operateOnTempFile(File file) throws IOException { + final long size = Files.size(file.toPath()); + Files.deleteIfExists(file.toPath()); + return size; + } + +} + diff --git a/app/controllers/MyFileMultipartFormData.java b/app/controllers/MyFileMultipartFormData.java new file mode 100644 index 000000000..b91ffa203 --- /dev/null +++ b/app/controllers/MyFileMultipartFormData.java @@ -0,0 +1,68 @@ +package controllers; + +import play.api.mvc.MultipartFormData; +import play.mvc.Http; +import scala.Function1; +import scala.collection.Seq; +import scala.compat.java8.OptionConverters; +import scala.runtime.AbstractFunction1; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static scala.collection.JavaConverters.mapAsJavaMapConverter; +import static scala.collection.JavaConverters.seqAsJavaListConverter; + +/** + * Extends Http.MultipartFormData to use File specifically, + * converting from Scala API to Java API. + */ +class MyFileMultipartFormData extends Http.MultipartFormData { + + private play.api.mvc.MultipartFormData fileMultipartFormData; + + MyFileMultipartFormData(play.api.mvc.MultipartFormData fileMultipartFormData) { + this.fileMultipartFormData = fileMultipartFormData; + } + + @Override + public Map asFormUrlEncoded() { + return mapAsJavaMapConverter( + fileMultipartFormData + .asFormUrlEncoded() + .mapValues(arrayFunction()) + ).asJava(); + } + + // maps from Scala Seq to String array + private Function1, String[]> arrayFunction() { + return new AbstractFunction1, String[]>() { + @Override + public String[] apply(Seq v1) { + String[] array = new String[v1.size()]; + v1.copyToArray(array); + return array; + } + }; + } + + @Override + public List> getFiles() { + return seqAsJavaListConverter(fileMultipartFormData.files()) + .asJava() + .stream() + .map(this::convert) + .collect(Collectors.toList()); + } + + private FilePart convert(MultipartFormData.FilePart filePart) { + return new FilePart<>( + filePart.key(), + filePart.filename(), + OptionConverters.toJava(filePart.contentType()).orElse(null), + filePart.ref() + ); + } +} diff --git a/app/controllers/MyMultipartFormDataBodyParser.java b/app/controllers/MyMultipartFormDataBodyParser.java new file mode 100644 index 000000000..52928af14 --- /dev/null +++ b/app/controllers/MyMultipartFormDataBodyParser.java @@ -0,0 +1,127 @@ +package controllers; + +import akka.stream.IOResult; +import akka.stream.Materializer; +import akka.stream.javadsl.FileIO; +import akka.stream.javadsl.Sink; +import akka.util.ByteString; +import play.core.j.JavaParsers; +import play.core.parsers.Multipart; +import play.libs.F; +import play.libs.streams.Accumulator; +import play.mvc.BodyParser; +import play.mvc.Http; +import play.mvc.Result; +import scala.Function1; +import scala.Option; +import scala.runtime.AbstractFunction1; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.EnumSet; +import java.util.Set; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; + +/** + * This class is a custom body parser that farms out most of the work of + * multipart form processing to the underlying core parser, but specifies + * that form data should be treated as type "File" instead of the default + * "TemporaryFile". + */ +@SuppressWarnings("WeakerAccess") +class MyMultipartFormDataBodyParser implements BodyParser> { + private final play.api.mvc.BodyParser> delegate; + + private final Materializer materializer; + private final int maxLength; + + @Inject + public MyMultipartFormDataBodyParser(Materializer materializer, play.api.http.HttpConfiguration config) { + this.maxLength = (int) config.parser().maxDiskBuffer(); + this.materializer = materializer; + this.delegate = multipartFormDataBodyParser(); + } + + /** + * Delegates underlying functionality to another body parser and converts the + * result to Java API. + */ + @Override + public Accumulator>> apply(Http.RequestHeader request) { + return delegate.apply(request._underlyingHeader()) + .asJava() + .map(result -> { + if (result.isLeft()) { + return F.Either.Left(result.left().get().asJava()); + } else { + final play.api.mvc.MultipartFormData scalaData = result.right().get(); + return F.Either.Right(new MyFileMultipartFormData(scalaData)); + } + }, + JavaParsers.trampoline() + ); + } + + /** + * Generates a temp file directly without going through TemporaryFile. + */ + private File generateTempFile() { + try { + final FileAttribute> attr = PosixFilePermissions.asFileAttribute(EnumSet.of(OWNER_READ, OWNER_WRITE)); + final Path path = Files.createTempFile("multipartBody", "tempFile", attr); + return path.toFile(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Returns a BodyParser using a custom Accumulator from an Akka Streams sink that + * takes FileIO from ByteString and puts it into a FilePart with File. + */ + private play.api.mvc.BodyParser> multipartFormDataBodyParser() { + return Multipart.multipartParser(maxLength, asScalaFunction(fileInfo -> { + final String filename = fileInfo.fileName(); + final String partname = fileInfo.partName(); + final Option contentType = fileInfo.contentType(); + + final File file = generateTempFile(); + + final play.api.mvc.MultipartFormData.FilePart part = new play.api.mvc.MultipartFormData.FilePart<>(partname, filename, contentType, file); + + final Sink> sink = FileIO.toFile(file); + return Accumulator.fromSink( + sink.mapMaterializedValue(completionStage -> + completionStage.thenApplyAsync(results -> part) + )).asScala(); + }), materializer); + } + + /** + * Utility function to convert a java.util.Function into a scala.Function1. + */ + private Function1 asScalaFunction(Function function) { + // This is needed because Multipart.multipartParser takes a Function1 directly, + // and there is no equivalent Java wrapper. + return new AbstractFunction1() { + @Override + public R apply(T1 t1) { + return function.apply(t1); + } + }; + } + +} + + + diff --git a/app/views/index.scala.html b/app/views/index.scala.html new file mode 100644 index 000000000..ce9956b9e --- /dev/null +++ b/app/views/index.scala.html @@ -0,0 +1,9 @@ +@(form: Form[controllers.FormData])(implicit messages: play.i18n.Messages) + +@main("Welcome to Play") { + + @helper.form(action = routes.HomeController.upload, 'enctype -> "multipart/form-data") { + @helper.inputFile(form("name")) + + } +} diff --git a/app/views/main.scala.html b/app/views/main.scala.html new file mode 100644 index 000000000..9414f4be6 --- /dev/null +++ b/app/views/main.scala.html @@ -0,0 +1,23 @@ +@* + * This template is called from the `index` template. This template + * handles the rendering of the page header and body tags. It takes + * two arguments, a `String` for the title of the page and an `Html` + * object to insert into the body of the page. + *@ +@(title: String)(content: Html) + + + + + @* Here's where we render the page title `String`. *@ + @title + + + + + + @* And here's where we render the `Html` object containing + * the page content. *@ + @content + + diff --git a/build.sbt b/build.sbt new file mode 100644 index 000000000..13aa0c233 --- /dev/null +++ b/build.sbt @@ -0,0 +1,7 @@ +name := """play-fileupload-java""" + +version := "1.0-SNAPSHOT" + +lazy val root = (project in file(".")).enablePlugins(PlayJava) + +scalaVersion := "2.11.8" diff --git a/conf/application.conf b/conf/application.conf new file mode 100644 index 000000000..602fd67cd --- /dev/null +++ b/conf/application.conf @@ -0,0 +1 @@ +play.crypto.secret="changeme" diff --git a/conf/logback.xml b/conf/logback.xml new file mode 100644 index 000000000..86ec12c0a --- /dev/null +++ b/conf/logback.xml @@ -0,0 +1,41 @@ + + + + + + + ${application.home:-.}/logs/application.log + + %date [%level] from %logger in %thread - %message%n%xException + + + + + + %coloredLevel %logger{15} - %message%n%xException{10} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/routes b/conf/routes new file mode 100644 index 000000000..7cb62e6ad --- /dev/null +++ b/conf/routes @@ -0,0 +1,11 @@ +# Routes +# This file defines all application routes (Higher priority routes first) +# ~~~~ + +# An example controller showing a sample home page +GET / controllers.HomeController.index + +POST /upload controllers.HomeController.upload + +# Map static resources from the /public folder to the /assets URL path +GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 000000000..5ea0ff21f --- /dev/null +++ b/project/build.properties @@ -0,0 +1,4 @@ +#Activator-generated Properties +#Sun May 29 09:20:38 PDT 2016 +template.uuid=28ae6884-7b61-401c-834c-704789d1228a +sbt.version=0.13.11 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 000000000..1529c0710 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,8 @@ +// The Play plugin +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.3") + +// Play enhancer - this automatically generates getters/setters for public fields +// and rewrites accessors of these fields to use the getters/setters. Remove this +// plugin if you prefer not to have this feature, or disable on a per project +// basis using disablePlugins(PlayEnhancer) in your build.sbt +addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % "1.1.0") diff --git a/public/images/favicon.png b/public/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d92d2ae47434d9a61c90bc205e099b673b9dd5 GIT binary patch literal 687 zcmV;g0#N;lP)ezT{T_ZJ?}AL z5NC{NW(ESID=>(O3&Eg8 zmA9J&6c`h4_f6L;=bU>_H8aNG`kfvCj9zomNt)?O;rzWqZs0LEt%1WB218%1fo9uB zsW^yhBR7C(mqN%GEK9&msg0~ zWY?#bf4q8G-~2KttQZ($odJvy&_-~f?9*ThK@fwR$U^1)p*8=_+^3BXx0$i1BC8XC zr21u6D5nVK&^!dOAw&|1E;qC3uFNj3*Jj#&%Oje@0D-nhfmM*o%^5f}-pxQ07(95H z3|LoV>V19w#rLgmRmtVy9!T3M3FUE3><0T8&b3yEsWcLW`0(=1+qsqc(k(ymBLK0h zK!6(6$7MX~M`-QA2$wk7n(7hhkJ}4Rwi-Vd(_ZFX1Yk7TXuB0IJYpo@kLb2G8m)E{ z`9v=!hi}fOytKckfN^C@6+Z*+MVI9-W_p@_3yyR#UYc0FTpD}i#k>c!wYCS)4v@E$ zchZCo=zV@)`v^$;V18ixdjFMY#q^2$wEX%{f(XD8POnsn$bpbClpC@hPxjzyO>pY|*pF3UU2tYcCN?rUk{Sskej70Mmu9vPwMYhO1m{AxAt(zqDT|0jP7FaX=6 V`?~}E4H^Id002ovPDHLkV1hC)G==~G literal 0 HcmV?d00001 diff --git a/public/javascripts/hello.js b/public/javascripts/hello.js new file mode 100644 index 000000000..02ee13c7c --- /dev/null +++ b/public/javascripts/hello.js @@ -0,0 +1,3 @@ +if (window.console) { + console.log("Welcome to your Play application's JavaScript!"); +} diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css new file mode 100644 index 000000000..e69de29bb From 1e72c08423f1440335798e1b3e72bef86748fe18 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 16 Jun 2016 07:30:02 +0200 Subject: [PATCH 02/47] Move everything out to delegate body parser --- README.md | 224 +++++++++++++++++- ...DelegatingMultipartFormDataBodyParser.java | 154 ++++++++++++ app/controllers/HomeController.java | 15 +- app/controllers/MyFileMultipartFormData.java | 68 ------ .../MyMultipartFormDataBodyParser.java | 94 ++------ 5 files changed, 406 insertions(+), 149 deletions(-) create mode 100644 app/controllers/DelegatingMultipartFormDataBodyParser.java delete mode 100644 app/controllers/MyFileMultipartFormData.java diff --git a/README.md b/README.md index 853d6f702..c2c5ade54 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,225 @@ -## Play File Upload using a custom BodyParser +# Play File Upload using a custom BodyParser This is a sample project that shows how to upload a file through Akka Streams using a custom BodyParser using Akka Streams using the Java API. + +## Default MultipartFormData Body Parser + +Play's Java API specifies a BodyParser.MultipartFormData class which uses a TemporaryFile wrapper class that creates a file under a "temporary" name and then deletes it only when the system is under GC pressure. + +``` java +@BodyParser.Of(BodyParser.MultipartFormData.class) +public Result upload() throws IOException { + final Http.MultipartFormData formData = request().body().asMultipartFormData(); + final Http.MultipartFormData.FilePart filePart = formData.getFile("name"); + final File file = filePart.getFile(); + final long data = operateOnTempFile(file); + return ok("file size = " + data + ""); +} +``` + +## Customizing the Body Parser + +There are cases where it's useful to have more control over where and Play uploads multi part form data. In this case, we'd like to get access to the accumulated byte stream for each file part and generate a file directly, without going through TemporaryFile. + +In short, we want to replace: + +``` +@BodyParser.Of(BodyParser.MultipartFormData.class) +``` + +with + +``` +@BodyParser.Of(MyMultipartFormDataBodyParser.class) +``` + +And we want to change as little code as possible. The underlying mechanics are simple. `MyMultipartFormDataBodyParser` does all the work of setting up a custom file part handler using a method called `createFilePartHandler`: + +``` java +class MyMultipartFormDataBodyParser extends DelegatingMultipartFormDataBodyParser { + + @Inject + public MyMultipartFormDataBodyParser(Materializer materializer, play.api.http.HttpConfiguration config) { + super(materializer, config.parser().maxDiskBuffer()); + } + + /** + * Creates a file part handler that uses a custom accumulator. + */ + @Override + public Function>> createFilePartHandler() { + return (Multipart.FileInfo fileInfo) -> { + final String filename = fileInfo.fileName(); + final String partname = fileInfo.partName(); + final String contentType = fileInfo.contentType().getOrElse(null); + final File file = generateTempFile(); + + final Sink> sink = FileIO.toFile(file); + return Accumulator.fromSink( + sink.mapMaterializedValue(completionStage -> + completionStage.thenApplyAsync(results -> { + //noinspection unchecked + return new Http.MultipartFormData.FilePart(partname, + filename, + contentType, + file); + }) + )); + }; + } + + /** + * Generates a temp file directly without going through TemporaryFile. + */ + private File generateTempFile() { + try { + final FileAttribute> attr = PosixFilePermissions.asFileAttribute(EnumSet.of(OWNER_READ, OWNER_WRITE)); + final Path path = Files.createTempFile("multipartBody", "tempFile", attr); + return path.toFile(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + +} +``` + +The core Accumulator is generated from an `akka.streams.FileIO` sink which writes out bytes to the filesystem, and exposes a CompletionStage when the write operation has been completed. + +Because this code delegates to the Scala API implementation, the underlying `DelegatingMultipartFormDataBodyParser` exposes an abstract method: + +``` java +abstract Function>> createFilePartHandler(); +``` + +`DelegatingMultipartFormDataBodyParser` does not know about any particular type, only `FilePart`, and so it falls to the implementation to fill in the details. + +In addition, `DelegatingMultipartFormDataBodyParser` does all the housekeeping necessary to map between Java and the underlying details. This code is verbose, but illustrates that pure Java can do all the work natively of working with Scala, rather than having to add a Scala class: + +``` java +abstract class DelegatingMultipartFormDataBodyParser implements BodyParser> { + + private final Materializer materializer; + private final long maxLength; + private final play.api.mvc.BodyParser> delegate; + + public DelegatingMultipartFormDataBodyParser(Materializer materializer, long maxLength) { + this.maxLength = maxLength; + this.materializer = materializer; + delegate = multipartParser(); + } + + /** + * Returns a FilePartHandler expressed as a Java function. + */ + abstract Function>> createFilePartHandler(); + + /** + * Calls out to the Scala API to create a multipart parser. + */ + private play.api.mvc.BodyParser> multipartParser() { + ScalaFilePartHandler filePartHandler = new ScalaFilePartHandler(); + //noinspection unchecked + return Multipart.multipartParser((int) maxLength, filePartHandler, materializer); + } + + private class ScalaFilePartHandler extends AbstractFunction1>> { + @Override + public play.api.libs.streams.Accumulator> apply(Multipart.FileInfo fileInfo) { + return createFilePartHandler() + .apply(fileInfo) + .asScala() + .map(new JavaFilePartToScalaFilePart(), materializer.executionContext()); + } + } + + private class JavaFilePartToScalaFilePart extends AbstractFunction1, play.api.mvc.MultipartFormData.FilePart> { + @Override + public play.api.mvc.MultipartFormData.FilePart apply(Http.MultipartFormData.FilePart filePart) { + return toScala(filePart); + } + } + + /** + * Delegates underlying functionality to another body parser and converts the + * result to Java API. + */ + @Override + public play.libs.streams.Accumulator>> apply(Http.RequestHeader request) { + return delegate.apply(request._underlyingHeader()) + .asJava() + .map(result -> { + if (result.isLeft()) { + return F.Either.Left(result.left().get().asJava()); + } else { + final play.api.mvc.MultipartFormData scalaData = result.right().get(); + return F.Either.Right(new DelegatingMultipartFormData(scalaData)); + } + }, + JavaParsers.trampoline() + ); + } + + + /** + * Extends Http.MultipartFormData to use File specifically, + * converting from Scala API to Java API. + */ + private class DelegatingMultipartFormData extends Http.MultipartFormData { + + private play.api.mvc.MultipartFormData scalaFormData; + + DelegatingMultipartFormData(play.api.mvc.MultipartFormData scalaFormData) { + this.scalaFormData = scalaFormData; + } + + @Override + public Map asFormUrlEncoded() { + return mapAsJavaMapConverter( + scalaFormData.asFormUrlEncoded().mapValues(arrayFunction()) + ).asJava(); + } + + // maps from Scala Seq to String array + private Function1, String[]> arrayFunction() { + return new AbstractFunction1, String[]>() { + @Override + public String[] apply(Seq v1) { + String[] array = new String[v1.size()]; + v1.copyToArray(array); + return array; + } + }; + } + + @Override + public List> getFiles() { + return seqAsJavaListConverter(scalaFormData.files()) + .asJava() + .stream() + .map(part -> toJava(part)) + .collect(Collectors.toList()); + } + + } + + private Http.MultipartFormData.FilePart toJava(play.api.mvc.MultipartFormData.FilePart filePart) { + return new Http.MultipartFormData.FilePart<>( + filePart.key(), + filePart.filename(), + OptionConverters.toJava(filePart.contentType()).orElse(null), + filePart.ref() + ); + } + + private play.api.mvc.MultipartFormData.FilePart toScala(Http.MultipartFormData.FilePart filePart) { + return new play.api.mvc.MultipartFormData.FilePart<>( + filePart.getKey(), + filePart.getFilename(), + Option.apply(filePart.getContentType()), + filePart.getFile() + ); + } +} +``` + diff --git a/app/controllers/DelegatingMultipartFormDataBodyParser.java b/app/controllers/DelegatingMultipartFormDataBodyParser.java new file mode 100644 index 000000000..586cd84be --- /dev/null +++ b/app/controllers/DelegatingMultipartFormDataBodyParser.java @@ -0,0 +1,154 @@ +package controllers; + + +import akka.stream.Materializer; +import akka.util.ByteString; +import play.core.j.JavaParsers; +import play.core.parsers.Multipart; +import play.libs.F; +import play.mvc.BodyParser; +import play.mvc.Http; +import play.mvc.Result; +import scala.Function1; +import scala.Option; +import scala.collection.Seq; +import scala.compat.java8.OptionConverters; +import scala.runtime.AbstractFunction1; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static scala.collection.JavaConverters.mapAsJavaMapConverter; +import static scala.collection.JavaConverters.seqAsJavaListConverter; + +/** + * An abstract body parser that exposes a file part handler as an + * abstract method and delegates the implementation to the underlying + * Scala multipartParser. + */ +abstract class DelegatingMultipartFormDataBodyParser implements BodyParser> { + + private final Materializer materializer; + private final long maxLength; + private final play.api.mvc.BodyParser> delegate; + + public DelegatingMultipartFormDataBodyParser(Materializer materializer, long maxLength) { + this.maxLength = maxLength; + this.materializer = materializer; + delegate = multipartParser(); + } + + /** + * Returns a FilePartHandler expressed as a Java function. + */ + abstract Function>> createFilePartHandler(); + + /** + * Calls out to the Scala API to create a multipart parser. + */ + private play.api.mvc.BodyParser> multipartParser() { + ScalaFilePartHandler filePartHandler = new ScalaFilePartHandler(); + //noinspection unchecked + return Multipart.multipartParser((int) maxLength, filePartHandler, materializer); + } + + private class ScalaFilePartHandler extends AbstractFunction1>> { + @Override + public play.api.libs.streams.Accumulator> apply(Multipart.FileInfo fileInfo) { + return createFilePartHandler() + .apply(fileInfo) + .asScala() + .map(new JavaFilePartToScalaFilePart(), materializer.executionContext()); + } + } + + private class JavaFilePartToScalaFilePart extends AbstractFunction1, play.api.mvc.MultipartFormData.FilePart> { + @Override + public play.api.mvc.MultipartFormData.FilePart apply(Http.MultipartFormData.FilePart filePart) { + return toScala(filePart); + } + } + + /** + * Delegates underlying functionality to another body parser and converts the + * result to Java API. + */ + @Override + public play.libs.streams.Accumulator>> apply(Http.RequestHeader request) { + return delegate.apply(request._underlyingHeader()) + .asJava() + .map(result -> { + if (result.isLeft()) { + return F.Either.Left(result.left().get().asJava()); + } else { + final play.api.mvc.MultipartFormData scalaData = result.right().get(); + return F.Either.Right(new DelegatingMultipartFormData(scalaData)); + } + }, + JavaParsers.trampoline() + ); + } + + + /** + * Extends Http.MultipartFormData to use File specifically, + * converting from Scala API to Java API. + */ + private class DelegatingMultipartFormData extends Http.MultipartFormData { + + private play.api.mvc.MultipartFormData scalaFormData; + + DelegatingMultipartFormData(play.api.mvc.MultipartFormData scalaFormData) { + this.scalaFormData = scalaFormData; + } + + @Override + public Map asFormUrlEncoded() { + return mapAsJavaMapConverter( + scalaFormData.asFormUrlEncoded().mapValues(arrayFunction()) + ).asJava(); + } + + // maps from Scala Seq to String array + private Function1, String[]> arrayFunction() { + return new AbstractFunction1, String[]>() { + @Override + public String[] apply(Seq v1) { + String[] array = new String[v1.size()]; + v1.copyToArray(array); + return array; + } + }; + } + + @Override + public List> getFiles() { + return seqAsJavaListConverter(scalaFormData.files()) + .asJava() + .stream() + .map(part -> toJava(part)) + .collect(Collectors.toList()); + } + + } + + private Http.MultipartFormData.FilePart toJava(play.api.mvc.MultipartFormData.FilePart filePart) { + return new Http.MultipartFormData.FilePart<>( + filePart.key(), + filePart.filename(), + OptionConverters.toJava(filePart.contentType()).orElse(null), + filePart.ref() + ); + } + + private play.api.mvc.MultipartFormData.FilePart toScala(Http.MultipartFormData.FilePart filePart) { + return new play.api.mvc.MultipartFormData.FilePart<>( + filePart.getKey(), + filePart.getFilename(), + Option.apply(filePart.getContentType()), + filePart.getFile() + ); + } +} diff --git a/app/controllers/HomeController.java b/app/controllers/HomeController.java index 1dc04dd19..097bfd1f5 100644 --- a/app/controllers/HomeController.java +++ b/app/controllers/HomeController.java @@ -1,10 +1,7 @@ package controllers; import play.data.Form; -import play.mvc.BodyParser; -import play.mvc.Controller; -import play.mvc.Http; -import play.mvc.Result; +import play.mvc.*; import views.html.index; import javax.inject.Inject; @@ -28,17 +25,15 @@ public HomeController(play.data.FormFactory formFactory) { public Result index() { Form form = formFactory.form(FormData.class); - play.mvc.Http.Context context = play.mvc.Http.Context.current(); + Http.Context context = Http.Context.current(); return ok(index.render(form, context.messages())); } @BodyParser.Of(MyMultipartFormDataBodyParser.class) public Result upload() throws IOException { - final Http.Context context = Http.Context.current(); - final Http.Request request = context.request(); - - final Http.MultipartFormData.FilePart filePart = request.body().asMultipartFormData().getFile("name"); - final File file = (File) filePart.getFile(); + final Http.MultipartFormData formData = request().body().asMultipartFormData(); + final Http.MultipartFormData.FilePart filePart = formData.getFile("name"); + final File file = filePart.getFile(); final long data = operateOnTempFile(file); return ok("file size = " + data + ""); } diff --git a/app/controllers/MyFileMultipartFormData.java b/app/controllers/MyFileMultipartFormData.java deleted file mode 100644 index b91ffa203..000000000 --- a/app/controllers/MyFileMultipartFormData.java +++ /dev/null @@ -1,68 +0,0 @@ -package controllers; - -import play.api.mvc.MultipartFormData; -import play.mvc.Http; -import scala.Function1; -import scala.collection.Seq; -import scala.compat.java8.OptionConverters; -import scala.runtime.AbstractFunction1; - -import java.io.File; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import static scala.collection.JavaConverters.mapAsJavaMapConverter; -import static scala.collection.JavaConverters.seqAsJavaListConverter; - -/** - * Extends Http.MultipartFormData to use File specifically, - * converting from Scala API to Java API. - */ -class MyFileMultipartFormData extends Http.MultipartFormData { - - private play.api.mvc.MultipartFormData fileMultipartFormData; - - MyFileMultipartFormData(play.api.mvc.MultipartFormData fileMultipartFormData) { - this.fileMultipartFormData = fileMultipartFormData; - } - - @Override - public Map asFormUrlEncoded() { - return mapAsJavaMapConverter( - fileMultipartFormData - .asFormUrlEncoded() - .mapValues(arrayFunction()) - ).asJava(); - } - - // maps from Scala Seq to String array - private Function1, String[]> arrayFunction() { - return new AbstractFunction1, String[]>() { - @Override - public String[] apply(Seq v1) { - String[] array = new String[v1.size()]; - v1.copyToArray(array); - return array; - } - }; - } - - @Override - public List> getFiles() { - return seqAsJavaListConverter(fileMultipartFormData.files()) - .asJava() - .stream() - .map(this::convert) - .collect(Collectors.toList()); - } - - private FilePart convert(MultipartFormData.FilePart filePart) { - return new FilePart<>( - filePart.key(), - filePart.filename(), - OptionConverters.toJava(filePart.contentType()).orElse(null), - filePart.ref() - ); - } -} diff --git a/app/controllers/MyMultipartFormDataBodyParser.java b/app/controllers/MyMultipartFormDataBodyParser.java index 52928af14..feac4baf9 100644 --- a/app/controllers/MyMultipartFormDataBodyParser.java +++ b/app/controllers/MyMultipartFormDataBodyParser.java @@ -5,16 +5,10 @@ import akka.stream.javadsl.FileIO; import akka.stream.javadsl.Sink; import akka.util.ByteString; -import play.core.j.JavaParsers; import play.core.parsers.Multipart; -import play.libs.F; import play.libs.streams.Accumulator; -import play.mvc.BodyParser; import play.mvc.Http; -import play.mvc.Result; -import scala.Function1; import scala.Option; -import scala.runtime.AbstractFunction1; import javax.inject.Inject; import java.io.File; @@ -33,43 +27,39 @@ import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; /** - * This class is a custom body parser that farms out most of the work of - * multipart form processing to the underlying core parser, but specifies - * that form data should be treated as type "File" instead of the default - * "TemporaryFile". + * This class is a custom body parser with a custom file part handler + * that uses a file that can come from anywhere in the system. */ -@SuppressWarnings("WeakerAccess") -class MyMultipartFormDataBodyParser implements BodyParser> { - private final play.api.mvc.BodyParser> delegate; - - private final Materializer materializer; - private final int maxLength; +class MyMultipartFormDataBodyParser extends DelegatingMultipartFormDataBodyParser { @Inject public MyMultipartFormDataBodyParser(Materializer materializer, play.api.http.HttpConfiguration config) { - this.maxLength = (int) config.parser().maxDiskBuffer(); - this.materializer = materializer; - this.delegate = multipartFormDataBodyParser(); + super(materializer, config.parser().maxDiskBuffer()); } /** - * Delegates underlying functionality to another body parser and converts the - * result to Java API. + * Creates a file part handler that uses a custom accumulator. */ @Override - public Accumulator>> apply(Http.RequestHeader request) { - return delegate.apply(request._underlyingHeader()) - .asJava() - .map(result -> { - if (result.isLeft()) { - return F.Either.Left(result.left().get().asJava()); - } else { - final play.api.mvc.MultipartFormData scalaData = result.right().get(); - return F.Either.Right(new MyFileMultipartFormData(scalaData)); - } - }, - JavaParsers.trampoline() - ); + public Function>> createFilePartHandler() { + return (Multipart.FileInfo fileInfo) -> { + final String filename = fileInfo.fileName(); + final String partname = fileInfo.partName(); + final String contentType = fileInfo.contentType().getOrElse(null); + final File file = generateTempFile(); + + final Sink> sink = FileIO.toFile(file); + return Accumulator.fromSink( + sink.mapMaterializedValue(completionStage -> + completionStage.thenApplyAsync(results -> { + //noinspection unchecked + return new Http.MultipartFormData.FilePart(partname, + filename, + contentType, + file); + }) + )); + }; } /** @@ -85,42 +75,6 @@ private File generateTempFile() { } } - /** - * Returns a BodyParser using a custom Accumulator from an Akka Streams sink that - * takes FileIO from ByteString and puts it into a FilePart with File. - */ - private play.api.mvc.BodyParser> multipartFormDataBodyParser() { - return Multipart.multipartParser(maxLength, asScalaFunction(fileInfo -> { - final String filename = fileInfo.fileName(); - final String partname = fileInfo.partName(); - final Option contentType = fileInfo.contentType(); - - final File file = generateTempFile(); - - final play.api.mvc.MultipartFormData.FilePart part = new play.api.mvc.MultipartFormData.FilePart<>(partname, filename, contentType, file); - - final Sink> sink = FileIO.toFile(file); - return Accumulator.fromSink( - sink.mapMaterializedValue(completionStage -> - completionStage.thenApplyAsync(results -> part) - )).asScala(); - }), materializer); - } - - /** - * Utility function to convert a java.util.Function into a scala.Function1. - */ - private Function1 asScalaFunction(Function function) { - // This is needed because Multipart.multipartParser takes a Function1 directly, - // and there is no equivalent Java wrapper. - return new AbstractFunction1() { - @Override - public R apply(T1 t1) { - return function.apply(t1); - } - }; - } - } From fad811b9b268595978a3ee8f8c64574f1c25f605 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 16 Jun 2016 13:35:11 +0200 Subject: [PATCH 03/47] Formatting cleanup --- README.md | 5 +++-- app/controllers/MyMultipartFormDataBodyParser.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c2c5ade54..eef9236c3 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,8 @@ class MyMultipartFormDataBodyParser extends DelegatingMultipartFormDataBodyParse */ private File generateTempFile() { try { - final FileAttribute> attr = PosixFilePermissions.asFileAttribute(EnumSet.of(OWNER_READ, OWNER_WRITE)); + final EnumSet attrs = EnumSet.of(OWNER_READ, OWNER_WRITE); + final FileAttribute attr = PosixFilePermissions.asFileAttribute(attrs); final Path path = Files.createTempFile("multipartBody", "tempFile", attr); return path.toFile(); } catch (IOException e) { @@ -89,7 +90,7 @@ The core Accumulator is generated from an `akka.streams.FileIO` sink which write Because this code delegates to the Scala API implementation, the underlying `DelegatingMultipartFormDataBodyParser` exposes an abstract method: ``` java -abstract Function>> createFilePartHandler(); +abstract Function>> createFilePartHandler(); ``` `DelegatingMultipartFormDataBodyParser` does not know about any particular type, only `FilePart`, and so it falls to the implementation to fill in the details. diff --git a/app/controllers/MyMultipartFormDataBodyParser.java b/app/controllers/MyMultipartFormDataBodyParser.java index feac4baf9..ef82f5773 100644 --- a/app/controllers/MyMultipartFormDataBodyParser.java +++ b/app/controllers/MyMultipartFormDataBodyParser.java @@ -67,7 +67,8 @@ public Function> attr = PosixFilePermissions.asFileAttribute(EnumSet.of(OWNER_READ, OWNER_WRITE)); + final EnumSet attrs = EnumSet.of(OWNER_READ, OWNER_WRITE); + final FileAttribute attr = PosixFilePermissions.asFileAttribute(attrs); final Path path = Files.createTempFile("multipartBody", "tempFile", attr); return path.toFile(); } catch (IOException e) { From 6995555e879aecc5b26fe8ed271b884b1d1e29dc Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Wed, 5 Oct 2016 12:30:41 -0500 Subject: [PATCH 04/47] travis (#1) --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..d6b214bb2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: scala +scala: +- 2.11.8 +jdk: +- oraclejdk8 +cache: + directories: + - "$HOME/.ivy2/cache" +before_cache: +- rm -rf $HOME/.ivy2/cache/com.typesafe.play/* +- rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/* +- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm +notifications: + slack: + secure: cw1Jfpq0W7w4fv7rLYm/mx2T/pNYoJ+zN+X5voxPfWE1/0QVHaFTXRBh7MwdUxE++B6wITVWoJiJoEc/qiqweeFPgL4U+rph3wY6f/xDL1M8xB2OrHnSAzTh6DubDlGtBMi2FDREU2tsdPJQ1XXcYeGr0mTLw5C59G9ZnnM45PcFuwW++3ojzAg7HA8Rc4nw3pvcpMkKywVNw/Q+4zeAdbrCmN+GI3LSa2tdc1SuN5JaM8r/w2Tx/yOnrraYKyi+QpSX6fOk0XMDsI1FpQY4z6253upIgctdf8BPCfz/dQlns8fCmBEQFQeZ0Sk42N1kWuB80FFRBkxH4NMAruZ7eoMbz26LK4br7oTKofHZ2o76952EsoWyGMJyVmL6MGvtAs5L7wv/8q3S0McleTzuEVMD6eL6D6EsbMDEZmgvv/D4dRuIdGydsJMH6ThpXzpJkj86Wqg9/F/Q1z8/y3+OlC8IO14Z8j0PqdKIZdAjQkIWjUKNFJgsagNVUfQFDHpNIMXhrLP68PfzK6XVc2m+iBizje7FM3laxbmrwd2ga1a3tIeIi50q1c4GWLapnv8hj2scTShRyyl4OD/k+6S/mUaAfbVijuAy9V3kKkXPbHAsGDPemjbZB+WCE9pM3chP4DG1dnTdw348b9tdWBtaJ3+lCDtnhTvMQcI74YL88CE= From 32a4a750f08de1081d845d6b706b03a8a524ba29 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Sat, 18 Feb 2017 19:47:44 -0800 Subject: [PATCH 05/47] Updated with template-control on 2017-02-16T20:10:39.548Z (#9) /LICENSE: wrote /LICENSE **/build.sbt: scalaVersion := "2.12.1" **/build.properties: sbt.version=0.13.13 **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.12") --- LICENSE | 11 +++++------ build.sbt | 2 +- project/build.properties | 2 +- project/plugins.sbt | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/LICENSE b/LICENSE index 4ca0c6488..b018ae2bc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,7 @@ -This software is licensed under the Apache 2 license, quoted below. +License +------- +Written in 2016 by Lightbend -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with -the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. -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. +You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see . diff --git a/build.sbt b/build.sbt index 13aa0c233..4d369eadb 100644 --- a/build.sbt +++ b/build.sbt @@ -4,4 +4,4 @@ version := "1.0-SNAPSHOT" lazy val root = (project in file(".")).enablePlugins(PlayJava) -scalaVersion := "2.11.8" +scalaVersion := "2.12.1" diff --git a/project/build.properties b/project/build.properties index 5ea0ff21f..5f2f54343 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,4 +1,4 @@ #Activator-generated Properties #Sun May 29 09:20:38 PDT 2016 template.uuid=28ae6884-7b61-401c-834c-704789d1228a -sbt.version=0.13.11 +sbt.version=0.13.13 diff --git a/project/plugins.sbt b/project/plugins.sbt index 1529c0710..707c53ef6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.3") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.12") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 94928f22a233e0e1f6910e42649401c3eaea51af Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 28 Mar 2017 15:04:13 -0700 Subject: [PATCH 06/47] Upgrade branch SNAPSHOT using TemplateControl (#10) * Updated with template-control on 2017-02-19T05:31:38.596Z /LICENSE: wrote /LICENSE **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M1") * Make everything compile --- .../DelegatingMultipartFormDataBodyParser.java | 8 +++++--- app/controllers/MyMultipartFormDataBodyParser.java | 5 +++-- build.sbt | 1 + project/plugins.sbt | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/controllers/DelegatingMultipartFormDataBodyParser.java b/app/controllers/DelegatingMultipartFormDataBodyParser.java index 586cd84be..b9a161965 100644 --- a/app/controllers/DelegatingMultipartFormDataBodyParser.java +++ b/app/controllers/DelegatingMultipartFormDataBodyParser.java @@ -3,6 +3,7 @@ import akka.stream.Materializer; import akka.util.ByteString; +import play.api.http.HttpErrorHandler; import play.core.j.JavaParsers; import play.core.parsers.Multipart; import play.libs.F; @@ -31,12 +32,14 @@ abstract class DelegatingMultipartFormDataBodyParser implements BodyParser> { private final Materializer materializer; + private final HttpErrorHandler errorHandler; private final long maxLength; private final play.api.mvc.BodyParser> delegate; - public DelegatingMultipartFormDataBodyParser(Materializer materializer, long maxLength) { + public DelegatingMultipartFormDataBodyParser(Materializer materializer, long maxLength, HttpErrorHandler errorHandler) { this.maxLength = maxLength; this.materializer = materializer; + this.errorHandler = errorHandler; delegate = multipartParser(); } @@ -50,8 +53,7 @@ public DelegatingMultipartFormDataBodyParser(Materializer materializer, long max */ private play.api.mvc.BodyParser> multipartParser() { ScalaFilePartHandler filePartHandler = new ScalaFilePartHandler(); - //noinspection unchecked - return Multipart.multipartParser((int) maxLength, filePartHandler, materializer); + return Multipart.multipartParser((int) maxLength, filePartHandler, errorHandler, materializer); } private class ScalaFilePartHandler extends AbstractFunction1>> { diff --git a/app/controllers/MyMultipartFormDataBodyParser.java b/app/controllers/MyMultipartFormDataBodyParser.java index ef82f5773..994064193 100644 --- a/app/controllers/MyMultipartFormDataBodyParser.java +++ b/app/controllers/MyMultipartFormDataBodyParser.java @@ -5,6 +5,7 @@ import akka.stream.javadsl.FileIO; import akka.stream.javadsl.Sink; import akka.util.ByteString; +import play.api.http.HttpErrorHandler; import play.core.parsers.Multipart; import play.libs.streams.Accumulator; import play.mvc.Http; @@ -33,8 +34,8 @@ class MyMultipartFormDataBodyParser extends DelegatingMultipartFormDataBodyParser { @Inject - public MyMultipartFormDataBodyParser(Materializer materializer, play.api.http.HttpConfiguration config) { - super(materializer, config.parser().maxDiskBuffer()); + public MyMultipartFormDataBodyParser(Materializer materializer, play.api.http.HttpConfiguration config, HttpErrorHandler errorHandler) { + super(materializer, config.parser().maxDiskBuffer(), errorHandler); } /** diff --git a/build.sbt b/build.sbt index 4d369eadb..87e345a06 100644 --- a/build.sbt +++ b/build.sbt @@ -5,3 +5,4 @@ version := "1.0-SNAPSHOT" lazy val root = (project in file(".")).enablePlugins(PlayJava) scalaVersion := "2.12.1" + diff --git a/project/plugins.sbt b/project/plugins.sbt index 707c53ef6..9036913b0 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.12") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M3") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From c89f10154079f64d499446edd80786f62b56f434 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Sat, 8 Apr 2017 21:18:58 -0700 Subject: [PATCH 07/47] Updated with template-control on 2017-04-09T04:09:08.106Z (#15) /LICENSE: wrote /LICENSE **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M4") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 9036913b0..13b97ff21 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M3") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M4") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 5cacb59e9a14d9b9895dfc2d44974d5f81a45573 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 11 Apr 2017 07:54:47 -0700 Subject: [PATCH 08/47] Updated with template-control on 2017-04-11T04:20:39.121Z (#16) /LICENSE: wrote /LICENSE **/build.properties: sbt.version=0.13.15 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 5f2f54343..536b7d8d3 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,4 +1,4 @@ #Activator-generated Properties #Sun May 29 09:20:38 PDT 2016 template.uuid=28ae6884-7b61-401c-834c-704789d1228a -sbt.version=0.13.13 +sbt.version=0.13.15 From 98f57ac2e0c571bd7f8da3a1b85330c214109b44 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 18 Apr 2017 12:51:53 -0700 Subject: [PATCH 09/47] Updated with template-control on 2017-04-18T19:22:41.720Z (#17) /LICENSE: wrote /LICENSE **/build.sbt: scalaVersion := "2.12.2" --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 87e345a06..302b8489f 100644 --- a/build.sbt +++ b/build.sbt @@ -4,5 +4,5 @@ version := "1.0-SNAPSHOT" lazy val root = (project in file(".")).enablePlugins(PlayJava) -scalaVersion := "2.12.1" +scalaVersion := "2.12.2" From 89ccf924aa5eeb7008a3f6a44e674941d65f41e4 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 27 Apr 2017 18:51:41 -0700 Subject: [PATCH 10/47] Updated with template-control on 2017-04-28T01:46:58.652Z (#19) /LICENSE: wrote /LICENSE **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M5") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 13b97ff21..9affd7acd 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M4") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M5") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 9d314b99c4b857673ce6ea3f8778f65b6496bbc0 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Sun, 4 Jun 2017 00:14:59 +0200 Subject: [PATCH 11/47] Updated with template-control on 2017-06-03T16:01:28.263Z (#24) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-RC2") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 9affd7acd..357c1b168 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M5") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-RC2") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 69b0f615068e02bb370fd75d79a793c209e6bd30 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Fri, 30 Jun 2017 11:47:48 -0700 Subject: [PATCH 12/47] Updated with template-control on 2017-06-23T02:18:35.708Z (#25) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 357c1b168..035b6e152 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-RC2") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 84af57143e32d75ad4bdf4a266e1ce18ea320036 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Fri, 30 Jun 2017 13:56:42 -0700 Subject: [PATCH 13/47] Update for 2.6.x and add tests (#28) * Update for 2.6.x and add test * Fix package --- .travis.yml | 2 +- README.md | 139 +--------------- ...DelegatingMultipartFormDataBodyParser.java | 156 ------------------ .../MyMultipartFormDataBodyParser.java | 53 +++--- build.sbt | 3 +- conf/application.conf | 2 +- conf/logback.xml | 3 + project/build.properties | 3 - test/controllers/HomeControllerTest.java | 55 ++++++ 9 files changed, 88 insertions(+), 328 deletions(-) delete mode 100644 app/controllers/DelegatingMultipartFormDataBodyParser.java create mode 100644 test/controllers/HomeControllerTest.java diff --git a/.travis.yml b/.travis.yml index d6b214bb2..9cd48f887 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: scala scala: -- 2.11.8 +- 2.12.2 jdk: - oraclejdk8 cache: diff --git a/README.md b/README.md index eef9236c3..4cd021cd3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This is a sample project that shows how to upload a file through Akka Streams us Play's Java API specifies a BodyParser.MultipartFormData class which uses a TemporaryFile wrapper class that creates a file under a "temporary" name and then deletes it only when the system is under GC pressure. -``` java +```java @BodyParser.Of(BodyParser.MultipartFormData.class) public Result upload() throws IOException { final Http.MultipartFormData formData = request().body().asMultipartFormData(); @@ -35,7 +35,7 @@ with And we want to change as little code as possible. The underlying mechanics are simple. `MyMultipartFormDataBodyParser` does all the work of setting up a custom file part handler using a method called `createFilePartHandler`: -``` java +```java class MyMultipartFormDataBodyParser extends DelegatingMultipartFormDataBodyParser { @Inject @@ -73,9 +73,7 @@ class MyMultipartFormDataBodyParser extends DelegatingMultipartFormDataBodyParse */ private File generateTempFile() { try { - final EnumSet attrs = EnumSet.of(OWNER_READ, OWNER_WRITE); - final FileAttribute attr = PosixFilePermissions.asFileAttribute(attrs); - final Path path = Files.createTempFile("multipartBody", "tempFile", attr); + final Path path = Files.createTempFile("multipartBody", "tempFile"); return path.toFile(); } catch (IOException e) { throw new IllegalStateException(e); @@ -89,138 +87,9 @@ The core Accumulator is generated from an `akka.streams.FileIO` sink which write Because this code delegates to the Scala API implementation, the underlying `DelegatingMultipartFormDataBodyParser` exposes an abstract method: -``` java +```java abstract Function>> createFilePartHandler(); ``` `DelegatingMultipartFormDataBodyParser` does not know about any particular type, only `FilePart`, and so it falls to the implementation to fill in the details. - -In addition, `DelegatingMultipartFormDataBodyParser` does all the housekeeping necessary to map between Java and the underlying details. This code is verbose, but illustrates that pure Java can do all the work natively of working with Scala, rather than having to add a Scala class: - -``` java -abstract class DelegatingMultipartFormDataBodyParser implements BodyParser> { - - private final Materializer materializer; - private final long maxLength; - private final play.api.mvc.BodyParser> delegate; - - public DelegatingMultipartFormDataBodyParser(Materializer materializer, long maxLength) { - this.maxLength = maxLength; - this.materializer = materializer; - delegate = multipartParser(); - } - - /** - * Returns a FilePartHandler expressed as a Java function. - */ - abstract Function>> createFilePartHandler(); - - /** - * Calls out to the Scala API to create a multipart parser. - */ - private play.api.mvc.BodyParser> multipartParser() { - ScalaFilePartHandler filePartHandler = new ScalaFilePartHandler(); - //noinspection unchecked - return Multipart.multipartParser((int) maxLength, filePartHandler, materializer); - } - - private class ScalaFilePartHandler extends AbstractFunction1>> { - @Override - public play.api.libs.streams.Accumulator> apply(Multipart.FileInfo fileInfo) { - return createFilePartHandler() - .apply(fileInfo) - .asScala() - .map(new JavaFilePartToScalaFilePart(), materializer.executionContext()); - } - } - - private class JavaFilePartToScalaFilePart extends AbstractFunction1, play.api.mvc.MultipartFormData.FilePart> { - @Override - public play.api.mvc.MultipartFormData.FilePart apply(Http.MultipartFormData.FilePart filePart) { - return toScala(filePart); - } - } - - /** - * Delegates underlying functionality to another body parser and converts the - * result to Java API. - */ - @Override - public play.libs.streams.Accumulator>> apply(Http.RequestHeader request) { - return delegate.apply(request._underlyingHeader()) - .asJava() - .map(result -> { - if (result.isLeft()) { - return F.Either.Left(result.left().get().asJava()); - } else { - final play.api.mvc.MultipartFormData scalaData = result.right().get(); - return F.Either.Right(new DelegatingMultipartFormData(scalaData)); - } - }, - JavaParsers.trampoline() - ); - } - - - /** - * Extends Http.MultipartFormData to use File specifically, - * converting from Scala API to Java API. - */ - private class DelegatingMultipartFormData extends Http.MultipartFormData { - - private play.api.mvc.MultipartFormData scalaFormData; - - DelegatingMultipartFormData(play.api.mvc.MultipartFormData scalaFormData) { - this.scalaFormData = scalaFormData; - } - - @Override - public Map asFormUrlEncoded() { - return mapAsJavaMapConverter( - scalaFormData.asFormUrlEncoded().mapValues(arrayFunction()) - ).asJava(); - } - - // maps from Scala Seq to String array - private Function1, String[]> arrayFunction() { - return new AbstractFunction1, String[]>() { - @Override - public String[] apply(Seq v1) { - String[] array = new String[v1.size()]; - v1.copyToArray(array); - return array; - } - }; - } - - @Override - public List> getFiles() { - return seqAsJavaListConverter(scalaFormData.files()) - .asJava() - .stream() - .map(part -> toJava(part)) - .collect(Collectors.toList()); - } - - } - - private Http.MultipartFormData.FilePart toJava(play.api.mvc.MultipartFormData.FilePart filePart) { - return new Http.MultipartFormData.FilePart<>( - filePart.key(), - filePart.filename(), - OptionConverters.toJava(filePart.contentType()).orElse(null), - filePart.ref() - ); - } - - private play.api.mvc.MultipartFormData.FilePart toScala(Http.MultipartFormData.FilePart filePart) { - return new play.api.mvc.MultipartFormData.FilePart<>( - filePart.getKey(), - filePart.getFilename(), - Option.apply(filePart.getContentType()), - filePart.getFile() - ); - } -} -``` diff --git a/app/controllers/DelegatingMultipartFormDataBodyParser.java b/app/controllers/DelegatingMultipartFormDataBodyParser.java deleted file mode 100644 index b9a161965..000000000 --- a/app/controllers/DelegatingMultipartFormDataBodyParser.java +++ /dev/null @@ -1,156 +0,0 @@ -package controllers; - - -import akka.stream.Materializer; -import akka.util.ByteString; -import play.api.http.HttpErrorHandler; -import play.core.j.JavaParsers; -import play.core.parsers.Multipart; -import play.libs.F; -import play.mvc.BodyParser; -import play.mvc.Http; -import play.mvc.Result; -import scala.Function1; -import scala.Option; -import scala.collection.Seq; -import scala.compat.java8.OptionConverters; -import scala.runtime.AbstractFunction1; - -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static scala.collection.JavaConverters.mapAsJavaMapConverter; -import static scala.collection.JavaConverters.seqAsJavaListConverter; - -/** - * An abstract body parser that exposes a file part handler as an - * abstract method and delegates the implementation to the underlying - * Scala multipartParser. - */ -abstract class DelegatingMultipartFormDataBodyParser implements BodyParser> { - - private final Materializer materializer; - private final HttpErrorHandler errorHandler; - private final long maxLength; - private final play.api.mvc.BodyParser> delegate; - - public DelegatingMultipartFormDataBodyParser(Materializer materializer, long maxLength, HttpErrorHandler errorHandler) { - this.maxLength = maxLength; - this.materializer = materializer; - this.errorHandler = errorHandler; - delegate = multipartParser(); - } - - /** - * Returns a FilePartHandler expressed as a Java function. - */ - abstract Function>> createFilePartHandler(); - - /** - * Calls out to the Scala API to create a multipart parser. - */ - private play.api.mvc.BodyParser> multipartParser() { - ScalaFilePartHandler filePartHandler = new ScalaFilePartHandler(); - return Multipart.multipartParser((int) maxLength, filePartHandler, errorHandler, materializer); - } - - private class ScalaFilePartHandler extends AbstractFunction1>> { - @Override - public play.api.libs.streams.Accumulator> apply(Multipart.FileInfo fileInfo) { - return createFilePartHandler() - .apply(fileInfo) - .asScala() - .map(new JavaFilePartToScalaFilePart(), materializer.executionContext()); - } - } - - private class JavaFilePartToScalaFilePart extends AbstractFunction1, play.api.mvc.MultipartFormData.FilePart> { - @Override - public play.api.mvc.MultipartFormData.FilePart apply(Http.MultipartFormData.FilePart filePart) { - return toScala(filePart); - } - } - - /** - * Delegates underlying functionality to another body parser and converts the - * result to Java API. - */ - @Override - public play.libs.streams.Accumulator>> apply(Http.RequestHeader request) { - return delegate.apply(request._underlyingHeader()) - .asJava() - .map(result -> { - if (result.isLeft()) { - return F.Either.Left(result.left().get().asJava()); - } else { - final play.api.mvc.MultipartFormData scalaData = result.right().get(); - return F.Either.Right(new DelegatingMultipartFormData(scalaData)); - } - }, - JavaParsers.trampoline() - ); - } - - - /** - * Extends Http.MultipartFormData to use File specifically, - * converting from Scala API to Java API. - */ - private class DelegatingMultipartFormData extends Http.MultipartFormData { - - private play.api.mvc.MultipartFormData scalaFormData; - - DelegatingMultipartFormData(play.api.mvc.MultipartFormData scalaFormData) { - this.scalaFormData = scalaFormData; - } - - @Override - public Map asFormUrlEncoded() { - return mapAsJavaMapConverter( - scalaFormData.asFormUrlEncoded().mapValues(arrayFunction()) - ).asJava(); - } - - // maps from Scala Seq to String array - private Function1, String[]> arrayFunction() { - return new AbstractFunction1, String[]>() { - @Override - public String[] apply(Seq v1) { - String[] array = new String[v1.size()]; - v1.copyToArray(array); - return array; - } - }; - } - - @Override - public List> getFiles() { - return seqAsJavaListConverter(scalaFormData.files()) - .asJava() - .stream() - .map(part -> toJava(part)) - .collect(Collectors.toList()); - } - - } - - private Http.MultipartFormData.FilePart toJava(play.api.mvc.MultipartFormData.FilePart filePart) { - return new Http.MultipartFormData.FilePart<>( - filePart.key(), - filePart.filename(), - OptionConverters.toJava(filePart.contentType()).orElse(null), - filePart.ref() - ); - } - - private play.api.mvc.MultipartFormData.FilePart toScala(Http.MultipartFormData.FilePart filePart) { - return new play.api.mvc.MultipartFormData.FilePart<>( - filePart.getKey(), - filePart.getFilename(), - Option.apply(filePart.getContentType()), - filePart.getFile() - ); - } -} diff --git a/app/controllers/MyMultipartFormDataBodyParser.java b/app/controllers/MyMultipartFormDataBodyParser.java index 994064193..2cd857601 100644 --- a/app/controllers/MyMultipartFormDataBodyParser.java +++ b/app/controllers/MyMultipartFormDataBodyParser.java @@ -8,30 +8,22 @@ import play.api.http.HttpErrorHandler; import play.core.parsers.Multipart; import play.libs.streams.Accumulator; +import play.mvc.BodyParser; import play.mvc.Http; -import scala.Option; import javax.inject.Inject; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.PosixFilePermissions; -import java.util.EnumSet; -import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.function.Function; -import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; -import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; - /** * This class is a custom body parser with a custom file part handler * that uses a file that can come from anywhere in the system. */ -class MyMultipartFormDataBodyParser extends DelegatingMultipartFormDataBodyParser { +class MyMultipartFormDataBodyParser extends BodyParser.DelegatingMultipartFormDataBodyParser { @Inject public MyMultipartFormDataBodyParser(Materializer materializer, play.api.http.HttpConfiguration config, HttpErrorHandler errorHandler) { @@ -43,24 +35,7 @@ public MyMultipartFormDataBodyParser(Materializer materializer, play.api.http.Ht */ @Override public Function>> createFilePartHandler() { - return (Multipart.FileInfo fileInfo) -> { - final String filename = fileInfo.fileName(); - final String partname = fileInfo.partName(); - final String contentType = fileInfo.contentType().getOrElse(null); - final File file = generateTempFile(); - - final Sink> sink = FileIO.toFile(file); - return Accumulator.fromSink( - sink.mapMaterializedValue(completionStage -> - completionStage.thenApplyAsync(results -> { - //noinspection unchecked - return new Http.MultipartFormData.FilePart(partname, - filename, - contentType, - file); - }) - )); - }; + return this::apply; } /** @@ -68,15 +43,31 @@ public Function attrs = EnumSet.of(OWNER_READ, OWNER_WRITE); - final FileAttribute attr = PosixFilePermissions.asFileAttribute(attrs); - final Path path = Files.createTempFile("multipartBody", "tempFile", attr); + final Path path = Files.createTempFile("multipartBody", "tempFile"); return path.toFile(); } catch (IOException e) { throw new IllegalStateException(e); } } + private Accumulator> apply(Multipart.FileInfo fileInfo) { + final String filename = fileInfo.fileName(); + final String partname = fileInfo.partName(); + final String contentType = fileInfo.contentType().getOrElse(null); + final File file = generateTempFile(); + + final Sink> sink = FileIO.toFile(file); + return Accumulator.fromSink( + sink.mapMaterializedValue(completionStage -> + completionStage.thenApplyAsync(results -> { + //noinspection unchecked + return new Http.MultipartFormData.FilePart<>(partname, + filename, + contentType, + file); + }) + )); + } } diff --git a/build.sbt b/build.sbt index 302b8489f..2893823a5 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -name := """play-fileupload-java""" +name := """play-java-fileupload-example""" version := "1.0-SNAPSHOT" @@ -6,3 +6,4 @@ lazy val root = (project in file(".")).enablePlugins(PlayJava) scalaVersion := "2.12.2" +javacOptions += "-Xlint:deprecation" \ No newline at end of file diff --git a/conf/application.conf b/conf/application.conf index 602fd67cd..758a442b4 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -1 +1 @@ -play.crypto.secret="changeme" +# https://www.playframework.com/documentation/latest/Configuration \ No newline at end of file diff --git a/conf/logback.xml b/conf/logback.xml index 86ec12c0a..76222f5d4 100644 --- a/conf/logback.xml +++ b/conf/logback.xml @@ -33,6 +33,9 @@ + + + diff --git a/project/build.properties b/project/build.properties index 536b7d8d3..64317fdae 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,4 +1 @@ -#Activator-generated Properties -#Sun May 29 09:20:38 PDT 2016 -template.uuid=28ae6884-7b61-401c-834c-704789d1228a sbt.version=0.13.15 diff --git a/test/controllers/HomeControllerTest.java b/test/controllers/HomeControllerTest.java new file mode 100644 index 000000000..199643951 --- /dev/null +++ b/test/controllers/HomeControllerTest.java @@ -0,0 +1,55 @@ +package controllers; + +import akka.stream.IOResult; +import akka.stream.Materializer; +import akka.stream.javadsl.FileIO; +import akka.stream.javadsl.Source; +import akka.util.ByteString; +import org.junit.Test; +import play.Application; +import play.libs.Files; +import play.mvc.Http; +import play.mvc.Result; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.concurrent.CompletionStage; + +import static java.nio.file.Files.createTempFile; +import static java.nio.file.Files.write; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static play.test.Helpers.*; + +public class HomeControllerTest { + + @Test + public void testFileUpload() { + Application app = fakeApplication(); + running(app, () -> { + try { + + Files.TemporaryFileCreator temporaryFileCreator = app.injector().instanceOf(Files.TemporaryFileCreator.class); + Materializer materializer = app.injector().instanceOf(Materializer.class); + + Path tempfilePath = createTempFile(null, "tempfile"); + write(tempfilePath, "My string to save".getBytes("utf-8")); + + Source> source = FileIO.fromPath(tempfilePath); + Http.MultipartFormData.FilePart> part = new Http.MultipartFormData.FilePart<>("name", "filename", "text/plain", source); + Http.RequestBuilder request = fakeRequest() + .method(POST) + .bodyMultipart(singletonList(part), temporaryFileCreator, materializer) + .uri("/upload"); + + Result result = route(app, request); + String actual = contentAsString(result); + assertEquals("file size = 17", actual); + } catch (IOException e) { + fail(e.getMessage()); + } + }); + } + +} From 2bc6b093c810b751d32bb5cbcdd51a82a3d09855 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Fri, 30 Jun 2017 14:35:53 -0700 Subject: [PATCH 14/47] Add guice (#29) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 2893823a5..a8db5beba 100644 --- a/build.sbt +++ b/build.sbt @@ -6,4 +6,4 @@ lazy val root = (project in file(".")).enablePlugins(PlayJava) scalaVersion := "2.12.2" -javacOptions += "-Xlint:deprecation" \ No newline at end of file +libraryDependencies += guice From cf2b3a95fd3ee49b71142530a9b5dee36b75905e Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Thu, 6 Jul 2017 20:29:47 -0700 Subject: [PATCH 15/47] Updated with template-control on 2017-07-07T00:45:45.079Z (#30) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.1") --- conf/application.conf | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/application.conf b/conf/application.conf index 758a442b4..cb94680e7 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -1 +1 @@ -# https://www.playframework.com/documentation/latest/Configuration \ No newline at end of file +# https://www.playframework.com/documentation/latest/Configuration diff --git a/project/plugins.sbt b/project/plugins.sbt index 035b6e152..3d97a9e67 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.1") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 4611a236f5c93c41cd3b29ccf597bc4e871bd235 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 13 Jul 2017 12:32:30 -0700 Subject: [PATCH 16/47] Add CSRF check (#31) --- app/controllers/HomeController.java | 6 ++++-- app/views/index.scala.html | 3 ++- build.sbt | 2 ++ test/browsers/BrowserTest.java | 32 +++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 test/browsers/BrowserTest.java diff --git a/app/controllers/HomeController.java b/app/controllers/HomeController.java index 097bfd1f5..af6c82317 100644 --- a/app/controllers/HomeController.java +++ b/app/controllers/HomeController.java @@ -25,10 +25,12 @@ public HomeController(play.data.FormFactory formFactory) { public Result index() { Form form = formFactory.form(FormData.class); - Http.Context context = Http.Context.current(); - return ok(index.render(form, context.messages())); + return ok(index.render(form)); } + /** + * This method uses MyMultipartFormDataBodyParser as the body parser + */ @BodyParser.Of(MyMultipartFormDataBodyParser.class) public Result upload() throws IOException { final Http.MultipartFormData formData = request().body().asMultipartFormData(); diff --git a/app/views/index.scala.html b/app/views/index.scala.html index ce9956b9e..e7daff336 100644 --- a/app/views/index.scala.html +++ b/app/views/index.scala.html @@ -1,9 +1,10 @@ -@(form: Form[controllers.FormData])(implicit messages: play.i18n.Messages) +@(form: Form[controllers.FormData]) @main("Welcome to Play") { @helper.form(action = routes.HomeController.upload, 'enctype -> "multipart/form-data") { @helper.inputFile(form("name")) + @helper.CSRF.formField } } diff --git a/build.sbt b/build.sbt index a8db5beba..205db52cd 100644 --- a/build.sbt +++ b/build.sbt @@ -7,3 +7,5 @@ lazy val root = (project in file(".")).enablePlugins(PlayJava) scalaVersion := "2.12.2" libraryDependencies += guice + +testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-v") diff --git a/test/browsers/BrowserTest.java b/test/browsers/BrowserTest.java new file mode 100644 index 000000000..f07788612 --- /dev/null +++ b/test/browsers/BrowserTest.java @@ -0,0 +1,32 @@ +package browsers; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.fluentlenium.core.domain.FluentWebElement; +import org.junit.Test; +import org.openqa.selenium.By; +import play.test.WithBrowser; + +import static org.junit.Assert.assertTrue; + +public class BrowserTest extends WithBrowser { + + @Test + public void uploadFile() throws IOException { + Path tmpPath = Files.createTempFile(null, null); + Files.write(tmpPath, "hello".getBytes()); + + // http://fluentlenium.org/docs/#filling-forms + // https://saucelabs.com/resources/articles/best-practices-tips-selenium-file-upload + browser.goTo("/"); + FluentWebElement nameElement = browser.find(By.name("name")).first(); + nameElement.click(); + nameElement.fill().with(tmpPath.toAbsolutePath().toString()); + nameElement.submit(); + + assertTrue(browser.pageSource().equals("file size = 5")); + } + +} From 7bc1e1d96d63c0b6c0c01e5f25f8229e945f8774 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 20 Jul 2017 00:06:43 -0300 Subject: [PATCH 17/47] Updated with template-control on 2017-07-20T01:10:13.445Z (#33) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.2") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 3d97a9e67..ee9b31df6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.1") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.2") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From fdae664c830f6c58c5c1f056b1137bc75cd380fd Mon Sep 17 00:00:00 2001 From: Rich Dougherty Date: Sat, 12 Aug 2017 16:30:42 +1200 Subject: [PATCH 18/47] Updated with template-control on 2017-08-12T02:43:20.463Z (#34) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index ee9b31df6..cba4b29a7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.2") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 27827b788ce22f494a628af54e22901c1943f3e4 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Thu, 14 Sep 2017 17:06:27 -0700 Subject: [PATCH 19/47] Updated with template-control on 2017-09-14T23:29:36.576Z (#35) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.5") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index cba4b29a7..95fc353f1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.5") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 8bda0d4a3388e54da474db7ba86e2ff9d2d5cba8 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Thu, 12 Oct 2017 07:43:24 -0700 Subject: [PATCH 20/47] Upgrade branch 2.6.x using TemplateControl (#37) * Updated with template-control on 2017-10-05T23:18:59.175Z **/build.properties: sbt.version=1.0.2 **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.6") **/plugins.sbt: addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % "1.1.1") * Update play-enhancer to 1.2.2 --- .travis.yml | 2 +- build.sbt | 2 +- project/build.properties | 2 +- project/plugins.sbt | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9cd48f887..e3d78beb9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: scala scala: -- 2.12.2 +- 2.12.3 jdk: - oraclejdk8 cache: diff --git a/build.sbt b/build.sbt index 205db52cd..58a1ab167 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ version := "1.0-SNAPSHOT" lazy val root = (project in file(".")).enablePlugins(PlayJava) -scalaVersion := "2.12.2" +scalaVersion := "2.12.3" libraryDependencies += guice diff --git a/project/build.properties b/project/build.properties index 64317fdae..b7dd3cb2a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.15 +sbt.version=1.0.2 diff --git a/project/plugins.sbt b/project/plugins.sbt index 95fc353f1..53c585765 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,8 +1,8 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.5") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.6") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this // plugin if you prefer not to have this feature, or disable on a per project // basis using disablePlugins(PlayEnhancer) in your build.sbt -addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % "1.1.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % "1.2.2") From bd48e06a540b03522ba5d1b16568de52e47f4958 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Wed, 1 Nov 2017 21:58:39 -0700 Subject: [PATCH 21/47] Updated with template-control on 2017-11-02T02:51:11.210Z (#39) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.7") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 53c585765..5dd2e8a85 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.6") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.7") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 3762988e81da53e546fc3fcd4bb615e2d921b810 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Fri, 8 Dec 2017 19:41:18 -0800 Subject: [PATCH 22/47] Updated with template-control on 2017-12-09T03:16:27.551Z (#40) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.9") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 5dd2e8a85..f44388673 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.7") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.9") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 7a7248b69df49c952f5457a78abfcfbcd9999484 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 14 Dec 2017 15:12:49 -0200 Subject: [PATCH 23/47] Add Gradle configuration and Java 9 (#41) --- .gitignore | 2 + .travis.yml | 23 ++- README.md | 15 +- build.gradle | 41 ++++++ build.sbt | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 172 +++++++++++++++++++++++ gradlew.bat | 84 +++++++++++ scripts/script-helper | 13 ++ scripts/test-gradle | 13 ++ scripts/test-sbt | 8 ++ 12 files changed, 369 insertions(+), 9 deletions(-) create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 scripts/script-helper create mode 100755 scripts/test-gradle create mode 100755 scripts/test-sbt diff --git a/.gitignore b/.gitignore index eb372fc71..0e21b3a46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ logs target +build /.idea /.idea_modules /.classpath +/.gradle /.project /.settings /RUNNING_PID diff --git a/.travis.yml b/.travis.yml index e3d78beb9..c9b70ad0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,36 @@ language: scala scala: -- 2.12.3 +# When updating Scala versions, also check the excludes +# in build matrix below. +- 2.11.12 +- 2.12.4 jdk: - oraclejdk8 +- oraclejdk9 +env: + matrix: + - SCRIPT=scripts/test-sbt + - SCRIPT=scripts/test-gradle +script: +- $SCRIPT cache: directories: - "$HOME/.ivy2/cache" + - "$HOME/.gradle/caches" before_cache: - rm -rf $HOME/.ivy2/cache/com.typesafe.play/* - rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/* - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm + +# Exclude some combinations from build matrix. See: +# https://docs.travis-ci.com/user/customizing-the-build/#Build-Matrix +matrix: + exclude: + - scala: 2.11.12 + jdk: oraclejdk9 + +# See https://blog.travis-ci.com/2014-03-13-slack-notifications/ +# created with travis encrypt command line tool notifications: slack: secure: cw1Jfpq0W7w4fv7rLYm/mx2T/pNYoJ+zN+X5voxPfWE1/0QVHaFTXRBh7MwdUxE++B6wITVWoJiJoEc/qiqweeFPgL4U+rph3wY6f/xDL1M8xB2OrHnSAzTh6DubDlGtBMi2FDREU2tsdPJQ1XXcYeGr0mTLw5C59G9ZnnM45PcFuwW++3ojzAg7HA8Rc4nw3pvcpMkKywVNw/Q+4zeAdbrCmN+GI3LSa2tdc1SuN5JaM8r/w2Tx/yOnrraYKyi+QpSX6fOk0XMDsI1FpQY4z6253upIgctdf8BPCfz/dQlns8fCmBEQFQeZ0Sk42N1kWuB80FFRBkxH4NMAruZ7eoMbz26LK4br7oTKofHZ2o76952EsoWyGMJyVmL6MGvtAs5L7wv/8q3S0McleTzuEVMD6eL6D6EsbMDEZmgvv/D4dRuIdGydsJMH6ThpXzpJkj86Wqg9/F/Q1z8/y3+OlC8IO14Z8j0PqdKIZdAjQkIWjUKNFJgsagNVUfQFDHpNIMXhrLP68PfzK6XVc2m+iBizje7FM3laxbmrwd2ga1a3tIeIi50q1c4GWLapnv8hj2scTShRyyl4OD/k+6S/mUaAfbVijuAy9V3kKkXPbHAsGDPemjbZB+WCE9pM3chP4DG1dnTdw348b9tdWBtaJ3+lCDtnhTvMQcI74YL88CE= diff --git a/README.md b/README.md index 4cd021cd3..71909a34a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # Play File Upload using a custom BodyParser +[![Build Status](https://travis-ci.org/playframework/play-java-fileupload-example.svg?branch=2.6.x)](https://travis-ci.org/playframework/play-java-fileupload-example) + This is a sample project that shows how to upload a file through Akka Streams using a custom BodyParser using Akka Streams using the Java API. ## Default MultipartFormData Body Parser - + Play's Java API specifies a BodyParser.MultipartFormData class which uses a TemporaryFile wrapper class that creates a file under a "temporary" name and then deletes it only when the system is under GC pressure. - + ```java @BodyParser.Of(BodyParser.MultipartFormData.class) public Result upload() throws IOException { @@ -23,13 +25,13 @@ There are cases where it's useful to have more control over where and Play uploa In short, we want to replace: -``` +```java @BodyParser.Of(BodyParser.MultipartFormData.class) ``` -with +with: -``` +```java @BodyParser.Of(MyMultipartFormDataBodyParser.class) ``` @@ -86,10 +88,9 @@ class MyMultipartFormDataBodyParser extends DelegatingMultipartFormDataBodyParse The core Accumulator is generated from an `akka.streams.FileIO` sink which writes out bytes to the filesystem, and exposes a CompletionStage when the write operation has been completed. Because this code delegates to the Scala API implementation, the underlying `DelegatingMultipartFormDataBodyParser` exposes an abstract method: - + ```java abstract Function>> createFilePartHandler(); ``` `DelegatingMultipartFormDataBodyParser` does not know about any particular type, only `FilePart`, and so it falls to the implementation to fill in the details. - diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..2539ff2bf --- /dev/null +++ b/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'play' + id 'idea' +} + +def playVersion = '2.6.9' +def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") + +model { + components { + play { + platform play: playVersion, scala: scalaVersion, java: '1.8' + injectedRoutesGenerator = true + + sources { + twirlTemplates { + defaultImports = TwirlImports.JAVA + } + } + } + } +} + +dependencies { + play "com.typesafe.play:play-guice_$scalaVersion:$playVersion" + play "com.typesafe.play:play-logback_$scalaVersion:$playVersion" + play "com.typesafe.play:filters-helpers_$scalaVersion:$playVersion" +} + +repositories { + jcenter() + maven { + name "lightbend-maven-releases" + url "https://repo.lightbend.com/lightbend/maven-release" + } + ivy { + name "lightbend-ivy-release" + url "https://repo.lightbend.com/lightbend/ivy-releases" + layout "ivy" + } +} diff --git a/build.sbt b/build.sbt index 58a1ab167..c4691587d 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ version := "1.0-SNAPSHOT" lazy val root = (project in file(".")).enablePlugins(PlayJava) -scalaVersion := "2.12.3" +scalaVersion := "2.12.4" libraryDependencies += guice diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..01b8bf6b1f99cad9213fc495b33ad5bbab8efd20 GIT binary patch literal 54329 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

<5G2M1WZP5 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..e95643d6a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/scripts/script-helper b/scripts/script-helper new file mode 100644 index 000000000..ac39d9a51 --- /dev/null +++ b/scripts/script-helper @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +java_version=$(java -version 2>&1 | java -version 2>&1 | awk -F '"' '/version/ {print $2}') + +if [[ $java_version = 9* ]] ; then + echo "The build is using Java 9 ($java_version). We need additional JVM parameters" + export _JAVA_OPTIONS="$_JAVA_OPTIONS --add-modules=java.xml.bind" +else + echo "The build is NOT using Java 9 ($java_version). No addional JVM params needed." +fi diff --git a/scripts/test-gradle b/scripts/test-gradle new file mode 100755 index 000000000..298c7ebe1 --- /dev/null +++ b/scripts/test-gradle @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +. "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/script-helper" + +# Using cut because TRAVIS_SCALA_VERSION is the full Scala +# version (for example 2.12.4), but Gradle expects just the +# binary version (for example 2.12) +scala_binary_version=$(echo $TRAVIS_SCALA_VERSION | cut -c1-4) + +echo "+------------------------------+" +echo "| Executing tests using Gradle |" +echo "+------------------------------+" +./gradlew -Dscala.binary.version=$scala_binary_version test -i --stacktrace diff --git a/scripts/test-sbt b/scripts/test-sbt new file mode 100755 index 000000000..0425367b1 --- /dev/null +++ b/scripts/test-sbt @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +. "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/script-helper" + +echo "+----------------------------+" +echo "| Executing tests using sbt |" +echo "+----------------------------+" +sbt ++$TRAVIS_SCALA_VERSION test From f8ef46413945bbc5850e9a2b3261ecdf4ad80d9b Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 21 Dec 2017 18:08:18 -0200 Subject: [PATCH 24/47] Updated with template-control on 2017-12-21T19:35:22.480Z (#42) **/build.properties: sbt.version=1.0.4 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index b7dd3cb2a..394cb75cf 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.2 +sbt.version=1.0.4 From e22b179a5a4a6a43d51c3f9f77039a86947ee11f Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Thu, 21 Dec 2017 17:26:55 -0800 Subject: [PATCH 25/47] Updated with template-control on 2017-12-21T20:59:17.023Z (#43) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.10") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index f44388673..d023e8bc8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.9") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.10") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 24699ab74c13586c51fdb3de67fce6b11852b455 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 2 Jan 2018 15:46:42 -0200 Subject: [PATCH 26/47] Upgrade branch 2.6.x using TemplateControl (#44) * Updated with template-control on 2017-12-22T16:49:12.373Z **/test-gradle: ./gradlew -Dscala.binary.version=$scala_binary_version check -i --stacktrace * Add run permission to test-gradle script --- scripts/test-gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test-gradle b/scripts/test-gradle index 298c7ebe1..84a051a20 100755 --- a/scripts/test-gradle +++ b/scripts/test-gradle @@ -10,4 +10,4 @@ scala_binary_version=$(echo $TRAVIS_SCALA_VERSION | cut -c1-4) echo "+------------------------------+" echo "| Executing tests using Gradle |" echo "+------------------------------+" -./gradlew -Dscala.binary.version=$scala_binary_version test -i --stacktrace +./gradlew -Dscala.binary.version=$scala_binary_version check -i --stacktrace From 99c6176560dee70abc1e2c2ca786035166b9ab57 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 9 Jan 2018 21:02:51 -0200 Subject: [PATCH 27/47] Upgrade branch 2.6.x using TemplateControl (#46) * Updated with template-control on 2018-01-09T20:17:21.678Z **/build.properties: sbt.version=1.1.0 **build.gradle: def playVersion = "2.6.10" * test-gradle-permissions --- build.gradle | 2 +- project/build.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 2539ff2bf..f613d7794 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' } -def playVersion = '2.6.9' +def playVersion = "2.6.10" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") model { diff --git a/project/build.properties b/project/build.properties index 394cb75cf..8b697bbb9 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.4 +sbt.version=1.1.0 From 84735c31a7e335d96ec396ab73d0945d2f8d547e Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 11 Jan 2018 17:17:06 -0200 Subject: [PATCH 28/47] Fix cross build to sbt 1.1.0 (#47) --- build.sbt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.sbt b/build.sbt index c4691587d..1e06dee44 100644 --- a/build.sbt +++ b/build.sbt @@ -6,6 +6,8 @@ lazy val root = (project in file(".")).enablePlugins(PlayJava) scalaVersion := "2.12.4" +crossScalaVersions := Seq("2.11.12", "2.12.4") + libraryDependencies += guice testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-v") From 3fe58ba5324253ec0faedc166490606b54eed12f Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Fri, 12 Jan 2018 12:18:13 -0200 Subject: [PATCH 29/47] Upgrade branch 2.6.x using TemplateControl (#48) * Updated with template-control on 2018-01-11T21:32:26.636Z **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.11") **build.gradle: def playVersion = "2.6.11" * test-gradle-permissions --- build.gradle | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f613d7794..fb9ac6e50 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' } -def playVersion = "2.6.10" +def playVersion = "2.6.11" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") model { diff --git a/project/plugins.sbt b/project/plugins.sbt index d023e8bc8..75c6cbb3a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.10") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.11") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From ab4a0204fafb5f197669efe83ef3fd785515e7ba Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Fri, 2 Mar 2018 18:18:58 -0300 Subject: [PATCH 30/47] Updated with template-control on 2018-03-02T18:56:28.047Z (#49) **/build.properties: sbt.version=1.1.1 **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.12") **build.gradle: def playVersion = "2.6.12" --- build.gradle | 2 +- project/build.properties | 2 +- project/plugins.sbt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index fb9ac6e50..cdfc6c393 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' } -def playVersion = "2.6.11" +def playVersion = "2.6.12" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") model { diff --git a/project/build.properties b/project/build.properties index 8b697bbb9..31334bbd3 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.0 +sbt.version=1.1.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index 75c6cbb3a..486a3de7b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.11") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.12") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From d61a4539ae3e0b3cb0408cfa83fed9b582f7b1b8 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Wed, 21 Mar 2018 13:42:06 -0700 Subject: [PATCH 31/47] Add explicit note on upload limit (#51) * Add explicit note on upload limit Fixes https://github.com/playframework/play-scala-fileupload-example/issues/50 * Update application.conf * Update application.conf --- conf/application.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conf/application.conf b/conf/application.conf index cb94680e7..b0023095d 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -1 +1,5 @@ # https://www.playframework.com/documentation/latest/Configuration + +# Sets the maximum file size that can be uploaded to 1024k. +# https://www.playframework.com/documentation/2.6.x/JavaBodyParsers#Content-length-limits +play.http.parser.maxMemoryBuffer=1024k From bbf6aef2acf4a5a3ca69592f48e4ac0f0947ce73 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Sat, 7 Apr 2018 10:50:55 -0300 Subject: [PATCH 32/47] Updated with template-control on 2018-04-06T19:34:57.877Z (#52) **/build.properties: sbt.version=1.1.2 **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.13") **build.gradle: def playVersion = "2.6.13" --- build.gradle | 2 +- project/build.properties | 2 +- project/plugins.sbt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index cdfc6c393..4d6eb6858 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' } -def playVersion = "2.6.12" +def playVersion = "2.6.13" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") model { diff --git a/project/build.properties b/project/build.properties index 31334bbd3..05313438a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.1 +sbt.version=1.1.2 diff --git a/project/plugins.sbt b/project/plugins.sbt index 486a3de7b..4978b7b3e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.12") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.13") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From be951640ba0381aa949decdce226e42f19eb6c4b Mon Sep 17 00:00:00 2001 From: Rich Dougherty Date: Tue, 29 May 2018 03:17:11 +1200 Subject: [PATCH 33/47] Updated with template-control on 2018-05-27T23:54:09.370Z (#54) **build.sbt: scalaVersion := "2.12.6" **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.15") **build.gradle: def playVersion = "2.6.15" --- build.gradle | 2 +- build.sbt | 2 +- project/plugins.sbt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 4d6eb6858..508e1ab60 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' } -def playVersion = "2.6.13" +def playVersion = "2.6.15" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") model { diff --git a/build.sbt b/build.sbt index 1e06dee44..f527f93f1 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ version := "1.0-SNAPSHOT" lazy val root = (project in file(".")).enablePlugins(PlayJava) -scalaVersion := "2.12.4" +scalaVersion := "2.12.6" crossScalaVersions := Seq("2.11.12", "2.12.4") diff --git a/project/plugins.sbt b/project/plugins.sbt index 4978b7b3e..2bc7b30b9 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.13") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.15") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 0c1ec804ae9ab8e28dade0143160ac9ad9a01384 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Sat, 9 Jun 2018 00:04:51 -0400 Subject: [PATCH 34/47] Upgrade branch 2.6.x using TemplateControl (#55) * Updated with template-control on 2018-06-08T21:15:48.659Z **/build.properties: sbt.version=1.1.6 * Add Java 10 and 11 to Travis build --- .travis.yml | 16 +++++++++++++++- gradle/wrapper/gradle-wrapper.properties | 2 +- project/build.properties | 2 +- scripts/script-helper | 8 ++++---- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9b70ad0d..f5e7558f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,12 @@ scala: # When updating Scala versions, also check the excludes # in build matrix below. - 2.11.12 -- 2.12.4 +- 2.12.6 jdk: - oraclejdk8 - oraclejdk9 +- oraclejdk10 +- oraclejdk11 env: matrix: - SCRIPT=scripts/test-sbt @@ -28,6 +30,18 @@ matrix: exclude: - scala: 2.11.12 jdk: oraclejdk9 + - scala: 2.11.12 + jdk: oraclejdk10 + - scala: 2.11.12 + jdk: oraclejdk11 + allow_failures: + # We should allow failures here since Java 11 removed some modules including + # java.xml.bind which we are adding when running with Java 9+. For more details + # see http://openjdk.java.net/jeps/320 + # + # Play already has a fix for that, but it needs to be backported and released + # for 2.6.x: https://github.com/playframework/playframework/pull/8382 + - jdk: oraclejdk11 # See https://blog.travis-ci.com/2014-03-13-slack-notifications/ # created with travis encrypt command line tool diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5ce78ed19..5a17e7455 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/project/build.properties b/project/build.properties index 05313438a..d6e35076c 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.2 +sbt.version=1.1.6 diff --git a/scripts/script-helper b/scripts/script-helper index ac39d9a51..9a2faa643 100644 --- a/scripts/script-helper +++ b/scripts/script-helper @@ -5,9 +5,9 @@ set -o pipefail java_version=$(java -version 2>&1 | java -version 2>&1 | awk -F '"' '/version/ {print $2}') -if [[ $java_version = 9* ]] ; then - echo "The build is using Java 9 ($java_version). We need additional JVM parameters" - export _JAVA_OPTIONS="$_JAVA_OPTIONS --add-modules=java.xml.bind" +if [[ $java_version = 1.8* ]] ; then + echo "The build is using Java 8 ($java_version). No addional JVM params needed." else - echo "The build is NOT using Java 9 ($java_version). No addional JVM params needed." + echo "The build is using Java 9+ ($java_version). We need additional JVM parameters" + export _JAVA_OPTIONS="$_JAVA_OPTIONS --add-modules=java.xml.bind" fi From ced012da7ee778cc54a205a78a777acef48573ba Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Mon, 16 Jul 2018 17:55:27 -0400 Subject: [PATCH 35/47] Updated with template-control on 2018-07-16T18:38:45.481Z (#56) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.16") **build.gradle: def playVersion = "2.6.16" --- build.gradle | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 508e1ab60..aecf987de 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' } -def playVersion = "2.6.15" +def playVersion = "2.6.16" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") model { diff --git a/project/plugins.sbt b/project/plugins.sbt index 2bc7b30b9..09049600e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.15") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.16") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 0c4e6d96f28c0bb7c78ec4f3bc35b9edb5046f33 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 19 Jul 2018 12:26:43 -0400 Subject: [PATCH 36/47] Updated with template-control on 2018-07-19T01:58:54.727Z (#57) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.17") **build.gradle: def playVersion = "2.6.17" --- build.gradle | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index aecf987de..462e29eb8 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' } -def playVersion = "2.6.16" +def playVersion = "2.6.17" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") model { diff --git a/project/plugins.sbt b/project/plugins.sbt index 09049600e..690afa99e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.16") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.17") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 3db7879489fe0deafd44efef241888b7113df261 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Mon, 20 Aug 2018 19:36:45 -0400 Subject: [PATCH 37/47] Updated with template-control on 2018-08-20T20:37:13.644Z (#59) **/build.properties: sbt.version=1.2.1 **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.18") **build.gradle: def playVersion = "2.6.18" **gradle/wrapper/gradle-wrapper.properties: distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- project/build.properties | 2 +- project/plugins.sbt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 462e29eb8..0bdd2f5ed 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' } -def playVersion = "2.6.17" +def playVersion = "2.6.18" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") model { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5a17e7455..89dba2d9d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/project/build.properties b/project/build.properties index d6e35076c..5620cc502 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.6 +sbt.version=1.2.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index 690afa99e..15a090665 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.17") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.18") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From be4d6335645dc2244ca365fa0db0c641718c0a1a Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 11 Sep 2018 18:09:26 -0400 Subject: [PATCH 38/47] Updated with template-control on 2018-09-11T20:14:49.858Z (#60) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.19") **build.gradle: def playVersion = "2.6.19" --- build.gradle | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 0bdd2f5ed..bb7824b7e 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' } -def playVersion = "2.6.18" +def playVersion = "2.6.19" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") model { diff --git a/project/plugins.sbt b/project/plugins.sbt index 15a090665..033adf540 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.18") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.19") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 5e63c891d406e3a85d8047d0a14a1bd3aa4c0872 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Tue, 9 Oct 2018 09:49:06 +0200 Subject: [PATCH 39/47] Upgrade branch 2.6.x using TemplateControl (#61) ``` Updated with template-control on 2018-10-08T19:11:41.066Z **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.20") **build.gradle: def playVersion = "2.6.20" ``` --- build.gradle | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index bb7824b7e..9348ca001 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' } -def playVersion = "2.6.19" +def playVersion = "2.6.20" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") model { diff --git a/project/plugins.sbt b/project/plugins.sbt index 033adf540..b34a9c095 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.19") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.20") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 06dc3281873b83620a763c8e36bfeaec40140a59 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Thu, 6 Dec 2018 15:26:17 +0100 Subject: [PATCH 40/47] Upgrade branch 2.6.x using TemplateControl (#64) * Updated with template-control on 2018-11-29T15:50:43.891Z /.mergify.yml: wrote /.mergify.yml **build.sbt: scalaVersion := "2.12.7" * Remove JDK 9 & 10 --- .mergify.yml | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ .travis.yml | 7 +------ build.sbt | 2 +- 3 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 .mergify.yml diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 000000000..4a37a16dd --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,57 @@ +pull_request_rules: + - name: automatic merge on CI success require review + conditions: + - status-success=continuous-integration/travis-ci/pr + - "#approved-reviews-by>=1" + - "#changes-requested-reviews-by=0" + - label=merge-when-green + - label!=block-merge + actions: + merge: + method: squash + strict: smart + + - name: automatic merge on CI success for TemplateControl + conditions: + - status-success=continuous-integration/travis-ci/pr + - label=merge-when-green + - label=template-control + - label!=block-merge + actions: + merge: + method: squash + strict: smart + + # delete any branch when already merged + # doesn't matter if marked with labels or not + - name: delete branch after merge + conditions: + - merged + actions: + delete_head_branch: {} + + # delete 'merge-when-green' label if present and merged + - name: remove label after merge + conditions: + - merged + - label=merge-when-green + actions: + label: + remove: [merge-when-green] + + # delete 'template-control' label if present and merged + - name: remove label after merge + conditions: + - merged + - label=template-control + actions: + label: + remove: [template-control] + + - name: auto add wip + conditions: + # match a few flavours of wip + - title~=^(\[wip\]( |:) |\[WIP\]( |:) |wip( |:) |WIP( |:)).* + actions: + label: + add: ["block-merge"] \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index f5e7558f0..d55b6f767 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,6 @@ scala: - 2.12.6 jdk: - oraclejdk8 -- oraclejdk9 -- oraclejdk10 - oraclejdk11 env: matrix: @@ -27,11 +25,8 @@ before_cache: # Exclude some combinations from build matrix. See: # https://docs.travis-ci.com/user/customizing-the-build/#Build-Matrix matrix: + fast_finish: true exclude: - - scala: 2.11.12 - jdk: oraclejdk9 - - scala: 2.11.12 - jdk: oraclejdk10 - scala: 2.11.12 jdk: oraclejdk11 allow_failures: diff --git a/build.sbt b/build.sbt index f527f93f1..e713132a3 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ version := "1.0-SNAPSHOT" lazy val root = (project in file(".")).enablePlugins(PlayJava) -scalaVersion := "2.12.6" +scalaVersion := "2.12.7" crossScalaVersions := Seq("2.11.12", "2.12.4") From 31231b1fa09a5b21a7623b98dc129a3813ac921c Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Fri, 4 Jan 2019 13:58:47 -0500 Subject: [PATCH 41/47] Updated with template-control on 2019-01-04T17:13:23.621Z (#65) /.travis.yml: wrote /.travis.yml **build.sbt: scalaVersion := "2.12.8" **/build.properties: sbt.version=1.2.8 --- .travis.yml | 73 +++++++++++++++++++++------------------- build.sbt | 2 +- project/build.properties | 2 +- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/.travis.yml b/.travis.yml index d55b6f767..1e8c0e7c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,45 +1,50 @@ language: scala scala: -# When updating Scala versions, also check the excludes -# in build matrix below. -- 2.11.12 -- 2.12.6 -jdk: -- oraclejdk8 -- oraclejdk11 + - 2.12.8 + +before_install: + - curl -sL https://github.com/shyiko/jabba/raw/master/install.sh | bash && . ~/.jabba/jabba.sh + env: + global: + - JABBA_HOME=$HOME/.jabba matrix: - - SCRIPT=scripts/test-sbt - - SCRIPT=scripts/test-gradle -script: -- $SCRIPT -cache: - directories: - - "$HOME/.ivy2/cache" - - "$HOME/.gradle/caches" -before_cache: -- rm -rf $HOME/.ivy2/cache/com.typesafe.play/* -- rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/* -- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm + # There is no concise way to specify multi-dimensional build matrix: + # https://github.com/travis-ci/travis-ci/issues/1519 + - SCRIPT=scripts/test-sbt TRAVIS_JDK=adopt@1.8.192-12 + - SCRIPT=scripts/test-sbt TRAVIS_JDK=adopt@1.11.0-1 + - SCRIPT=scripts/test-gradle TRAVIS_JDK=adopt@1.8.192-12 + - SCRIPT=scripts/test-gradle TRAVIS_JDK=adopt@1.11.0-1 # Exclude some combinations from build matrix. See: # https://docs.travis-ci.com/user/customizing-the-build/#Build-Matrix matrix: fast_finish: true - exclude: - - scala: 2.11.12 - jdk: oraclejdk11 allow_failures: - # We should allow failures here since Java 11 removed some modules including - # java.xml.bind which we are adding when running with Java 9+. For more details - # see http://openjdk.java.net/jeps/320 - # - # Play already has a fix for that, but it needs to be backported and released - # for 2.6.x: https://github.com/playframework/playframework/pull/8382 - - jdk: oraclejdk11 + # Current release of Gradle still does not supports Play 2.7.x releases + # As soon as there is a release of Gradle that fixes that, we can then + # remove this allowed failure. + - env: SCRIPT=scripts/test-gradle TRAVIS_JDK=adopt@1.8.192-12 + - env: SCRIPT=scripts/test-gradle TRAVIS_JDK=adopt@1.11.0-1 + # Java 11 is still not fully supported. It is good that we are already + # testing our sample applications to better discover possible problems + # but we can allow failures here too. + - env: SCRIPT=scripts/test-sbt TRAVIS_JDK=adopt@1.11.0-1 + +install: + - $JABBA_HOME/bin/jabba install $TRAVIS_JDK + - unset _JAVA_OPTIONS + - export JAVA_HOME="$JABBA_HOME/jdk/$TRAVIS_JDK" && export PATH="$JAVA_HOME/bin:$PATH" && java -Xmx32m -version -# See https://blog.travis-ci.com/2014-03-13-slack-notifications/ -# created with travis encrypt command line tool -notifications: - slack: - secure: cw1Jfpq0W7w4fv7rLYm/mx2T/pNYoJ+zN+X5voxPfWE1/0QVHaFTXRBh7MwdUxE++B6wITVWoJiJoEc/qiqweeFPgL4U+rph3wY6f/xDL1M8xB2OrHnSAzTh6DubDlGtBMi2FDREU2tsdPJQ1XXcYeGr0mTLw5C59G9ZnnM45PcFuwW++3ojzAg7HA8Rc4nw3pvcpMkKywVNw/Q+4zeAdbrCmN+GI3LSa2tdc1SuN5JaM8r/w2Tx/yOnrraYKyi+QpSX6fOk0XMDsI1FpQY4z6253upIgctdf8BPCfz/dQlns8fCmBEQFQeZ0Sk42N1kWuB80FFRBkxH4NMAruZ7eoMbz26LK4br7oTKofHZ2o76952EsoWyGMJyVmL6MGvtAs5L7wv/8q3S0McleTzuEVMD6eL6D6EsbMDEZmgvv/D4dRuIdGydsJMH6ThpXzpJkj86Wqg9/F/Q1z8/y3+OlC8IO14Z8j0PqdKIZdAjQkIWjUKNFJgsagNVUfQFDHpNIMXhrLP68PfzK6XVc2m+iBizje7FM3laxbmrwd2ga1a3tIeIi50q1c4GWLapnv8hj2scTShRyyl4OD/k+6S/mUaAfbVijuAy9V3kKkXPbHAsGDPemjbZB+WCE9pM3chP4DG1dnTdw348b9tdWBtaJ3+lCDtnhTvMQcI74YL88CE= +script: + - $SCRIPT + +before_cache: + - find $HOME/.ivy2 -name "ivydata-*.properties" -delete + - find $HOME/.sbt -name "*.lock" -delete + +cache: + directories: + - "$HOME/.ivy2/cache" + - "$HOME/.gradle/caches" + - "$HOME/.jabba/jdk" diff --git a/build.sbt b/build.sbt index e713132a3..97685e900 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ version := "1.0-SNAPSHOT" lazy val root = (project in file(".")).enablePlugins(PlayJava) -scalaVersion := "2.12.7" +scalaVersion := "2.12.8" crossScalaVersions := Seq("2.11.12", "2.12.4") diff --git a/project/build.properties b/project/build.properties index 5620cc502..c0bab0494 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.2.1 +sbt.version=1.2.8 From 0a75df15557b453c6c8420af37c9e74f1269feda Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Tue, 8 Jan 2019 17:00:31 +0100 Subject: [PATCH 42/47] Updated with template-control on 2019-01-08T14:44:42.021Z (#70) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.21") **build.gradle: def playVersion = "2.6.21" --- build.gradle | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 9348ca001..c54e7124d 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' } -def playVersion = "2.6.20" +def playVersion = "2.6.21" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") model { diff --git a/project/plugins.sbt b/project/plugins.sbt index b34a9c095..08d6fe8fe 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.20") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.21") // Play enhancer - this automatically generates getters/setters for public fields // and rewrites accessors of these fields to use the getters/setters. Remove this From 8187547c943c7bde32ab89dd5c097273d42a3ce2 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Wed, 16 Jan 2019 13:53:21 +0100 Subject: [PATCH 43/47] Upgrade branch 2.6.x using TemplateControl (#72) ``` Updated with template-control on 2019-01-16T12:08:46.120Z /LICENSE: wrote /LICENSE /NOTICE: wrote /NOTICE /.mergify.yml: wrote /.mergify.yml ``` --- .mergify.yml | 10 ++--- LICENSE | 119 ++++++++++++++++++++++++++++++++++++++++++++++++--- NOTICE | 8 ++++ 3 files changed, 125 insertions(+), 12 deletions(-) create mode 100644 NOTICE diff --git a/.mergify.yml b/.mergify.yml index 4a37a16dd..3549efd41 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -22,16 +22,13 @@ pull_request_rules: method: squash strict: smart - # delete any branch when already merged - # doesn't matter if marked with labels or not - name: delete branch after merge conditions: - merged actions: delete_head_branch: {} - # delete 'merge-when-green' label if present and merged - - name: remove label after merge + - name: remove merge-when-green label after merge conditions: - merged - label=merge-when-green @@ -39,8 +36,7 @@ pull_request_rules: label: remove: [merge-when-green] - # delete 'template-control' label if present and merged - - name: remove label after merge + - name: remove template-control label after merge conditions: - merged - label=template-control @@ -54,4 +50,4 @@ pull_request_rules: - title~=^(\[wip\]( |:) |\[WIP\]( |:) |wip( |:) |WIP( |:)).* actions: label: - add: ["block-merge"] \ No newline at end of file + add: ["block-merge"] diff --git a/LICENSE b/LICENSE index b018ae2bc..670154e35 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,116 @@ -License -------- -Written in 2016 by Lightbend +CC0 1.0 Universal -To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. +Statement of Purpose -You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see . +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + diff --git a/NOTICE b/NOTICE new file mode 100644 index 000000000..6d6c034d3 --- /dev/null +++ b/NOTICE @@ -0,0 +1,8 @@ +Written by Lightbend + +To the extent possible under law, the author(s) have dedicated all copyright and +related and neighboring rights to this software to the public domain worldwide. +This software is distributed without any warranty. + +You should have received a copy of the CC0 Public Domain Dedication along with +this software. If not, see . From 3d310e407e6c4c21d621a26f482e3a1044995a0b Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Thu, 17 Jan 2019 19:31:47 +0100 Subject: [PATCH 44/47] Upgrade branch 2.6.x using TemplateControl (#73) ``` Updated with template-control on 2019-01-17T15:10:57.657Z /.mergify.yml: wrote /.mergify.yml ``` --- .mergify.yml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 3549efd41..b215a7709 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -4,7 +4,6 @@ pull_request_rules: - status-success=continuous-integration/travis-ci/pr - "#approved-reviews-by>=1" - "#changes-requested-reviews-by=0" - - label=merge-when-green - label!=block-merge actions: merge: @@ -15,7 +14,6 @@ pull_request_rules: conditions: - status-success=continuous-integration/travis-ci/pr - label=merge-when-green - - label=template-control - label!=block-merge actions: merge: @@ -35,19 +33,3 @@ pull_request_rules: actions: label: remove: [merge-when-green] - - - name: remove template-control label after merge - conditions: - - merged - - label=template-control - actions: - label: - remove: [template-control] - - - name: auto add wip - conditions: - # match a few flavours of wip - - title~=^(\[wip\]( |:) |\[WIP\]( |:) |wip( |:) |WIP( |:)).* - actions: - label: - add: ["block-merge"] From 0ed5d2231159753d6ef2bbd95805eb385bd0b7f8 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Fri, 1 Feb 2019 14:11:10 +0100 Subject: [PATCH 45/47] Upgrade branch 2.6.x using TemplateControl (#74) ``` Updated with template-control on 2019-02-01T10:40:33.199Z /.mergify.yml: wrote /.mergify.yml ``` --- .mergify.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index b215a7709..fbbe4380f 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -25,11 +25,3 @@ pull_request_rules: - merged actions: delete_head_branch: {} - - - name: remove merge-when-green label after merge - conditions: - - merged - - label=merge-when-green - actions: - label: - remove: [merge-when-green] From 7e3fac092abbd1a34b0375733ad0c89e90390d55 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 14 Feb 2019 00:39:44 -0500 Subject: [PATCH 46/47] Updated with template-control on 2019-02-13T20:25:39.476Z (#76) /.mergify.yml: wrote /.mergify.yml --- .mergify.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index fbbe4380f..32f8689ae 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,7 +1,7 @@ pull_request_rules: - name: automatic merge on CI success require review conditions: - - status-success=continuous-integration/travis-ci/pr + - status-success=Travis CI - Pull Request - "#approved-reviews-by>=1" - "#changes-requested-reviews-by=0" - label!=block-merge @@ -12,7 +12,7 @@ pull_request_rules: - name: automatic merge on CI success for TemplateControl conditions: - - status-success=continuous-integration/travis-ci/pr + - status-success=Travis CI - Pull Request - label=merge-when-green - label!=block-merge actions: From 3582b98d4b8fc479f58f6d3aace5c84bd4af1aca Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 23 Apr 2019 15:49:54 +0100 Subject: [PATCH 47/47] Nest play-java-fileupload-example --- .../.gitignore | 0 .../.mergify.yml | 0 .../.travis.yml | 0 LICENSE => play-java-fileupload-example/LICENSE | 0 NOTICE => play-java-fileupload-example/NOTICE | 0 README.md => play-java-fileupload-example/README.md | 0 .../app}/controllers/FormData.java | 0 .../app}/controllers/HomeController.java | 0 .../controllers/MyMultipartFormDataBodyParser.java | 0 .../app}/views/index.scala.html | 0 .../app}/views/main.scala.html | 0 .../build.gradle | 0 build.sbt => play-java-fileupload-example/build.sbt | 0 .../conf}/application.conf | 0 .../conf}/logback.xml | 0 {conf => play-java-fileupload-example/conf}/routes | 0 .../gradle}/wrapper/gradle-wrapper.jar | Bin .../gradle}/wrapper/gradle-wrapper.properties | 0 gradlew => play-java-fileupload-example/gradlew | 0 .../gradlew.bat | 0 .../project}/build.properties | 0 .../project}/plugins.sbt | 0 .../public}/images/favicon.png | Bin .../public}/javascripts/hello.js | 0 .../public}/stylesheets/main.css | 0 .../scripts}/script-helper | 0 .../scripts}/test-gradle | 0 .../scripts}/test-sbt | 0 .../test}/browsers/BrowserTest.java | 0 .../test}/controllers/HomeControllerTest.java | 0 30 files changed, 0 insertions(+), 0 deletions(-) rename .gitignore => play-java-fileupload-example/.gitignore (100%) rename .mergify.yml => play-java-fileupload-example/.mergify.yml (100%) rename .travis.yml => play-java-fileupload-example/.travis.yml (100%) rename LICENSE => play-java-fileupload-example/LICENSE (100%) rename NOTICE => play-java-fileupload-example/NOTICE (100%) rename README.md => play-java-fileupload-example/README.md (100%) rename {app => play-java-fileupload-example/app}/controllers/FormData.java (100%) rename {app => play-java-fileupload-example/app}/controllers/HomeController.java (100%) rename {app => play-java-fileupload-example/app}/controllers/MyMultipartFormDataBodyParser.java (100%) rename {app => play-java-fileupload-example/app}/views/index.scala.html (100%) rename {app => play-java-fileupload-example/app}/views/main.scala.html (100%) rename build.gradle => play-java-fileupload-example/build.gradle (100%) rename build.sbt => play-java-fileupload-example/build.sbt (100%) rename {conf => play-java-fileupload-example/conf}/application.conf (100%) rename {conf => play-java-fileupload-example/conf}/logback.xml (100%) rename {conf => play-java-fileupload-example/conf}/routes (100%) rename {gradle => play-java-fileupload-example/gradle}/wrapper/gradle-wrapper.jar (100%) rename {gradle => play-java-fileupload-example/gradle}/wrapper/gradle-wrapper.properties (100%) rename gradlew => play-java-fileupload-example/gradlew (100%) rename gradlew.bat => play-java-fileupload-example/gradlew.bat (100%) rename {project => play-java-fileupload-example/project}/build.properties (100%) rename {project => play-java-fileupload-example/project}/plugins.sbt (100%) rename {public => play-java-fileupload-example/public}/images/favicon.png (100%) rename {public => play-java-fileupload-example/public}/javascripts/hello.js (100%) rename {public => play-java-fileupload-example/public}/stylesheets/main.css (100%) rename {scripts => play-java-fileupload-example/scripts}/script-helper (100%) rename {scripts => play-java-fileupload-example/scripts}/test-gradle (100%) rename {scripts => play-java-fileupload-example/scripts}/test-sbt (100%) rename {test => play-java-fileupload-example/test}/browsers/BrowserTest.java (100%) rename {test => play-java-fileupload-example/test}/controllers/HomeControllerTest.java (100%) diff --git a/.gitignore b/play-java-fileupload-example/.gitignore similarity index 100% rename from .gitignore rename to play-java-fileupload-example/.gitignore diff --git a/.mergify.yml b/play-java-fileupload-example/.mergify.yml similarity index 100% rename from .mergify.yml rename to play-java-fileupload-example/.mergify.yml diff --git a/.travis.yml b/play-java-fileupload-example/.travis.yml similarity index 100% rename from .travis.yml rename to play-java-fileupload-example/.travis.yml diff --git a/LICENSE b/play-java-fileupload-example/LICENSE similarity index 100% rename from LICENSE rename to play-java-fileupload-example/LICENSE diff --git a/NOTICE b/play-java-fileupload-example/NOTICE similarity index 100% rename from NOTICE rename to play-java-fileupload-example/NOTICE diff --git a/README.md b/play-java-fileupload-example/README.md similarity index 100% rename from README.md rename to play-java-fileupload-example/README.md diff --git a/app/controllers/FormData.java b/play-java-fileupload-example/app/controllers/FormData.java similarity index 100% rename from app/controllers/FormData.java rename to play-java-fileupload-example/app/controllers/FormData.java diff --git a/app/controllers/HomeController.java b/play-java-fileupload-example/app/controllers/HomeController.java similarity index 100% rename from app/controllers/HomeController.java rename to play-java-fileupload-example/app/controllers/HomeController.java diff --git a/app/controllers/MyMultipartFormDataBodyParser.java b/play-java-fileupload-example/app/controllers/MyMultipartFormDataBodyParser.java similarity index 100% rename from app/controllers/MyMultipartFormDataBodyParser.java rename to play-java-fileupload-example/app/controllers/MyMultipartFormDataBodyParser.java diff --git a/app/views/index.scala.html b/play-java-fileupload-example/app/views/index.scala.html similarity index 100% rename from app/views/index.scala.html rename to play-java-fileupload-example/app/views/index.scala.html diff --git a/app/views/main.scala.html b/play-java-fileupload-example/app/views/main.scala.html similarity index 100% rename from app/views/main.scala.html rename to play-java-fileupload-example/app/views/main.scala.html diff --git a/build.gradle b/play-java-fileupload-example/build.gradle similarity index 100% rename from build.gradle rename to play-java-fileupload-example/build.gradle diff --git a/build.sbt b/play-java-fileupload-example/build.sbt similarity index 100% rename from build.sbt rename to play-java-fileupload-example/build.sbt diff --git a/conf/application.conf b/play-java-fileupload-example/conf/application.conf similarity index 100% rename from conf/application.conf rename to play-java-fileupload-example/conf/application.conf diff --git a/conf/logback.xml b/play-java-fileupload-example/conf/logback.xml similarity index 100% rename from conf/logback.xml rename to play-java-fileupload-example/conf/logback.xml diff --git a/conf/routes b/play-java-fileupload-example/conf/routes similarity index 100% rename from conf/routes rename to play-java-fileupload-example/conf/routes diff --git a/gradle/wrapper/gradle-wrapper.jar b/play-java-fileupload-example/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from gradle/wrapper/gradle-wrapper.jar rename to play-java-fileupload-example/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/play-java-fileupload-example/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from gradle/wrapper/gradle-wrapper.properties rename to play-java-fileupload-example/gradle/wrapper/gradle-wrapper.properties diff --git a/gradlew b/play-java-fileupload-example/gradlew similarity index 100% rename from gradlew rename to play-java-fileupload-example/gradlew diff --git a/gradlew.bat b/play-java-fileupload-example/gradlew.bat similarity index 100% rename from gradlew.bat rename to play-java-fileupload-example/gradlew.bat diff --git a/project/build.properties b/play-java-fileupload-example/project/build.properties similarity index 100% rename from project/build.properties rename to play-java-fileupload-example/project/build.properties diff --git a/project/plugins.sbt b/play-java-fileupload-example/project/plugins.sbt similarity index 100% rename from project/plugins.sbt rename to play-java-fileupload-example/project/plugins.sbt diff --git a/public/images/favicon.png b/play-java-fileupload-example/public/images/favicon.png similarity index 100% rename from public/images/favicon.png rename to play-java-fileupload-example/public/images/favicon.png diff --git a/public/javascripts/hello.js b/play-java-fileupload-example/public/javascripts/hello.js similarity index 100% rename from public/javascripts/hello.js rename to play-java-fileupload-example/public/javascripts/hello.js diff --git a/public/stylesheets/main.css b/play-java-fileupload-example/public/stylesheets/main.css similarity index 100% rename from public/stylesheets/main.css rename to play-java-fileupload-example/public/stylesheets/main.css diff --git a/scripts/script-helper b/play-java-fileupload-example/scripts/script-helper similarity index 100% rename from scripts/script-helper rename to play-java-fileupload-example/scripts/script-helper diff --git a/scripts/test-gradle b/play-java-fileupload-example/scripts/test-gradle similarity index 100% rename from scripts/test-gradle rename to play-java-fileupload-example/scripts/test-gradle diff --git a/scripts/test-sbt b/play-java-fileupload-example/scripts/test-sbt similarity index 100% rename from scripts/test-sbt rename to play-java-fileupload-example/scripts/test-sbt diff --git a/test/browsers/BrowserTest.java b/play-java-fileupload-example/test/browsers/BrowserTest.java similarity index 100% rename from test/browsers/BrowserTest.java rename to play-java-fileupload-example/test/browsers/BrowserTest.java diff --git a/test/controllers/HomeControllerTest.java b/play-java-fileupload-example/test/controllers/HomeControllerTest.java similarity index 100% rename from test/controllers/HomeControllerTest.java rename to play-java-fileupload-example/test/controllers/HomeControllerTest.java