/******************************************************************************* * 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.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; 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.model.Severity; import org.eclipse.skalli.model.User; import org.eclipse.skalli.model.ValidationException; import org.eclipse.skalli.services.Services; import org.eclipse.skalli.services.entity.EntityServices; import org.eclipse.skalli.services.extension.ExtensionService; import org.eclipse.skalli.services.extension.ExtensionServices; import org.eclipse.skalli.services.group.GroupUtils; import org.eclipse.skalli.services.issues.Issues; import org.eclipse.skalli.services.issues.IssuesService; import org.eclipse.skalli.services.project.ProjectService; import org.eclipse.skalli.services.template.ProjectTemplate; import org.eclipse.skalli.services.template.ProjectTemplateService; import org.eclipse.skalli.services.user.UserServices; import org.eclipse.skalli.view.ext.ExtensionFormService; import org.eclipse.skalli.view.ext.Navigator; import org.eclipse.skalli.view.ext.ProjectEditContext; import org.eclipse.skalli.view.ext.ProjectEditMode; import org.eclipse.skalli.view.internal.application.ProjectApplication; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.terminal.ThemeResource; import com.vaadin.ui.Alignment; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.CssLayout; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Panel; import com.vaadin.ui.ProgressIndicator; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; public class ProjectEditPanel extends Panel implements ProjectPanel { private static final long serialVersionUID = 2377962084815410728L; private static final Logger LOG = LoggerFactory.getLogger(ProjectEditPanel.class); private static final String STYLE_EDIT_PROJECT = "prjedt"; //$NON-NLS-1$ private static final String STYLE_EDIT_PROJECT_LAYOUT = "prjedt-layout"; //$NON-NLS-1$ private static final String STYLE_EDIT_PROJECT_BUTTONS = "prjedt-buttons"; //$NON-NLS-1$ private static final String STYLE_EDIT_PROJECT_ERROR = "prjedt-errorLabel"; //$NON-NLS-1$ private static final String STYLE_ISSUES = "prjedt-issues"; //$NON-NLS-1$ private static final String HEADER = "header_"; //$NON-NLS-1$ private static final String FOOTER = "footer_"; //$NON-NLS-1$ private static final String BUTTON_OK = "button_ok"; //$NON-NLS-1$ private static final String BUTTON_CANCEL = "button_cancel"; //$NON-NLS-1$ private static final String BUTTON_EXPAND_ALL = "button_expand_all"; //$NON-NLS-1$ private static final String BUTTON_COLLAPSE_ALL = "button_collapse_all"; //$NON-NLS-1$ private static final String BUTTON_VALIDATE = "button_validate"; //$NON-NLS-1$ private static final String PANEL_WIDTH = "600px"; //$NON-NLS-1$ private static final String CONFIRM_POPUP_WIDTH = "350px"; //$NON-NLS-1$ private final ThemeResource ICON_BUTTON_OK = new ThemeResource("icons/button/ok.png"); //$NON-NLS-1$ private final ThemeResource ICON_BUTTON_CANCEL = new ThemeResource("icons/button/cancel.png"); //$NON-NLS-1$ private final ThemeResource ICON_BUTTON_EXPAND_ALL = new ThemeResource("icons/button/openall.png"); //$NON-NLS-1$ private final ThemeResource ICON_BUTTON_COLLAPSE_ALL = new ThemeResource("icons/button/closeall.png"); //$NON-NLS-1$ private final ThemeResource ICON_BUTTON_VALIDATE = new ThemeResource("icons/button/validate.png"); //$NON-NLS-1$ private static final String WARN_EXTENSION_DISABLED = "Extension <i>{0}</i> has been disabled"; private static final String WARN_EXTENSION_INHERITED = "Extension <i>{0}</i> is inherited from parent project <i>{1}</i>"; private Project project; private ProjectTemplate projectTemplate; private ProjectEditMode mode; private Project modifiedProject; private Map<String, ProjectEditPanelEntry> panels; private SortedSet<ProjectEditPanelEntry> sortedPanels; private Set<String> selectableExtensions; private Map<String, String> displayNames; private ProjectApplication application; private Navigator navigator; private Label headerLabel; private Label footerLabel; private Button headerCheckButton; private Button footerCheckButton; private CssLayout indicatorArea; private ProgressIndicator progressIndicator; private ProgressThread progressThread; private ValidatorThread validatorThread; private Issues persistedIssues; /** * Creates a project edit view for * <ul> * <li>browsing of an existing project in readonly mode ({@link ProjectEditMode#VIEW_PROJECT})</li> * <li>editing of an existing project ({@link ProjectEditMode#EDIT_PROJECT})</li> * <li>editing of a new project ({@link ProjectEditMode#NEW_PROJECT})</li> * </ul> * * @param application the application. * @param navigator the navigator used to leave the dialog. * @param project the project to browse or modify, or a freshly created project. * (see {@link ProjectService#createProject(String, String)}). * @param mode one of {@link ProjectEditMode#VIEW_PROJECT}, {@link ProjectEditMode#EDIT_PROJECT} or * {@link ProjectEditMode#NEW_PROJECT}. */ public ProjectEditPanel(ProjectApplication application, Navigator navigator, Project project, ProjectEditMode mode) { this.application = application; this.navigator = navigator; this.mode = mode; this.project = project; // If the project has been persisted before, do not modify the // cached project instance, but modify a copy loaded directly from storage. if (ProjectEditMode.NEW_PROJECT.equals(mode)) { modifiedProject = project; } else { ProjectService projectService = ((ProjectService)EntityServices.getByEntityClass(Project.class)); modifiedProject = projectService.loadEntity(Project.class, project.getUuid()); } ProjectTemplateService templateService = Services.getRequiredService(ProjectTemplateService.class); projectTemplate = templateService.getProjectTemplateById(project.getProjectTemplateId()); initializeSelectableExtensions(); loadPersistedIssues(); initializePanelEntries(); setSizeFull(); setStyleName(STYLE_EDIT_PROJECT); renderContent((VerticalLayout) getContent()); } @Override public Project getProject() { return project; } /** * Loads persisted issues for the project. */ private void loadPersistedIssues() { if (modifiedProject.getUuid() == null) { LOG.info("New project, no issues available. Skipping loading of issues."); return; } IssuesService issuesService = Services.getService(IssuesService.class); if (issuesService == null) { LOG.warn("No issue service available. Skipping loading of issues."); return; } persistedIssues = issuesService.loadEntity(Issues.class, modifiedProject.getUuid()); } /** * Initializes the {@link #selectableExtensions} field: * Calls {@link ProjectTemplateService#getSelectableExtensions(ProjectTemplate, Project)} to determine * all extensions that * <ul> * <li>can work with the given template (see {@link ExtensionService#getProjectTemplateIdservice()})</li> * <li>are not in the template's exclude list (if an exclude list is specified, * see {@link ProjectTemplate#getExcludedExtensions()})</li> * <li>are in the templates include list (if an include list is specified, * see {@link ProjectTemplate#getIncludedExtensions()})</li> * </ul> * If all checks succeed, the extension's class name is added to <code>selectableExtensions</code>. */ private void initializeSelectableExtensions() { ProjectTemplateService projectTemplateService = Services.getRequiredService(ProjectTemplateService.class); selectableExtensions = new HashSet<String>(); for (Class<? extends ExtensionEntityBase> extension : projectTemplateService.getSelectableExtensions( projectTemplate, modifiedProject)) { selectableExtensions.add(extension.getName()); } } /** * Creates and initializes the panels. * Iterates over all available {@link ExtensionFormService} and creates a {@link ProjectEditPanelEntry} * for each form service that is supported by the project template of the project to edit. */ @SuppressWarnings("rawtypes") private void initializePanelEntries() { panels = new HashMap<String,ProjectEditPanelEntry>(); sortedPanels = new TreeSet<ProjectEditPanelEntry>(new ProjectEditPanelComparator(projectTemplate)); displayNames = new HashMap<String, String>(); Context context = new Context(application, projectTemplate, mode); Iterator<ExtensionFormService> extensionFormFactories = Services.getServiceIterator(ExtensionFormService.class); while (extensionFormFactories.hasNext()) { ExtensionFormService extensionFormFactory = extensionFormFactories.next(); String extensionClassName = extensionFormFactory.getExtensionClass().getName(); if (selectableExtensions.contains(extensionClassName)) { ProjectEditPanelEntry entry = new ProjectEditPanelEntry( modifiedProject, extensionFormFactory, context, application); entry.setWidth(PANEL_WIDTH); panels.put(extensionClassName, entry); sortedPanels.add(entry); displayNames.put(extensionClassName, entry.getDisplayName()); } } } void renderContent(VerticalLayout content) { content.setStyleName(STYLE_EDIT_PROJECT_LAYOUT); headerCheckButton = renderButtons(content, true); renderProgessIndicator(content); headerLabel = renderMessageArea(content); renderPanels(content); footerLabel = renderMessageArea(content); footerCheckButton = renderButtons(content, false); renderPersistedIssues(); } private void renderProgessIndicator(VerticalLayout layout) { indicatorArea = new CssLayout(); indicatorArea.setVisible(false); indicatorArea.setMargin(true); indicatorArea.setWidth(PANEL_WIDTH); indicatorArea.addComponent(new Label("<strong>Project is checked for issues</strong>", Label.CONTENT_XHTML)); progressIndicator = new ProgressIndicator(); progressIndicator.setWidth("300px"); progressIndicator.setIndeterminate(false); indicatorArea.addComponent(progressIndicator); layout.addComponent(indicatorArea); layout.setComponentAlignment(indicatorArea, Alignment.MIDDLE_CENTER); } private Label renderMessageArea(VerticalLayout layout) { CssLayout messageArea = new CssLayout(); messageArea.setMargin(true); messageArea.setWidth(PANEL_WIDTH); Label label = new Label("", Label.CONTENT_XHTML); //$NON-NLS-1$ label.addStyleName(STYLE_ISSUES); label.setVisible(false); messageArea.addComponent(label); layout.addComponent(messageArea); layout.setComponentAlignment(messageArea, Alignment.MIDDLE_CENTER); return label; } private void setMessage(Label label, String message) { if (StringUtils.isNotEmpty(message)) { label.setValue(message); label.setVisible(true); } else { label.setVisible(false); } label.requestRepaint(); } private void setMessage(String message) { setMessage(headerLabel, message); setMessage(footerLabel, message); } private void setMessage(SortedSet<Issue> issues, Map<String, String> displayNames) { String message = Issue.asHTMLList(null, issues, displayNames); setMessage(headerLabel, message); setMessage(footerLabel, message); } /** * Renders a OK/Cancel/Validate/Expand All/Collapse All button bar. */ @SuppressWarnings("serial") private Button renderButtons(VerticalLayout layout, boolean header) { CssLayout buttons = new CssLayout(); buttons.addStyleName(STYLE_EDIT_PROJECT_BUTTONS); String prefix = header ? HEADER : FOOTER; Button okButton = new Button("OK"); okButton.setIcon(ICON_BUTTON_OK); okButton.setDescription("Save the modified project"); okButton.addStyleName(prefix + BUTTON_OK); okButton.addListener(new OKButtonListener()); buttons.addComponent(okButton); Button cancelButton = new Button("Cancel"); cancelButton.setIcon(ICON_BUTTON_CANCEL); cancelButton.setDescription("Discard all changes to the project"); cancelButton.addStyleName(prefix + BUTTON_CANCEL); cancelButton.addListener(new CancelButtonListener()); buttons.addComponent(cancelButton); Button checkButton = new Button("Check"); checkButton.setIcon(ICON_BUTTON_VALIDATE); checkButton.setDescription("Checks the modified project for issues without saving it"); checkButton.addStyleName(prefix + BUTTON_VALIDATE); checkButton.addListener(new Button.ClickListener() { @Override public void buttonClick(ClickEvent event) { validateModifiedProject(); } }); buttons.addComponent(checkButton); Button expandAllButton = new Button("Expand All"); expandAllButton.setIcon(ICON_BUTTON_EXPAND_ALL); expandAllButton.setDescription("Expand all panels"); expandAllButton.addStyleName(prefix + BUTTON_EXPAND_ALL); expandAllButton.addListener(new Button.ClickListener() { @Override public void buttonClick(ClickEvent event) { expandAllPanels(); } }); buttons.addComponent(expandAllButton); Button collapseAllButton = new Button("Collapse All"); collapseAllButton.setIcon(ICON_BUTTON_COLLAPSE_ALL); collapseAllButton.setDescription("Collapse all panels"); collapseAllButton.addStyleName(prefix + BUTTON_COLLAPSE_ALL); collapseAllButton.addListener(new Button.ClickListener() { @Override public void buttonClick(ClickEvent event) { collapseAllPanels(); } }); buttons.addComponent(collapseAllButton); layout.addComponent(buttons); layout.setComponentAlignment(buttons, Alignment.MIDDLE_CENTER); return checkButton; } /** * Renders the panels in the order defined by the comparator of {@link #sortedPanels}. */ private void renderPanels(VerticalLayout layout) { for (ProjectEditPanelEntry entry : sortedPanels) { layout.addComponent(entry); layout.setComponentAlignment(entry, Alignment.MIDDLE_CENTER); } } private void collapseAllPanels() { for (ProjectEditPanelEntry entry : sortedPanels) { entry.collapse(); } } private void expandAllPanels() { for (ProjectEditPanelEntry entry : sortedPanels) { entry.expand(); } } /** * Validates the modified project with {@link Severity#INFO} and renders the result. */ private void validateModifiedProject() { headerCheckButton.setEnabled(false); footerCheckButton.setEnabled(false); commitForms(); renderIssues(null, false); progressIndicator.setValue(0f); indicatorArea.setVisible(true); progressThread = new ProgressThread(); validatorThread = new ValidatorThread(); progressThread.start(); validatorThread.start(); } private void updateProgressIndicator() { if (validatorThread != null && validatorThread.isFinished()) { indicatorArea.setVisible(false); if (persistedIssues != null) { persistedIssues.addLatestDuration(validatorThread.getDuration()); } progressThread.interrupt(); validatorThread.interrupt(); progressThread = null; validatorThread = null; headerCheckButton.setEnabled(true); footerCheckButton.setEnabled(true); } else if (progressThread != null) { progressIndicator.setValue(progressThread.progress()); } else { progressIndicator.setValue(0f); } } private class ProgressThread extends Thread { private static final long SLEEPING_TIME = 300L; // 1 seconds milliseconds private static final long UNKNOWN_AVERAGE_DURATION = 30000L; // 10 seconds private long elapsedTime; private long averageDuration; @Override public void run() { averageDuration = UNKNOWN_AVERAGE_DURATION; if (persistedIssues != null && persistedIssues.getAverageDuration() > 0) { averageDuration = persistedIssues.getAverageDuration(); } for (; elapsedTime < averageDuration; elapsedTime += SLEEPING_TIME) { try { Thread.sleep(SLEEPING_TIME); } catch (InterruptedException e) { return; } synchronized (getApplication()) { updateProgressIndicator(); } } } private float progress() { return Math.min((float) elapsedTime / averageDuration, 0.95f); // never return 100% } } private class ValidatorThread extends Thread { private long startTime; private long duration = -1L; @Override public void run() { ProjectService projectService = ((ProjectService)EntityServices.getByEntityClass(Project.class)); startTime = System.currentTimeMillis(); SortedSet<Issue> issues = projectService.validate(modifiedProject, Severity.INFO); synchronized (getApplication()) { renderIssues(issues, false); if (issues.size() == 0) { getWindow().showNotification("No Issues Found"); } duration = System.currentTimeMillis() - startTime; updateProgressIndicator(); } } public boolean isFinished() { return duration >= 0; } public long getDuration() { return duration; } } /** * Renders the persisted validation issues. If the persisted issues are * stale, render a corresponding message instead. */ private void renderPersistedIssues() { if (persistedIssues != null) { if (persistedIssues.isStale()) { setMessage("<ul><li class=\"STALE\">" + "No information about issues available. Use the <b>Validate</b> button " + "above to validate the project now.</li></ul>"); } else { renderIssues(persistedIssues.getIssues(), false); } } } /** * Renders validation issues given by a <code>ValidationException</code> * merged with the persisted issues (if any). * * @param e the validation exception to render. */ private void renderIssues(ValidationException e) { TreeSet<Issue> issues = new TreeSet<Issue>(e.getIssues()); renderIssues(issues, true); } /** * Renders the given validation issues. * * If <code>collapseValid</code> is set all panels without issues will be collapsed * to focus the view of the user to panels with issues. */ private void renderIssues(SortedSet<Issue> issues, boolean collapseValid) { Map<String, SortedSet<Issue>> sortedIssues = new HashMap<String, SortedSet<Issue>>(); sortIssuesByExtension(issues, sortedIssues); // render issues that are related to an extension for (ProjectEditPanelEntry entry : sortedPanels) { String extensionName = entry.getExtensionClassName(); SortedSet<Issue> extensionIssues = sortedIssues.get(extensionName); entry.showIssues(extensionIssues, collapseValid); } // render issues in the message areas setMessage(issues, displayNames); } /** * Sorts the issues by extension and adds them to <code>extensionIssues</code> according to the * extension they belong to. */ private void sortIssuesByExtension(SortedSet<Issue> issues, Map<String, SortedSet<Issue>> extensionIssues) { if (issues != null) { for (Issue issue : issues) { Class<? extends ExtensionEntityBase> extension = issue.getExtension(); if (extension != null) { String extensionName = extension.getName(); addIssue(extensionIssues, extensionName, issue); } } } } private void addIssue(Map<String, SortedSet<Issue>> issues, String extensionName, Issue issue) { SortedSet<Issue> set = issues.get(extensionName); if (set == null) { set = new TreeSet<Issue>(); } set.add(issue); issues.put(extensionName, set); } /** * Commits the forms of all enabled panels, so that fresh input from the Vaadin * fields is copied to the modified project. Note, this method does not persist * the modified project! */ private void commitForms() { for (ProjectEditPanelEntry entry : sortedPanels) { if (entry.isEnabled()) { try { entry.commit(); } catch (InvalidValueException e) { // we do not support Vaadin validation (see DefaultProjectFieldFactory), // but if something bad happens, we at least should log it LOG.error(e.getMessage(), e); } } } } /** * Persists the current modified project and removes persisted issues. * * Sets the current system time as {@link Project#setRegistered(long) registration time}, * if a new project is saved for the first time. * * @throws ValidationException if there are {@link org.eclipse.skalli.model.ext.Severity#FATAL fatal} * validation issues. */ private void commit() throws ValidationException { if (ProjectEditMode.NEW_PROJECT.equals(mode)) { modifiedProject.setRegistered(System.currentTimeMillis()); } commitModifiedProject(); clearPersistedIssues(); } private void commitModifiedProject() throws ValidationException { ProjectService projectService = ((ProjectService)EntityServices.getByEntityClass(Project.class)); projectService.persist(modifiedProject, application.getLoggedInUser()); } /** * Removes persisted issues for this project, but marks the corresponding * {@link Issues} entry as stale. */ private void clearPersistedIssues() throws ValidationException { IssuesService issuesService = Services.getService(IssuesService.class); if (issuesService != null) { Issues emptyIssues = new Issues(modifiedProject.getUuid()); emptyIssues.setStale(true); issuesService.persist(emptyIssues, application.getLoggedInUser()); } } private class OKButtonListener implements Button.ClickListener { private static final long serialVersionUID = 6531396291087032954L; @Override public void buttonClick(ClickEvent event) { try { commitForms(); List<String> dataLossWarnings = getDataLossWarnings(); List<String> confirmationWarnings = getConfirmationWarnings(); if (dataLossWarnings.isEmpty() && confirmationWarnings.isEmpty()) { doCommit(); } else { ConfirmPopup popup = new ConfirmPopup(dataLossWarnings, confirmationWarnings, new ConfirmPopup.OnConfirmation() { @Override public void onConfirmation(boolean confirmed) { if (confirmed) { doCommit(); } } }); getWindow().addWindow(popup); } } catch (RuntimeException e) { // If something bad happens in a validator, in the form commit or // while persisting the project, we log the incident and render the exception renderException(e); LOG.error(e.getMessage(), e); } } private void renderException(Throwable t) { StringBuilder sb = new StringBuilder(); sb.append("<strong class=\"").append(STYLE_EDIT_PROJECT_ERROR).append("\">"); //$NON-NLS-1$ //$NON-NLS-2$ sb.append("Failed to commit changes. An internal error occured. See log for details."); sb.append("</strong>"); //$NON-NLS-1$ setMessage(sb.toString()); } private void doCommit() { try { commit(); application.refresh(modifiedProject); application.refresh(project.getParentProject()); application.refresh(modifiedProject.getParentProject()); if (!modifiedProject.isDeleted()) { refreshSubProjects(modifiedProject); navigator.navigateProjectView(modifiedProject); } else { navigator.navigateWelcomeView(); } } catch (ValidationException e) { renderIssues(e); LOG.debug(e.getMessage(), e); } } private void refreshSubProjects(EntityBase parent) { EntityBase next = parent.getFirstChild(); while (next != null) { application.refresh((Project)next); refreshSubProjects(next); next = next.getNextSibling(); } } private List<String> getDataLossWarnings() { List<String> warnings = new ArrayList<String>(); for (ExtensionEntityBase extension : project.getAllExtensions()) { Class<? extends ExtensionEntityBase> extensionClass = extension.getClass(); if (modifiedProject.isInherited(extensionClass) && !project.isInherited(extensionClass)) { warnings.add(asWarning(WARN_EXTENSION_INHERITED, extension)); } else if (modifiedProject.getExtension(extensionClass) == null) { warnings.add(asWarning(WARN_EXTENSION_DISABLED, extension)); } } return warnings; } private List<String> getConfirmationWarnings() { User modifier = UserServices.getUser(application.getLoggedInUser()); List<String> warnings = new ArrayList<String>(); for (ExtensionEntityBase extension : project.getAllExtensions()) { Class<? extends ExtensionEntityBase> extensionClass = extension.getClass(); ExtensionService<?> extensionService = ExtensionServices.getByExtensionClass(extensionClass); if (extensionService != null) { warnings.addAll(extensionService.getConfirmationWarnings(project, modifiedProject, modifier)); } } return warnings; } private String asWarning(String pattern, ExtensionEntityBase extension) { String displayName = displayNames.get(extension.getClass().getName()); if (pattern == WARN_EXTENSION_DISABLED) { return MessageFormat.format(pattern, displayName); } return MessageFormat.format(pattern, displayName, ((Project) extension.getExtensibleEntity()).getName()); } } private static class ConfirmPopup extends Window implements Button.ClickListener { private static final long serialVersionUID = 6695547216798144892L; public interface OnConfirmation { public void onConfirmation(boolean confirmed); } private OnConfirmation callback; private Button yes = new Button("Yes", this); private Button no = new Button("No", this); public ConfirmPopup(List<String> dataLossWarnings, List<String> confirmationWarnings, OnConfirmation callback) { super("Confirmation of Changes"); setModal(true); setWidth(CONFIRM_POPUP_WIDTH); this.callback = callback; StringBuilder sb = new StringBuilder(); append(sb, "The following changes will <strong>remove data permanently</strong> from the project:", dataLossWarnings); append(sb, "The following changes might not be your intention:", confirmationWarnings); sb.append("<p>Continue anyway?</p>"); Label content = new Label(sb.toString(), Label.CONTENT_XHTML); addComponent(content); HorizontalLayout hl = new HorizontalLayout(); hl.setSpacing(true); hl.addComponent(yes); hl.addComponent(no); addComponent(hl); } @SuppressWarnings("nls") private void append(StringBuilder sb, String title, List<String> messages) { if (messages.size() > 0) { sb.append("<p>").append(title).append("</p>"); sb.append("<p><ul>"); for (String message : messages) { sb.append("<li>").append(message).append("</li>"); } sb.append("</ul></p>"); } } @Override public void buttonClick(ClickEvent event) { if (getParent() != null) { ((Window) getParent()).removeWindow(this); } callback.onConfirmation(event.getSource() == yes); } } private class CancelButtonListener implements Button.ClickListener { private static final long serialVersionUID = 4567366927195161150L; @Override public void buttonClick(ClickEvent event) { if (mode.equals(ProjectEditMode.EDIT_PROJECT)) { navigator.navigateProjectView(project); } else if (mode.equals(ProjectEditMode.NEW_PROJECT)) { navigator.navigateWelcomeView(); } } } private class Context implements ProjectEditContext { private final ProjectApplication application; private final ProjectTemplate projectTemplate; private final ProjectEditMode mode; public Context(ProjectApplication application, ProjectTemplate projectTemplate, ProjectEditMode mode) { this.projectTemplate = projectTemplate; this.application = application; this.mode = mode; } @Override public ProjectTemplate getProjectTemplate() { return projectTemplate; } @Override public boolean isAdministrator() { return GroupUtils.isAdministrator(application.getLoggedInUser()); } @Override public ProjectEditMode getProjectEditMode() { return mode; } @Override public void onPropertyChanged(String extensionClassName, String propertyName, Object propertyValue) { for (ProjectEditPanelEntry entry : sortedPanels) { entry.onPropertyChanged(extensionClassName, propertyName, propertyValue); } } @Override public Object getProperty(String extensionClassName, String propertyName) { ProjectEditPanelEntry entry = panels.get(extensionClassName); return entry != null? entry.getProperty(propertyName) : null; } @Override public void setProperty(String extensionClassName, String propertyName, Object propertyValue) { ProjectEditPanelEntry entry = panels.get(extensionClassName); if (entry != null) { entry.setProperty(propertyName, propertyValue); } } @Override public boolean hasPanel(String extensionClassName) { return panels.get(extensionClassName) != null; } @Override public boolean isEditable(String extensionClassName) { ProjectEditPanelEntry entry = panels.get(extensionClassName); if (entry == null) { throw new IllegalArgumentException(MessageFormat.format("No panel for extension {0}", extensionClassName)); } return entry.isEditable(); } @Override public boolean isInherited(String extensionClassName) { ProjectEditPanelEntry entry = panels.get(extensionClassName); if (entry == null) { throw new IllegalArgumentException(MessageFormat.format("No panel for extension {0}", extensionClassName)); } return entry.isInherited(); } @Override public boolean isDisabled(String extensionClassName) { ProjectEditPanelEntry entry = panels.get(extensionClassName); if (entry == null) { throw new IllegalArgumentException(MessageFormat.format("No panel for extension {0}", extensionClassName)); } return entry.isDisabled(); } @Override public boolean isExpanded(String extensionClassName) { ProjectEditPanelEntry entry = panels.get(extensionClassName); if (entry == null) { throw new IllegalArgumentException(MessageFormat.format("No panel for extension {0}", extensionClassName)); } return entry.isExpanded(); } } private static class ProjectEditPanelComparator implements Comparator<ProjectEditPanelEntry> { private ProjectTemplate projectTemplate; public ProjectEditPanelComparator(ProjectTemplate projectTemplate) { this.projectTemplate = projectTemplate; } @Override public int compare(ProjectEditPanelEntry o1, ProjectEditPanelEntry o2) { int result = Float.compare(getRank(o1), getRank(o2)); if (result == 0) { // same rank: compare states (required < visible < enabled result = compareStates(o1, o2); if (result == 0) { // same rank and state: order captions alphabetically result = o1.getDisplayName().compareTo(o2.getDisplayName()); } } return result; } private float getRank(ProjectEditPanelEntry o) { float rank = projectTemplate.getRank(o.getExtensionClassName()); if (rank < 0) { rank = o.getRank(); if (rank < 0) { rank = Float.MAX_VALUE; } } return rank; } private int compareStates(ProjectEditPanelEntry o1, ProjectEditPanelEntry o2) { String ext1 = o1.getExtensionClassName(); String ext2 = o2.getExtensionClassName(); int result = compareStates(projectTemplate.isVisible(ext1), projectTemplate.isVisible(ext2)); if (result == 0) { result = compareStates(projectTemplate.isEnabled(ext1), projectTemplate.isEnabled(ext2)); } return result; } private int compareStates(boolean state1, boolean state2) { if (state1) { return state2 ? 0 : -1; } else { return state2 ? 1 : 0; } } } }