Unit/SONAR/Checkstyle in ONAP-REST
[policy/engine.git] / ONAP-XACML / src / main / java / org / onap / policy / xacml / std / pap / StdEngine.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP-XACML
4  * ================================================================================
5  * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.xacml.std.pap;
22
23 import com.att.research.xacml.api.pap.PAPException;
24 import com.att.research.xacml.api.pap.PDP;
25 import com.att.research.xacml.api.pap.PDPGroup;
26 import com.att.research.xacml.api.pap.PDPPIPConfig;
27 import com.att.research.xacml.api.pap.PDPPolicy;
28 import com.att.research.xacml.api.pap.PDPStatus;
29 import com.att.research.xacml.util.XACMLProperties;
30 import com.google.common.base.Joiner;
31 import com.google.common.base.Splitter;
32 import com.google.common.collect.Sets;
33 import java.io.FileInputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.OutputStream;
37 import java.nio.file.FileVisitResult;
38 import java.nio.file.Files;
39 import java.nio.file.Path;
40 import java.nio.file.Paths;
41 import java.nio.file.SimpleFileVisitor;
42 import java.nio.file.attribute.BasicFileAttributes;
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.Enumeration;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Properties;
49 import java.util.Set;
50 import java.util.TreeSet;
51 import org.apache.commons.logging.Log;
52 import org.apache.commons.logging.LogFactory;
53 import org.onap.policy.common.logging.eelf.MessageCodes;
54 import org.onap.policy.common.logging.eelf.PolicyLogger;
55 import org.onap.policy.xacml.api.XACMLErrorConstants;
56 import org.onap.policy.xacml.api.pap.OnapPDP;
57 import org.onap.policy.xacml.api.pap.OnapPDPGroup;
58 import org.onap.policy.xacml.api.pap.PAPPolicyEngine;
59
60 /**
61  * This is a simple PAP engine that uses some property files and a simple directory structure in the file system to
62  * manage a policy repository and set of PDP nodes.
63  *
64  *
65  */
66 public class StdEngine extends StdPDPItemSetChangeNotifier implements PAPPolicyEngine {
67     public static final String pipPropertyFile = "pip.properties";
68
69     private static final String addGroup = "addGroup ";
70
71     private static Log logger = LogFactory.getLog(StdEngine.class);
72
73     public static final String PROP_PAP_REPO = "xacml.pap.pdps";
74     public static final String PROP_PAP_GROUPS = "xacml.pap.groups";
75     public static final String PROP_PAP_GROUPS_DEFAULT = "xacml.pap.groups.default";
76     public static final String PROP_PAP_GROUPS_DEFAULT_NAME = "default";
77     // this value will be accessed from XacmlPapServlet so that we know if a default group did not exist
78     // and was just added. This way, we can add the new group to the database.
79     public boolean wasDefaultGroupJustAdded = false;
80
81     protected final Path repository;
82     protected Set<StdPDPGroup> groups;
83
84     public StdEngine() throws PAPException, IOException {
85         //
86         // Get the location in the file system of our repository
87         //
88         this.repository = Paths.get(XACMLProperties.getProperty(PROP_PAP_REPO));
89         //
90         // Initialize
91         //
92         this.intialize();
93     }
94
95     public StdEngine(Properties properties) throws PAPException, IOException {
96         //
97         // Get the location in the file system of our repository
98         //
99         this.repository = Paths.get(properties.getProperty(PROP_PAP_REPO));
100         //
101         // Initialize
102         //
103         this.intialize();
104     }
105
106     public StdEngine(Path repository) throws PAPException, IOException {
107         //
108         // Save our location
109         //
110         this.repository = repository;
111         //
112         // Initialize
113         //
114         this.intialize();
115     }
116
117     private void intialize() throws PAPException, IOException {
118         //
119         // Sanity check the repository path
120         //
121         if (this.repository == null) {
122             throw new PAPException("No repository specified.");
123         }
124         if (Files.notExists(this.repository)) {
125             Files.createDirectory(repository);
126         }
127         if (!Files.isDirectory(this.repository)) {
128             throw new PAPException("Repository is NOT a directory: " + this.repository.toAbsolutePath());
129         }
130         if (!Files.isWritable(this.repository)) {
131             throw new PAPException("Repository is NOT writable: " + this.repository.toAbsolutePath());
132         }
133         //
134         // Load our groups
135         //
136         this.loadGroups();
137     }
138
139     private void loadGroups() throws PAPException {
140         //
141         // Create a properties object
142         //
143         Properties properties = new Properties();
144         Path file = Paths.get(this.repository.toString(), XACMLProperties.XACML_PROPERTIES_NAME);
145         try {
146             //
147             // Load the properties
148             //
149             try (InputStream is = new FileInputStream(file.toFile())) {
150                 properties.load(is);
151             }
152
153             //
154             // Parse it
155             //
156             this.groups = this.readProperties(this.repository, properties);
157         } catch (IOException e) {
158             PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine", "Failed to load properties file");
159             this.groups = new HashSet<>();
160         }
161         //
162         // Initialize the default group
163         //
164         PDPGroup defaultGroup = this.initializeDefaultGroup(file, properties);
165         logger.info("Default group is: " + defaultGroup.getId() + "=" + defaultGroup.getName());
166     }
167
168     private PDPGroup initializeDefaultGroup(Path file, Properties properties) throws PAPException {
169         wasDefaultGroupJustAdded = false;
170         //
171         // Make sure we have the default group
172         //
173         PDPGroup group = this.getDefaultGroup();
174         if (group != null) {
175             wasDefaultGroupJustAdded = true;
176             return group;
177         }
178         //
179         // We don't have the default group, create it
180         //
181         String defaultId = properties.getProperty(PROP_PAP_GROUPS_DEFAULT, PROP_PAP_GROUPS_DEFAULT_NAME);
182         if ("".equals(defaultId)) {
183             defaultId = PROP_PAP_GROUPS_DEFAULT_NAME;
184         }
185         logger.warn("Default group does NOT exist, creating " + defaultId);
186         Path defaultPath = Paths.get(this.repository.toString(), defaultId);
187         try {
188             //
189             // Does it exist?
190             //
191             if (Files.notExists(defaultPath)) {
192                 //
193                 // Create its directory
194                 //
195                 Files.createDirectory(defaultPath);
196                 //
197                 // Create property files
198                 //
199                 {
200                     Properties props = new Properties();
201                     props.setProperty(XACMLProperties.PROP_REFERENCEDPOLICIES, "");
202                     props.setProperty(XACMLProperties.PROP_ROOTPOLICIES, "");
203                     Path policyPath = Paths.get(defaultPath.toAbsolutePath().toString(), "xacml.policy.properties");
204                     Files.createFile(policyPath);
205                     try (OutputStream os = Files.newOutputStream(policyPath)) {
206                         props.store(os, "");
207                     } catch (IOException e) {
208                         PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine",
209                                 "Failed to write default policy properties");
210                     }
211                 }
212                 {
213                     Properties props = new Properties();
214                     props = setPIPProperties(props);
215                     Path pipPath = Paths.get(defaultPath.toAbsolutePath().toString(), "xacml.pip.properties");
216                     Files.createFile(pipPath);
217                     try (OutputStream os = Files.newOutputStream(pipPath)) {
218                         props.store(os, "");
219                     } catch (IOException e) {
220                         PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine",
221                                 "Failed to write default pip properties");
222                     }
223                 }
224             }
225             //
226             // Create the default group
227             //
228             StdPDPGroup newDefault = new StdPDPGroup(defaultId, true, "default",
229                     "The default group where new PDP's are put.", defaultPath);
230             //
231             // Add it to our list
232             //
233             this.groups.add(newDefault);
234             //
235             // Save our properties out since we have
236             // a new default group.
237             //
238             StdEngine.setGroupProperties(newDefault, properties);
239             //
240             // Save it to disk
241             //
242             try {
243                 try (OutputStream os = Files.newOutputStream(file)) {
244                     properties.store(os, "");
245                 }
246             } catch (IOException e) {
247                 PolicyLogger.error(MessageCodes.EXCEPTION_ERROR, e, "StdEngine",
248                         "Failed to save properties with new default group information.");
249             }
250             //
251             // Return it
252             //
253             wasDefaultGroupJustAdded = true;
254             return newDefault;
255         } catch (IOException e) {
256             PolicyLogger.error(MessageCodes.EXCEPTION_ERROR, e, "StdEngine", "Failed to create default group");
257             throw new PAPException("Failed to create default group");
258         }
259     }
260
261     @Override
262     public OnapPDPGroup getDefaultGroup() throws PAPException {
263         for (OnapPDPGroup group : this.groups) {
264             if (group.isDefaultGroup()) {
265                 return group;
266             }
267         }
268         //
269         // Default group doesn't exist
270         //
271         return null;
272     }
273
274     @Override
275     public OnapPDPGroup getGroup(String id) throws PAPException {
276         for (OnapPDPGroup g : this.groups) {
277             if (g.getId().equals(id)) {
278                 return g;
279             }
280         }
281         return null;
282     }
283
284     @Override
285     public void newGroup(String name, String description) throws PAPException, NullPointerException {
286         //
287         // Null check
288         //
289         if (name == null) {
290             throw new NullPointerException();
291         }
292         //
293         // Do we already have this group?
294         //
295         for (PDPGroup group : this.groups) {
296             if (group.getName().equals(name)) {
297                 throw new PAPException("Group with this name=" + name + " already exists.");
298             }
299         }
300
301
302         // create an Id that can be used as a file name and a properties file key.
303         // Ids must not contain \/:*?"<>|=,;
304         // The ID must also be unique within the current set of PDPGroups.
305         String id = createNewPDPGroupId(name);
306
307
308         //
309         // Construct the directory path
310         //
311         Path groupPath = Paths.get(this.repository.toString(), id);
312         //
313         // If it exists already
314         //
315         if (Files.exists(groupPath)) {
316             logger.warn(addGroup + id + " directory exists");
317         } else {
318             try {
319                 //
320                 // Create the directory
321                 //
322                 Files.createDirectory(groupPath);
323             } catch (IOException e) {
324                 PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine", "Failed to create " + groupPath);
325                 throw new PAPException("Failed to create " + id);
326             }
327         }
328         //
329         // Create the Policies
330         //
331
332         Path policyProperties = Paths.get(groupPath.toString(), "xacml.policy.properties");
333         if (Files.exists(policyProperties)) {
334             logger.warn(addGroup + id + " file exists");
335         } else {
336             Properties props = new Properties();
337             props.setProperty(XACMLProperties.PROP_REFERENCEDPOLICIES, "");
338             props.setProperty(XACMLProperties.PROP_ROOTPOLICIES, "");
339             try {
340                 Files.createFile(policyProperties);
341                 try (OutputStream os = Files.newOutputStream(policyProperties)) {
342                     props.store(os, "");
343                 }
344             } catch (IOException e) {
345                 PolicyLogger.error(MessageCodes.EXCEPTION_ERROR, e, "StdEngine", "Failed to create policyProperties");
346                 throw new PAPException("Failed to create " + id);
347             }
348         }
349         //
350         // Create the PIP config
351         //
352         Path pipProperties = Paths.get(groupPath.toString(), "xacml.pip.properties");
353         Properties props = new Properties();
354         if (Files.exists(pipProperties)) {
355             logger.warn(addGroup + id + " file exists.");
356         } else {
357             try {
358                 props = setPIPProperties(props);
359                 Files.createFile(pipProperties);
360                 try (OutputStream os = Files.newOutputStream(pipProperties)) {
361                     props.store(os, "");
362                 }
363             } catch (IOException e) {
364                 PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine", "Failed to create pipProperties");
365                 throw new PAPException("Failed to create " + id);
366             }
367
368         }
369         //
370         // Ok now add it
371         //
372         StdPDPGroup newGroup = new StdPDPGroup(id, name, description, groupPath);
373         // Add the default PIP configuration.
374         String list = props.getProperty(XACMLProperties.PROP_PIP_ENGINES);
375         if (list != null && list.length() > 0) {
376             Set<PDPPIPConfig> pipConfigs = new HashSet<>();
377             for (String pipID : list.split("[,]")) {
378                 StdPDPPIPConfig config = new StdPDPPIPConfig(pipID, props);
379                 if (config.isConfigured()) {
380                     pipConfigs.add(config);
381                 }
382             }
383             newGroup.setPipConfigs(pipConfigs);
384         }
385         if (this.groups.add(newGroup)) {
386             // save the new group in our properties and notify any listeners of the change
387             groupChanged(newGroup);
388         }
389
390     }
391
392
393
394     /**
395      * 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
396      * is then this method throws an exception. The name is supposed to be unique within the current set of groups, so
397      * creating the ID based on the name will create a unique string.
398      *
399      * @param name
400      * @return
401      */
402     private String createNewPDPGroupId(String name) {
403         String id = name;
404         // replace "bad" characters with sequences that will be ok for file names and properties keys.
405         id = id.replace(" ", "_sp_");
406         id = id.replace("\t", "_tab_");
407         id = id.replace("\\", "_bksl_");
408         id = id.replace("/", "_sl_");
409         id = id.replace(":", "_col_");
410         id = id.replace("*", "_ast_");
411         id = id.replace("?", "_q_");
412         id = id.replace("\"", "_quo_");
413         id = id.replace("<", "_lt_");
414         id = id.replace(">", "_gt_");
415         id = id.replace("|", "_bar_");
416         id = id.replace("=", "_eq_");
417         id = id.replace(",", "_com_");
418         id = id.replace(";", "_scom_");
419
420         return id;
421     }
422
423
424     @Override
425     public OnapPDP getPDP(String pdpId) throws PAPException {
426         for (OnapPDPGroup group : this.groups) {
427             for (OnapPDP pdp : group.getOnapPdps()) {
428                 if (pdp.getId().equals(pdpId)) {
429                     return pdp;
430                 }
431             }
432         }
433         return null;
434     }
435
436
437     @Override
438     public void movePDP(OnapPDP pdp, OnapPDPGroup newGroup) throws PAPException {
439         if (newGroup == null) {
440             throw new NullPointerException("You must specify which group the PDP will belong to.");
441         }
442         PDPGroup currentGroup = this.getPDPGroup(pdp);
443         if (currentGroup == null) {
444             throw new PAPException("PDP must already belong to a group.");
445         }
446         if (currentGroup.equals(newGroup)) {
447             logger.warn("Already in that group.");
448             return;
449         }
450         if (currentGroup instanceof StdPDPGroup && newGroup instanceof StdPDPGroup) {
451             if (((StdPDPGroup) currentGroup).removePDP(pdp)) {
452                 boolean result = ((StdPDPGroup) newGroup).addPDP(pdp);
453                 if (result) {
454                     //
455                     // Save the configuration
456                     //
457                     this.doSave();
458                 } else {
459                     PolicyLogger.error("Failed to add to new group, putting back into original group.");
460                     if (!((StdPDPGroup) currentGroup).removePDP(pdp)) {
461                         PolicyLogger
462                                 .error(MessageCodes.ERROR_DATA_ISSUE + "Failed to put PDP back into original group.");
463                     }
464                 }
465             }
466         } else {
467             String message = "Unknown PDP group class: " + newGroup.getClass().getCanonicalName() + " and "
468                     + currentGroup.getClass().getCanonicalName();
469             logger.warn(message);
470             throw new PAPException(message);
471         }
472     }
473
474
475     @Override
476     public void updatePDP(OnapPDP pdp) throws PAPException {
477         PDP currentPDP = this.getPDP(pdp.getId());
478         if (currentPDP == null) {
479             String message = "Unknown PDP id '" + pdp.getId() + "'";
480             logger.warn(message);
481             throw new PAPException(message);
482         }
483
484         // the only things that the user can change are name and description
485         currentPDP.setDescription(pdp.getDescription());
486         currentPDP.setName(pdp.getName());
487         if (currentPDP instanceof OnapPDP) {
488             ((OnapPDP) currentPDP).setJmxPort(pdp.getJmxPort());
489         }
490         this.doSave();
491     }
492
493     @Override
494     public void removePDP(OnapPDP pdp) throws PAPException {
495         PDPGroup group = this.getPDPGroup(pdp);
496         if (group == null) {
497             throw new NullPointerException();
498         }
499         if (group instanceof StdPDPGroup) {
500             boolean result = ((StdPDPGroup) group).removePDP(pdp);
501             if (result) {
502                 this.doSave();
503             }
504             return;
505         }
506         String message = "Unknown PDP group class: " + group.getClass().getCanonicalName();
507         logger.warn(message);
508         throw new PAPException(message);
509     }
510
511
512     @Override
513     /**
514      * Should never be called - Detailed status is held on the PDP, not the PAP
515      */
516     public PDPStatus getStatus(OnapPDP pdp) throws PAPException {
517         return getPDP(pdp.getId()).getStatus();
518     }
519
520     @Override
521     public void publishPolicy(String id, String name, boolean isRoot, InputStream policy, OnapPDPGroup group)
522             throws PAPException {
523         if (group == null) {
524             throw new NullPointerException();
525         }
526         if (group instanceof StdPDPGroup && this.groups.contains(group)) {
527             ((StdPDPGroup) group).publishPolicy(id, name, isRoot, policy);
528             return;
529         }
530         logger.warn("unknown PDP Group: " + group);
531         throw new PAPException("Unknown PDP Group: " + group.getId());
532     }
533
534
535     @Override
536     public void copyPolicy(PDPPolicy policy, OnapPDPGroup group) throws PAPException {
537         //
538         // Currently not used on the PAP side. This is done by ((StdPDPGroup) group).copyPolicyToFile
539         //
540     }
541
542
543     @Override
544     public void removePolicy(PDPPolicy policy, OnapPDPGroup group) throws PAPException {
545         if (group == null) {
546             throw new NullPointerException();
547         }
548         if (group instanceof StdPDPGroup && this.groups.contains(group)) {
549             ((StdPDPGroup) group).removePolicy(policy);
550             return;
551         }
552         logger.warn("unknown PDP Group: " + group);
553         throw new PAPException("Unknown PDP Group: " + group.getId());
554     }
555
556
557     //
558     // HELPER methods
559     //
560
561     private Set<StdPDPGroup> readProperties(Path repository, Properties properties) throws PAPException {
562         Set<StdPDPGroup> pdpGroups = new HashSet<>();
563         //
564         // See if there is a groups property
565         //
566         String groupList = properties.getProperty(PROP_PAP_GROUPS, "");
567         if (groupList == null) {
568             logger.warn("null group list " + PROP_PAP_GROUPS);
569             groupList = "";
570         }
571         if (logger.isDebugEnabled()) {
572             logger.debug("group list: " + groupList);
573         }
574         //
575         // Iterate the groups, converting to a set ensures we have unique groups.
576         //
577         for (String id : Splitter.on(',').trimResults().omitEmptyStrings().split(groupList)) {
578             //
579             // Add our Group Object
580             //
581             StdPDPGroup g = new StdPDPGroup(id.trim(),
582                     id.equals(properties.getProperty(PROP_PAP_GROUPS_DEFAULT, PROP_PAP_GROUPS_DEFAULT_NAME)),
583                     properties, Paths.get(repository.toString(), id));
584
585             //
586             // Add it in
587             //
588             pdpGroups.add(g);
589         }
590         //
591         // Dump what we got
592         //
593         if (logger.isDebugEnabled()) {
594             logger.debug("PDP Group List: " + pdpGroups.toString());
595         }
596         return pdpGroups;
597     }
598
599     private void saveConfiguration() throws PAPException, IOException {
600         //
601         // Create our properties object
602         //
603         Properties properties = new Properties() {
604             private static final long serialVersionUID = 1L;
605
606             // For Debugging it is helpful for the file to be in a sorted order,
607             // any by returning the keys in the natural Alpha order for strings we get close enough.
608             // TreeSet is sorted, and this just overrides the normal Properties method to get the keys.
609             @Override
610             public synchronized Enumeration<Object> keys() {
611                 return Collections.enumeration(new TreeSet<Object>(super.keySet()));
612             }
613         };
614         //
615         // Iterate our groups
616         //
617         List<String> ids = new ArrayList<>();
618         for (PDPGroup group : this.groups) {
619             ids.add(group.getId());
620             properties.setProperty(group.getId() + ".name", group.getName() == null ? "" : group.getName());
621             properties.setProperty(group.getId() + ".description",
622                     group.getDescription() == null ? "" : group.getDescription());
623             //
624             // Iterate its PDPs
625             //
626             List<String> pdps = new ArrayList<>();
627             for (PDP pdp : group.getPdps()) {
628                 pdps.add(pdp.getId());
629                 properties.setProperty(pdp.getId() + ".name", pdp.getName() == null ? "" : pdp.getName());
630                 properties.setProperty(pdp.getId() + ".description",
631                         pdp.getDescription() == null ? "" : pdp.getDescription());
632                 if (pdp instanceof OnapPDP) {
633                     properties.setProperty(pdp.getId() + ".jmxport",
634                             (((OnapPDP) pdp).getJmxPort() == 0 ? "" : ((OnapPDP) pdp).getJmxPort()).toString());
635                 }
636             }
637             String pdpList = "";
638             if (pdps.size() == 1) {
639                 pdpList = pdps.get(0);
640             } else if (pdps.size() > 1) {
641                 pdpList = Joiner.on(',').skipNulls().join(pdps);
642             }
643             if (logger.isDebugEnabled()) {
644                 logger.debug("Group " + group.getId() + " PDPS: " + pdpList);
645             }
646             properties.setProperty(group.getId() + ".pdps", pdpList);
647         }
648         if (ids.isEmpty()) {
649             throw new PAPException("Inconsistency - we have NO groups. We should have at least one.");
650         }
651         String groupList = "";
652         if (ids.size() == 1) {
653             groupList = ids.get(0);
654         } else if (ids.size() > 1) {
655             groupList = Joiner.on(',').skipNulls().join(ids);
656         }
657         logger.info("New Group List: " + groupList);
658
659         properties.setProperty(PROP_PAP_GROUPS, groupList);
660         //
661         // Get the default group
662         //
663         PDPGroup defaultGroup = this.getDefaultGroup();
664         if (defaultGroup == null) {
665             throw new PAPException("Invalid state - no default group.");
666         }
667         properties.setProperty(PROP_PAP_GROUPS_DEFAULT, defaultGroup.getId());
668         //
669         // Now we can save the file
670         //
671         Path file = Paths.get(this.repository.toString(), "xacml.properties");
672         try (OutputStream os = Files.newOutputStream(file)) {
673             properties.store(os, "");
674         }
675     }
676
677     public static void removeGroupProperties(String id, Properties properties) {
678         for (Object key : properties.keySet()) {
679             if (key.toString().startsWith(id + ".")) {
680                 properties.remove(key);
681             }
682         }
683     }
684
685     public static void setGroupProperties(PDPGroup group, Properties properties) {
686         //
687         // make sure its in the list of groups
688         //
689         Iterable<String> groups =
690                 Splitter.on(',').trimResults().omitEmptyStrings().split(properties.getProperty(PROP_PAP_GROUPS, ""));
691         boolean inList = false;
692         for (String g : groups) {
693             if (g.equals(group.getId())) {
694                 inList = true;
695             }
696         }
697         if (!inList) {
698             Set<String> grps = Sets.newHashSet(groups);
699             grps.add(group.getId());
700             String newGroupList;
701             if (grps.size() == 1) {
702                 newGroupList = grps.iterator().next();
703             } else if (grps.size() > 1) {
704                 newGroupList = Joiner.on(',').skipNulls().join(grps);
705             } else {
706                 newGroupList = "";
707             }
708             logger.info("New Group List: " + newGroupList);
709             properties.setProperty(PROP_PAP_GROUPS, newGroupList);
710         }
711         //
712         // Set its properties
713         //
714         properties.setProperty(group.getId() + ".name", group.getName());
715         properties.setProperty(group.getId() + ".description", group.getDescription());
716         //
717         // Set its PDP list
718         //
719         if (!group.getPdps().isEmpty()) {
720             String pdpList = "";
721             if (group.getPdps().size() == 1) {
722                 pdpList = group.getPdps().iterator().next().getId();
723             } else if (group.getPdps().size() > 1) {
724                 Set<String> ids = new HashSet<>();
725                 for (PDP pdp : group.getPdps()) {
726                     ids.add(pdp.getId());
727                 }
728                 pdpList = Joiner.on(',').skipNulls().join(ids);
729             }
730             properties.setProperty(group.getId() + ".pdps", pdpList);
731         } else {
732             properties.setProperty(group.getId() + ".pdps", "");
733         }
734     }
735
736
737     public void changed() {
738         if (logger.isDebugEnabled()) {
739             logger.debug("changed");
740         }
741         this.doSave();
742         this.fireChanged();
743     }
744
745     public void groupChanged(OnapPDPGroup group) {
746         if (logger.isDebugEnabled()) {
747             logger.debug("groupChanged: " + group);
748         }
749         this.doSave();
750         this.firePDPGroupChanged(group);
751     }
752
753
754     public void pdpChanged(OnapPDP pdp) {
755         if (logger.isDebugEnabled()) {
756             logger.debug("pdpChanged: " + pdp);
757         }
758         this.doSave();
759         this.firePDPChanged(pdp);
760     }
761
762     private void doSave() {
763         try {
764             //
765             // Save the configuration
766             //
767             this.saveConfiguration();
768         } catch (IOException | PAPException e) {
769             PolicyLogger.error(MessageCodes.ERROR_PROCESS_FLOW, e, "StdEngine", "Failed to save configuration");
770         }
771     }
772
773     private Properties setPIPProperties(Properties props) {
774         props.setProperty(XACMLProperties.PROP_PIP_ENGINES, "AAF");
775         props.setProperty("AAF.name", "AAFEngine");
776         props.setProperty("AAF.description", "AAFEngine to communicate with AAF to take decisions");
777         props.setProperty("AAF.classname", "org.onap.policy.xacml.std.pip.engines.aaf.AAFEngine");
778         // read from PIP properties file.
779         Path file = Paths.get(pipPropertyFile);
780         if (!Files.notExists(file)) {
781             InputStream in;
782             Properties prop = new Properties();
783             try {
784                 in = new FileInputStream(file.toFile());
785                 prop.load(in);
786             } catch (IOException e) {
787                 PolicyLogger.error(
788                         XACMLErrorConstants.ERROR_SYSTEM_ERROR + "can not load the pip properties from file" + e);
789             }
790             props = prop;
791         }
792         return props;
793     }
794
795
796     @Override
797     public Set<OnapPDPGroup> getOnapPDPGroups() throws PAPException {
798         final Set<OnapPDPGroup> grps = new HashSet<>();
799         for (OnapPDPGroup g : this.groups) {
800             grps.add(g);
801         }
802         return Collections.unmodifiableSet(grps);
803     }
804
805     @Override
806     public OnapPDPGroup getPDPGroup(OnapPDP pdp) throws PAPException {
807         for (OnapPDPGroup group : this.groups) {
808             if (group.getPdps().contains(pdp)) {
809                 return group;
810             }
811         }
812         return null;
813     }
814
815     @Override
816     public void setDefaultGroup(OnapPDPGroup group) throws PAPException {
817         boolean changesMade = false;
818         for (OnapPDPGroup aGroup : groups) {
819             if (aGroup.getId().equals(group.getId())) {
820                 if (!aGroup.isDefaultGroup()) {
821                     if (aGroup instanceof StdPDPGroup) {
822                         ((StdPDPGroup) aGroup).setDefault(true);
823                         changesMade = true;
824                     } else {
825                         throw new IllegalArgumentException(
826                                 "Group in groups of unknown type '" + aGroup.getClass().getName() + "'");
827                     }
828                 }
829             } else {
830                 // not the new default group
831                 if (aGroup.isDefaultGroup()) {
832                     if (aGroup instanceof StdPDPGroup) {
833                         ((StdPDPGroup) aGroup).setDefault(false);
834                         changesMade = true;
835                     } else {
836                         throw new IllegalArgumentException(
837                                 "Group in groups of unknown type '" + aGroup.getClass().getName() + "'");
838                     }
839                 }
840             }
841         }
842         if (changesMade) {
843             this.doSave();
844         }
845
846         return;
847
848     }
849
850     @Override
851     public void newPDP(String id, OnapPDPGroup group, String name, String description, int jmxport)
852             throws PAPException, NullPointerException {
853         if (group == null) {
854             throw new PAPException("You must specify which group the PDP will belong to.");
855         }
856         if (!this.groups.contains(group)) {
857             throw new PAPException("Unknown group, not in our list.");
858         }
859         for (OnapPDP p : group.getOnapPdps()) {
860             if (p.getId().equals(id)) {
861                 throw new PAPException("A PDP with this ID exists.");
862             }
863         }
864         if (group instanceof StdPDPGroup) {
865             StdPDP pdp = new StdPDP(id, name, description, jmxport);
866             if (((StdPDPGroup) group).addPDP(pdp)) {
867                 //
868                 // Save the properties and notify any listeners
869                 //
870                 pdpChanged(pdp);
871                 return;
872             }
873         }
874         return;
875
876     }
877
878     @Override
879     public void updateGroup(OnapPDPGroup group) throws PAPException {
880         if (group == null || group.getId() == null) {
881             throw new PAPException("Group or id is null");
882         }
883         if (group.getName() == null || group.getName().trim().length() == 0) {
884             throw new PAPException("New name for group cannot be null or blank");
885         }
886         StdPDPGroup existingGroup = (StdPDPGroup) getGroup(group.getId());
887         if (existingGroup == null) {
888             throw new PAPException("Update found no existing group with id '" + group.getId() + "'");
889         }
890
891
892         // We do dramatically different things when the Name changes
893         // because the Name is essentially the identity of the group (as the User knows it) so when the Identity changes
894         // we have to change the group ID.
895         if (group.getName().equals(existingGroup.getName())) {
896
897             // update the disk
898             try {
899                 ((StdPDPGroup) group).saveGroupConfiguration();
900             } catch (IOException e) {
901                 throw new PAPException(
902                         "Unable to save new configuration for '" + group.getName() + "': " + e.getMessage(), e);
903             }
904             // update the group in the set by simply replacing the old instance with the new one
905             this.groups.remove(existingGroup);
906             this.groups.add((StdPDPGroup) group);
907
908         } else {
909             // the name/identity of the group has changed
910             // generate the new id
911             String newId = createNewPDPGroupId(group.getName());
912
913             // make sure no other group uses the new id
914             for (OnapPDPGroup g : groups) {
915                 if (g.getId().equals(newId)) {
916                     throw new PAPException("Replacement name maps to ID '" + newId + "' which is already in use");
917                 }
918             }
919             ((StdPDPGroup) group).setId(newId);
920
921             // rename the existing directory to the new id
922             Path oldPath = existingGroup.getDirectory();
923             Path newPath = Paths.get(oldPath.getParent().toString(), newId);
924             ((StdPDPGroup) group).setDirectory(newPath);
925
926             try {
927                 boolean success = oldPath.toFile().renameTo(newPath.toFile());
928                 if (!success) {
929                     throw new PAPException("Unable to rename directory; reason unknown");
930                 }
931             } catch (Exception e) {
932                 PolicyLogger.error(MessageCodes.ERROR_PROCESS_FLOW, e, "StdEngine", "Unable to rename directory");
933                 throw new PAPException(
934                         "Unable to move directory from '" + oldPath + "' to '" + newPath + "': " + e.getMessage(), e);
935             }
936             // update the disk
937             try {
938                 ((StdPDPGroup) group).saveGroupConfiguration();
939             } catch (IOException e) {
940                 throw new PAPException(
941                         "Unable to save new configuration for '" + group.getName() + "': " + e.getMessage(), e);
942             }
943
944             // save the new group into the Set
945             groups.remove(existingGroup);
946             groups.add((StdPDPGroup) group);
947
948         }
949
950         // perhaps only the group changed, but if the name/id changed it may look to a listener like more than one group
951         changed();
952
953
954     }
955
956     @Override
957     public void removeGroup(OnapPDPGroup group, OnapPDPGroup newGroup) throws PAPException, NullPointerException {
958         if (group == null) {
959             throw new NullPointerException();
960         }
961         //
962         // Does this group exist?
963         //
964         if (!this.groups.contains(group)) {
965             PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE + "This group doesn't exist.");
966             throw new PAPException("The group '" + group.getId() + "' does not exist");
967         }
968         //
969         // Is it the default group?
970         //
971         if (group.isDefaultGroup()) {
972             throw new PAPException("You cannot delete the default group.");
973         }
974         Set<OnapPDP> pdps = group.getOnapPdps();
975         //
976         // Are there PDPs? If so, then we need a target group
977         //
978         if (!pdps.isEmpty() && newGroup == null) {
979             throw new NullPointerException(
980                     "Group targeted for deletion has PDPs, you must provide a new group for them.");
981         }
982         //
983         // Move the PDPs
984         //
985         if (!pdps.isEmpty()) {
986             if (!(newGroup instanceof StdPDPGroup)) {
987                 throw new PAPException("Unexpected class for newGroup: " + newGroup.getClass().getCanonicalName());
988             }
989             // The movePDP function will modify the set of PDPs in the group.
990             // To avoid concurrent modification exceptions we need to duplicate the list before calling that function.
991             List<OnapPDP> pdpList = new ArrayList<>();
992             for (OnapPDP pdp : pdps) {
993                 pdpList.add(pdp);
994             }
995             // now we can use the PDPs from the list without having ConcurrentAccessExceptions
996             for (OnapPDP pdp : pdpList) {
997                 this.movePDP(pdp, newGroup);
998             }
999         }
1000         //
1001         // remove the directory for the group
1002         //
1003         String id = group.getId();
1004         Path groupPath = Paths.get(this.repository.toString(), id);
1005         //
1006         // If it exists already
1007         //
1008         if (!Files.exists(groupPath)) {
1009             logger.warn("removeGroup " + id + " directory does not exist" + groupPath.toString());
1010         } else {
1011             try {
1012                 Files.walkFileTree(groupPath, new SimpleFileVisitor<Path>() {
1013
1014                     @Override
1015                     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
1016                         Files.delete(file);
1017                         return super.visitFile(file, attrs);
1018                     }
1019
1020                 });
1021                 //
1022                 // delete the directory
1023                 //
1024                 Files.delete(groupPath);
1025             } catch (IOException e) {
1026                 PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, "StdEngine", "Failed to delete " + groupPath);
1027                 throw new PAPException("Failed to delete " + id);
1028             }
1029         }
1030
1031         // remove the group from the set of all groups
1032         groups.remove(group);
1033
1034         //
1035         // Save changes
1036         //
1037         changed();
1038         this.doSave();
1039         return;
1040
1041     }
1042
1043     @Override
1044     public void updateGroup(OnapPDPGroup group, String userName) throws PAPException {
1045         // To pass the userId for PDP Audit log maintenance.
1046
1047     }
1048
1049 }