/*******************************************************************************
* Copyright (c) 2010-2014 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.skalli.view.internal.window;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.skalli.model.EntityBase;
import org.eclipse.skalli.model.ExtensionEntityBase;
import org.eclipse.skalli.model.Issue;
import org.eclipse.skalli.model.Project;
import org.eclipse.skalli.services.extension.ExtensionService;
import org.eclipse.skalli.services.extension.ExtensionServices;
import org.eclipse.skalli.services.permit.Permit;
import org.eclipse.skalli.services.permit.Permits;
import org.eclipse.skalli.view.ext.ExtensionFormService;
import org.eclipse.skalli.view.ext.ExtensionStreamSource;
import org.eclipse.skalli.view.ext.ProjectEditContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vaadin.Application;
import com.vaadin.terminal.StreamResource;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.ComponentContainer;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.Embedded;
import com.vaadin.ui.Field;
import com.vaadin.ui.Form;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.NativeButton;
/**
* Entry of a {@link ProjectEditPanel}.
* Note: this class has a natural ordering that might not be consistent with equals.
* Two entries are equal if and only if their internal <code>ProjectEditForm</code>
* are identical (in the sense of == not equals!). However, the natural ordering
* of entries is based on their rank.
*/
class ProjectEditPanelEntry extends CustomComponent {
private static final long serialVersionUID = 6723243553337561399L;
private static final String BUTTON_TEXT_DISABLE = "disable";
private static final String BUTTON_TEXT_DISABLED = "disabled";
private static final String BUTTON_TEXT_EDIT = "edit";
private static final String BUTTON_TEXT_EDITABLE = "editable";
private static final String BUTTON_TEXT_INHERIT = "inherit";
private static final String BUTTON_TEXT_INHERITED = "inherited";
private static final String BUTTON_TEXT_SHOW = "show";
private static final String BUTTON_TEXT_HIDE = "hide";
private static final String STYLE_TRAY = "tray"; //$NON-NLS-1$
private static final String STYLE_TRAY_HEADER = "header"; //$NON-NLS-1$
private static final String STYLE_TRAY_HEADER_ICON = "header-icon"; //$NON-NLS-1$
private static final String STYLE_TRAY_HEADER_LABEL = "header-label"; //$NON-NLS-1$
private static final String STYLE_TRAY_DESCRIPTION = "description"; //$NON-NLS-1$
private static final String STYLE_TRAY_OPEN = "open"; //$NON-NLS-1$
private static final String STYLE_TRAY_CLOSED = "closed"; //$NON-NLS-1$
private static final String STYLE_BUTTON_SELECTED = "selected"; //$NON-NLS-1$
private static final String STYLE_ISSUES = "prjedt-issues"; //$NON-NLS-1$
private static final String EXTENSIONS = "extensions"; //$NON-NLS-1$
private static final Logger LOG = LoggerFactory.getLogger(ProjectEditPanelEntry.class);
private Project project;
private Class<? extends ExtensionEntityBase> extensionClass;
private String extensionClassName;
private ExtensionEntityBase extension;
private ExtensionService<? extends ExtensionEntityBase> extensionService;
private ProjectEditContext context;
private Application application;
private Set<String> readOnlyFieldsIds = new HashSet<String>();
private Set<String> disabledFieldsIds = new HashSet<String>();
// service to create a Vaadin form for the extension
// and retrieve caption/description/icon for the tray
private ExtensionFormService<?> formService;
// the actual form instance, content of tray */
private Form form;
// the outer tray container
private ComponentContainer tray;
private Embedded trayIcon;
// buttons of the tray
private Button visibleButton;
private Button inheritButton;
private Button editButton;
private Button disableButton;
// the current state of the tray
private TrayState state;
private boolean disableAllowed;
private boolean inheritAllowed;
// issue rendering
private Label issueLabel;
private enum TrayState {
DISABLED, EDITABLE_INVISIBLE, EDITABLE_VISIBLE, INHERITED_INVISIBLE, INHERITED_VISIBLE
}
public ProjectEditPanelEntry(Project project, ExtensionFormService<?> formService, ProjectEditContext context,
Application application) {
this.project = project;
this.formService = formService;
this.context = context;
this.application = application;
extensionClass = formService.getExtensionClass();
extensionClassName = extensionClass.getName();
if (!project.isInherited(extensionClass)) {
extension = project.getExtension(extensionClass);
}
extensionService = ExtensionServices.getByExtensionClass(extensionClass);
if (extensionService == null) {
throw new IllegalStateException(MessageFormat.format(
"ExtensionFormService{0} has no corresponding ExtensionService", formService.getClass()));
}
boolean isEnabledIntially = context.getProjectTemplate().isEnabled(extensionClassName);
if (extensionClass.equals(project.getClass())) {
disableAllowed = false;
inheritAllowed = false;
}
else {
disableAllowed = true;
inheritAllowed = true;
}
tray = createTray();
layoutNewEditForm();
if (extensionClass.equals(project.getClass())) {
setState(TrayState.EDITABLE_VISIBLE);
}
else if (isEnabledIntially && project.isInherited(extensionClass)) {
setState(TrayState.INHERITED_VISIBLE);
}
else if (project.isInherited(extensionClass)) {
setState(TrayState.INHERITED_INVISIBLE);
}
else if (isEnabledIntially && extension != null) {
setState(TrayState.EDITABLE_VISIBLE);
}
else if (extension != null) {
setState(TrayState.EDITABLE_VISIBLE);
}
else {
setState(TrayState.DISABLED);
}
String extensionShortName = extensionService.getShortName();
if (!Permits.isAllowed(Permit.ACTION_DELETE, project, EXTENSIONS, extensionShortName)) {
disableButton.setEnabled(false);
}
if (!Permits.isAllowed(Permit.ACTION_POST, project, EXTENSIONS, extensionShortName)) {
editButton.setEnabled(false);
}
if (!Permits.isAllowed(Permit.ACTION_PUT, project, EXTENSIONS, extensionShortName,
"properties", "inherited")) { //$NON-NLS-1$ //$NON-NLS-2$
inheritButton.setEnabled(false);
}
setCompositionRoot(tray);
}
public boolean isValid() {
return form != null ? form.isValid() : true;
}
public void commit() {
if (form != null) {
form.commit();
}
}
public void expand() {
switch (state) {
case EDITABLE_INVISIBLE:
setState(TrayState.EDITABLE_VISIBLE);
break;
case INHERITED_INVISIBLE:
setState(TrayState.INHERITED_VISIBLE);
break;
}
}
public void collapse() {
switch (state) {
case EDITABLE_VISIBLE:
setState(TrayState.EDITABLE_INVISIBLE);
break;
case INHERITED_VISIBLE:
setState(TrayState.INHERITED_INVISIBLE);
break;
}
}
public boolean isDisabled() {
return TrayState.DISABLED.equals(state);
}
public boolean isExpanded() {
return TrayState.EDITABLE_VISIBLE.equals(state) || TrayState.INHERITED_VISIBLE.equals(state);
}
public boolean isEditable() {
return TrayState.EDITABLE_VISIBLE.equals(state) || TrayState.EDITABLE_INVISIBLE.equals(state);
}
public boolean isInherited() {
return TrayState.INHERITED_VISIBLE.equals(state) || TrayState.INHERITED_INVISIBLE.equals(state);
}
public String getDisplayName() {
return extensionService.getCaption();
}
public float getRank() {
return formService.getRank();
}
public Class<? extends ExtensionEntityBase> getExtensionClass() {
return extensionClass;
}
public String getExtensionClassName() {
return extensionClassName;
}
public void onPropertyChanged(String extensionClassName, String propertyId, Object newValue) {
if (EntityBase.class.getName().equals(extensionClassName) && EntityBase.PROPERTY_PARENT_ENTITY.equals(propertyId)) {
project.setParentEntity((EntityBase) newValue);
if (TrayState.INHERITED_INVISIBLE.equals(state) || TrayState.INHERITED_VISIBLE.equals(state)) {
inheritExtension();
setState(state);
}
}
if (formService.listenOnPropertyChanged(extensionClassName, propertyId, newValue)) {
form.commit();
layoutNewEditForm();
}
}
public Object getProperty(String propertyName) {
if (!isDisabled() && extension != null && extension.hasProperty(propertyName)) {
form.commit();
return extension.getProperty(propertyName);
}
return null;
}
public void setProperty(String propertyName, Object propertyValue) {
if (isInherited()) {
return;
}
if (isDisabled()) {
setState(TrayState.EDITABLE_VISIBLE);
}
if (extension != null && extension.hasProperty(propertyName)) {
extension.setProperty(propertyName, propertyValue);
layoutNewEditForm();
expand();
}
}
public void showIssues(SortedSet<Issue> issues, boolean collapseValid) {
if (issueLabel == null) {
issueLabel = new Label(StringUtils.EMPTY, Label.CONTENT_XHTML);
issueLabel.setWidth(100, UNITS_PERCENTAGE);
issueLabel.addStyleName(STYLE_ISSUES);
tray.addComponent(issueLabel);
}
if (issues == null || issues.isEmpty()) {
issueLabel.setValue(StringUtils.EMPTY);
issueLabel.setVisible(false);
if (collapseValid) {
collapse();
}
} else {
issueLabel.setValue(Issue.asHTMLList(null, issues));
issueLabel.setVisible(true);
expand();
}
}
@SuppressWarnings("serial")
private ComponentContainer createTray() {
CssLayout layout = new CssLayout();
layout.setStyleName(STYLE_TRAY);
layout.setMargin(true);
HorizontalLayout header = new HorizontalLayout();
header.setSpacing(true);
header.setStyleName(STYLE_TRAY_HEADER);
header.setWidth("100%"); //$NON-NLS-1$
trayIcon = new Embedded();
trayIcon.setStyleName(STYLE_TRAY_HEADER_ICON);
final String icon = formService.getIconPath();
if (StringUtils.isNotBlank(icon)) {
trayIcon.setSource(new StreamResource(new ExtensionStreamSource(formService.getClass(), icon),
FilenameUtils.getName(icon), application));
}
header.addComponent(trayIcon);
header.setComponentAlignment(trayIcon, Alignment.MIDDLE_LEFT);
header.setExpandRatio(trayIcon, 0);
Label captionLabel = new Label(getCaptionWithAnchor(), Label.CONTENT_XHTML);
captionLabel.setStyleName(STYLE_TRAY_HEADER_LABEL);
header.addComponent(captionLabel);
header.setExpandRatio(captionLabel, 1);
header.setComponentAlignment(captionLabel, Alignment.MIDDLE_LEFT);
editButton = new Button();
layoutLinkButton(editButton, header, new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
switch (state) {
case DISABLED:
setState(TrayState.EDITABLE_VISIBLE);
break;
case INHERITED_VISIBLE:
setState(TrayState.EDITABLE_VISIBLE);
break;
case INHERITED_INVISIBLE:
setState(TrayState.EDITABLE_VISIBLE);
break;
}
}
});
inheritButton = new Button();
layoutLinkButton(inheritButton, header, new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
form.commit();
switch (state) {
case DISABLED:
setState(TrayState.INHERITED_VISIBLE);
break;
case EDITABLE_VISIBLE:
setState(TrayState.INHERITED_VISIBLE);
break;
case EDITABLE_INVISIBLE:
setState(TrayState.INHERITED_VISIBLE);
break;
}
}
});
disableButton = new Button();
layoutLinkButton(disableButton, header, new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
form.commit();
switch (state) {
case EDITABLE_INVISIBLE:
setState(TrayState.DISABLED);
break;
case EDITABLE_VISIBLE:
setState(TrayState.DISABLED);
break;
case INHERITED_INVISIBLE:
setState(TrayState.DISABLED);
break;
case INHERITED_VISIBLE:
setState(TrayState.DISABLED);
break;
}
}
});
visibleButton = new NativeButton();
layoutIconButton(visibleButton, header, new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
switch (state) {
case EDITABLE_INVISIBLE:
setState(TrayState.EDITABLE_VISIBLE);
break;
case EDITABLE_VISIBLE:
setState(TrayState.EDITABLE_INVISIBLE);
break;
case INHERITED_INVISIBLE:
setState(TrayState.INHERITED_VISIBLE);
break;
case INHERITED_VISIBLE:
setState(TrayState.INHERITED_INVISIBLE);
break;
}
}
});
layout.addComponent(header);
CssLayout content = new CssLayout();
Label descriptionLabel = new Label(extensionService.getDescription(), Label.CONTENT_XHTML);
descriptionLabel.setStyleName(STYLE_TRAY_DESCRIPTION);
content.addComponent(descriptionLabel);
layout.addComponent(content);
return layout;
}
@SuppressWarnings("nls")
private String getCaptionWithAnchor() {
StringBuilder sb = new StringBuilder();
sb.append("<a id=\"");
sb.append(extensionClassName);
sb.append("\" name=\"");
sb.append(extensionClassName);
sb.append("\"><!-- --></a>");
sb.append(StringEscapeUtils.escapeHtml(extensionService.getCaption()));
return sb.toString();
}
private void setState(TrayState newState) {
switch (newState) {
case DISABLED:
if (state != null) {
disableExtension();
}
editButton.setEnabled(true);
editButton.setCaption(BUTTON_TEXT_EDIT);
layoutInheritButton(BUTTON_TEXT_INHERIT, true);
layoutDisableButton(BUTTON_TEXT_DISABLED, false);
visibleButton.setEnabled(false);
visibleButton.setDescription(BUTTON_TEXT_SHOW);
visibleButton.setStyleName(STYLE_TRAY_CLOSED);
form.setVisible(false);
setFormEnabled(false);
setFormReadOnly(true);
editButton.removeStyleName(STYLE_BUTTON_SELECTED);
inheritButton.removeStyleName(STYLE_BUTTON_SELECTED);
disableButton.addStyleName(STYLE_BUTTON_SELECTED);
break;
case INHERITED_VISIBLE:
if (TrayState.DISABLED.equals(state) ||
TrayState.EDITABLE_INVISIBLE.equals(state) ||
TrayState.EDITABLE_VISIBLE.equals(state)) {
inheritExtension();
}
editButton.setEnabled(true);
editButton.setCaption(BUTTON_TEXT_EDIT);
layoutInheritButton(BUTTON_TEXT_INHERITED, false);
layoutDisableButton(BUTTON_TEXT_DISABLE, true);
visibleButton.setEnabled(true);
visibleButton.setDescription(BUTTON_TEXT_HIDE);
visibleButton.setStyleName(STYLE_TRAY_OPEN);
form.setVisible(true);
setFormEnabled(true);
setFormReadOnly(true);
editButton.removeStyleName(STYLE_BUTTON_SELECTED);
inheritButton.addStyleName(STYLE_BUTTON_SELECTED);
disableButton.removeStyleName(STYLE_BUTTON_SELECTED);
break;
case INHERITED_INVISIBLE:
if (TrayState.DISABLED.equals(state) ||
TrayState.EDITABLE_INVISIBLE.equals(state) ||
TrayState.EDITABLE_VISIBLE.equals(state)) {
inheritExtension();
}
editButton.setEnabled(true);
editButton.setCaption(BUTTON_TEXT_EDIT);
layoutInheritButton(BUTTON_TEXT_INHERITED, false);
layoutDisableButton(BUTTON_TEXT_DISABLE, true);
visibleButton.setEnabled(true);
visibleButton.setDescription(BUTTON_TEXT_SHOW);
visibleButton.setStyleName(STYLE_TRAY_CLOSED);
form.setVisible(false);
setFormEnabled(false);
setFormReadOnly(false);
editButton.removeStyleName(STYLE_BUTTON_SELECTED);
inheritButton.addStyleName(STYLE_BUTTON_SELECTED);
disableButton.removeStyleName(STYLE_BUTTON_SELECTED);
break;
case EDITABLE_VISIBLE:
if (TrayState.DISABLED.equals(state) ||
TrayState.INHERITED_INVISIBLE.equals(state) ||
TrayState.INHERITED_VISIBLE.equals(state)) {
editExtension();
}
editButton.setEnabled(false);
editButton.setCaption(BUTTON_TEXT_EDITABLE);
layoutInheritButton(BUTTON_TEXT_INHERIT, true);
layoutDisableButton(BUTTON_TEXT_DISABLE, true);
visibleButton.setEnabled(true);
visibleButton.setDescription(BUTTON_TEXT_HIDE);
visibleButton.setStyleName(STYLE_TRAY_OPEN);
form.setVisible(true);
setFormEnabled(true);
setFormReadOnly(false);
editButton.addStyleName(STYLE_BUTTON_SELECTED);
inheritButton.removeStyleName(STYLE_BUTTON_SELECTED);
disableButton.removeStyleName(STYLE_BUTTON_SELECTED);
break;
case EDITABLE_INVISIBLE:
if (TrayState.DISABLED.equals(state) ||
TrayState.INHERITED_INVISIBLE.equals(state) ||
TrayState.INHERITED_VISIBLE.equals(state)) {
editExtension();
}
editButton.setEnabled(false);
editButton.setCaption(BUTTON_TEXT_EDITABLE);
layoutInheritButton(BUTTON_TEXT_INHERIT, true);
layoutDisableButton(BUTTON_TEXT_DISABLE, true);
visibleButton.setEnabled(true);
visibleButton.setDescription(BUTTON_TEXT_SHOW);
visibleButton.setStyleName(STYLE_TRAY_CLOSED);
form.setVisible(false);
setFormEnabled(true);
setFormReadOnly(false);
editButton.addStyleName(STYLE_BUTTON_SELECTED);
inheritButton.removeStyleName(STYLE_BUTTON_SELECTED);
disableButton.removeStyleName(STYLE_BUTTON_SELECTED);
break;
}
state = newState;
}
private void setFormReadOnly(boolean readonly) {
form.setReadOnly(readonly);
for (String readOnlyFieldId: readOnlyFieldsIds) {
form.getField(readOnlyFieldId).setReadOnly(true);
}
}
private void setFormEnabled(boolean enabled) {
form.setEnabled(enabled);
for (String disabledFieldsId: disabledFieldsIds) {
form.getField(disabledFieldsId).setEnabled(false);
}
}
private void layoutNewEditForm() {
Form newForm;
try {
newForm = formService.createForm(project, context);
} catch (RuntimeException e) {
LOG.error("Failed to render '" + StringEscapeUtils.escapeHtml(getDisplayName()) + "' form", e);
newForm = new Form();
tray.addComponent(new Label("<span style=\"font-weight:bold;color:red\">Sorry, can't display this form. " +
"An internal error occurred. Please notify the administrator.</span>", Label.CONTENT_XHTML));
}
if (form != null) {
tray.replaceComponent(form, newForm);
} else {
tray.addComponent(newForm);
}
form = newForm;
initFieldStates();
}
private void initFieldStates() {
readOnlyFieldsIds.clear();
disabledFieldsIds.clear();
for (Object propertyId: form.getItemPropertyIds()) {
Field field = form.getField(propertyId);
if (field.isReadOnly()) {
readOnlyFieldsIds.add((String)propertyId);
}
if (!field.isEnabled()) {
disabledFieldsIds.add((String)propertyId);
}
}
}
@SuppressWarnings("deprecation")
private void layoutLinkButton(Button button, HorizontalLayout layout, ClickListener listener) {
button.setStyle(Button.STYLE_LINK);
button.addListener(listener);
layout.addComponent(button);
layout.setExpandRatio(button, 0);
layout.setComponentAlignment(button, Alignment.MIDDLE_RIGHT);
}
private void layoutIconButton(Button button, HorizontalLayout layout, ClickListener listener) {
button.addListener(listener);
layout.addComponent(button);
layout.setExpandRatio(button, 0);
layout.setComponentAlignment(button, Alignment.MIDDLE_RIGHT);
}
private void layoutDisableButton(String text, boolean enabled) {
if (disableAllowed) {
disableButton.setEnabled(enabled);
disableButton.setCaption(text);
} else {
disableButton.setEnabled(false);
disableButton.setCaption(BUTTON_TEXT_DISABLE);
}
}
private void layoutInheritButton(String text, boolean enabled) {
if (inheritAllowed) {
inheritButton.setEnabled(enabled);
inheritButton.setCaption(text);
} else {
inheritButton.setEnabled(false);
inheritButton.setCaption(BUTTON_TEXT_INHERIT);
}
}
private void inheritExtension() {
if (extension == null && !project.isInherited(extensionClass)) {
extension = project.getExtension(extensionClass);
}
project.setInherited(extensionClass, true);
layoutNewEditForm();
}
private void editExtension() {
project.setInherited(extensionClass, false);
if (extension == null) {
extension = formService.newExtensionInstance();
}
project.addExtension(extension);
layoutNewEditForm();
}
private void disableExtension() {
if (extension == null && !project.isInherited(extensionClass)) {
extension = project.getExtension(extensionClass);
}
project.removeExtension(extensionClass);
tray.removeComponent(form);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null) {
if (obj instanceof ProjectEditPanelEntry) {
return formService == ((ProjectEditPanelEntry) obj).formService;
}
if (obj instanceof ExtensionFormService) {
return formService == (ExtensionFormService<?>) obj;
}
}
return false;
}
@Override
public int hashCode() {
return formService.hashCode();
}
@Override
public String toString() {
return extensionService.getCaption();
}
}