Java tutorial
/** * Copyright 2012 Leonid Bogdanov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file 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. */ package org.lbogdanov.poker.web.page; import static org.atmosphere.cpr.FrameworkConfig.ATMOSPHERE_RESOURCE; import static org.lbogdanov.poker.core.Constants.LABEL_MAX_LENGTH; import java.text.DateFormat; import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.shiro.authz.annotation.RequiresUser; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Page; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.attributes.AjaxCallListener; import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; import org.apache.wicket.ajax.markup.html.form.AjaxFallbackButton; import org.apache.wicket.atmosphere.EventBus; import org.apache.wicket.atmosphere.ResourceRegistrationListener; import org.apache.wicket.atmosphere.Subscribe; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.JavaScriptHeaderItem; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.TextArea; import org.apache.wicket.model.Model; import org.apache.wicket.request.http.flow.AbortWithHttpErrorCodeException; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.request.resource.CssResourceReference; import org.apache.wicket.request.resource.ResourceReference; import org.atmosphere.cpr.AtmosphereResource; import org.atmosphere.cpr.Broadcaster; import org.atmosphere.cpr.BroadcasterFactory; import org.lbogdanov.poker.core.Session; import org.lbogdanov.poker.core.SessionService; import org.lbogdanov.poker.core.UserService; import org.lbogdanov.poker.web.markup.BodylessLabel; import org.lbogdanov.poker.web.markup.LimitableLabel; import org.lbogdanov.poker.web.plugin.CustomScrollbarPlugin; import org.lbogdanov.poker.web.util.ChatMessage; import org.lbogdanov.poker.web.util.Message; import org.lbogdanov.poker.web.util.OriginFilter; import org.ocpsoft.prettytime.Duration; import org.ocpsoft.prettytime.PrettyTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Strings; import com.google.common.collect.Iterables; /** * Represents a Planning Poker session page. * * @author Leonid Bogdanov */ @RequiresUser public class SessionPage extends AbstractPage { /** * Subscribes clients to corresponding named {@link Broadcaster}s depending on the current <code>Session</code> code. */ public static final class Subscriber implements ResourceRegistrationListener { private static final Subscriber INSTANCE = new Subscriber(); /** * Returns a single instance of <code>Subscriber</code>. * * @return the <code>Subscriber</code> instance */ public static Subscriber get() { return INSTANCE; } /** * {@inheritDoc} */ @Override public void resourceRegistered(String uuid, Page page) { if (page instanceof SessionPage) { String channel = ((SessionPage) page).session.getCode(); // quickly get an AtmosphereResource from a current HttpServletRequest HttpServletRequest request = (HttpServletRequest) page.getRequest().getContainerRequest(); AtmosphereResource resource = (AtmosphereResource) request.getAttribute(ATMOSPHERE_RESOURCE); if (resource != null) { Broadcaster broadcaster = BroadcasterFactory.getDefault().lookup(channel, true); broadcaster.addAtmosphereResource(resource); } else { LOG.warn("Couldn't get AtmosphereResource for a request"); } } } /** * {@inheritDoc} */ @Override public void resourceUnregistered(String uuid) { } private Subscriber() { } } private static final Logger LOG = LoggerFactory.getLogger(SessionPage.class); private static final ResourceReference CSS = new CssResourceReference(SessionPage.class, "session.css"); private static final ResourceReference JS = new PageScriptResourceReference(SessionPage.class, "session.js"); @Inject private SessionService sessionService; @Inject private UserService userService; @Inject private ObjectMapper mapper; private Session session; /** * Creates a new instance of <code>Session</code> page. */ public SessionPage(PageParameters parameters) { session = sessionService.find(parameters.get("code").toString()); if (session == null) { throw new AbortWithHttpErrorCodeException(HttpServletResponse.SC_NOT_FOUND, "Session not found"); } final TextArea<String> chatMsg = new TextArea<String>("chatMsg", Model.of("")); Form<?> chatForm = new Form<Void>("chatForm"); chatForm.add(chatMsg, new AjaxFallbackButton("chatSend", chatForm) { @Override protected void onSubmit(AjaxRequestTarget target, Form<?> form) { ChatMessage message = new ChatMessage(getSession().getId(), userService.getCurrentUser(), chatMsg.getModelObject()); post(session.getCode(), message); } @Override protected void updateAjaxAttributes(AjaxRequestAttributes attributes) { super.updateAjaxAttributes(attributes); AjaxCallListener listener = new AjaxCallListener(); listener.onPrecondition("return $('#chatMsg').val().length > 0;") .onBeforeSend("Poker.toggleForm('chatForm', true);") .onComplete("Poker.msgSent(jqXHR); Poker.toggleForm('chatForm', false);"); attributes.getAjaxCallListeners().add(listener); } }); LimitableLabel name = new LimitableLabel("session.name", session.getName()); if (!Strings.isNullOrEmpty(session.getDescription())) { name.add(AttributeModifier.append("class", "tip"), AttributeModifier.append("title", session.getDescription())); } add(chatForm.setOutputMarkupId(true), name.setMaxLength(LABEL_MAX_LENGTH), new BodylessLabel("session.code", session.getCode()).setMaxLength(LABEL_MAX_LENGTH), new BodylessLabel("session.author", session.getAuthor()).setMaxLength(LABEL_MAX_LENGTH), new BodylessLabel("session.created", formatDate(session.getCreated())) .setMaxLength(LABEL_MAX_LENGTH)); } /** * {@inheritDoc} */ @Override public void renderHead(IHeaderResponse response) { super.renderHead(response); response.render(JavaScriptHeaderItem.forReference(I18N)); response.render(JavaScriptHeaderItem.forReference(CustomScrollbarPlugin.get())); } /** * Asynchronously publishes messages to the session participants via Atmosphere framework. * * @param target the <code>AjaxRequestTarget</code> instance * @param msg the message to publish * @throws Exception if any error occurred */ @Subscribe(contextAwareFilter = OriginFilter.class) public void publishMessage(AjaxRequestTarget target, Message<?> msg) throws Exception { if (target == null) { LOG.warn("Couldn't sent async message, target was null"); } else { target.appendJavaScript(String.format("Poker.dispatch(%s);", mapper.writeValueAsString(msg))); } } /** * {@inheritDoc} */ @Override protected ResourceReference getCss() { return CSS; } /** * {@inheritDoc} */ @Override protected ResourceReference getJavaScript() { return JS; } private static void post(Object channel, Message<?> message) { Broadcaster broadcaster = BroadcasterFactory.getDefault().lookup(channel); if (broadcaster == null) { LOG.info("No active Broadcaster for a channel {}", channel); } else { EventBus eventBus = EventBus.get(); for (AtmosphereResource resource : broadcaster.getAtmosphereResources()) { eventBus.post(message, resource); } } } private String formatDate(Date created) { final long MILLIS_PER_WEEK = TimeUnit.DAYS.toMillis(7); Locale locale = getLocale(); PrettyTime prettyTime = new PrettyTime(locale); Duration largest = Iterables.getFirst(prettyTime.calculatePreciseDuration(created), null); if (largest != null && largest.getUnit().getMillisPerUnit() < MILLIS_PER_WEEK) { return prettyTime.format(largest); } else { // fallback to an absolute date string when difference is more than a week return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale).format(created); } } }