/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 de.unioninvestment.eai.portal.portlet.crud; import com.vaadin.annotations.PreserveOnRefresh; import com.vaadin.annotations.Theme; import com.vaadin.server.*; import com.vaadin.server.VaadinPortletSession.PortletListener; import com.vaadin.ui.*; import de.unioninvestment.eai.portal.portlet.crud.UiHistory.HistoryAware; import de.unioninvestment.eai.portal.portlet.crud.config.resource.Config; import de.unioninvestment.eai.portal.portlet.crud.domain.events.ShowPopupEvent; import de.unioninvestment.eai.portal.portlet.crud.domain.events.ShowPopupEventHandler; import de.unioninvestment.eai.portal.portlet.crud.domain.exception.BusinessException; import de.unioninvestment.eai.portal.portlet.crud.domain.model.ModelBuilder; import de.unioninvestment.eai.portal.portlet.crud.domain.model.ModelFactory; import de.unioninvestment.eai.portal.portlet.crud.domain.model.ModelPreferences; import de.unioninvestment.eai.portal.portlet.crud.domain.model.Portlet; import de.unioninvestment.eai.portal.portlet.crud.domain.model.datasource.DatasourceInfos; import de.unioninvestment.eai.portal.portlet.crud.domain.support.InitializingUI; import de.unioninvestment.eai.portal.portlet.crud.domain.validation.ModelValidator; import de.unioninvestment.eai.portal.portlet.crud.mvp.events.ConfigurationUpdatedEvent; import de.unioninvestment.eai.portal.portlet.crud.mvp.events.ConfigurationUpdatedEventHandler; import de.unioninvestment.eai.portal.portlet.crud.mvp.presenters.DatasourceInfoPresenter; import de.unioninvestment.eai.portal.portlet.crud.mvp.presenters.PortletPresenter; import de.unioninvestment.eai.portal.portlet.crud.mvp.presenters.PresenterFactory; import de.unioninvestment.eai.portal.portlet.crud.mvp.presenters.configuration.PortletConfigurationPresenter; import de.unioninvestment.eai.portal.portlet.crud.mvp.views.DatasourceInfoView; import de.unioninvestment.eai.portal.portlet.crud.mvp.views.ui.BusinessExceptionMessage; import de.unioninvestment.eai.portal.portlet.crud.mvp.views.ui.Popup; import de.unioninvestment.eai.portal.portlet.crud.mvp.views.ui.PortletUriFragmentUtility; import de.unioninvestment.eai.portal.portlet.crud.mvp.views.ui.RequestProcessingLabel; import de.unioninvestment.eai.portal.portlet.crud.scripting.model.ScriptModelBuilder; import de.unioninvestment.eai.portal.portlet.crud.scripting.model.ScriptModelFactory; import de.unioninvestment.eai.portal.portlet.crud.scripting.model.ScriptPortlet; import de.unioninvestment.eai.portal.portlet.crud.services.ConfigurationService; import de.unioninvestment.eai.portal.portlet.crud.services.RequestProcessingLogService; import de.unioninvestment.eai.portal.support.vaadin.CrudVaadinPortlet; import de.unioninvestment.eai.portal.support.vaadin.LiferayUI; import de.unioninvestment.eai.portal.support.vaadin.PortletUtils; import de.unioninvestment.eai.portal.support.vaadin.context.Context; import de.unioninvestment.eai.portal.support.vaadin.mvp.EventBus; import de.unioninvestment.eai.portal.support.vaadin.support.UnconfiguredMessage; import de.unioninvestment.eai.portal.support.vaadin.timing.TimingPortletListener; import de.unioninvestment.eai.portal.support.vaadin.validation.ValidationException; import org.apache.commons.lang.StringEscapeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.web.portlet.context.PortletApplicationContextUtils; import javax.portlet.*; import java.io.IOException; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.Set; import java.util.UUID; /** * CRUD PortletPresenter Applikationsklasse. Enthaelt das Management der * verschiedenen PortletPresenter-Modus. * * * @author carsten.mjartan * */ @Theme("crud2go") @PreserveOnRefresh @SuppressWarnings("deprecation") public class CrudUI extends LiferayUI implements PortletListener, ShowPopupEventHandler, InitializingUI, HistoryAware { private static final long serialVersionUID = 1L; private static final String INSTANCE_ID = UUID.randomUUID().toString(); private static final String LIFECYCLE_DATEFORMAT = "dd.MM.yyyy HH:mm:ss.SSS"; private static final Logger LIFECYCLE_LOGGER = LoggerFactory .getLogger(CrudUI.class.getPackage().getName() + ".lifecycle"); private static final Logger LOG = LoggerFactory.getLogger(CrudUI.class); public static final String ROLE_ADMIN = "portlet-crud-adm"; private Portlet portletDomain; private PortletPresenter portletGui; private DatasourceInfoPresenter datasourceInfo; private VerticalLayout viewPage = new VerticalLayout(); private ComponentContainer editPage = new VerticalLayout(); private ComponentContainer helpPage; @Autowired private transient Settings settings; @Autowired private transient EventBus eventBus; @Autowired private transient ConfigurationService configurationService; @Autowired private transient PresenterFactory presenterFactory; @Autowired private transient ModelFactory modelFactory; @Autowired private transient ScriptModelFactory scriptModelFactory; @Autowired private transient GuiBuilder guiBuilder; @Autowired private transient UiHistory uiHistory; private PortletUriFragmentUtility portletUriFragmentUtility; private PortletConfigurationPresenter configurationPresenter; private Set<Component> registeredComponents = new HashSet<Component>(); public enum ConfigStatus { START, UNKNOWN, NO_CONFIG, UNCONFIGURED, CONFIGURED } ConfigStatus status = ConfigStatus.START; boolean initializing = true; boolean configChanged = false; Config portletConfig; private RequestProcessingLabel requestProcessingLabel; private TimingPortletListener timingPortletListener; private UiHistoryState historyState; ModelValidator modelValidator = new ModelValidator(); public enum LifecycleEvent { CRUD2GO_INIT, CRUD2GO_UI_INIT, CRUD2GO_UI_DETACH, CRUD2GO_SHUTDOWN } /** * Initialisierung des PortletPresenter. * */ @Override public void doInit(VaadinRequest request) { setId(UUID.randomUUID().toString()); logLifecycleEvent(LifecycleEvent.CRUD2GO_UI_INIT); autowireUiDependencies(request); uiHistory.add(this); VaadinSession.getCurrent().setErrorHandler(new CrudErrorHandler()); applyBrowserLocale(); helpPage = initializeHelpPage(); setContent(viewPage); tryToSetPortletTitle(Context.getMessage("portlet.crud.window.name")); recreateEditPage(); refreshViews(); getPortletSession().addPortletListener(this); } public int getHistoryLimit() { Integer historyLimit = settings.getUiHistoryLimit(); if (portletConfig != null) { Integer specificHistoryLimit = portletConfig.getPortletConfig() .getHistoryLimit(); if (specificHistoryLimit != null) { historyLimit = specificHistoryLimit; } } return historyLimit; } @Override public void setHistoryState(UiHistoryState state) { this.historyState = state; } @Override public UiHistoryState getHistoryState() { return historyState; } @Override public void setLastHeartbeatTimestamp(long lastHeartbeat) { super.setLastHeartbeatTimestamp(lastHeartbeat); uiHistory.handleHeartbeat(this); } public static void logLifecycleEvent(LifecycleEvent event) { String now = new SimpleDateFormat(LIFECYCLE_DATEFORMAT) .format(new Date()); String uiId = ""; String url = ""; UI ui = UI.getCurrent(); if (ui != null) { url = ui.getPage().getLocation().toString(); uiId = ui.getId(); } String sessionId = ""; PortletRequest portletRequest = VaadinPortletService .getCurrentPortletRequest(); if (portletRequest != null) { PortletSession session = portletRequest.getPortletSession(false); sessionId = session == null ? portletRequest .getRequestedSessionId() : session.getId(); } LIFECYCLE_LOGGER.info(MessageFormat.format("{0};{1};{2};{3};{4};{5}", now, INSTANCE_ID, uiId, url, sessionId, event.name())); } private void autowireUiDependencies(VaadinRequest request) { ApplicationContext context = getSpringContext(request); if (context != null) { context.getAutowireCapableBeanFactory().autowireBean(this); } } protected ApplicationContext getSpringContext(VaadinRequest request) { PortletRequest currentRequest = VaadinPortletService .getCurrentPortletRequest(); if (currentRequest != null) { PortletContext pc = currentRequest.getPortletSession() .getPortletContext(); org.springframework.context.ApplicationContext springContext = PortletApplicationContextUtils .getRequiredWebApplicationContext(pc); return springContext; } else { throw new IllegalStateException( "Found no current portlet request. Did you subclass PortletApplication in your Vaadin Application?"); } } private void tryToSetPortletTitle(String title) { String realTitle = title != null ? title : Context .getMessage("portlet.crud.window.name"); String escapedTitle = StringEscapeUtils.escapeJavaScript(realTitle); PortletResponse portletResponse = VaadinPortletService .getCurrentResponse().getPortletResponse(); if (portletResponse instanceof RenderResponse) { ((RenderResponse) portletResponse).setTitle(realTitle); } else { // document.querySelectorAll("#portlet_crudportlet_WAR_eaiadministration_INSTANCE_qeH6QK9czlb6 .portlet-title-text")[0].childNodes[0].nodeValue // = 'Releaseplaner - Applikationen' String ie8plusUpdate = "document.querySelectorAll('#portlet_" + getPortletId() + " .portlet-title-text')[0]" + ".childNodes[0].nodeValue = '" + escapedTitle + "'"; String defaultUpdate = "document.getElementById('portlet_" + getPortletId() + "').getElementsByClassName('portlet-title-text')[0]" + ".childNodes[0].nodeValue = '" + escapedTitle + "'"; String all = "if (document.querySelectorAll) { " + ie8plusUpdate + " } else { " + defaultUpdate + " };"; JavaScript.getCurrent().execute(all); } } private void cachePortletTitle(String title) { PortletPreferences preferences = VaadinPortletService .getCurrentPortletRequest().getPreferences(); String oldTitle = preferences.getValue( CrudVaadinPortlet.PORTLET_TITLE_PREF_KEY, null); if (oldTitle == null || !oldTitle.equals(title)) { try { preferences.setValue(CrudVaadinPortlet.PORTLET_TITLE_PREF_KEY, title); preferences.store(); } catch (ReadOnlyException e) { LOG.error("Failed to update portlet title in preferences", e); } catch (ValidatorException e) { LOG.error("Failed to update portlet title in preferences", e); } catch (IOException e) { LOG.error("Failed to update portlet title in preferences", e); } } } private void applyBrowserLocale() { setLocale(Page.getCurrent().getWebBrowser().getLocale()); } private void initializeEventBus() { // remove old references to domain handlers from the Event Bus eventBus.reset(); eventBus.addHandler(ShowPopupEvent.class, this); eventBus.addHandler(ConfigurationUpdatedEvent.class, new ConfigurationUpdatedEventHandler() { private static final long serialVersionUID = 1L; @Override public void onConfigurationUpdated( ConfigurationUpdatedEvent event) { handleConfigurationUpdatedEvent(event); } }); } private void handleConfigurationUpdatedEvent(ConfigurationUpdatedEvent event) { cleanupViewPage(); initializeEventBus(); configChanged = true; if (!event.isConfigurable()) { PortletUtils.switchPortletMode(PortletMode.VIEW); } } private ComponentContainer initializeHelpPage() { DatasourceInfos datasourceInfos = new DatasourceInfos(); DatasourceInfoView datasourceInfoView = new DatasourceInfoView( datasourceInfos.getContainer()); this.datasourceInfo = new DatasourceInfoPresenter(datasourceInfoView, datasourceInfos); VerticalLayout help = new VerticalLayout(); help.setSpacing(true); help.addComponent(new Link(Context .getMessage("portlet.crud.page.help.message"), new ExternalResource(settings.getHelpUrl()))); help.addComponent(new Label(Context.getMessage( "portlet.crud.page.help.buildNumberLabel", settings.getBuildNumber()))); help.addComponent(this.datasourceInfo.getView()); return help; } private Config getConfiguration() { return configurationService.getPortletConfig(getPortletId(), getCommunityId()); } public void addToView(Component component) { if (registeredComponents.add(component)) { logAddToView(component); viewPage.addComponent(component); } } public void removeAddedComponentsFromView() { for (Component component : registeredComponents) { logRemoveFromView(component); viewPage.removeComponent(component); } } private void logAddToView(Component component) { logViewChange(component, "Füge Komponente", " hinzu"); } private void logRemoveFromView(Component component) { logViewChange(component, "Entferne Komponente", ""); } private void logViewChange(Component component, String msg1, String msg2) { if (LOG.isDebugEnabled()) { LOG.debug("Application [" + this.hashCode() + "]: " + msg1 + " [" + component.getClass().getName() + "@" + Integer.toHexString(component.hashCode()) + "]" + msg2 + (component.getCaption() != null ? " (" + component.getCaption() + ")" : "") + "; ViewPage: [" + viewPage + "]."); } } /** * Callback-Methode, die im Fall eines Commits des Konfigurationsformulars * aufgerufen wird. */ public void refreshViews() { try { LOG.info("Refreshing views"); initializing = true; cleanupViewPage(); initializeEventBus(); LOG.debug("Loading configuration"); portletConfig = getConfiguration(); status = getConfigStatus(portletConfig); if (status == ConfigStatus.CONFIGURED) { initializeModelAndViews(portletConfig); addServerProcessingInfo(); } else { viewPage.addComponent(new UnconfiguredMessage()); } } catch (BusinessException e) { viewPage.addComponent(new BusinessExceptionMessage(e)); } catch (Exception e) { LOG.error("Error refreshing configuration", e); if (settings.isDebugMode()) { Notification.show("Debug Information", e.getMessage(), Notification.Type.ERROR_MESSAGE); } viewPage.addComponent(new BusinessExceptionMessage( "portlet.crud.error.internal")); } finally { initializing = false; } } private void addServerProcessingInfo() { if (settings.isDisplayRequestProcessingInfo() || settings.isRequestLogEnabled()) { if (requestProcessingLabel == null) { RequestProcessingLogService requestProcessingLogService = Context .getBean(RequestProcessingLogService.class); requestProcessingLabel = new RequestProcessingLabel( requestProcessingLogService, settings.isDisplayRequestProcessingInfo()); timingPortletListener = new TimingPortletListener( requestProcessingLabel); getPortletSession().addPortletListener(timingPortletListener); } viewPage.addComponent(requestProcessingLabel); viewPage.setComponentAlignment(requestProcessingLabel, Alignment.MIDDLE_RIGHT); } } private ConfigStatus getConfigStatus(Config portletConfig) { if (portletConfig == null) { return ConfigStatus.NO_CONFIG; } else { if (configurationService.isConfigured(portletConfig, VaadinPortletService.getCurrentPortletRequest() .getPreferences())) { return ConfigStatus.CONFIGURED; } else { return ConfigStatus.UNCONFIGURED; } } } private void initializeModelAndViews(Config portletConfig) { try { String portletId = getPortletId(); LOG.debug("Building domain model"); ModelBuilder modelBuilder = modelFactory.getBuilder(eventBus, portletConfig, createModelPreferences()); portletDomain = modelBuilder.build(); LOG.debug("Building scripting model"); ScriptModelBuilder scriptModelBuilder = scriptModelFactory .getBuilder(eventBus, portletDomain, modelBuilder.getModelToConfigMapping()); ScriptPortlet scriptPortlet = scriptModelBuilder.build(); if(settings.isDebugMode()){ try { LOG.debug("Debug mode: Validating model"); modelValidator.validateModel(modelBuilder, portletDomain, portletConfig.getPortletConfig()); } catch (Exception e) { // only show a warning, but don't fail initialization Notification.show("Debug Information - Validation failed", e.getMessage(), Notification.Type.ERROR_MESSAGE); } } LOG.debug("Building GUI"); portletGui = guiBuilder.build(portletDomain); portletDomain.handleLoad(); viewPage.addComponent(portletGui.getView()); provideBackButtonFunctionality(portletId); tryToSetPortletTitle(portletDomain.getTitle()); cachePortletTitle(portletDomain.getTitle()); } catch (ValidationException ve) { throw new BusinessException(ve.getCode(), ve.getArgs()); } this.datasourceInfo.setPortletConfig(portletConfig.getPortletConfig()); } /** * @return model preferences taken from portlet instance preferences */ ModelPreferences createModelPreferences() { PortletPreferences prefs = VaadinPortletService .getCurrentPortletRequest().getPreferences(); ModelPreferences modelPrefs = new ModelPreferences(); String pageHeight = prefs.getValue("portlet.page.height", null); modelPrefs.setPageHeight(pageHeight); String minimumHeight = prefs.getValue("portlet.page.minimum-height", null); if (minimumHeight != null) { modelPrefs.setPageMinimumHeight(Integer.parseInt(minimumHeight)); } return modelPrefs; } private void provideBackButtonFunctionality(String portletId) { WebBrowser browser = getPage().getWebBrowser(); if (browser != null && !browser.isIE()) { portletUriFragmentUtility = new PortletUriFragmentUtility(eventBus, portletDomain, portletId); } else { LOG.info("Browser not detected or is Internet Explorer. Disabling back button functionality"); // ...weil sie zu einem unschönen Refresh des Browsers // führt } } private void cleanupViewPage() { dropConfiguration(); destroyDomain(); removeTimingPortletListener(); removeAddedComponentsFromView(); viewPage.removeAllComponents(); addHiddenPortletId(viewPage); } private void removeTimingPortletListener() { if (timingPortletListener != null) { getPortletSession().removePortletListener(timingPortletListener); timingPortletListener = null; } } private void recreateEditPage() { editPage.removeAllComponents(); configurationPresenter = presenterFactory .portletConfigurationPresenter(); editPage.addComponent(configurationPresenter.getView()); } /** * Drops reference to PortletConfiguration. */ private void dropConfiguration() { this.portletConfig = null; this.status = ConfigStatus.UNKNOWN; } private void destroyDomain() { portletDomain = null; portletGui = null; } /** * Erzeugt ein verstecktes HTML-Element mit der PortletPresenter-Window-ID * für die Akzeptanztests. * * @param container */ private void addHiddenPortletId(ComponentContainer container) { Label labelForPortletId = new Label(getPortletId()); labelForPortletId.setStyleName("crudPortletId"); labelForPortletId.setHeight(0, AbstractComponent.UNITS_PIXELS); container.addComponent(labelForPortletId); Label labelForCommunityId = new Label(String.valueOf(getCommunityId())); labelForCommunityId.setStyleName("crudPortletCommunityId"); labelForCommunityId.setHeight(0, AbstractComponent.UNITS_PIXELS); container.addComponent(labelForCommunityId); } @Override public void detach() { getPortletSession().removePortletListener(this); removeTimingPortletListener(); logLifecycleEvent(LifecycleEvent.CRUD2GO_UI_DETACH); super.detach(); } private VaadinPortletSession getPortletSession() { // deprecated, but currently the only way VaadinPortletSession portletSession = (VaadinPortletSession) VaadinPortletSession .getCurrent(); return portletSession; } /** * Setzt den konfigurierten PortletPresenter-Title während des * Render-Request. Erkennung von Reloads / Seitenwechseln. * * @param ui * ist hier immer NULL. Da die PortletSession aber nur pro * Portletinstanz gilt kann <code>this</code> verwendet werden. */ @Override public void handleRenderRequest(final RenderRequest request, RenderResponse response, UI ui) { LOG.debug("Handling render request..."); if (getSession() != null) { if (portletDomain != null) { if (portletDomain.getTitle() != null) { response.setTitle(portletDomain.getTitle()); } if (!configChanged && request.getPortletMode() == PortletMode.VIEW) { accessSynchronously(new Runnable() { @Override public void run() { portletDomain.handleReload(); } }); } } if (portletUriFragmentUtility != null) { // portletUriFragmentUtility.setInitialFragment(); } accessSynchronously(new Runnable() { @Override public void run() { handleViewChange(request); } }); configChanged = false; } else { // TODO check production logs and then remove query // should not happen any more as listener is removed on detach LOG.warn("Session is NULL during render request"); } } @Override public void handleActionRequest(ActionRequest request, ActionResponse response, UI ui) { // keine Aktion LOG.debug("Handling action request..."); } @Override public void handleEventRequest(EventRequest request, EventResponse response, UI ui) { // keine Aktion } /** * Hier wird auf den Wechsel des {@link PortletMode} reagiert, der durch den * Container an das PortletPresenter gemeldet wird. */ @Override public void handleResourceRequest(ResourceRequest request, ResourceResponse response, UI ui) { LOG.debug("Handling resource request..."); handleViewChange(request); } private void handleViewChange(PortletRequest request) { UI oldUI = UI.getCurrent(); try { UI.setCurrent(this); // if (ui == null) { // return; // } if (request.getPortletMode() == PortletMode.VIEW) { if (getContent() != viewPage && status == ConfigStatus.UNKNOWN) { refreshViews(); } setContent(viewPage); } else if (request.getPortletMode() == PortletMode.EDIT) { LOG.info("Request on EDIT page!"); if (getContent() != editPage) { configurationPresenter.refresh(portletConfig); } setContent(editPage); } else if (request.getPortletMode() == PortletMode.HELP) { this.datasourceInfo.refresh(); setContent(helpPage); } } finally { UI.setCurrent(oldUI); } } @Override public void showPopup(ShowPopupEvent event) { Popup popup = new Popup(event.getSource().getTitle(), event.getSource() .getBody(), event.getSource().getContentType()); addWindow(popup); } ComponentContainer getViewContent() { return viewPage; } ComponentContainer getEditContent() { return editPage; } ComponentContainer getHelpContent() { return helpPage; } void setConfigurationPresenter( PortletConfigurationPresenter configurationPresenter) { this.configurationPresenter = configurationPresenter; } Portlet getPortletDomain() { return portletDomain; } void setPortletDomain(Portlet portletDomain) { this.portletDomain = portletDomain; } public static CrudUI getCurrent() { return (CrudUI) UI.getCurrent(); } public boolean isInitializing() { return initializing; } public Config getPortletConfig() { return portletConfig; } }