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