/*
* Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
*
* This file is part of the Jspresso framework.
*
* Jspresso is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jspresso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Jspresso. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jspresso.framework.application.action;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.jspresso.framework.action.ActionContextConstants;
import org.jspresso.framework.application.backend.IBackendController;
import org.jspresso.framework.application.frontend.IFrontendController;
import org.jspresso.framework.application.model.Module;
import org.jspresso.framework.binding.ConnectorHelper;
import org.jspresso.framework.binding.ICollectionConnector;
import org.jspresso.framework.binding.ICompositeValueConnector;
import org.jspresso.framework.binding.IValueConnector;
import org.jspresso.framework.model.descriptor.IModelDescriptor;
import org.jspresso.framework.util.event.IItemSelectable;
import org.jspresso.framework.util.event.ISelectable;
import org.jspresso.framework.util.i18n.ITranslationProvider;
import org.jspresso.framework.view.ICompositeView;
import org.jspresso.framework.view.IView;
import org.jspresso.framework.view.descriptor.IPropertyViewDescriptor;
/**
* Abstract class for all objects that need to manipulate an action context. It
* contains helper methods that takes the developer away from the standard
* context internal knowledge. Action developers can (should) use these helper
* methods (for reference manual readers, give an eye to the linked javadoc).
*
* @author Vincent Vandenschrick
*/
public abstract class AbstractActionContextAware {
/**
* Gets the (string) action command out of the context. The action command is
* an arbitrary character string that can be set by the UI component
* triggering the action. It is stored using the
* {@code ActionContextConstants.ACTION_COMMAND} standard key.
*
* @param context
* the action context.
* @return the (string) action command if it exists in the action context or
* null.
*/
protected String getActionCommand(Map<String, Object> context) {
return (String) context.get(ActionContextConstants.ACTION_COMMAND);
}
/**
* Gets the action parameter out of the context. The action parameter is a
* general purpose context entry that can be used to pass an arbitrary
* parameter along the action chain. It is stored using the
* {@code ActionContextConstants.ACTION_PARAM} standard key.
*
* @param <T>
* type inference return.
* @param context
* the action context.
* @return the action parameter if it exists in the action context or null.
*/
@SuppressWarnings("unchecked")
protected <T> T getActionParameter(Map<String, Object> context) {
return (T) context.get(ActionContextConstants.ACTION_PARAM);
}
/**
* Gets the backend controller out of the action context using either :
* <ul>
* <li>the context frontend controller if it exists</li>
* <li>the {@code ActionContextConstants.BACK_CONTROLLER} standard key if
* the action was triggered without going through a frontend controller (a
* batch action for instance).</li>
* </ul>
*
* @param context
* the action context.
* @return the backend controller.
*/
protected IBackendController getBackendController(Map<String, Object> context) {
IFrontendController<?, ?, ?> frontController = getFrontendController(context);
if (frontController != null) {
return frontController.getBackendController();
}
return (IBackendController) context
.get(ActionContextConstants.BACK_CONTROLLER);
}
/**
* Gets the frontend controller out of the action context using the
* {@code ActionContextConstants.FRONT_CONTROLLER} standard key. If the
* action was triggered without going through a frontend controller (a batch
* action for instance), this method might return null.
*
* @param <E>
* the actual gui component type used.
* @param <F>
* the actual icon type used.
* @param <G>
* the actual action type used.
* @param context
* the action context.
* @return the frontend controller.
*/
@SuppressWarnings("unchecked")
protected <E, F, G> IFrontendController<E, F, G> getFrontendController(
Map<String, Object> context) {
return (IFrontendController<E, F, G>) context
.get(ActionContextConstants.FRONT_CONTROLLER);
}
/**
* Retrieves the locale the action has to use to execute. This method
* delegates to the context backend controller that in turn will delegate to
* the session.
*
* @param context
* the action context.
* @return the locale the action executes in.
*/
protected Locale getLocale(Map<String, Object> context) {
IFrontendController<?, ?, ?> frontController = getFrontendController(context);
if (frontController != null) {
return frontController.getLocale();
}
return getBackendController(context).getLocale();
}
/**
* Gets the model this action was triggered on.
*
* @param <T>
* type inference return.
* @param context
* the action context.
* @return the model.
*/
protected <T> T getModel(Map<String, Object> context) {
return getModel(null, context);
}
/**
* Gets the model this action was triggered on.
*
* @param <T>
* type inference return.
* @param viewPath
* the view index path to follow.
* <ul>
* <li>A positive integer n means the nth child.</li>
* <li>A negative integer -n means the nth parent.</li>
* </ul>
* @param context
* the action context.
* @return the model.
*/
protected <T> T getModel(int[] viewPath, Map<String, Object> context) {
IValueConnector modelConnector = getModelConnector(viewPath, context);
if (modelConnector != null) {
if (modelConnector instanceof ICompositeValueConnector) {
return modelConnector.getConnectorValue();
} else if (modelConnector.getModelProvider() != null) {
return modelConnector.getModelProvider().getModel();
}
}
return null;
}
/**
* Gets the model connector this action was triggered on. The model connector
* is the versatile binding structure that adapts the actual model to the
* Jspresso binding architecture. The actual model is stored in the model
* connector value. Unless developing very generic actions, this method will
* rarely be used in favor of the more concrete {@code getXXXModel}
* context accessors.
*
* @param context
* the action context.
* @return the model connector this action was triggered on.
*/
protected IValueConnector getModelConnector(Map<String, Object> context) {
return getModelConnector(null, context);
}
/**
* Gets the model connector this action was triggered on. The model connector
* is the versatile binding structure that adapts the actual model to the
* Jspresso binding architecture. The actual model is stored in the model
* connector value. Unless developing very generic actions, this method will
* rarely be used in favor of the more concrete {@code getXXXModel}
* context accessors.
*
* @param viewPath
* the view index path to follow.
* <ul>
* <li>A positive integer n means the nth child.</li>
* <li>A negative integer -n means the nth parent.</li>
* </ul>
* @param context
* the action context.
* @return the model connector this action was triggered on.
*/
protected IValueConnector getModelConnector(int[] viewPath,
Map<String, Object> context) {
IValueConnector viewConnector = getViewConnector(viewPath, context);
if (viewConnector != null) {
IValueConnector modelConnector = viewConnector.getModelConnector();
if (modelConnector != null) {
// This is to deal with double binding
while (modelConnector.getModelConnector() != null) {
modelConnector = modelConnector.getModelConnector();
}
}
return modelConnector;
}
return null;
}
/**
* Retrieves the model descriptor from the context using the
* {@code ActionContextConstants.MODEL_DESCRIPTOR} standard key. The
* model descriptor is registered in the action context based on the model of
* the view to which the action is attached.
*
* @param context
* the action context.
* @return the model descriptor this model action was triggered on.
*/
protected IModelDescriptor getModelDescriptor(Map<String, Object> context) {
return (IModelDescriptor) context
.get(ActionContextConstants.MODEL_DESCRIPTOR);
}
/**
* Gets the module this action has been executed on. The value is immutable
* during the action chain and is stored using the
* {@code ActionContextConstants.MODULE} key.
*
* @param context
* the action context.
* @return the module this action executes on.
*/
protected Module getModule(Map<String, Object> context) {
return (Module) context.get(ActionContextConstants.MODULE);
}
/**
* Gets the current module from the context. The value might change during the
* action chain if one of the action navigates the workspace/module. It is
* stored using the {@code ActionContextConstants.CURRENT_MODULE} key.
*
* @param context
* the action context.
* @return the module this action executes on.
*/
protected Module getCurrentModule(Map<String, Object> context) {
return (Module) context.get(ActionContextConstants.CURRENT_MODULE);
}
/**
* Gets the parent model this action was triggered on.
*
* @param <T>
* type inference return.
* @param context
* the action context.
* @return the parent model.
*/
protected <T> T getParentModel(Map<String, Object> context) {
IValueConnector modelConnector = getModelConnector(context);
if (modelConnector != null) {
if (modelConnector.getParentConnector() != null) {
return modelConnector.getParentConnector().getConnectorValue();
}
}
return null;
}
/**
* Gets the selected indices out of the UI component if it is a collection
* component (table, list, ...). More accurately, the selected indices are
* taken from the view connector that adapts the UI component to the Jspresso
* binding architecture.
*
* @param context
* the action context.
* @return the selected indices stored in the action context.
*/
protected int[] getSelectedIndices(Map<String, Object> context) {
return getSelectedIndices(null, context);
}
/**
* Gets the selected indices out of the UI component if it is a collection
* component (table, list, ...). More accurately, the selected indices are
* taken from the view connector that adapts the UI component to the Jspresso
* binding architecture.
*
* @param viewPath
* the view index path to follow.
* <ul>
* <li>A positive integer n means the nth child.</li>
* <li>A negative integer -n means the nth parent.</li>
* </ul>
* @param context
* the action context.
* @return the selected indices stored in the action context.
*/
protected int[] getSelectedIndices(int[] viewPath, Map<String, Object> context) {
int[] selectedIndices = null;
IValueConnector selectableConnector = getViewConnector(viewPath, context);
while (selectableConnector != null && !(selectableConnector instanceof ISelectable)) {
if (selectableConnector.getParentConnector() != null) {
selectableConnector = selectableConnector.getParentConnector();
} else {
// To cope with double binding
selectableConnector = selectableConnector.getModelConnector();
}
}
if (selectableConnector != null) {
selectedIndices = ((ISelectable) selectableConnector).getSelectedIndices();
}
return selectedIndices;
}
/**
* This is a versatile helper method that retrieves the selected model either
* from the 1st selected child connector if the action was triggered on a
* collection connector or from the connector itself.
*
* @param <T>
* type inference return.
* @param context
* the action context.
* @return the selected model.
*/
protected <T> T getSelectedModel(Map<String, Object> context) {
return getSelectedModel(null, context);
}
/**
* This is a versatile helper method that retrieves the selected model either
* from the 1st selected child connector if the action was triggered on a
* collection connector or from the connector itself.
*
* @param <T>
* type inference return.
* @param viewPath
* the view index path to follow.
* <ul>
* <li>A positive integer n means the nth child.</li>
* <li>A negative integer -n means the nth parent.</li>
* </ul>
* @param context
* the action context.
* @return the selected model.
*/
protected <T> T getSelectedModel(int[] viewPath, Map<String, Object> context) {
IValueConnector viewConnector = getViewConnector(viewPath, context);
T selectedModel;
if (viewConnector instanceof IItemSelectable) {
selectedModel = ((IItemSelectable) viewConnector).getSelectedItem();
if (selectedModel instanceof IValueConnector) {
selectedModel = ((IValueConnector) selectedModel).getConnectorValue();
}
} else {
selectedModel = getModel(viewPath, context);
}
return selectedModel;
}
/**
* This is a versatile helper method that retrieves the selected models model
* either from the selected child connectors if the action was triggered on a
* collection connector or from the connector itself.
*
* @param <T>
* type inference return.
* @param context
* the action context.
* @return the list of selected models.
*/
protected <T> List<T> getSelectedModels(Map<String, Object> context) {
return getSelectedModels(null, context);
}
/**
* This is a versatile helper method that retrieves the selected models model
* either from the selected child connectors if the action was triggered on a
* collection connector or from the connector itself.
*
* @param <T>
* type inference return.
* @param viewPath
* the view index path to follow.
* <ul>
* <li>A positive integer n means the nth child.</li>
* <li>A negative integer -n means the nth parent.</li>
* </ul>
* @param context
* the action context.
* @return the list of selected models.
*/
@SuppressWarnings("unchecked")
protected <T> List<T> getSelectedModels(int[] viewPath,
Map<String, Object> context) {
IValueConnector modelConnector = getModelConnector(viewPath, context);
if (modelConnector == null) {
return null;
}
List<T> models;
if (modelConnector instanceof ICollectionConnector) {
models = new ArrayList<>();
int[] selectedIndices = getSelectedIndices(viewPath, context);
if (selectedIndices != null && selectedIndices.length > 0) {
for (int selectedIndice : selectedIndices) {
IValueConnector childConnector = ((ICollectionConnector) modelConnector)
.getChildConnector(selectedIndice);
if (childConnector != null) {
models.add((T) childConnector.getConnectorValue());
}
}
}
} else {
T model = getSelectedModel(viewPath, context);
models = Collections.singletonList(model);
}
return models;
}
/**
* Gets he application translation provider out of the action context. This
* method simply delegates to the context backend controller.
*
* @param context
* the action context.
* @return the translation provider.
*/
protected ITranslationProvider getTranslationProvider(
Map<String, Object> context) {
return getBackendController(context);
}
/**
* Sets the action parameter out of the context. The action parameter is a
* general purpose context entry that can be used to pass an arbitrary
* parameter along the action chain. It is stored using the
* {@code ActionContextConstants.ACTION_PARAM} standard key.
*
* @param actionParam
* the action parameter to set to the context.
* @param context
* the action context.
*/
protected void setActionParameter(Object actionParam,
Map<String, Object> context) {
context.put(ActionContextConstants.ACTION_PARAM, actionParam);
}
/**
* Sets the selected indices of the UI component if it is a collection
* component (table, list, ...). More accurately, the selected indices are set
* to the view connector that adapts the UI component to the Jspresso binding
* architecture.
*
* @param selectedIndices
* the selected indices to store in the action context.
* @param context
* the action context.
*/
protected void setSelectedIndices(int[] selectedIndices,
Map<String, Object> context) {
setSelectedIndices(null, selectedIndices, context);
}
/**
* Sets the selected indices of the UI component if it is a collection
* component (table, list, ...). More accurately, the selected indices are set
* to the view connector that adapts the UI component to the Jspresso binding
* architecture.
*
* @param viewPath
* the view index path to follow.
* <ul>
* <li>A positive integer n means the nth child.</li>
* <li>A negative integer -n means the nth parent.</li>
* </ul>
* @param selectedIndices
* the selected indices to store in the action context.
* @param context
* the action context.
*/
protected void setSelectedIndices(int[] viewPath, int[] selectedIndices,
Map<String, Object> context) {
IValueConnector selectableConnector = getViewConnector(viewPath, context);
while (selectableConnector != null
&& !(selectableConnector instanceof ISelectable)) {
selectableConnector = selectableConnector.getParentConnector();
}
if (selectableConnector != null) {
((ISelectable) selectableConnector).setSelectedIndices(selectedIndices);
}
}
/**
* Retrieves the selected models indices out of the model connector if it's a
* collection connector and set them as selected indices in the action
* context.
*
* @param selectedModels
* the list of models to select in the view connector.
* @param context
* the action context.
*/
protected void setSelectedModels(Collection<?> selectedModels,
Map<String, Object> context) {
setSelectedModels(null, selectedModels, context);
}
/**
* Retrieves the selected models indices out of the model connector if it's a
* collection connector and set them as selected indices in the action
* context.
*
* @param viewPath
* the view index path to follow.
* <ul>
* <li>A positive integer n means the nth child.</li>
* <li>A negative integer -n means the nth parent.</li>
* </ul>
* @param selectedModels
* the list of models to select in the view connector.
* @param context
* the action context.
*/
protected void setSelectedModels(int[] viewPath,
Collection<?> selectedModels, Map<String, Object> context) {
IValueConnector modelConnector = getModelConnector(viewPath, context);
if (modelConnector instanceof ICollectionConnector) {
setSelectedIndices(viewPath, ConnectorHelper.getIndicesOf(
(ICollectionConnector) modelConnector, selectedModels), context);
}
}
/**
* Gets the property view descriptor out of the context. The property view
* descriptor is only filled when an action is triggered from a table column
* or a form field. It contains a reference to the column/field descriptor the
* action was triggered from.
*
* @param context
* the action context.
* @return the property view descriptor.
*/
protected IPropertyViewDescriptor getPropertyViewDescriptor(
Map<String, Object> context) {
return (IPropertyViewDescriptor) context
.get(ActionContextConstants.PROPERTY_VIEW_DESCRIPTOR);
}
/**
* This is a utility method which is able to retrieve the view this action has
* been executed on from its context. It uses well-known context keys of the
* action context which are:
* <ul>
* <li> {@code ActionContextConstants.VIEW} to get the the view the action
* executes on.
* </ul>
* <p>
* The returned view mainly serves for acting on the view component the action
* has to be triggered on.
*
* @param <T>
* type inference return.
* @param context
* the action context.
* @return the view this action was triggered on.
*/
protected <T> IView<T> getView(Map<String, Object> context) {
return getView(null, context);
}
/**
* This is a utility method which is able to retrieve the view this action has
* been executed on from its context. It uses well-known context keys of the
* action context which are:
* <ul>
* <li> {@code ActionContextConstants.VIEW} to get the the view the action
* executes on.
* </ul>
* <p>
* The returned view mainly serves for acting on the view component the action
* has to be triggered on.
*
* @param <T>
* type inference return.
* @param viewPath
* the view index path to follow.
* <ul>
* <li>A positive integer n means the nth child.</li> <li>A negative
* integer -n means the nth parent.</li>
* </ul>
* @param context
* the action context.
* @return the view this action was triggered on.
*/
@SuppressWarnings("unchecked")
protected <T> IView<T> getView(int[] viewPath, Map<String, Object> context) {
return navigate((IView<T>) context.get(ActionContextConstants.VIEW),
viewPath);
}
/**
* This is a utility method which is able to retrieve the view connector this
* action has been executed on from its context. It uses well-known context
* keys of the action context which are:
* <ul>
* <li> {@code ActionContextConstants.VIEW_CONNECTOR} to get the the view
* value connector the action executes on.
* </ul>
* <p>
* The returned connector mainly serves for acting on the view component the
* action has to be triggered on.
*
* @param context
* the action context.
* @return the value connector this action was triggered on.
*/
protected IValueConnector getViewConnector(Map<String, Object> context) {
return getViewConnector(null, context);
}
/**
* This is a utility method which is able to retrieve the view connector this
* action has been executed on from its context. It uses well-known context
* keys of the action context which are:
* <ul>
* <li> {@code ActionContextConstants.VIEW_CONNECTOR} to get the the view
* value connector the action executes on.
* </ul>
* <p>
* The returned connector mainly serves for acting on the view component the
* action has to be triggered on.
*
* @param viewPath
* the view index path to follow.
* <ul>
* <li>A positive integer n means the nth child.</li> <li>A negative
* integer -n means the nth parent.</li>
* </ul>
* @param context
* the action context.
* @return the value connector this action was triggered on.
*/
protected IValueConnector getViewConnector(int[] viewPath,
Map<String, Object> context) {
if (viewPath == null || viewPath.length == 0) {
return (IValueConnector) context
.get(ActionContextConstants.VIEW_CONNECTOR);
}
IView<?> view = getView(viewPath, context);
if (view != null) {
return view.getConnector();
}
return null;
}
/**
* Starts from a view and navigates the view hierarchy following an index
* navigation path.
*
* @param <T>
* The root class of the view peers.
* @param fromView
* the view to start from.
* @param viewPath
* the view index path to follow.
* <ul>
* <li>A positive integer n means the nth child.</li>
* <li>A negative integer -n means the nth parent.</li>
* </ul>
* @return the view navigated to.
*/
protected <T> IView<T> navigate(IView<T> fromView, int... viewPath) {
IView<T> target = fromView;
if (viewPath != null) {
for (int nextIndex : viewPath) {
if (target != null) {
if (nextIndex < 0) {
for (int j = 0; j > nextIndex; j--) {
if (target != null) {
target = target.getParent();
}
}
} else {
if (target instanceof ICompositeView<?>
&& ((ICompositeView<?>) target).getChildren() != null
&& nextIndex < ((ICompositeView<?>) target).getChildren()
.size()) {
target = ((ICompositeView<T>) target).getChildren()
.get(nextIndex);
} else {
target = null;
}
}
}
}
}
return target;
}
}