All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rexsl.page.inset.FlashInset Maven / Gradle / Ivy

/**
 * Copyright (c) 2011-2013, ReXSL.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 1) Redistributions of source code must retain the above
 * copyright notice, this list of conditions and the following
 * disclaimer. 2) Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution. 3) Neither the name of the ReXSL.com nor
 * the names of its contributors may be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.rexsl.page.inset;

import com.jcabi.aspects.Loggable;
import com.rexsl.page.BasePage;
import com.rexsl.page.CookieBuilder;
import com.rexsl.page.Inset;
import com.rexsl.page.JaxbBundle;
import com.rexsl.page.Resource;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.logging.Level;
import javax.validation.constraints.NotNull;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.Validate;

/**
 * Flash message (through cookie).
 *
 * @author Yegor Bugayenko ([email protected])
 * @version $Id$
 * @since 0.4.8
 * @see BasePage
 * @see Flash messages in RESTful interfaces
 */
@ToString
@EqualsAndHashCode(of = "resource")
@Loggable(Loggable.DEBUG)
@SuppressWarnings("PMD.TooManyMethods")
public final class FlashInset implements Inset {

    /**
     * ImmutableHeader name.
     */
    private static final String HEADER = "X-Rexsl-Flash";

    /**
     * Cookie name.
     */
    private static final String COOKIE = "Rexsl-Flash";

    /**
     * The resource.
     */
    private final transient Resource resource;

    /**
     * Public ctor.
     * @param res The resource
     */
    public FlashInset(@NotNull final Resource res) {
        this.resource = res;
    }

    @Override
    public void render(@NotNull final BasePage page,
        @NotNull final Response.ResponseBuilder builder) {
        if (this.resource.httpHeaders().getCookies()
            .containsKey(FlashInset.COOKIE)) {
            final FlashInset.Flash cookie = new FlashInset.Flash(
                this.resource.httpHeaders().getCookies().get(FlashInset.COOKIE)
            );
            page.append(
                FlashInset.bundle(
                    cookie.level(),
                    cookie.message(),
                    cookie.msec()
                )
            );
            builder.cookie(
                new CookieBuilder(this.resource.uriInfo().getBaseUri())
                    .name(FlashInset.COOKIE)
                    .build()
            );
        }
    }

    /**
     * Make a bundle.
     * @param level Logging level
     * @param msg Message
     * @param msec Time spent
     * @return JaxbBundle injectable into the page
     */
    public static JaxbBundle bundle(@NotNull final Level level,
        @NotNull final String msg, final long msec) {
        return new JaxbBundle("flash")
            .add("message", msg).up()
            .add("level", level.toString()).up()
            .add("msec", Long.toString(msec)).up();
    }

    /**
     * Make a bundle.
     * @param level Logging level
     * @param msg Message
     * @return JaxbBundle injectable into the page
     * @since 0.4.16
     */
    public static JaxbBundle bundle(final Level level, final String msg) {
        return FlashInset.bundle(level, msg, 0L);
    }

