/*
* Copyright 2010 Instituto Superior Tecnico
*
* https://fenix-ashes.ist.utl.pt/
*
* This file is part of the vaadin-framework.
*
* The vaadin-framework Infrastructure is free software: you can
* redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version
* 3 of the License, or (at your option) any later version.*
*
* vaadin-framework is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with vaadin-framework. If not, see <http://www.gnu.org/licenses/>.
*
*/
package pt.ist.vaadinframework;
import java.lang.reflect.Field;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.regex.Pattern;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import pt.ist.bennu.core.applicationTier.Authenticate.UserView;
import pt.ist.bennu.core.domain.VirtualHost;
import pt.ist.bennu.core.domain.contents.Node;
import pt.ist.bennu.core.domain.exceptions.DomainException;
import pt.ist.bennu.vaadin.domain.contents.VaadinNode;
import pt.ist.fenixWebFramework.servlets.filters.SetUserViewFilter;
import pt.ist.vaadinframework.annotation.EmbeddedComponentUtils;
import pt.ist.vaadinframework.fragment.FragmentQuery;
import pt.ist.vaadinframework.terminal.DefaultSystemErrorWindow;
import pt.ist.vaadinframework.terminal.DomainExceptionErrorMessage;
import pt.ist.vaadinframework.terminal.SystemErrorWindow;
import pt.ist.vaadinframework.ui.EmbeddedComponentContainer;
import pt.utl.ist.fenix.tools.util.i18n.Language;
import com.vaadin.Application;
import com.vaadin.data.Buffered;
import com.vaadin.data.Buffered.SourceException;
import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.service.ApplicationContext.TransactionListener;
import com.vaadin.terminal.gwt.server.WebApplicationContext;
import com.vaadin.ui.AbstractField;
import com.vaadin.ui.Form;
import com.vaadin.ui.Window;
/**
* <p>
* Application to manage components embedded in JSPs sharing the same session. This application needs to be configured at startup
* with the mappings between textual patterns and the {@link EmbeddedComponentContainer}'s that will be attached to the embedded
* {@link Window}. On embedding a component the hostItem application must set the {@link EmbeddedApplication#VAADIN_PARAM } to a
* textual parameter that will be matched to the patterns specified, the first pattern to match will define the
* {@link EmbeddedComponentContainer} that will be instantiated. The parameter will also be sent to the newly instantiated
* container.
* </p>
*
* <p>
* For example: Lets say we have a component that displays information about a Product given its ID. First add the pattern to the
* resolver by doing:
*
* <pre>
* {@code EmbeddedApplication.addResolutionPattern(Pattern.compile("product-(.*)"), ProductContainer.class);}
* </pre>
*
* In the hostItem application set the {@link EmbeddedApplication#VAADIN_PARAM } like:
*
* <pre>
* {@code request.getSession().setAttribute(EmbeddedApplication.VAADIN_PARAM, "product-10042786");}
* </pre>
*
* As you defined a group in the pattern the group will be sent as an argument to
* {@link EmbeddedComponentContainer#setArguments(String...)}.
* </p>
*
* To use this feature client applications have to do to following:
*
* <li>
* <ul>
* Add EmbeddedApplication servlet to web.xml:
*
* <pre>
* {@code
* <servlet>
* <servlet-name>VaadinEmbedded</servlet-name>
* <servlet-class>com.vaadin.terminal.gwt.server.ApplicationServlet</servlet-class>
* <init-param>
* <description>Vaadin Embedded Application class</description>
* <param-name>application</param-name>
* <param-value>pt.ist.vaadinframework.EmbeddedApplication</param-value>
* </init-param>
* </servlet>
*
* <servlet-mapping>
* <servlet-name>VaadinEmbedded</servlet-name>
* <url-pattern>/vaadin/*</url-pattern>
* </servlet-mapping>
* }
* </pre>
*
* </ul>
* <ul>
*
* Then redirect to a jsp with the following code:
*
* <pre>
* {@code
* <script type="text/javascript">
* var vaadin = { vaadinConfigurations: { 'vaadin': {appUri:'<%= request.getContextPath() + "/vaadin" %>', pathInfo: '/', themeUri:'<%= request.getContextPath() + "/VAADIN/themes/default" %>'}}};
* </script>
* <script language='javascript' src='<%= request.getContextPath() + "/VAADIN/widgetsets/com.vaadin.terminal.gwt.DefaultWidgetSet/com.vaadin.terminal.gwt.DefaultWidgetSet.nocache.js" %>'></script>
* <div id="vaadin"/>
* }
* </pre>
*
* </ul>
* </li>
*
* @author Pedro Santos (pedro.miguel.santos@ist.utl.pt)
* @version 1.0
*/
@SuppressWarnings("serial")
public class EmbeddedApplication extends Application implements VaadinResourceConstants {
private static final Map<String, Class<? extends EmbeddedComponentContainer>> pages = new HashMap<>();
private static SystemErrorWindow errorWindow = new DefaultSystemErrorWindow();
@Override
public void init() {
getContext().addTransactionListener(new TransactionListener() {
@Override
public void transactionStart(Application application, Object transactionData) {
application.setLocale(Language.getLocale());
}
@Override
public void transactionEnd(Application application, Object transactionData) {
application.setLocale(null);
}
});
setTheme(VirtualHost.getVirtualHostForThread().getTheme().getName());
setMainWindow(new EmbeddedWindow());
}
public static void open(Application application, Class<? extends EmbeddedComponentContainer> clazz, String... args) {
((EmbeddedApplication) application).open(clazz, args);
}
public static void open(Application application, String fragment) {
((EmbeddedApplication) application).open(fragment);
}
public void open(Class<? extends EmbeddedComponentContainer> clazz, String... args) {
final FragmentQuery fragmentQuery = new FragmentQuery(clazz, args);
open(fragmentQuery.getQueryString());
}
public void open(String fragment) {
((EmbeddedWindow) getMainWindow()).open(fragment);
}
public void back() {
((EmbeddedWindow) getMainWindow()).back();
}
public static void back(Application application) {
((EmbeddedApplication) application).back();
}
public void refresh() {
((EmbeddedWindow) getMainWindow()).refresh();
}
public static void refresh(Application application) {
((EmbeddedApplication) application).refresh();
}
@Override
public void close() {
HttpSession session = ((WebApplicationContext) getContext()).getHttpSession();
final UserView userView = (UserView) session.getAttribute(SetUserViewFilter.USER_SESSION_ATTRIBUTE);
if (userView != null) {
userView.getUser().setLastLogoutDateTime(new DateTime());
}
pt.ist.fenixWebFramework.security.UserView.setUser(null);
session.removeAttribute(SetUserViewFilter.USER_SESSION_ATTRIBUTE);
session.invalidate();
super.close();
}
/**
* @see com.vaadin.Application#getWindow(java.lang.String)
*/
@Override
public Window getWindow(String name) {
// If the window is identified by name, we are good to go
Window window = super.getWindow(name);
// If not, we must create a new window for this new browser window/tab
if (window == null) {
window = new EmbeddedWindow();
// Use the random name given by the framework to identify this
// window in future
window.setName(name);
addWindow(window);
// Move to the url to remember the name in the future
// window.open(new ExternalResource(window.getURL()));
}
return window;
}
/**
* Adds a new pattern to the resolver, the pattern can have groups that when
* captured will be supplied to the corresponding {@link EmbeddedComponentContainer} using
* {@link EmbeddedComponentContainer#setArguments(String...)}.
*
* @param pattern
* The compiled {@link Pattern} instance.
* @param type
* The container that will be instantiated if the pattern matches
* the {@link EmbeddedApplication#VAADIN_PARAM}.
*/
public static void addPage(Class<? extends EmbeddedComponentContainer> page) {
pages.put(EmbeddedComponentUtils.getAnnotationPath(EmbeddedComponentUtils.getAnnotation(page)), page);
}
public static Class<? extends EmbeddedComponentContainer> getPage(String path) {
if (path == null || StringUtils.isEmpty(path)) {
final VirtualHost virtualHost = VirtualHost.getVirtualHostForThread();
final SortedSet<Node> nodes = virtualHost.getOrderedTopLevelNodes();
for (final Node node : nodes) {
if (node.isAccessible() && node instanceof VaadinNode) {
return pages.get(((VaadinNode) node).getArgument());
}
}
}
return pages.get(path);
}
public static SystemMessages getSystemMessages() {
return new CustomizedSystemMessages() {
@Override
public String getSessionExpiredCaption() {
return VaadinResources.getString(SYSTEM_TITLE_SESSION_EXPIRED);
}
@Override
public String getSessionExpiredMessage() {
return VaadinResources.getString(SYSTEM_MESSAGE_SESSION_EXPIRED);
}
@Override
public String getCommunicationErrorCaption() {
return VaadinResources.getString(SYSTEM_TITLE_COMMUNICATION_ERROR);
}
@Override
public String getCommunicationErrorMessage() {
return VaadinResources.getString(SYSTEM_MESSAGE_COMMUNICATION_ERROR);
}
@Override
public String getAuthenticationErrorCaption() {
return VaadinResources.getString(SYSTEM_TITLE_AUTHENTICATION_ERROR);
}
@Override
public String getAuthenticationErrorMessage() {
return VaadinResources.getString(SYSTEM_MESSAGE_AUTHENTICATION_ERROR);
}
@Override
public String getInternalErrorCaption() {
return VaadinResources.getString(SYSTEM_TITLE_INTERNAL_ERROR);
}
@Override
public String getInternalErrorMessage() {
return VaadinResources.getString(SYSTEM_MESSAGE_INTERNAL_ERROR);
}
@Override
public String getOutOfSyncCaption() {
return VaadinResources.getString(SYSTEM_TITLE_OUTOFSYNC_ERROR);
}
@Override
public String getOutOfSyncMessage() {
return VaadinResources.getString(SYSTEM_MESSAGE_OUTOFSYNC_ERROR);
}
@Override
public String getCookiesDisabledCaption() {
return VaadinResources.getString(SYSTEM_TITLE_COOKIES_DISABLED_ERROR);
}
@Override
public String getCookiesDisabledMessage() {
return VaadinResources.getString(SYSTEM_MESSAGE_COOKIES_DISABLED_ERROR);
}
};
}
public static void registerErrorWindow(SystemErrorWindow customErrorWindow) {
errorWindow = customErrorWindow;
}
@Override
public void terminalError(com.vaadin.terminal.Terminal.ErrorEvent event) {
final Throwable t = event.getThrowable();
if (t instanceof SocketException) {
// Most likely client browser closed socket
VaadinFrameworkLogger.getLogger().info(
"SocketException in CommunicationManager." + " Most likely client (browser) closed socket.");
return;
}
if (findInvalidValueException(t) != null) {
// validation errors are handled by their fields
return;
}
DomainException de = findDomainExceptionCause(t);
Buffered source = findSource(t);
if (de != null && source != null) {
setErrorsOn(source, de);
} else {
setErrorsOn(source, null);
VaadinFrameworkLogger.getLogger().error("Uncaught Error", t);
errorWindow.showError(getMainWindow(), t);
}
}
private Buffered findSource(Throwable t) {
if (t instanceof SourceException) {
return ((SourceException) t).getSource();
}
if (t.getCause() != null) {
return findSource(t.getCause());
}
return null;
}
private DomainException findDomainExceptionCause(Throwable t) {
if (t instanceof DomainException) {
return (DomainException) t;
}
if (t.getCause() != null) {
return findDomainExceptionCause(t.getCause());
}
return null;
}
private InvalidValueException findInvalidValueException(Throwable t) {
if (t instanceof InvalidValueException) {
return (InvalidValueException) t;
}
if (t.getCause() != null) {
return findInvalidValueException(t.getCause());
}
return null;
}
private static void setErrorsOn(Buffered source, DomainException message) {
DomainExceptionErrorMessage se = null;
if (message != null) {
se = new DomainExceptionErrorMessage(source, message);
}
if (source != null) {
if (source instanceof Form) {
Form form = (Form) source;
try {
Field formError = Form.class.getDeclaredField("currentBufferedSourceException");
formError.setAccessible(true);
formError.set(form, se);
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (SecurityException e) {
} catch (NoSuchFieldException e) {
}
for (Object propertyId : form.getItemPropertyIds()) {
((AbstractField) form.getField(propertyId)).setCurrentBufferedSourceException(null);
}
} else if (source instanceof AbstractField) {
AbstractField field = (AbstractField) source;
field.setCurrentBufferedSourceException(se);
}
}
}
}