/* 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 org.apache.isis.viewer.wicket.ui.components.actionmenu.serviceactions; import java.io.Serializable; import java.util.Collections; import java.util.List; import com.google.common.base.Strings; import com.google.common.collect.Lists; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.Page; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.SubmitLink; import org.apache.wicket.markup.html.link.AbstractLink; import org.apache.wicket.model.Model; import org.apache.isis.core.commons.authentication.AuthenticationSession; import org.apache.isis.core.metamodel.adapter.ObjectAdapter; import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager; import org.apache.isis.core.metamodel.consent.Consent; import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy; import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet; import org.apache.isis.core.metamodel.facets.members.cssclassfa.CssClassFaPosition; import org.apache.isis.core.metamodel.spec.ObjectSpecification; import org.apache.isis.core.metamodel.spec.feature.ObjectAction; import org.apache.isis.core.metamodel.specloader.SpecificationLoader; import org.apache.isis.core.runtime.system.context.IsisContext; import org.apache.isis.core.runtime.system.persistence.PersistenceSession; import org.apache.isis.core.runtime.system.session.IsisSessionFactory; import org.apache.isis.viewer.wicket.model.links.LinkAndLabel; import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento; import org.apache.isis.viewer.wicket.model.models.ActionModel; import org.apache.isis.viewer.wicket.model.models.EntityModel; import org.apache.isis.viewer.wicket.ui.components.actionmenu.CssClassFaBehavior; import org.apache.isis.viewer.wicket.ui.pages.PageAbstract; import org.apache.isis.viewer.wicket.ui.util.Components; import org.apache.isis.viewer.wicket.ui.util.CssClassAppender; class CssMenuItem implements Serializable { private static final long serialVersionUID = 1L; public static final String ID_MENU_LINK = "menuLink"; public static class Builder { private final CssMenuItem cssMenuItem; private Builder(final String name) { cssMenuItem = new CssMenuItem(name); } public Builder parent(final CssMenuItem parent) { cssMenuItem.setParent(parent); return this; } public <T extends Page> Builder link() { final AbstractLink link = new SubmitLink(ID_MENU_LINK); return link(link); } public <T extends Page> Builder link(final AbstractLink link) { assert link.getId().equals(ID_MENU_LINK); cssMenuItem.setLink(link); return this; } public <T extends Page> Builder enabled(final String disabledReasonIfAny) { cssMenuItem.setEnabled(disabledReasonIfAny == null); cssMenuItem.setDisabledReason(disabledReasonIfAny); return this; } public Builder describedAs(String descriptionIfAny) { cssMenuItem.setDescription(descriptionIfAny); return this; } public Builder returnsBlobOrClob(boolean blobOrClob) { cssMenuItem.setReturnsBlobOrClob(blobOrClob); return this; } public Builder prototyping(boolean prototype) { cssMenuItem.setPrototyping(prototype); return this; } public Builder requiresSeparator(boolean separator) { cssMenuItem.setRequiresSeparator(separator); return this; } public Builder withActionIdentifier(String actionIdentifier) { cssMenuItem.setActionIdentifier(actionIdentifier); return this; } public Builder withCssClass(String cssClass) { cssMenuItem.setCssClass(cssClass); return this; } public Builder withCssClassFa(String cssClassFa) { cssMenuItem.setCssClassFa(cssClassFa); return this; } public Builder withCssClassFaPosition(final CssClassFaPosition position) { cssMenuItem.setCssClassFaPosition(position); return this; } /** * Returns the built {@link CssMenuItem}, associating with {@link #parent(CssMenuItem) parent} (if specified). */ public CssMenuItem build() { if (cssMenuItem.parent != null) { cssMenuItem.parent.subMenuItems.add(cssMenuItem); } return cssMenuItem; } } private final String name; private final List<CssMenuItem> subMenuItems = Lists.newArrayList(); private CssMenuItem parent; private AbstractLink link; private boolean enabled = true; // unless disabled private String disabledReason; private boolean blobOrClob = false; // unless set otherwise private boolean prototype = false; // unless set otherwise private boolean requiresSeparator = false; // unless set otherwise static final String ID_MENU_LABEL = "menuLabel"; static final String ID_SUB_MENU_ITEMS = "subMenuItems"; private String actionIdentifier; private String cssClass; private String cssClassFa; private CssClassFaPosition cssClassFaPosition; private String description; /** * Factory method returning {@link Builder builder}. */ public static Builder newMenuItem(final String name) { return new Builder(name); } public void setActionIdentifier(String actionIdentifier) { this.actionIdentifier = actionIdentifier; } public void setPrototyping(boolean prototype) { this.prototype = prototype; } public boolean isPrototyping() { return prototype; } private boolean separator; public boolean isSeparator() { return separator; } public void setSeparator(final boolean separator) { this.separator = separator; } public void setRequiresSeparator(boolean requiresSeparator) { this.requiresSeparator = requiresSeparator; } /** * Requires a separator before it */ public boolean requiresSeparator() { return requiresSeparator; } private CssMenuItem(final String name) { this.name = name; } public String getName() { return name; } public boolean hasParent() { return parent != null; } private void setParent(final CssMenuItem parent) { this.parent = parent; } public Builder newSubMenuItem(final String name) { return CssMenuItem.newMenuItem(name).parent(this); } public List<CssMenuItem> getSubMenuItems() { return Collections.unmodifiableList(subMenuItems); } public void replaceSubMenuItems(List<CssMenuItem> menuItems) { subMenuItems.clear(); subMenuItems.addAll(menuItems); } public boolean hasSubMenuItems() { return subMenuItems.size() > 0; } public AbstractLink getLink() { return link; } private void setLink(final AbstractLink link) { this.link = link; } public boolean isEnabled() { return enabled; } private void setEnabled(final boolean enabled) { this.enabled = enabled; } public void setReturnsBlobOrClob(boolean blobOrClob) { this.blobOrClob = blobOrClob; } public void setCssClass(String cssClass) { this.cssClass = cssClass; } public String getCssClass() { return cssClass; } public void setCssClassFa(String cssClassFa) { this.cssClassFa = cssClassFa; } public String getCssClassFa() { return cssClassFa; } public void setCssClassFaPosition(final CssClassFaPosition position) { this.cssClassFaPosition = position; } public CssClassFaPosition getCssClassFaPosition() { return cssClassFaPosition; } /** * Only populated if not {@link #isEnabled() enabled}. */ public String getDisabledReason() { return disabledReason; } public void setDisabledReason(final String disabledReason) { this.disabledReason = disabledReason; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } // ////////////////////////////////////////////////////////////// // To add submenu items // ////////////////////////////////////////////////////////////// /** * Creates a {@link Builder} for a submenu item invoking an action on the provided {@link ObjectAdapterMemento * target adapter}. */ Builder newSubMenuItem(ServiceAndAction serviceAndAction) { final EntityModel targetEntityModel = serviceAndAction.serviceEntityModel; final ObjectAction objectAction = serviceAndAction.objectAction; final boolean separator = serviceAndAction.separator; final ServiceActionLinkFactory actionLinkFactory = serviceAndAction.linkAndLabelFactory; final ObjectAdapter serviceAdapter = targetEntityModel.load(AdapterManager.ConcurrencyChecking.NO_CHECK); final ObjectSpecification serviceSpec = serviceAdapter.getSpecification(); if (serviceSpec.isHidden()) { return null; } // check visibility final Consent visibility = objectAction.isVisible( serviceAdapter, InteractionInitiatedBy.USER, ActionModel.WHERE_FOR_ACTION_INVOCATION); if (visibility.isVetoed()) { return null; } // check usability final Consent usability = objectAction.isUsable( serviceAdapter, InteractionInitiatedBy.USER, ActionModel.WHERE_FOR_ACTION_INVOCATION ); final String reasonDisabledIfAny = usability.getReason(); final DescribedAsFacet describedAsFacet = objectAction.getFacet(DescribedAsFacet.class); final String descriptionIfAny = describedAsFacet != null ? describedAsFacet.value() : null; // build the link final LinkAndLabel linkAndLabel = actionLinkFactory.newLink(objectAction, PageAbstract.ID_MENU_LINK); if (linkAndLabel == null) { // can only get a null if invisible, so this should not happen given the visibility guard above return null; } final AbstractLink link = linkAndLabel.getLink(); final String actionLabel = linkAndLabel.getLabel(); Builder builder = newSubMenuItem(actionLabel) .link(link) .describedAs(descriptionIfAny) .enabled(reasonDisabledIfAny) .returnsBlobOrClob(ObjectAction.Util.returnsBlobOrClob(objectAction)) .prototyping(objectAction.isPrototype()) .requiresSeparator(separator) .withActionIdentifier(ObjectAction.Util.actionIdentifierFor(objectAction)) .withCssClass(ObjectAction.Util.cssClassFor(objectAction, serviceAdapter)) .withCssClassFa(ObjectAction.Util.cssClassFaFor(objectAction)) .withCssClassFaPosition(ObjectAction.Util.cssClassFaPositionFor(objectAction)); return builder; } // ////////////////////////////////////////////////////////////// // Build wicket components from the menu item. // ////////////////////////////////////////////////////////////// void addTo(final MarkupContainer markupContainer) { final Component menuItemComponent = addMenuItemComponentTo(markupContainer); addSubMenuItemComponentsIfAnyTo(markupContainer); addCssClassAttributesIfRequired(menuItemComponent); } private Component addMenuItemComponentTo(final MarkupContainer markupContainer) { final AbstractLink link = getLink(); final Label label = new Label(CssMenuItem.ID_MENU_LABEL, Model.of(this.getName())); if (link != null) { // show link... markupContainer.add(link); link.add(label); if (this.description != null) { label.add(new AttributeModifier("title", Model.of(description))); } if (this.blobOrClob) { link.add(new CssClassAppender("noVeil")); } if (this.prototype) { link.add(new CssClassAppender("prototype")); } if (this.cssClass != null) { link.add(new CssClassAppender(this.cssClass)); } link.add(new CssClassAppender(this.actionIdentifier)); String cssClassFa = getCssClassFa(); if (!Strings.isNullOrEmpty(cssClassFa)) { label.add(new CssClassFaBehavior(cssClassFa, getCssClassFaPosition())); } if (!this.isEnabled()) { link.add(new AttributeModifier("title", Model.of(this.getDisabledReason()))); link.add(new CssClassAppender("disabled")); link.setEnabled(false); } // .. and hide label Components.permanentlyHide(markupContainer, CssMenuItem.ID_MENU_LABEL); return link; } else { // hide link... Components.permanentlyHide(markupContainer, ID_MENU_LINK); // ... and show label, along with disabled reason label.add(new AttributeModifier("title", Model.of(this.getDisabledReason()))); label.add(new AttributeModifier("class", Model.of("disabled"))); markupContainer.add(label); return label; } } private void addSubMenuItemComponentsIfAnyTo(final MarkupContainer menuItemMarkup) { final List<CssMenuItem> subMenuItems = getSubMenuItems(); if (subMenuItems.isEmpty()) { Components.permanentlyHide(menuItemMarkup, CssMenuItem.ID_SUB_MENU_ITEMS); } else { menuItemMarkup.add(new CssSubMenuItemsPanel(CssMenuItem.ID_SUB_MENU_ITEMS, subMenuItems)); } } private void addCssClassAttributesIfRequired(final Component linkComponent) { if (!hasSubMenuItems()) { return; } if (this.hasParent()) { linkComponent.add(new CssClassAppender("parent")); } else { linkComponent.add(new CssClassAppender("top-parent")); } } // ////////////////////////////////////////////////////////////// // dependencies // ////////////////////////////////////////////////////////////// AuthenticationSession getAuthenticationSession() { return getIsisSessionFactory().getCurrentSession().getAuthenticationSession(); } SpecificationLoader getSpecificationLoader() { return getIsisSessionFactory().getSpecificationLoader(); } PersistenceSession getPersistenceSession() { return getIsisSessionFactory().getCurrentSession().getPersistenceSession(); } IsisSessionFactory getIsisSessionFactory() { return IsisContext.getSessionFactory(); } }