aboutsummaryrefslogtreecommitdiffstats
path: root/ECOMP-XACML/src/main/java/org/openecomp/policy/xacml/std/pap/StdEngine.java
blob: 951f89110e6329ee0f94390dbcbb897351b0ae2f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121

@media only all and (prefers-color-scheme: dark) {
.highlight .hll { background-color: #49483e }
.highlight .c { color: #75715e } /* Comment */
.highlight .err { color: #960050; background-color: #1e0010 } /* Error */
.highlight .k { color: #66d9ef } /* Keyword */
.highlight .l { color: #ae81ff } /* Literal */
.highlight .n { color: #f8f8f2 } /* Name */
.highlight .o { color: #f92672 } /* Operator */
.highlight .p { color: #f8f8f2 } /* Punctuation */
.highlight .ch { color: #75715e } /* Comment.Hashbang */
.highlight .cm { color: #75715e } /* Comment.Multiline */
.highlight .cp { color: #75715e } /* Comment.Preproc */
.highlight .cpf { color: #75715e } /* Comment.PreprocFile */
.highlight .c1 { color: #75715e } /* Comment.Single */
.highlight .cs { color: #75715e } /* Comment.Special */
.highlight .gd { color: #f92672 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gi { color: #a6e22e } /* Generic.Inserted */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #75715e } /* Generic.Subheading */
.highlight .kc { color: #66d9ef } /* Keyword.Constant */
.highlight .kd { color: #66d9ef } /* Keyword.Declaration */
.highlight .kn { color: #f92672 } /* Keyword.Namespace */
.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */
.highlight .kr { color: #66d9ef } /* Keyword.Reserved */
.highlight .kt { color: #66d9ef } /* Keyword.Type */
.highlight .ld { color: #e6db74 } /* Literal.Date */
.highlight .m { color: #ae81ff } /* Literal.Number */
.highlight .s { color: #e6db74 } /* Literal.String */
.highlight .na { color: #a6e22e } /* Name.Attribute */
.highlight .nb { color: #f8f8f2 } /* Name.Builtin */
.highlight .nc { color: #a6e22e } /* Name.Class */
.highlight .no { color: #66d9ef } /* Name.Constant */
.highlight .nd { color: #a6e22e } /* Name.Decorator */
.highlight .ni { color: #f8f8f2 } /* Name.Entity */
.highlight .ne { color: #a6e22e } /* Name.Exception */
.highlight .nf { color: #a6e22e } /* Name.Function */
.highlight .nl { color: #f8f8f2 } /* Name.Label */
.highlight .nn { color: #f8f8f2 } /* Name.Namespace */
.highlight .nx { color: #a6e22e } /* Name.Other */
.highlight .py { color: #f8f8f2 } /* Name.Property */
.highlight .nt { color: #f92672 } /* Name.Tag */
.highlight .nv { color: #f8f8f2 } /* Name.Variable */
.highlight .ow { color: #f92672 } /* Operator.Word */
.highlight .w { color: #f8f8f2 } /* Text.Whitespace */
.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */
.highlight .mf { color: #ae81ff } /* Literal.Number.Float */
.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */
.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */
.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */
.highlight .sa { color: #e6db74 } /* Literal.String.Affix */
.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */
.highlight .sc { color: #e6db74 } /* Literal.String.Char */
.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */
.highlight .sd { color: #e6db74 } /* Literal.String.Doc */
.highlight .s2 { color: #e6db74 } /* Literal.String.Double */
.highlight .se { color: #ae81ff } /* Literal.String.Escape */
.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */
.highlight .si { color: #e6db74 } /* Literal.String.Interpol */
.highlight .sx { color: #e6db74 } /* Literal.String.Other */
.highlight .sr { color: #e6db74 } /* Literal.String.Regex */
.highlight .s1 { color: #e6db74 } /* Literal.String.Single */
.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */
.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #a6e22e } /* Name.Function.Magic */
.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */
.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */
.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */
.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */
}
@media (prefers-color-scheme: light) {
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
}
/*-
 * ============LICENSE_START=======================================================
 *  Copyright (C) 2021 Nokia.
 * ================================================================================
 * 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.
 *
 * SPDX-License-Identifier: Apache-2.0
 * ============LICENSE_END=========================================================
 */

package org.onap.oom.certservice.cmpv2client.impl;

import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERGeneralizedTime;
import org.bouncycastle.asn1.cmp.PKIBody;
import org.bouncycastle.asn1.cmp.PKIHeader;
import org.bouncycastle.asn1.cmp.PKIHeaderBuilder;
import org.bouncycastle.asn1.cmp.PKIMessage;
import org.bouncycastle.asn1.crmf.CertReqMessages;
import org.bouncycastle.asn1.crmf.CertReqMsg;
import org.bouncycastle.asn1.crmf.CertRequest;
import org.bouncycastle.asn1.crmf.CertTemplateBuilder;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.cert.cmp.GeneralPKIMessage;
import org.bouncycastle.cert.cmp.ProtectedPKIMessage;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.util.Date;

final class PkiTestUtils {

    private static final String CN_TEST_SUBJECT = "CN=test1Subject";
    private static final String CN_TEST_ISSUER = "CN=test2Issuer";
    private static final int TEST_CERT_REQUEST_ID = 1432;
    private static final int PVNO = 0;
    private static final String BC_PROVIDER = "BC";
    private static final String RSA = "RSA";

    private PkiTestUtils() {
    }

    static PKIBody getTestPkiBody(AlgorithmIdentifier signingAlgorithm) {
        CertTemplateBuilder certTemplateBuilder =
                new CertTemplateBuilder()
                        .setIssuer(new X500Name(CN_TEST_ISSUER))
                        .setSubject(new X500Name(CN_TEST_SUBJECT))
                        .setSigningAlg(signingAlgorithm);

        CertRequest certRequest = new CertRequest(TEST_CERT_REQUEST_ID, certTemplateBuilder.build(), null);
        CertReqMsg certReqMsg = new CertReqMsg(certRequest, null, null);

        CertReqMessages certReqMessages = new CertReqMessages(certReqMsg);
        return new PKIBody(0, certReqMessages);
    }

    static PKIHeader getTestPkiHeader(AlgorithmIdentifier protectionAlgorithm) {
        PKIHeaderBuilder pkiHeader = new PKIHeaderBuilder(
                PVNO,
                new GeneralName(new X500Name(CN_TEST_SUBJECT)),
                new GeneralName(new X500Name(CN_TEST_ISSUER)));
        pkiHeader.setProtectionAlg(protectionAlgorithm);
        pkiHeader.setMessageTime(new DERGeneralizedTime(new Date()));
        return pkiHeader.build();
    }

    static ProtectedPKIMessage getProtectedPkiMessage(PKIHeader pkiHeader, PKIBody pkiBody, DERBitString messageProtection) {
        PKIMessage pkiMessage = new PKIMessage(pkiHeader, pkiBody, messageProtection);
        GeneralPKIMessage generalPkiMessage = new GeneralPKIMessage(pkiMessage);
        return new ProtectedPKIMessage(generalPkiMessage);
    }

    static KeyPair getKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA, BC_PROVIDER);
        return keyPairGenerator.generateKeyPair();
    }

    static ContentVerifierProvider getContentVerifierProvider(PublicKey publicKey) throws OperatorCreationException {
        return new JcaContentVerifierProviderBuilder()
                .setProvider(BC_PROVIDER)
                .build(publicKey);
    }
}
id='n800' href='#n800'>800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034
/*-
 * ============LICENSE_START=======================================================
 * ECOMP-XACML
 * ================================================================================
 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
 * ================================================================================
 * 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.
 * ============LICENSE_END=========================================================
 */
package org.openecomp.policy.xacml.std.pap;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openecomp.policy.common.logging.eelf.MessageCodes;
import org.openecomp.policy.common.logging.eelf.PolicyLogger;
import org.openecomp.policy.xacml.api.XACMLErrorConstants;
import org.openecomp.policy.xacml.api.pap.EcompPDP;
import org.openecomp.policy.xacml.api.pap.EcompPDPGroup;
import org.openecomp.policy.xacml.api.pap.PAPPolicyEngine;

import com.att.research.xacml.api.pap.PAPException;
import com.att.research.xacml.api.pap.PDP;
import com.att.research.xacml.api.pap.PDPGroup;
import com.att.research.xacml.api.pap.PDPPIPConfig;
import com.att.research.xacml.api.pap.PDPPolicy;
import com.att.research.xacml.api.pap.PDPStatus;
import com.att.research.xacml.util.XACMLProperties;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Sets;

/**
 * This is a simple PAP engine that uses some property files and a simple directory
 * structure in the file system to manage a policy repository and set of PDP nodes.
 * 
 *
 */
public class StdEngine extends StdPDPItemSetChangeNotifier implements PAPPolicyEngine {
	public static final String pipPropertyFile = "pip.properties";

	private static Log	logger	= LogFactory.getLog(StdEngine.class);

	public static String	PROP_PAP_REPO = "xacml.pap.pdps";
	public static String	PROP_PAP_GROUPS = "xacml.pap.groups";
	public static String	PROP_PAP_GROUPS_DEFAULT = "xacml.pap.groups.default";
	public static String	PROP_PAP_GROUPS_DEFAULT_NAME = "default";
	//this value will be accessed from XacmlPapServlet so that we know if a default group did not exist
	//and was just added. This way, we can add the new group to the database.
	public boolean wasDefaultGroupJustAdded = false;

    protected final Path repository;
	protected Set<StdPDPGroup> groups;
		
	public StdEngine() throws PAPException, IOException {
		//
		// Get the location in the file system of our repository
		//
		this.repository = Paths.get(XACMLProperties.getProperty(PROP_PAP_REPO));
		//
		// Initialize
		//
		this.intialize();
	}
	
	public StdEngine(Properties properties) throws PAPException, IOException {
		//
		// Get the location in the file system of our repository
		//
		this.repository = Paths.get(properties.getProperty(PROP_PAP_REPO));
		//
		// Initialize
		//
		this.intialize();
	}
	
	public StdEngine(Path repository) throws PAPException, IOException {
		//
		// Save our location
		//
		this.repository = repository;
		//
		// Initialize
		//
		this.intialize();
	}
	
	private void intialize() throws PAPException, IOException {
		//
		// Sanity check the repository path
		//
		if (this.repository == null) {
			throw new PAPException ("No repository specified.");
		}
		if (Files.notExists(this.repository)) {
			Files.createDirectory(repository);
		}
		if (Files.isDirectory(this.repository) == false) {
			throw new PAPException ("Repository is NOT a directory: " + this.repository.toAbsolutePath());
		}
		if (Files.isWritable(this.repository) == false) {
			throw new PAPException ("Repository is NOT writable: " + this.repository.toAbsolutePath());
		}
		//
		// Load our groups
		//
		this.loadGroups();
	}
	
	private void loadGroups() throws PAPException {
		//
		// Create a properties object
		//
		Properties properties = new Properties();
		Path file = Paths.get(this.repository.toString(), XACMLProperties.XACML_PROPERTIES_NAME);
		try {
			//
			// Load the properties
			//
			try (InputStream is = new FileInputStream(file.toFile())) {
				properties.load(is);
			}

			//
			// Parse it
			//
			this.groups = this.readProperties(this.repository, properties);
		} catch (IOException e) {
			PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine", "Failed to load properties file");
			this.groups = new HashSet<StdPDPGroup>();
		}
		//
		// Initialize the default group
		//
		PDPGroup defaultGroup = this.initializeDefaultGroup(file, properties);
		logger.info("Default group is: " + defaultGroup.getId() + "=" + defaultGroup.getName());
	}
	
	private PDPGroup initializeDefaultGroup(Path file, Properties properties) throws PAPException {
		wasDefaultGroupJustAdded = false;
		//
		// Make sure we have the default group
		//
		PDPGroup group = this.getDefaultGroup();
		if (group != null) {
			return group;
		}
		//
		// We don't have the default group, create it
		//
		String defaultId = properties.getProperty(PROP_PAP_GROUPS_DEFAULT, PROP_PAP_GROUPS_DEFAULT_NAME);
		if(defaultId == null){
			defaultId = PROP_PAP_GROUPS_DEFAULT_NAME;
		}
		if(defaultId.equals("")){
			defaultId = PROP_PAP_GROUPS_DEFAULT_NAME;
		}
		//we're going to check one more time in case the PROP_PAP_GROUPS_DEFAULT_NAME doesn't exist
		if(defaultId == null){
			defaultId = "default";
		}
		if(defaultId.equals("")){
			defaultId = "default";
		}
		logger.warn("Default group does NOT exist, creating " + defaultId);
		Path defaultPath = Paths.get(this.repository.toString(), defaultId);
		try {
			//
			// Does it exist?
			//
			if (Files.notExists(defaultPath)) {
				//
				// Create its directory
				//
				Files.createDirectory(defaultPath);
				//
				// Create property files
				//
				{
					Properties props = new Properties();
					props.setProperty(XACMLProperties.PROP_REFERENCEDPOLICIES, "");
					props.setProperty(XACMLProperties.PROP_ROOTPOLICIES, "");
					Path policyPath = Paths.get(defaultPath.toAbsolutePath().toString(), "xacml.policy.properties");
					Files.createFile(policyPath);
					try (OutputStream os = Files.newOutputStream(policyPath)) {
						props.store(os, "");
					} catch (IOException e) {
						PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine", "Failed to write default policy properties");
					}
				}
				{
					Properties props = new Properties();
					props = setPIPProperties(props);
					Path pipPath = Paths.get(defaultPath.toAbsolutePath().toString(), "xacml.pip.properties");
					Files.createFile(pipPath);
					try (OutputStream os = Files.newOutputStream(pipPath)) {
						props.store(os, "");
					} catch (IOException e) {
						PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine", "Failed to write default pip properties");
					}					
				}
			}
			//
			// Create the default group
			//
			StdPDPGroup newDefault = new StdPDPGroup(defaultId, true, "default", "The default group where new PDP's are put.", defaultPath);
			//
			// Add it to our list
			//
			this.groups.add(newDefault);
			//
			// Save our properties out since we have
			// a new default group.
			//
			StdEngine.setGroupProperties(newDefault, properties);
			//
			// Save it to disk
			//
			try {
				try (OutputStream os = Files.newOutputStream(file)) {
					properties.store(os, "");
				}
			} catch (IOException e) {
				PolicyLogger.error(MessageCodes.EXCEPTION_ERROR, e, "StdEngine", "Failed to save properties with new default group information.");
			}
			//
			// Return it
			//
			wasDefaultGroupJustAdded = true;
			return newDefault;
		} catch (IOException e) {
			PolicyLogger.error(MessageCodes.EXCEPTION_ERROR, e, "StdEngine", "Failed to create default group");
			throw new PAPException("Failed to create default group");
		}
	}
	
	@Override
	public EcompPDPGroup getDefaultGroup() throws PAPException{
		for (EcompPDPGroup group : this.groups) {
			if (group.isDefaultGroup()) {
				return group;
			}
		}
		//
		// Default group doesn't exist
		//
		return null;
	}

	@Override
	public EcompPDPGroup getGroup(String id) throws PAPException {
		for (EcompPDPGroup g: this.groups) {
			if (g.getId().equals(id)) {
				return g;
			}
		}
		return null;
	}

	@Override
	public void	newGroup(String name, String description) throws PAPException, NullPointerException{	
		//
		// Null check
		//
		if (name == null) {
			throw new NullPointerException();
		}
		//
		// Do we already have this group?
		//
		for (PDPGroup group : this.groups) {
			if (group.getName().equals(name)) {
				throw new PAPException("Group with this name=" + name + " already exists.");
			}
		}
		
		
		// create an Id that can be used as a file name and a properties file key.
		// Ids must not contain \/:*?"<>|=,;
		// The ID must also be unique within the current set of PDPGroups.
		String id = createNewPDPGroupId(name);
		
		
		//
		// Construct the directory path
		//
		Path groupPath = Paths.get(this.repository.toString(), id);
		//
		// If it exists already
		//
		if (Files.exists(groupPath)) {
			logger.warn("addGroup " + id + " directory exists" + groupPath.toString());
		} else {
			try {
				//
				// Create the directory
				//
				Files.createDirectory(groupPath);
			} catch (IOException e) {
				PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine", "Failed to create " + groupPath);
				throw new PAPException("Failed to create " + id);
			}
		}
		//
		// Create the Policies
		//

		Path policyProperties = Paths.get(groupPath.toString(), "xacml.policy.properties");
		if (Files.exists(policyProperties)) {
			logger.warn("addGroup " + id + " file exists: " + policyProperties.toString());
		} else {
			Properties props = new Properties();
			props.setProperty(XACMLProperties.PROP_REFERENCEDPOLICIES, "");
			props.setProperty(XACMLProperties.PROP_ROOTPOLICIES, "");
			try {
				Files.createFile(policyProperties);
				try (OutputStream os = Files.newOutputStream(policyProperties)) {
					props.store(os, "");
				}
			} catch (IOException e) {
				PolicyLogger.error(MessageCodes.EXCEPTION_ERROR, e, "StdEngine", "Failed to create " + policyProperties);
				throw new PAPException("Failed to create " + id);
			}
		}
		//
		// Create the PIP config
		//
		Path pipProperties = Paths.get(groupPath.toString(), "xacml.pip.properties");
		Properties props = new Properties();
		if (Files.exists(pipProperties)) {
			logger.warn("addGroup " + id + " file exists: " + pipProperties.toString());
		} else {
			try {
				props = setPIPProperties(props);
				Files.createFile(pipProperties);
				try (OutputStream os = Files.newOutputStream(pipProperties)) {
					props.store(os, "");
				}
			} catch (IOException e) {
				PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine", "Failed to create " + pipProperties);
				throw new PAPException("Failed to create " + id);
			}	

		}
		//
		// Ok now add it
		//
		StdPDPGroup newGroup = new StdPDPGroup(id, name, description, groupPath);
		// Add the default PIP configuration. 
		String list = props.getProperty(XACMLProperties.PROP_PIP_ENGINES);
        if (list != null && list.length() > 0) {
            Set<PDPPIPConfig> pipConfigs = new HashSet<PDPPIPConfig>();
            for (String pipID : list.split("[,]")) {
                StdPDPPIPConfig config = new StdPDPPIPConfig(pipID, props);
                if (config.isConfigured()) {
                    pipConfigs.add(config);
                }
            }
            newGroup.setPipConfigs(pipConfigs);
        }
		if (this.groups.add(newGroup)) {
			// save the new group in our properties and notify any listeners of the change
			groupChanged(newGroup);
		}

	}
	

	
	
	/**
	 * Helper to create a new Group ID.
	 * Use the Name field to create the Id.
	 * The Name is expected to not be null; if it is then this method throws an exception.
	 * The name is supposed to be unique within the current set of groups,
	 * so creating the ID based on the name will create a unique string.
	 * 
	 * @param name
	 * @return
	 */
	private String createNewPDPGroupId(String name) {
		String id = name;
		// replace "bad" characters with sequences that will be ok for file names and properties keys.
		id = id.replace(" ", "_sp_");
		id = id.replace("\t", "_tab_");
		id = id.replace("\\", "_bksl_");
		id = id.replace("/", "_sl_");
		id = id.replace(":", "_col_");
		id = id.replace("*", "_ast_");
		id = id.replace("?", "_q_");
		id = id.replace("\"", "_quo_");
		id = id.replace("<", "_lt_");
		id = id.replace(">", "_gt_");
		id = id.replace("|", "_bar_");
		id = id.replace("=", "_eq_");
		id = id.replace(",", "_com_");
		id = id.replace(";", "_scom_");

		return id;
	}
	
	
	@Override
	public EcompPDP	getPDP(String pdpId) throws PAPException {
		for (EcompPDPGroup group : this.groups) {
			for (EcompPDP pdp : group.getEcompPdps()) {
				if (pdp.getId().equals(pdpId)) {
					return pdp;
				}
			}
		}
		return null;
	}

	
	@Override
	public void movePDP(EcompPDP pdp, EcompPDPGroup newGroup) throws PAPException {
		if (newGroup == null) {
			throw new NullPointerException("You must specify which group the PDP will belong to.");
		}
		PDPGroup currentGroup = this.getPDPGroup(pdp);
		if (currentGroup == null) {
			throw new PAPException("PDP must already belong to a group.");
		}
		if (currentGroup.equals(newGroup)) {
			logger.warn("Already in that group.");
			return;
		}
		if (currentGroup instanceof StdPDPGroup && newGroup instanceof StdPDPGroup) {
			if (((StdPDPGroup) currentGroup).removePDP(pdp)) {
				boolean result = ((StdPDPGroup) newGroup).addPDP(pdp);
				if (result) {
					//
					// Save the configuration
					//
					this.doSave();
				} else {
					PolicyLogger.error("Failed to add to new group, putting back into original group.");
					if (((StdPDPGroup) currentGroup).removePDP(pdp) == false) {
						PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE + "Failed to put PDP back into original group.");
					}
				}
			}
		} else {
			String message = "Unknown PDP group class: " + newGroup.getClass().getCanonicalName() + " and " + currentGroup.getClass().getCanonicalName();
			logger.warn(message);
			throw new PAPException(message);
		}
	}

	
	@Override
	public void	updatePDP(EcompPDP pdp) throws PAPException {
		PDP currentPDP = this.getPDP(pdp.getId());
		if (currentPDP == null) {
			String message = "Unknown PDP id '" + pdp.getId() + "'";
			logger.warn(message);
			throw new PAPException(message);
		}
		
		// the only things that the user can change are name and description
		currentPDP.setDescription(pdp.getDescription());
		currentPDP.setName(pdp.getName());
		if (currentPDP instanceof EcompPDP && pdp instanceof EcompPDP) {
			((EcompPDP)currentPDP).setJmxPort(((EcompPDP)pdp).getJmxPort());
		}
		this.doSave();
	}
	
	@Override
	public void removePDP(EcompPDP pdp) throws PAPException {
		PDPGroup group = this.getPDPGroup(pdp);
		if (group == null) {
			throw new NullPointerException();
		}
		if (group instanceof StdPDPGroup) {
			boolean result = ((StdPDPGroup) group).removePDP(pdp);
			if (result) {
				this.doSave();
			}
			return;
		}
		String message = "Unknown PDP group class: " + group.getClass().getCanonicalName();
		logger.warn(message);
		throw new PAPException(message);
	}

	
	@Override
	/**
	 * Should never be called - Detailed status is held on the PDP, not the PAP
	 */
	public PDPStatus getStatus(EcompPDP pdp) throws PAPException {
		return getPDP(pdp.getId()).getStatus();
	}
	
	@Override
	public void publishPolicy(String id, String name, boolean isRoot, InputStream policy, EcompPDPGroup group) throws PAPException {
		if (group == null) {
			throw new NullPointerException();
		}
		if (group instanceof StdPDPGroup && this.groups.contains(group)) {
			((StdPDPGroup) group).publishPolicy(id, name, isRoot, policy);
			return;
		}
		logger.warn("unknown PDP Group: " + group);
		throw new PAPException("Unknown PDP Group: " + group.getId());
	}

	// Currently not used on the PAP side.  This is done by ((StdPDPGroup) group).copyPolicyToFile
	@Override
	public void copyPolicy(PDPPolicy policy, EcompPDPGroup group)
			throws PAPException {
	}
	
	
	@Override
	public void removePolicy(PDPPolicy policy, EcompPDPGroup group) throws PAPException {
		if (group == null) {
			throw new NullPointerException();
		}
		if (group instanceof StdPDPGroup && this.groups.contains(group)) {
			((StdPDPGroup) group).removePolicy(policy);
			return;
		}
		logger.warn("unknown PDP Group: " + group);
		throw new PAPException("Unknown PDP Group: " + group.getId());
	}

	
	//
	// HELPER methods
	//
	
	private Set<StdPDPGroup>	readProperties(Path repository, Properties properties) throws PAPException {
		Set<StdPDPGroup> groups = new HashSet<StdPDPGroup>();
		//
		// See if there is a groups property
		//
		String groupList = properties.getProperty(PROP_PAP_GROUPS, "");
		if (groupList == null) {
			logger.warn("null group list " + PROP_PAP_GROUPS);
			groupList = "";
		}
		if (logger.isDebugEnabled()) {
			logger.debug("group list: " + groupList);
		}
		//
		// Iterate the groups, converting to a set ensures we have unique groups.
		//
		for (String id : Splitter.on(',').trimResults().omitEmptyStrings().split(groupList)) {
			//
			// Add our Group Object
			//
			StdPDPGroup g = new StdPDPGroup(id.trim(), 
								id.equals(properties.getProperty(PROP_PAP_GROUPS_DEFAULT, PROP_PAP_GROUPS_DEFAULT_NAME)), 
								properties,
								Paths.get(repository.toString(), id));

			//
			// Add it in
			//
			groups.add(g);
		}
		//
		// Dump what we got
		//
		if (logger.isDebugEnabled()) {
			logger.debug("PDP Group List: " + groups.toString());
		}
		return groups;
	}
	
	private void saveConfiguration() throws PAPException, IOException {
		//
		// Create our properties object
		//
		Properties properties = new Properties() {
			private static final long serialVersionUID = 1L;
			// For Debugging it is helpful for the file to be in a sorted order,
			// any by returning the keys in the natural Alpha order for strings we get close enough.
			// TreeSet is sorted, and this just overrides the normal Properties method to get the keys.
			@Override
		    public synchronized Enumeration<Object> keys() {
		        return Collections.enumeration(new TreeSet<Object>(super.keySet()));
		    }
	    };
		//
		// Iterate our groups
		//
		List<String> ids = new ArrayList<String>();
		for (PDPGroup group : this.groups) {
			ids.add(group.getId());
			properties.setProperty(group.getId() + ".name", (group.getName() == null ? "" : group.getName()));
			properties.setProperty(group.getId() + ".description", (group.getDescription() == null ? "" : group.getDescription()));
			//
			// Iterate its PDPs
			//
			List<String> pdps = new ArrayList<String>();
			for (PDP pdp : group.getPdps()) {
				pdps.add(pdp.getId());
				properties.setProperty(pdp.getId() + ".name", (pdp.getName() == null ? "" : pdp.getName()));
				properties.setProperty(pdp.getId() + ".description", (pdp.getDescription() == null ? "" : pdp.getDescription()));
				if (pdp instanceof EcompPDP) {
					properties.setProperty(pdp.getId() + ".jmxport", (((EcompPDP)pdp).getJmxPort()==0 ? "" : ((EcompPDP)pdp).getJmxPort()).toString());
				}
			}
			String pdpList = "";
			if (pdps.size() == 1) {
				pdpList = pdps.get(0);
			} else if (pdps.size() > 1) {
				pdpList = Joiner.on(',').skipNulls().join(pdps);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Group " + group.getId() + " PDPS: " + pdpList);
			}
			properties.setProperty(group.getId() + ".pdps", pdpList);
		}
		if (ids.isEmpty()) {
			throw new PAPException("Inconsistency - we have NO groups. We should have at least one.");
		}
		String groupList = "";
		if (ids.size() == 1) {
			groupList = ids.get(0);
		} else if (ids.size() > 1){
			groupList = Joiner.on(',').skipNulls().join(ids);
		}
		logger.info("New Group List: " + groupList);
		
		properties.setProperty(PROP_PAP_GROUPS, groupList);
		//
		// Get the default group
		//
		PDPGroup defaultGroup = this.getDefaultGroup();
		if (defaultGroup == null) {
			throw new PAPException("Invalid state - no default group.");
		}
		properties.setProperty(PROP_PAP_GROUPS_DEFAULT, defaultGroup.getId());
		//
		// Now we can save the file
		//
		Path file = Paths.get(this.repository.toString(), "xacml.properties");
		try (OutputStream os = Files.newOutputStream(file)) {
			properties.store(os, "");
		}
	}
	
	public static void	removeGroupProperties(String id, Properties properties) {
		for (Object key : properties.keySet()) {
			if (key.toString().startsWith(id + ".")) {
				properties.remove(key);
			}
		}
	}
	
	public static void setGroupProperties(PDPGroup group, Properties properties) {
		//
		// make sure its in the list of groups
		//
		Iterable<String> groups = Splitter.on(',').trimResults().omitEmptyStrings().split( properties.getProperty(PROP_PAP_GROUPS, ""));
		boolean inList = false;
		for (String g : groups) {
			if (g.equals(group.getId())) {
				inList = true;
			}
		}
		if (inList == false) {
			Set<String> grps = Sets.newHashSet(groups);
			grps.add(group.getId());
			String newGroupList = "";;
			if (grps.size() == 1) {
				newGroupList = grps.iterator().next();
			} else if (grps.size() > 1) {
				newGroupList = Joiner.on(',').skipNulls().join(grps);
			}
			logger.info("New Group List: " + newGroupList);
			properties.setProperty(PROP_PAP_GROUPS, newGroupList);
		}
		//
		// Set its properties
		//
		properties.setProperty(group.getId() + ".name", group.getName());
		properties.setProperty(group.getId() + ".description", group.getDescription());
		//
		// Set its PDP list
		//
		if (group.getPdps().size() > 0) {
			String pdpList = "";
			if (group.getPdps().size() == 1) {
				pdpList = group.getPdps().iterator().next().getId();
			} else if (group.getPdps().size() > 1) {
				Set<String> ids = new HashSet<String>();
				for (PDP pdp : group.getPdps()) {
					ids.add(pdp.getId());
				}
				pdpList = Joiner.on(',').skipNulls().join(ids);
			}
			properties.setProperty(group.getId() + ".pdps", pdpList);
		} else {
			properties.setProperty(group.getId() + ".pdps", "");
		}
	}

	
	public void changed() {
		if (logger.isDebugEnabled()) {
			logger.debug("changed");
		}
		this.doSave();
		this.fireChanged();
	}

	public void groupChanged(EcompPDPGroup group) {
		if (logger.isDebugEnabled()) {
			logger.debug("groupChanged: " + group);
		}
		this.doSave();
		this.firePDPGroupChanged(group);
	}


	public void pdpChanged(EcompPDP pdp) {
		if (logger.isDebugEnabled()) {
			logger.debug("pdpChanged: " + pdp);
		}
		this.doSave();
		this.firePDPChanged(pdp);
	}

	private void doSave() {
		try {
			//
			// Save the configuration
			//
			this.saveConfiguration();
		} catch (IOException e) {
			PolicyLogger.error(MessageCodes.ERROR_PROCESS_FLOW, e, "StdEngine", "Failed to save configuration");
		} catch (PAPException e) {
			PolicyLogger.error(MessageCodes.ERROR_PROCESS_FLOW, e, "StdEngine", "Failed to save configuration");
		}		
	}
	
	private Properties setPIPProperties(Properties props){
		props.setProperty(XACMLProperties.PROP_PIP_ENGINES, "AAF");
		props.setProperty("AAF.name", "AAFEngine");
		props.setProperty("AAF.description", "AAFEngine to communicate with AAF to take decisions");
		props.setProperty("AAF.classname","org.openecomp.policy.xacml.std.pip.engines.aaf.AAFEngine");
		// read from PIP properties file. 
		Path file = Paths.get(pipPropertyFile);
		if (!Files.notExists(file)) {
			InputStream in;
			Properties prop = new Properties();
			try {
				in = new FileInputStream(file.toFile());
				prop.load(in);
			} catch (IOException e) {
				PolicyLogger.error(XACMLErrorConstants.ERROR_SYSTEM_ERROR + "can not load the pip properties from file" +e);
			}
			props = prop;
		}
		return props;
	}

	
	@Override
	public Set<EcompPDPGroup> getEcompPDPGroups() throws PAPException {
		final Set<EcompPDPGroup> grps = new HashSet<EcompPDPGroup>();
		for (EcompPDPGroup g : this.groups) {
			grps.add(g);
		}
		return Collections.unmodifiableSet(grps);
	}

	@Override
	public EcompPDPGroup getPDPGroup(EcompPDP pdp) throws PAPException {
		for (EcompPDPGroup group : this.groups) {
			if (group.getPdps().contains(pdp)) {
				return group;
			}
		}
		return null;
	}

	@Override
	public void SetDefaultGroup(EcompPDPGroup group) throws PAPException {
		boolean changesMade = false;
		for (EcompPDPGroup aGroup : groups) {
			if (aGroup.getId().equals(group.getId())) {
				if ( ! aGroup.isDefaultGroup()) {
					if (aGroup instanceof StdPDPGroup) {
						((StdPDPGroup) aGroup).setDefault(true);
						changesMade = true;
					} else {
						throw new IllegalArgumentException("Group in groups of unknown type '" + aGroup.getClass().getName() + "'");
					}
				}
			} else {
				// not the new default group
				if (aGroup.isDefaultGroup()) {
					if (aGroup instanceof StdPDPGroup) {
						((StdPDPGroup) aGroup).setDefault(false);
						changesMade = true;
					} else {
						throw new IllegalArgumentException("Group in groups of unknown type '" + aGroup.getClass().getName() + "'");
					}
				}
			}
		}
		if (changesMade) {
			this.doSave();
		}
		
		return;	
		
	}

	@Override
	public void newPDP(String id, EcompPDPGroup group, String name, String description, int jmxport)
			throws PAPException, NullPointerException {
		if (group == null) {
			throw new PAPException("You must specify which group the PDP will belong to.");
		}
		if (this.groups.contains(group) == false) {
			throw new PAPException("Unknown group, not in our list.");
		}
		for (EcompPDP p : group.getEcompPdps()) {
			if (p.getId().equals(id)) {
				throw new PAPException("A PDP with this ID exists.");
			}
		}
		if (group instanceof StdPDPGroup) {
			StdPDP pdp = new StdPDP(id, name, description, jmxport);
			if (((StdPDPGroup) group).addPDP(pdp)) {
				//
				// Save the properties and notify any listeners
				//
				pdpChanged(pdp);
				return;
			}
		}
		return;
		
	}

	@Override
	public void updateGroup(EcompPDPGroup group) throws PAPException {
		if (group == null || group.getId() == null) {
			throw new PAPException("Group or id is null");
		}
		if (group.getName() == null || group.getName().trim().length() == 0) {
			throw new PAPException("New name for group cannot be null or blank");
		}
		StdPDPGroup existingGroup = (StdPDPGroup)getGroup(group.getId());
		if (existingGroup == null) {
			throw new PAPException("Update found no existing group with id '" + group.getId() + "'");
		}
		
		
		// We do dramatically different things when the Name changes
		// because the Name is essentially the identity of the group (as the User knows it) so when the Identity changes we have to change the group ID.
		if (group.getName().equals(existingGroup.getName())) {

			// update the disk
			try {
				((StdPDPGroup)group).saveGroupConfiguration();
			} catch (IOException e) {
				throw new PAPException("Unable to save new configuration for '" + group.getName() + "': " + e.getMessage());
			}
			// update the group in the set by simply replacing the old instance with the new one
			this.groups.remove(existingGroup);
			this.groups.add((StdPDPGroup)group);
			
		} else {
			// the name/identity of the group has changed
			// generate the new id
			String newId = createNewPDPGroupId(group.getName());
			
			// make sure no other group uses the new id
			for (EcompPDPGroup g : groups) {
				if (g.getId().equals(newId)) {
					throw new PAPException("Replacement name maps to ID '" + newId + "' which is already in use");
				}
			}
			((StdPDPGroup)group).setId(newId);

			// rename the existing directory to the new id
			Path oldPath = existingGroup.getDirectory();
			Path newPath = Paths.get(oldPath.getParent().toString(), newId);
			((StdPDPGroup)group).setDirectory(newPath);
			
			try {
				boolean success = oldPath.toFile().renameTo(newPath.toFile());
				if ( ! success) {
					throw new PAPException("Unable to rename directory; reason unknown");
				}
			} catch (Exception e) {
				PolicyLogger.error(MessageCodes.ERROR_PROCESS_FLOW, e, "StdEngine", "Unable to rename directory");
				throw new PAPException("Unable to move directory from '" + oldPath + "' to '" + newPath + "': " + e.getMessage());
			}
			// update the disk
			try {
				((StdPDPGroup)group).saveGroupConfiguration();
			} catch (IOException e) {
				throw new PAPException("Unable to save new configuration for '" + group.getName() + "': " + e.getMessage());
			}
			
			// save the new group into the Set
			groups.remove(existingGroup);
			groups.add((StdPDPGroup)group);
			
		}
		
		// perhaps only the group changed, but if the name/id changed it may look to a listener like more than one group
		changed();

		
	}

	@Override
	public void removeGroup(EcompPDPGroup group, EcompPDPGroup newGroup) throws PAPException, NullPointerException {
		if (group == null) {
			throw new NullPointerException();
		}
		//
		// Does this group exist?
		//
		if (this.groups.contains(group) == false) {
			PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE + "This group doesn't exist.");
			throw new PAPException("The group '" + group.getId() + "' does not exist");
		}
		//
		// Is it the default group?
		//
		if (group.isDefaultGroup()) {
			throw new PAPException("You cannot delete the default group.");
		}
		Set<EcompPDP> pdps = group.getEcompPdps();
		//
		// Are there PDPs? If so, then we need a target group
		//
		if (pdps.isEmpty() == false && newGroup == null) {
			throw new NullPointerException("Group targeted for deletion has PDPs, you must provide a new group for them.");
		}
		//
		// Move the PDPs
		//
		if (pdps.isEmpty() == false) {
			if (! (newGroup instanceof StdPDPGroup)) {
				throw new PAPException("Unexpected class for newGroup: " + newGroup.getClass().getCanonicalName());
			}
			// The movePDP function will modify the set of PDPs in the group.
			// To avoid concurrent modification exceptions we need to duplicate the list before calling that function.
			List<EcompPDP> pdpList = new ArrayList<EcompPDP>();
			for (EcompPDP pdp : pdps) {
				pdpList.add(pdp);
			}
			// now we can use the PDPs from the list without having ConcurrentAccessExceptions
			for (EcompPDP pdp : pdpList) {
				this.movePDP(pdp, newGroup);
			}
		}
		//
		// remove the directory for the group
		//
		String id = group.getId();
		Path groupPath = Paths.get(this.repository.toString(), id);
		//
		// If it exists already
		//
		if ( ! Files.exists(groupPath)) {
			logger.warn("removeGroup " + id + " directory does not exist" + groupPath.toString());
		} else {
			try {
				Files.walkFileTree(groupPath, new SimpleFileVisitor<Path>() {

					@Override
					public FileVisitResult visitFile(Path file,
							BasicFileAttributes attrs) throws IOException {
						Files.delete(file);
						return super.visitFile(file, attrs);
					}
					
				});
				//
				// delete the directory
				//
				Files.delete(groupPath);
			} catch (IOException e) {
				PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine", "Failed to delete " + groupPath);
				throw new PAPException("Failed to delete " + id);
			}
		}
		
		// remove the group from the set of all groups
		groups.remove(group);
		
		//
		// Save changes
		//
		changed();
		this.doSave();
		return;
		
	}

}