    /**
     * Create an exception that will redirect to the page with an error message.
     *
     * 

The difference between this method and a static * {@link #forward(URI,String,Level)} is in its awareness of a resource, * which is forwarding. Key benefit is that a non-static method adds extra * value to the cookie, which is time consumed by the resource until the * redirect happened. * * @param uri The URI to forward to * @param message The message to show as error * @param level Message level * @return The exception to throw */ public WebApplicationException redirect(@NotNull final URI uri, @NotNull final String message, @NotNull final Level level) { return new FlashException( Response.status(HttpURLConnection.HTTP_SEE_OTHER) .location(uri) .cookie( new FlashInset.Flash( uri, message, level, System.currentTimeMillis() - this.resource.started()) ) .header(FlashInset.HEADER, message) .entity(message) .build(), message, level ); } /** * Create an exception that will redirect to the page with an error message. * *

The difference between this method and a static * {@link #forward(URI,Exception)} is in its awareness of a resource, * which is forwarding. Key benefit is that a non-static method adds extra * value to the cookie, which is time consumed by the resource until the * redirect happened. * * @param uri The URI to forward to * @param cause The cause of this problem * @return The exception to throw */ public WebApplicationException redirect(@NotNull final URI uri, @NotNull final Exception cause) { return this.redirect(uri, cause.getMessage(), Level.SEVERE); } /** * Create an exception that will forward to the page with an error message. * @param uri The URI to forward to * @param message The message to show as error * @param level Message level * @return The exception to throw */ public static WebApplicationException forward(@NotNull final URI uri, @NotNull final String message, @NotNull final Level level) { return new FlashException( Response.status(HttpURLConnection.HTTP_SEE_OTHER) .location(uri) .cookie(new FlashInset.Flash(uri, message, level)) .header(FlashInset.HEADER, message) .entity(message) .build(), message, level ); } /** * Throw an exception that will forward to the page with an error message. * @param uri The URI to forward to * @param cause The cause of this problem * @return The exception to throw */ public static WebApplicationException forward(@NotNull final URI uri, @NotNull final Exception cause) { return FlashInset.forward(uri, cause.getMessage(), Level.SEVERE); } /** * The cookie. */ public static final class Flash extends NewCookie { /** * Total elements expected in a cookie text. */ private static final int TOTAL = 3; /** * The message. */ private final transient String msg; /** * Level of message. */ private final transient Level lvl; /** * Milliseconds consumed (or -1). */ private final transient long millis; /** * Public ctor, from a cookie encoded text. * @param cookie The cookie */ public Flash(@NotNull final Cookie cookie) { super(FlashInset.COOKIE, cookie.getValue()); Validate.notBlank(cookie.getValue(), "cookie value is blank"); final String decoded = FlashInset.Flash.decode(this.getValue()); final String[] parts = decoded.split(":", FlashInset.Flash.TOTAL); Validate.isTrue( parts.length == FlashInset.Flash.TOTAL, // @checkstyle LineLength (1 line) "can't decode cookie '%s' (decoded='%s'), %d parts expected but %d found", this.getValue(), decoded, FlashInset.Flash.TOTAL, parts.length ); Validate.isTrue( parts[0].matches("INFO|WARNING|SEVERE"), "can't detect level of cookie '%s' (decoded='%s')", this.getValue(), decoded ); this.lvl = Level.parse(parts[0]); Validate.isTrue( parts[1].matches("-1|\\d+"), "can't parse milliseconds in cookie '%s' (decoded='%s')", this.getValue(), decoded ); this.millis = Long.parseLong(parts[1]); Validate.notBlank( parts[2], "empty message in cookie '%s' (decoded='%s')", this.getValue(), decoded ); this.msg = parts[2]; } /** * Public ctor, from exact values. * @param base Base URI where we're using it * @param message The message * @param level The level */ public Flash(@NotNull final URI base, @NotNull final String message, @NotNull final Level level) { this(base, message, level, -1); } /** * Public ctor, from exact values. * @param base Base URI where we're using it * @param message The message * @param level The level * @param msec Milliseconds consumed * @checkstyle ParameterNumber (5 lines) */ public Flash(@NotNull final URI base, @NotNull final String message, @NotNull final Level level, final long msec) { super( new CookieBuilder(base) .name(FlashInset.COOKIE) .value(FlashInset.Flash.encode(message, level, msec)) .temporary() .build() ); Validate.notBlank(message, "flash message can't be empty"); this.msg = message; this.lvl = level; Validate.isTrue( msec >= 0L || msec == -1L, "milliseconds can either be positive or equal to -1" ); this.millis = msec; } /** * Get message. * @return The message */ public String message() { return this.msg; } /** * Get color of it. * @return The color */ public Level level() { return this.lvl; } /** * Milliseconds consumed. * @return The amount of them */ public long msec() { return this.millis; } @Override public String getPath() { return "/"; } /** * Encode message and color. * @param message The message * @param level The level * @param msec Milliseconds consumed * @return Encoded cookie value */ private static String encode(final String message, final Level level, final long msec) { try { return Base64.encodeBase64URLSafeString( String.format("%s:%d:%s", level, msec, message) .getBytes(CharEncoding.UTF_8) ); } catch (UnsupportedEncodingException ex) { throw new IllegalStateException(ex); } } /** * Decode text. * @param text The text to decode (cookie value) * @return Decoded value */ private static String decode(final String text) { try { return new String( Base64.decodeBase64(text), CharEncoding.UTF_8 ); } catch (UnsupportedEncodingException ex) { throw new IllegalStateException(ex); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy