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