package org.exist.client.xacml;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.xmldb.api.base.Collection;
import com.sun.xacml.Policy;
import com.sun.xacml.PolicySet;
import com.sun.xacml.Rule;
import com.sun.xacml.Target;
import com.sun.xacml.combine.OrderedPermitOverridesPolicyAlg;
import com.sun.xacml.combine.OrderedPermitOverridesRuleAlg;
import com.sun.xacml.combine.PolicyCombiningAlgorithm;
import com.sun.xacml.combine.RuleCombiningAlgorithm;
import com.sun.xacml.ctx.Result;
public class XACMLEditor extends JFrame implements ActionListener, TreeModelListener, TreeSelectionListener, WindowListener
{
private static final long serialVersionUID = 7029748734913443907L;
private static final String DEFAULT_DESCRIPTION = "This is a policy template. It will match and deny everything until you change the target and add rules.";
private static final String DEFAULT_RULE_DESCRIPTION = "This rule denies everything that is not permitted by the rules above it when " +
"used with the ordered permit overrides combining algorithm. Any rules below it will not be evaluated, so it should be the last rule";
private static final String DEFAULT_POLICY_ID = "NewPolicy";
private static final String DEFAULT_POLICY_SET_ID = "NewPolicySet";
private static final String DEFAULT_RULE_ID = "NewRule";
private static final String CLOSE = "Close";
private static final String SAVE = "Save";
private static final int MIN_FRAME_WIDTH = 600;
private static final int MIN_FRAME_HEIGHT = 350;
private static final int MINIMUM_TREE_WIDTH = 100;
private DatabaseInterface dbInterface;
private XACMLTreeModel model;
private JTree tree;
private NodeEditor editor;
private JSplitPane split;
@SuppressWarnings("unused")
private XACMLEditor() {}
public XACMLEditor(Collection systemCollection)
{
super("XACML Policy Editor");
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
addWindowListener(this);
dbInterface = new DatabaseInterface(systemCollection);
setupMenuBar();
createInterface();
//setSize(600, 480);
pack();
final Dimension size = getSize();
if(size.width <= MIN_FRAME_WIDTH)
{size.width = MIN_FRAME_WIDTH;}
if(size.height <= MIN_FRAME_HEIGHT)
{size.height = MIN_FRAME_HEIGHT;}
setSize(size);
}
private void createInterface()
{
split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
getContentPane().add(split);
model = new XACMLTreeModel(dbInterface.getPolicies());
model.addTreeModelListener(this);
tree = new JTree(model);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
final TreeMutator mutator = new TreeMutator(tree);
tree.addTreeSelectionListener(this);
tree.setCellRenderer(new CustomRenderer(mutator));
tree.setEditable(false);
tree.setShowsRootHandles(true);
tree.setRootVisible(false);
final Dimension minSize = tree.getMinimumSize();
if(minSize.width < MINIMUM_TREE_WIDTH)
{minSize.width = MINIMUM_TREE_WIDTH;}
tree.setMinimumSize(minSize);
final JScrollPane scroll = new JScrollPane(tree);
split.setLeftComponent(scroll);
split.setRightComponent(new JScrollPane());
split.setOneTouchExpandable(false);
}
public void close()
{
if(hasUnsavedChanges())
{
final String message = "There are unsaved changes. Do you want to save your changes before closing?";
final String title = "Save changes?";
final int ret = JOptionPane.showConfirmDialog(this, message, title, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
if(ret == JOptionPane.CANCEL_OPTION)
{return;}
else if(ret == JOptionPane.YES_OPTION)
{saveAll();}
}
dispose();
}
public boolean hasUnsavedChanges()
{
return model.hasUnsavedChanges();
}
public void saveAll()
{
if(editor != null)
{editor.pushChanges();}
dbInterface.writePolicies((RootNode)model.getRoot());
tree.repaint();
}
public static PolicySet createDefaultPolicySet(PolicyElementContainer parent)
{
return createDefaultPolicySet(createUniqueId(parent, DEFAULT_POLICY_SET_ID));
}
public static PolicySet createDefaultPolicySet(String policySetID)
{
final PolicyCombiningAlgorithm alg = new OrderedPermitOverridesPolicyAlg();
return new PolicySet(URI.create(policySetID), alg, createEmptyTarget());
}
public static Target createEmptyTarget()
{
return new Target(Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
}
public static Policy createDefaultPolicy(PolicyElementContainer parent)
{
return createDefaultPolicy(createUniqueId(parent, DEFAULT_POLICY_ID));
}
public static Policy createDefaultPolicy(String policyID)
{
final Target emptyTarget = createEmptyTarget();
final RuleCombiningAlgorithm alg = new OrderedPermitOverridesRuleAlg();
final Rule denyEverythingRule = createDefaultRule("DenyAll");
final List<Rule> rules = Collections.singletonList(denyEverythingRule);
return new Policy(URI.create(policyID), alg, DEFAULT_DESCRIPTION, emptyTarget, rules);
}
public static Rule createDefaultRule(PolicyElementContainer parent)
{
return createDefaultRule(createUniqueId(parent, DEFAULT_RULE_ID));
}
public static String createUniqueId(PolicyElementContainer parent, String base)
{
if(parent == null)
{throw new NullPointerException("Parent cannot be null");}
String newId = base;
for(int i = 2; parent.containsId(newId); ++i)
newId = base + Integer.toString(i);
return newId;
}
public static Rule createDefaultRule(String id)
{
return new Rule(URI.create(id), Result.DECISION_DENY, DEFAULT_RULE_DESCRIPTION, null, null);
}
private void setupMenuBar()
{
final JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
final JMenu file = new JMenu("File");
file.setMnemonic(KeyEvent.VK_F);
menuBar.add(file);
final JMenuItem saveItem = new JMenuItem(SAVE,KeyEvent.VK_S);
saveItem.setActionCommand(SAVE);
saveItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
saveItem.addActionListener(this);
file.add(saveItem);
final JMenuItem closeItem = new JMenuItem(CLOSE,KeyEvent.VK_W);
closeItem.setActionCommand(CLOSE);
closeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W,
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
closeItem.addActionListener(this);
file.add(closeItem);
}
public void actionPerformed(ActionEvent event)
{
final String actionCommand = event.getActionCommand();
if(CLOSE.equals(actionCommand))
{close();}
else if(SAVE.equals(actionCommand))
{saveAll();}
}
//this method fixes dumb repaint issues
//when components are updated
private void forceRepaint()
{
final Container contentPane = getContentPane();
contentPane.invalidate();
contentPane.validate();
contentPane.repaint();
}
public void valueChanged(TreeSelectionEvent event)
{
if(editor != null)
{
editor.pushChanges();
editor = null;
}
final TreePath selectedPath = event.getPath();
final Object value = selectedPath.getLastPathComponent();
if(value instanceof AbstractPolicyNode)
{editor = new AbstractPolicyEditor();}
else if(value instanceof RuleNode)
{editor = new RuleEditor();}
else if(value instanceof ConditionNode)
{editor = null;}//TODO: implement condition editing
else if(value instanceof TargetNode)
{editor = new TargetEditor(dbInterface);}
final int dividerLocation = split.getDividerLocation();
final JScrollPane scroll = ((JScrollPane)split.getRightComponent());
if(editor == null)
{scroll.setViewportView(null);}
else
{
editor.setNode((XACMLTreeNode)value);
scroll.setViewportView(editor.getComponent());
}
split.setDividerLocation(dividerLocation);
forceRepaint();
}
//WindowListener methods
public void windowActivated(WindowEvent event) {}
public void windowClosed(WindowEvent event) {}
public void windowClosing(WindowEvent event)
{
close();
}
public void windowDeactivated(WindowEvent event) {}
public void windowDeiconified(WindowEvent event) {}
public void windowIconified(WindowEvent event) {}
public void windowOpened(WindowEvent event) {}
private void treeChanged(TreeModelEvent event)
{
tree.revalidate();
tree.repaint();
}
//TreeModelListener methods
public void treeNodesChanged(TreeModelEvent event)
{
treeChanged(event);
}
public void treeNodesInserted(TreeModelEvent event)
{
treeChanged(event);
}
public void treeNodesRemoved(TreeModelEvent event)
{
treeChanged(event);
}
public void treeStructureChanged(TreeModelEvent event)
{
treeChanged(event);
}
}