Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into 28
Browse files Browse the repository at this point in the history
  • Loading branch information
dmzaytsev committed Mar 28, 2015
2 parents 8716371 + ad1f5e4 commit 301b7bd
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 33 deletions.
42 changes: 42 additions & 0 deletions src/main/java/org/takes/facets/flash/RsFlash.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,48 @@
/**
* Forwarding response.
*
* <p>This class helps you to automate flash message mechanism, by
* adding flash messages to your responses, for example:
*
* <pre>public final class TkDiscussion implements Take {
* &#64;Override
* public Response act() {
* // save the post to the database
* return new RsFlash(
* new RsForward(),
* "thanks for the post"
* );
* }
* }</pre>
*
* <p>This decorator will add the
* required "Set-Cookie" header to the response. This is all it is doing.
* The response is added to the cookie in URL-encoded format, together
* with the logging level. Flash messages could be of different severity,
* we're using Java logging levels for that, for example:
*
* <pre>public final class TkDiscussion implements Take {
* &#64;Override
* public Response act() {
* if (failed) {
* throw new RsFlash(
* new RsForward(),
* "can't save your post, sorry",
* java.util.logging.Level.SEVERE
* );
* }
* }
* }</pre>
*
* <p>This is how the HTTP response will look like (simplified):
*
* <pre> HTTP/1.1 303 See Other
* Set-Cookie: RsFlash=can%27t%20save%20your%20post%2C%20sorry/SEVERE</pre>
*
* <p>Here, the name of the cookie is {@code RsFlash}. You can change this
* default name using a constructor of {@link org.takes.facets.flash.RsFlash},
* but it's not recommended. It's better to use the default name.
*
* <p>The class is immutable and thread-safe.
*
* @author Yegor Bugayenko (yegor@teamed.io)
Expand Down
37 changes: 21 additions & 16 deletions src/main/java/org/takes/facets/flash/TsFlash.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,35 @@
import org.takes.Take;
import org.takes.Takes;
import org.takes.rq.RqCookies;
import org.takes.rq.RqWithHeader;
import org.takes.rs.RsWithCookie;

/**
* Takes that understands Flash cookie and converts it into a HTTP header.
*
* <p>This decorator helps your "takes" to automate flash messages and
* destroy cookies on their way back,
* from the browser to the server. This is what a browser will send back:
*
* <pre> GET / HTTP/1.1
* Host: www.example.com
* Cookie: RsFlash=can%27t%20save%20your%20post%2C%20sorry/SEVERE</pre>
*
* <p>This decorator adds "Set-Cookie" with an empty
* value to the response. That's all it's doing. All you need to do
* is to decorate your existing "takes", for example:
*
* <pre> new FtBasic(
* new TsFlash(TsFork(new FkRegex("/", "hello, world!"))), 8080
* ).start(Exit.NEVER);
* }</pre>
*
* <p>The class is immutable and thread-safe.
*
* @author Yegor Bugayenko (yegor@teamed.io)
* @version $Id$
* @since 0.1
*/
@EqualsAndHashCode(of = { "origin", "cookie", "header" })
@EqualsAndHashCode(of = { "origin", "cookie" })
public final class TsFlash implements Takes {

/**
Expand All @@ -56,29 +72,22 @@ public final class TsFlash implements Takes {
*/
private final transient String cookie;

/**
* Header name.
*/
private final transient String header;

/**
* Ctor.
* @param takes Original takes
*/
public TsFlash(final Takes takes) {
this(takes, RsFlash.class.getSimpleName(), "X-Takes-Flash");
this(takes, RsFlash.class.getSimpleName());
}

/**
* Ctor.
* @param takes Original takes
* @param name Cookie name
* @param hdr Header name
*/
public TsFlash(final Takes takes, final String name, final String hdr) {
public TsFlash(final Takes takes, final String name) {
this.origin = takes;
this.cookie = name;
this.header = hdr;
}

@Override
Expand All @@ -91,11 +100,7 @@ public Take route(final Request request) throws IOException {
@Override
public Response act() throws IOException {
return new RsWithCookie(
TsFlash.this.origin.route(
new RqWithHeader(
request, TsFlash.this.header, values.next()
)
).act(),
TsFlash.this.origin.route(request).act(),
TsFlash.this.cookie,
""
);
Expand Down
24 changes: 12 additions & 12 deletions src/main/java/org/takes/facets/flash/XeFlash.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import java.util.logging.Level;
import lombok.EqualsAndHashCode;
import org.takes.Request;
import org.takes.rq.RqHeaders;
import org.takes.rq.RqCookies;
import org.takes.rs.xe.XeSource;
import org.xembly.Directive;
import org.xembly.Directives;
Expand All @@ -42,7 +42,7 @@
* @version $Id$
* @since 0.1
*/
@EqualsAndHashCode(of = { "req", "header" })
@EqualsAndHashCode(of = { "req", "cookie" })
public final class XeFlash implements XeSource {

/**
Expand All @@ -51,35 +51,35 @@ public final class XeFlash implements XeSource {
private final transient Request req;

/**
* Header name.
* Cookie name.
*/
private final transient String header;
private final transient String cookie;

/**
* Ctor.
* @param request Request
*/
public XeFlash(final Request request) {
this(request, "X-Takes-Flash");
this(request, RsFlash.class.getSimpleName());
}

/**
* Ctor.
* @param request Request
* @param hdr Header name
* @param name Cookie name
*/
public XeFlash(final Request request, final String hdr) {
public XeFlash(final Request request, final String name) {
this.req = request;
this.header = hdr;
this.cookie = name;
}

@Override
public Iterable<Directive> toXembly() throws IOException {
final Iterator<String> headers =
new RqHeaders(this.req).header(this.header).iterator();
final Iterator<String> cookies =
new RqCookies(this.req).cookie(this.cookie).iterator();
final Directives dirs = new Directives();
if (headers.hasNext()) {
final String value = headers.next();
if (cookies.hasNext()) {
final String value = cookies.next();
dirs.add("flash")
.add("message").set(value).up()
.add("level").set(Level.INFO.toString());
Expand Down
123 changes: 122 additions & 1 deletion src/main/java/org/takes/facets/flash/package-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,128 @@
*/

/**
* Flash.
* Flash messages.
*
* <p>Flash messages is a useful technique that allows us to send short
* texts from one web page to another without any persistence on the server.
* Here is how it works. A web page performs some operation, for example
* making a post to a discussion thread. Then, the page returns a HTTP response
* with a redirection status 303. The response contains a "Set-Cookie" HTTP
* header with a flash message "thanks for the post".
*
* <p>The browser preserves the cookie
* and redirects to the page with the discussion thread. The browser makes
* a new HTTP request, to render the content of the discussion thread. This
* HTTP GET request contains a "Cookie" HTTP header with that message
* "thanks for the post". The server adds this message to the HTML page,
* informing the user about the action just completed, and returns a HTTP
* response. This response contains a "Set-Cookie" HTTP header with an empty
* value, which is a signal for the browser to remove the cookie.
*
* <p>The browser won't send the flash message twice, because it is deleted
* after the first time it was rendered in HTML by the server. The deletion
* is controlled by the server, when it returns an HTTP response with
* "Set-Cookie" header with an empty value.
*
* <p>Classes in this package helps to automate this mechanism. First,
* you add flash messages to your responses using
* {@link org.takes.facets.flash.RsFlash}:
*
* <pre>public final class TkDiscussion implements Take {
* &#64;Override
* public Response act() {
* // save the post to the database
* return new RsFlash(
* new RsForward(),
* "thanks for the post"
* );
* }
* }</pre>
*
* <p>This {@link org.takes.facets.flash.RsFlash} decorator will add the
* required "Set-Cookie" header to the response. This is all it is doing.
* The response is added to the cookie in URL-encoded format, together
* with the logging level. Flash messages could be of different severity,
* we're using Java logging levels for that, for example:
*
* <pre>public final class TkDiscussion implements Take {
* &#64;Override
* public Response act() {
* if (failed) {
* throw new RsFlash(
* new RsForward(),
* "can't save your post, sorry",
* java.util.logging.Level.SEVERE
* );
* }
* }
* }</pre>
*
* <p>This is how the HTTP response will look like (simplified):
*
* <pre> HTTP/1.1 303 See Other
* Set-Cookie: RsFlash=can%27t%20save%20your%20post%2C%20sorry/SEVERE</pre>
*
* <p>Here, the name of the cookie is {@code RsFlash}. You can change this
* default name using a constructor of {@link org.takes.facets.flash.RsFlash},
* but it's not recommended. It's better to use the default name.
*
* <p>The next step is to understand that cookie on its way back,
* from the browser to the server. This is what a browser will send back:
*
* <pre> GET / HTTP/1.1
* Host: www.example.com
* Cookie: RsFlash=can%27t%20save%20your%20post%2C%20sorry/SEVERE</pre>
*
* <p>There is a class {@link org.takes.facets.flash.TsFlash}, that
* decorates your existing "takes" and adds "Set-Cookie" with an empty
* value to the response. That's all it's doing. All you need to do
* is to decorate your existing "takes", for example:
*
* <pre> new FtBasic(
* new TsFlash(TsFork(new FkRegex("/", "hello, world!"))), 8080
* ).start(Exit.NEVER);
* }</pre>
*
* <p>The last step is to fetch that cookie from the request and add
* to the HTML page. You can use {@link org.takes.rq.RqCookies} for that
* (it's a pseudo-code, don't build HTML like this!):
*
* <pre>public final class TkDiscussion implements Take {
* private final Request req;
* &#64;Override
* public Response act() {
* String html = "this is our discussion thread...";
* final Iterator&lt;String&gt; cookies =
* new RqCookies(this.req).cookie("RsFlash").iterator();
* if (cookies.hasNext()) {
* html = cookies.next() + html;
* }
* return new RsHTML(html);
* }
* }
* }</pre>
*
* <p>If you're using Xembly to build XML output, you can use
* {@link org.takes.facets.flash.XeFlash} for fetching flash messages
* from cookies and adding them to XML:
*
* <pre>public final class TkDiscussion implements Take {
* private final Request req;
* &#64;Override
* public Response act() {
* return new RsXembly(
* new XeAppend(
* "page",
* new XeFlash(this.req),
* // your other Xembly sources
* )
* );
* }
* }</pre>
*
* <p>Don't forget that the cookie you receive is not just a flash message,
* but also other parameters, URL-encoded and separated by "slash".</p>
*
* @author Yegor Bugayenko (yegor@teamed.io)
* @version $Id$
Expand Down
6 changes: 2 additions & 4 deletions src/test/java/org/takes/facets/flash/XeFlashTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,15 @@ public final class XeFlashTest {
*/
@Test
public void generatesFlashData() throws IOException {
final String header = "x";
MatcherAssert.assertThat(
IOUtils.toString(
new RsXembly(
new XeAppend(
"root",
new XeFlash(
new RqWithHeader(
new RqFake(), header, "how are you"
),
header
new RqFake(), "Cookie", "RsFlash=how are you"
)
)
)
).body()
Expand Down

0 comments on commit 301b7bd

Please sign in to comment.