/* * Copyright 2017 Red Hat, Inc. and/or its affiliates. * * Licensed 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.kie.workbench.common.stunner.forms.client.widgets; import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.PostConstruct; import javax.enterprise.context.Dependent; import javax.enterprise.event.Event; import javax.enterprise.event.Observes; import javax.inject.Inject; import com.google.gwt.logging.client.LogConfiguration; import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.Widget; import org.jboss.errai.databinding.client.BindableProxy; import org.jboss.errai.databinding.client.BindableProxyFactory; import org.jboss.errai.databinding.client.HasProperties; import org.jboss.errai.databinding.client.api.DataBinder; import org.kie.workbench.common.forms.dynamic.client.DynamicFormRenderer; import org.kie.workbench.common.forms.dynamic.service.shared.FormRenderingContext; import org.kie.workbench.common.forms.dynamic.service.shared.RenderMode; import org.kie.workbench.common.forms.dynamic.service.shared.adf.DynamicFormModelGenerator; import org.kie.workbench.common.forms.dynamic.service.shared.impl.StaticModelFormRenderingContext; import org.kie.workbench.common.stunner.core.client.canvas.AbstractCanvasHandler; import org.kie.workbench.common.stunner.core.client.canvas.controls.select.SelectionControl; import org.kie.workbench.common.stunner.core.client.canvas.event.selection.CanvasClearSelectionEvent; import org.kie.workbench.common.stunner.core.client.canvas.event.selection.CanvasElementSelectedEvent; import org.kie.workbench.common.stunner.core.client.command.CanvasCommandFactory; import org.kie.workbench.common.stunner.core.client.command.CanvasCommandManager; import org.kie.workbench.common.stunner.core.client.session.ClientFullSession; import org.kie.workbench.common.stunner.core.client.session.ClientReadOnlySession; import org.kie.workbench.common.stunner.core.client.session.ClientSession; import org.kie.workbench.common.stunner.core.client.session.event.SessionDestroyedEvent; import org.kie.workbench.common.stunner.core.client.session.event.SessionOpenedEvent; import org.kie.workbench.common.stunner.core.diagram.Diagram; import org.kie.workbench.common.stunner.core.graph.Element; import org.kie.workbench.common.stunner.core.graph.content.definition.Definition; import org.kie.workbench.common.stunner.core.util.DefinitionUtils; import org.kie.workbench.common.stunner.forms.client.event.FormPropertiesOpened; import org.kie.workbench.common.stunner.forms.context.PathAwareFormContext; import org.uberfire.backend.vfs.Path; import org.uberfire.mvp.Command; import static org.uberfire.commons.validation.PortablePreconditions.checkNotNull; @Dependent public class FormPropertiesWidget implements IsWidget { private static Logger LOGGER = Logger.getLogger(FormPropertiesWidget.class.getName()); private final DefinitionUtils definitionUtils; private final CanvasCommandFactory<AbstractCanvasHandler> commandFactory; private final DynamicFormRenderer formRenderer; private final Event<FormPropertiesOpened> propertiesOpenedEvent; private ClientSession session; private FormFeaturesSessionProvider featuresSessionProvider; private final DynamicFormModelGenerator modelGenerator; protected FormPropertiesWidget() { this(null, null, null, null, null); } @Inject public FormPropertiesWidget(final DefinitionUtils definitionUtils, final CanvasCommandFactory<AbstractCanvasHandler> commandFactory, final DynamicFormRenderer formRenderer, final DynamicFormModelGenerator modelGenerator, final Event<FormPropertiesOpened> propertiesOpenedEvent) { this.definitionUtils = definitionUtils; this.commandFactory = commandFactory; this.formRenderer = formRenderer; this.modelGenerator = modelGenerator; this.propertiesOpenedEvent = propertiesOpenedEvent; } @PostConstruct public void init() { log(Level.INFO, "FormPropertiesWidget instance build."); } /** * Binds a session. */ public FormPropertiesWidget bind(final ClientSession session) { this.session = session; featuresSessionProvider = getFeaturesSessionProvider(session); if (null == featuresSessionProvider) { throw new UnsupportedOperationException("No client session type supported."); } return this; } /** * Unbinds a session. */ public FormPropertiesWidget unbind() { this.session = null; doClear(); return this; } /** * Shows properties of elements in current session as: * 1.- If any element selected on session control, show properties for it. * 2.- If no element selected on session control: * 2.1- If no canvas root fot the diagram, show the diagram's graph properties. * 2.2- If diagram has a canvas root, show the properties for that element. */ public void show() { this.show(null); } @SuppressWarnings("unchecked") public void show(final Command callback) { boolean done = false; if (null != session) { // Obtain first element selected on session, if any. String selectedItemUUID = null; final SelectionControl selectionControl = featuresSessionProvider.getSelectionControl(session); if (null != selectionControl) { final Collection<String> selectedItems = selectionControl.getSelectedItems(); if (null != selectedItems && !selectedItems.isEmpty()) { selectedItemUUID = selectedItems.iterator().next(); } } else { LOGGER.log(Level.WARNING, "Cannot show properties as session type does not provides " + "selection control's support."); } if (null == selectedItemUUID) { final Diagram<?, ?> diagram = getDiagram(); if (null != diagram) { final String cRoot = diagram.getMetadata().getCanvasRootUUID(); // Check if there exist any canvas root element. if (!isEmpty(cRoot)) { selectedItemUUID = cRoot; } } } if (null != selectedItemUUID) { showByUUID(selectedItemUUID, getSessionRenderMode(), callback); done = true; } } if (!done) { doClear(); } } /** * Show properties for the element with the given identifier. */ public void showByUUID(final String uuid, final RenderMode renderMode) { this.showByUUID(uuid, renderMode, null); } @SuppressWarnings("unchecked") public void showByUUID(final String uuid, final RenderMode renderMode, final Command callback) { final Element<? extends Definition<?>> element = (null != uuid && null != getCanvasHandler()) ? getCanvasHandler().getGraphIndex().get(uuid) : null; if (null != element) { final Object definition = element.getContent().getDefinition(); final BindableProxy<?> proxy = (BindableProxy<?>) BindableProxyFactory.getBindableProxy(definition); final Path diagramPath = session.getCanvasHandler().getDiagram().getMetadata().getPath(); final StaticModelFormRenderingContext generatedCtx = modelGenerator.getContextForModel(proxy.deepUnwrap()); final FormRenderingContext<?> pathAwareCtx = new PathAwareFormContext<>(generatedCtx, diagramPath); formRenderer.render(pathAwareCtx); formRenderer.addFieldChangeHandler((fieldName, newValue) -> { try { final HasProperties hasProperties = (HasProperties) DataBinder.forModel(definition).getModel(); final String pId = getModifiedPropertyId(hasProperties, fieldName); FormPropertiesWidget.this.executeUpdateProperty(element, pId, newValue); } catch (final Exception ex) { log(Level.SEVERE, "Something wrong happened refreshing the canvas for field '" + fieldName + "': " + ex.getCause()); } finally { if (null != callback) { callback.execute(); } } }); final String name = definitionUtils.getName(definition); propertiesOpenedEvent.fire(new FormPropertiesOpened(session, uuid, name)); } else { doClear(); if (null != callback) { callback.execute(); } } } @Override public Widget asWidget() { return formRenderer.asWidget(); } private AbstractCanvasHandler getCanvasHandler() { return session != null ? (AbstractCanvasHandler) session.getCanvasHandler() : null; } private Diagram<?, ?> getDiagram() { return null != getCanvasHandler() ? getCanvasHandler().getDiagram() : null; } private RenderMode getSessionRenderMode() { return getRenderMode(session); } private RenderMode getRenderMode(final ClientSession session) { return session instanceof ClientFullSession ? RenderMode.EDIT_MODE : RenderMode.PRETTY_MODE; } @SuppressWarnings("unchecked") void onCanvasElementSelectedEvent(@Observes CanvasElementSelectedEvent event) { checkNotNull("event", event); if (null != getCanvasHandler()) { final String uuid = event.getElementUUID(); showByUUID(uuid, getSessionRenderMode()); } } void CanvasClearSelectionEvent(@Observes CanvasClearSelectionEvent clearSelectionEvent) { checkNotNull("clearSelectionEvent", clearSelectionEvent); doClear(); } void onCanvasSessionOpened(@Observes SessionOpenedEvent sessionOpenedEvent) { checkNotNull("sessionOpenedEvent", sessionOpenedEvent); doOpenSession(sessionOpenedEvent.getSession()); } void onCanvasSessionDestroyed(@Observes SessionDestroyedEvent sessionDestroyedEvent) { checkNotNull("sessionDestroyedEvent", sessionDestroyedEvent); unbind(); } private void doOpenSession(final ClientSession session) { try { bind(session).show(); } catch (ClassCastException e) { // No writteable session. Do not show properties until read mode available. log(Level.INFO, "Session discarded for opening as not instance of full session."); } } private void doClear() { formRenderer.unBind(); } @SuppressWarnings("unchecked") private void executeUpdateProperty(final Element<? extends Definition<?>> element, final String propertyId, final Object value) { final CanvasCommandManager<AbstractCanvasHandler> commandManager = featuresSessionProvider.getCommandManager(session); if (null != commandManager) { commandManager.execute(getCanvasHandler(), commandFactory.updatePropertyValue(element, propertyId, value)); } else { LOGGER.log(Level.WARNING, "Cannot update property [" + propertyId + "] as session type is not supported."); } } private String getModifiedPropertyId(HasProperties model, String fieldName) { int separatorIndex = fieldName.indexOf("."); // Check if it is a nested property, if it is we must obtain the nested property instead of the root one. if (separatorIndex != -1) { String rootProperty = fieldName.substring(0, separatorIndex); fieldName = fieldName.substring(separatorIndex + 1); Object property = model.get(rootProperty); model = (HasProperties) DataBinder.forModel(property).getModel(); return getModifiedPropertyId(model, fieldName); } Object property = model.get(fieldName); return definitionUtils .getDefinitionManager() .adapters() .forProperty() .getId(property); } private FormFeaturesSessionProvider getFeaturesSessionProvider(final ClientSession session) { for (final FormFeaturesSessionProvider featureSessionProvider : FEATURE_SESSION_PROVIDERS) { if (featureSessionProvider.supports(session)) { return featureSessionProvider; } } return null; } /** * An ORDERED array of feature providers supported. */ private static final FormFeaturesSessionProvider[] FEATURE_SESSION_PROVIDERS = new FormFeaturesSessionProvider[]{ new FormFeaturesFullSessionProvider(), new FormFeaturesReadOnlySessionProvider()}; /** * This type provides required features that are specific for concrete client session types. */ private interface FormFeaturesSessionProvider<S extends ClientSession> { /** * Returns <code>true</code> is the session type is supported. */ boolean supports(ClientSession type); /** * Returns the session's selection control instance, if not available, it returns <code>null</code>. */ SelectionControl getSelectionControl(S session); /** * Returns the session's command manager instance, if not available, it returns <code>null</code>. */ CanvasCommandManager<AbstractCanvasHandler> getCommandManager(S session); } private static class FormFeaturesReadOnlySessionProvider implements FormFeaturesSessionProvider<ClientReadOnlySession> { @Override public boolean supports(final ClientSession type) { return type instanceof ClientReadOnlySession; } @Override public SelectionControl getSelectionControl(final ClientReadOnlySession session) { return cast(session).getSelectionControl(); } @Override public CanvasCommandManager<AbstractCanvasHandler> getCommandManager(final ClientReadOnlySession session) { return null; } private ClientReadOnlySession cast(final ClientSession session) { return (ClientReadOnlySession) session; } } private static class FormFeaturesFullSessionProvider implements FormFeaturesSessionProvider<ClientFullSession> { @Override public boolean supports(final ClientSession type) { return type instanceof ClientFullSession; } @Override public SelectionControl getSelectionControl(final ClientFullSession session) { return cast(session).getSelectionControl(); } @Override public CanvasCommandManager<AbstractCanvasHandler> getCommandManager(final ClientFullSession session) { return cast(session).getCommandManager(); } private ClientFullSession cast(final ClientSession session) { return (ClientFullSession) session; } } private boolean isEmpty(final String s) { return s == null || s.trim().length() == 0; } private void log(final Level level, final String message) { if (LogConfiguration.loggingIsEnabled()) { LOGGER.log(level, message); } } }