/*******************************************************************************
* Mission Control Technologies, Copyright (c) 2009-2012, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* The MCT platform is 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.
*
* MCT includes source code licensed under additional open source licenses. See
* the MCT Open Source Licenses file included with this distribution or the About
* MCT Licenses dialog available at runtime from the MCT Help menu for additional
* information.
*******************************************************************************/
package gov.nasa.arc.mct.gui.actions;
import gov.nasa.arc.mct.api.persistence.OptimisticLockException;
import gov.nasa.arc.mct.components.AbstractComponent;
import gov.nasa.arc.mct.components.ObjectManager;
import gov.nasa.arc.mct.gui.ActionContext;
import gov.nasa.arc.mct.gui.ContextAwareAction;
import gov.nasa.arc.mct.gui.OptionBox;
import gov.nasa.arc.mct.gui.housing.MCTContentArea;
import gov.nasa.arc.mct.gui.housing.MCTHousing;
import gov.nasa.arc.mct.gui.impl.ActionContextImpl;
import gov.nasa.arc.mct.gui.impl.WindowManagerImpl;
import gov.nasa.arc.mct.platform.spi.Platform;
import gov.nasa.arc.mct.platform.spi.PlatformAccess;
import gov.nasa.arc.mct.platform.spi.WindowManager;
import gov.nasa.arc.mct.policy.PolicyContext;
import gov.nasa.arc.mct.policy.PolicyInfo;
import gov.nasa.arc.mct.services.internal.component.Updatable;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
/**
* A Save action which includes any managed components of
* the component being saved (for instance, components modified
* in nested views.)
*/
public abstract class SaveAction extends ContextAwareAction{
private static final long serialVersionUID = 3940626077815919451L;
private static final ResourceBundle BUNDLE =
ResourceBundle.getBundle(
SaveAction.class.getName().substring(0,
SaveAction.class.getName().lastIndexOf("."))+".Bundle");
private ActionContextImpl actionContext;
public SaveAction() {
super(BUNDLE.getString("SaveAllAction.label"));
}
@Override
public boolean canHandle(ActionContext context) {
actionContext = (ActionContextImpl) context;
return getTargetComponent(actionContext) != null;
}
private boolean isComponentWriteableByUser(AbstractComponent component) {
Platform p = PlatformAccess.getPlatform();
PolicyContext policyContext = new PolicyContext();
policyContext.setProperty(PolicyContext.PropertyName.TARGET_COMPONENT.getName(), component);
policyContext.setProperty(PolicyContext.PropertyName.ACTION.getName(), 'w');
String inspectionKey = PolicyInfo.CategoryType.OBJECT_INSPECTION_POLICY_CATEGORY.getKey();
return p.getPolicyManager().execute(inspectionKey, policyContext).getStatus();
}
protected abstract AbstractComponent getTargetComponent(ActionContextImpl actionContext);
@Override
public boolean isEnabled() {
AbstractComponent ac = getTargetComponent(actionContext);
ObjectManager om = ac.getCapability(ObjectManager.class);
Set<AbstractComponent> modified = om != null ?
om.getAllModifiedObjects() :
Collections.<AbstractComponent>emptySet();
// Should enable if at least one object can be saved
boolean hasWriteableComponents =
!ac.isStale() &&
ac.isDirty() &&
isComponentWriteableByUser(ac);
if (!hasWriteableComponents) {
for (AbstractComponent mod : modified) {
if (isComponentWriteableByUser(mod)) {
hasWriteableComponents = true;
break;
}
}
}
return hasWriteableComponents;
}
/**
* This method is invoked when the client side object is stale. This can occur when another client
* even another window in the same application instance has saved the component after it has been loaded.
* This implementation will try again, which will overwrite the previous change; however, this is where
* configuration could be added to display a message instead.
*/
private void handleStaleObject(AbstractComponent ac) {
overwritePreviousChanges(ac);
}
private void overwritePreviousChanges(AbstractComponent ac) {
AbstractComponent updatedComp = PlatformAccess.getPlatform().getPersistenceProvider().getComponentFromStore(ac.getComponentId());
ac.getCapability(Updatable.class).setVersion(updatedComp.getVersion());
actionPerformed(null);
}
/**
* Prompt the user with a warning in the event that some objects cannot be saved.
* @param canSave set of components which can be saved
* @param cannotSave set of components which cannot be saved
* @return true if save should be completed, otherwise false
*/
private boolean handleWarnings(Collection<AbstractComponent> canSave,
Collection<AbstractComponent> cannotSave) {
// No need to warn if all modified objects can be saved
if (cannotSave.isEmpty()) {
return true;
}
// Get references to platform and window manager; these will be used a few times
Platform platform = PlatformAccess.getPlatform();
WindowManager windowManager = platform.getWindowManager();
// Can only complete action if there are no components which cannot be removed
String confirm = BUNDLE.getString("SaveConfirm");
String abort = BUNDLE.getString("SaveAbort");
String[] options = { confirm, abort };
// Issue a warning dialog to the user
Map<String, Object> hints = new HashMap<String, Object>();
hints.put(WindowManagerImpl.PARENT_COMPONENT, actionContext.getWindowManifestation());
hints.put(WindowManagerImpl.OPTION_TYPE, OptionBox.YES_NO_OPTION);
hints.put(WindowManagerImpl.MESSAGE_TYPE, OptionBox.WARNING_MESSAGE);
hints.put(WindowManagerImpl.MESSAGE_OBJECT, buildWarningPanel(canSave, cannotSave));
String choice = windowManager.showInputDialog(
BUNDLE.getString("SaveWarningTitle"), //title
"", // message - will be overridden by custom object
options, // options
null, // default option
hints); // hints
// Complete the action, if the user has confirmed it
return (confirm.equals(choice));
}
private JPanel buildWarningPanel(
Collection<AbstractComponent> canSave, Collection<AbstractComponent> cannotSave) {
JPanel panel = new JPanel(new BorderLayout());
panel.add(buildWarningPanel(
BUNDLE.getString("CanSaveTitle"),
BUNDLE.getString("CanSaveText"),
canSave),
BorderLayout.WEST);
panel.add(buildWarningPanel(
BUNDLE.getString("CannotSaveTitle"),
BUNDLE.getString("CannotSaveText"),
cannotSave),
BorderLayout.EAST);
return panel;
}
private JPanel buildWarningPanel(String title, String message, Collection<AbstractComponent> components) {
List<String> compList = new ArrayList<String>(components.size());
for (AbstractComponent comp : components) {
compList.add(comp.getDisplayName());
}
JPanel warning = new JPanel(new FlowLayout());
warning.setPreferredSize(new Dimension(400,300));
@SuppressWarnings({ "rawtypes", "unchecked" })
JList compJList = new JList(compList.toArray());
JScrollPane scrollPane = new JScrollPane(compJList);
scrollPane.setPreferredSize(new Dimension(300,200));
JLabel titleLabel = new JLabel(title);
titleLabel.setPreferredSize(new Dimension(300,20));
titleLabel.setVerticalAlignment(SwingConstants.BOTTOM);
titleLabel.setHorizontalAlignment(SwingConstants.LEFT);
JTextArea warningMessage = new JTextArea(message);
warningMessage.setWrapStyleWord(true);
warningMessage.setLineWrap(true);
warningMessage.setOpaque(false);
warningMessage.setPreferredSize(new Dimension(300,60));
warningMessage.setEditable(false);
warning.add(warningMessage);
warning.add(titleLabel);
warning.add(scrollPane);
return warning;
}
@Override
public void actionPerformed(ActionEvent e) {
AbstractComponent ac = getTargetComponent(actionContext);
ObjectManager om = ac.getCapability(ObjectManager.class);
Collection<AbstractComponent> modified = om != null ?
om.getAllModifiedObjects() :
Collections.<AbstractComponent>emptySet();
// Assemble objects to save.
// Make sure they pass the writeable-by-user test.
Set<AbstractComponent> canSave = new HashSet<AbstractComponent>();
Set<AbstractComponent> cannotSave = new HashSet<AbstractComponent>();
for (AbstractComponent mod : modified) {
(isComponentWriteableByUser(mod) ? canSave : cannotSave).add(mod);
}
if (ac.isDirty() && !ac.isStale()) {
(isComponentWriteableByUser(ac) ? canSave : cannotSave).add(ac);
}
if (handleWarnings(canSave, cannotSave)) {
try {
PlatformAccess.getPlatform().getPersistenceProvider().persist(canSave);
} catch (OptimisticLockException ole) {
handleStaleObject(ac);
}
if (om != null) {
om.notifySaved(canSave);
}
}
}
public static class ThisSaveAction extends SaveAction {
private static final long serialVersionUID = -8750182309057992525L;
@Override
protected AbstractComponent getTargetComponent(ActionContextImpl actionContext) {
MCTHousing housing = actionContext.getTargetHousing();
MCTContentArea contentArea = housing.getContentArea();
return contentArea == null ? null : contentArea.getHousedViewManifestation().getManifestedComponent();
}
}
public static class ObjectsSaveAction extends SaveAction {
private static final long serialVersionUID = -2536879130620462419L;
@Override
protected AbstractComponent getTargetComponent(ActionContextImpl actionContext) {
return actionContext.getInspectorComponent();
}
}
}