Fixes for sonar critical issues
[policy/engine.git] / PolicyEngineUtils / src / main / java / org / onap / policy / utils / BackUpMonitor.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * PolicyEngineUtils
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
21 package org.onap.policy.utils;
22
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Date;
26 import java.util.List;
27 import java.util.Properties;
28 import java.util.concurrent.TimeUnit;
29
30 import javax.persistence.EntityManager;
31 import javax.persistence.EntityManagerFactory;
32 import javax.persistence.EntityTransaction;
33 import javax.persistence.Persistence;
34 import javax.persistence.Query;
35
36 import org.apache.log4j.Logger;
37 import org.eclipse.persistence.config.PersistenceUnitProperties;
38 import org.onap.policy.api.PDPNotification;
39 import org.onap.policy.jpa.BackUpMonitorEntity;
40 import org.onap.policy.std.NotificationStore;
41 import org.onap.policy.std.StdPDPNotification;
42
43 import com.fasterxml.jackson.core.JsonProcessingException;
44 import com.fasterxml.jackson.databind.JsonNode;
45 import com.github.fge.jackson.JsonLoader;
46 import com.github.fge.jsonpatch.JsonPatch;
47 import com.github.fge.jsonpatch.JsonPatchException;
48 import com.github.fge.jsonpatch.diff.JsonDiff;
49
50 /**
51  * BackUp Monitor checks Backup Status with the Database and maintains Redundancy for Gateway Applications.
52  * 
53  */
54 public class BackUpMonitor {
55     private static final Logger LOGGER = Logger.getLogger(BackUpMonitor.class.getName());
56     private static final int DEFAULT_PING = 15000; // Value is in milliseconds.
57
58     private static BackUpMonitor instance = null;
59     private static String resourceName = null;
60     private static String resourceNodeName = null;
61     private static String notificationRecord = null;
62     private static String lastMasterNotification = null;
63     private static int pingInterval = DEFAULT_PING;
64     private static Boolean masterFlag = false;
65     private static Object lock = new Object();
66     private static Object notificationLock = new Object();
67     private static BackUpHandler handler = null;
68     private EntityManager em;
69     private EntityManagerFactory emf;
70
71     /*
72      * Enumeration for the Resource Node Naming. Add here if required.
73      */
74     public enum ResourceNode {
75         BRMS, ASTRA
76     }
77
78     private BackUpMonitor(String resourceNodeName, String resourceName, Properties properties, BackUpHandler handler)
79             throws BackUpMonitorException {
80         if (instance == null) {
81             instance = this;
82         }
83         BackUpMonitor.resourceNodeName = resourceNodeName;
84         BackUpMonitor.resourceName = resourceName;
85         BackUpMonitor.handler = handler;
86         // Create Persistence Entity
87         properties.setProperty(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML, "META-INF/persistencePU.xml");
88         emf = Persistence.createEntityManagerFactory("PolicyEngineUtils", properties);
89         if (emf == null) {
90             LOGGER.error("Unable to create Entity Manger Factory ");
91             throw new BackUpMonitorException("Unable to create Entity Manger Factory");
92         }
93         em = emf.createEntityManager();
94
95         // Check Database if this is Master or Slave.
96         checkDataBase();
97
98         // Start thread.
99         Thread t = new Thread(new BMonitor());
100         t.start();
101     }
102
103     /**
104      * Gets the BackUpMonitor Instance if given proper resourceName and properties. Else returns null.
105      * 
106      * @param resourceNodeName
107      *            String format of the Resource Node to which the resource Belongs to.
108      * @param resourceName
109      *            String format of the ResourceName. This needs to be Unique.
110      * @param properties
111      *            Properties format of the properties file.
112      * @return BackUpMonitor instance.
113      */
114     public static synchronized BackUpMonitor getInstance(String resourceNodeName, String resourceName,
115             Properties properties, BackUpHandler handler) throws BackUpMonitorException {
116         if (resourceNodeName == null || resourceNodeName.trim().equals("") || resourceName == null
117                 || resourceName.trim().equals("") || properties == null || handler == null) {
118             LOGGER.error("Error while getting Instance. Please check resourceName and/or properties file");
119             return null;
120         } else if ((resourceNodeName.equals(ResourceNode.ASTRA.toString())
121                 || resourceNodeName.equals(ResourceNode.BRMS.toString())) && validate(properties) && instance == null) {
122             LOGGER.info("Creating Instance of BackUpMonitor");
123             instance = new BackUpMonitor(resourceNodeName, resourceName, properties, handler);
124         }
125         return instance;
126     }
127
128     // This is to validate given Properties with required values.
129     private static Boolean validate(Properties properties) {
130         if (properties.getProperty("javax.persistence.jdbc.driver") == null
131                 || properties.getProperty("javax.persistence.jdbc.driver").trim().equals("")) {
132             LOGGER.error("javax.persistence.jdbc.driver property is empty");
133             return false;
134         }
135         if (properties.getProperty("javax.persistence.jdbc.url") == null
136                 || properties.getProperty("javax.persistence.jdbc.url").trim().equals("")) {
137             LOGGER.error("javax.persistence.jdbc.url property is empty");
138             return false;
139         }
140         if (properties.getProperty("javax.persistence.jdbc.user") == null
141                 || properties.getProperty("javax.persistence.jdbc.user").trim().equals("")) {
142             LOGGER.error("javax.persistence.jdbc.user property is empty");
143             return false;
144         }
145         if (properties.getProperty("javax.persistence.jdbc.password") == null
146                 || properties.getProperty("javax.persistence.jdbc.password").trim().equals("")) {
147             LOGGER.error("javax.persistence.jdbc.password property is empty");
148             return false;
149         }
150         if (properties.getProperty("ping_interval") == null
151                 || properties.getProperty("ping_interval").trim().equals("")) {
152             LOGGER.info("ping_interval property not specified. Taking default value");
153         } else {
154             try {
155                 pingInterval = Integer.parseInt(properties.getProperty("ping_interval").trim());
156             } catch (NumberFormatException e) {
157                 LOGGER.warn("Ignored invalid proeprty ping_interval. Taking default value: " + pingInterval);
158                 pingInterval = DEFAULT_PING;
159             }
160         }
161         return true;
162     }
163
164     // Sets the Flag for masterFlag to either True or False.
165     private static void setFlag(Boolean flag) {
166         synchronized (lock) {
167             masterFlag = flag;
168         }
169     }
170
171     /**
172      * Gets the Boolean value of Master(True) or Slave mode (False)
173      * 
174      * @return Boolean flag which if True means that the operation needs to be performed(Master mode) or if false the
175      *         operation is in slave mode.
176      */
177     public Boolean getFlag() {
178         synchronized (lock) {
179             return masterFlag;
180         }
181     }
182
183     // BackUpMonitor Thread
184     private class BMonitor implements Runnable {
185         @Override
186         public void run() {
187             LOGGER.info("Starting BackUpMonitor Thread.. ");
188             while (true) {
189                 try {
190                     TimeUnit.MILLISECONDS.sleep(pingInterval);
191                     checkDataBase();
192                 } catch (Exception e) {
193                     LOGGER.error("Error during Thread execution " + e.getMessage(), e);
194                 }
195             }
196         }
197     }
198
199     // Set Master
200     private static BackUpMonitorEntity setMaster(BackUpMonitorEntity bMEntity) {
201         bMEntity.setFlag("MASTER");
202         setFlag(true);
203         return bMEntity;
204     }
205
206     // Set Slave
207     private static BackUpMonitorEntity setSlave(BackUpMonitorEntity bMEntity) {
208         bMEntity.setFlag("SLAVE");
209         setFlag(false);
210         return bMEntity;
211     }
212
213     // Check Database and set the Flag.
214     private void checkDataBase() throws BackUpMonitorException {
215         EntityTransaction et = em.getTransaction();
216         setNotificationRecord();
217         // Clear Cache.
218         LOGGER.info("Clearing Cache");
219         em.getEntityManagerFactory().getCache().evictAll();
220         try {
221             LOGGER.info("Checking Datatbase for BackUpMonitor.. ");
222             et.begin();
223             Query query = em.createQuery("select b from BackUpMonitorEntity b where b.resourceNodeName = :nn");
224             if (resourceNodeName.equals(ResourceNode.ASTRA.toString())) {
225                 query.setParameter("nn", ResourceNode.ASTRA.toString());
226             } else if (resourceNodeName.equals(ResourceNode.BRMS.toString())) {
227                 query.setParameter("nn", ResourceNode.BRMS.toString());
228             }
229             List<?> bMList = query.getResultList();
230             if (bMList.isEmpty()) {
231                 // This is New. create an entry as Master.
232                 LOGGER.info("Adding resource " + resourceName + " to Database");
233                 BackUpMonitorEntity bMEntity = new BackUpMonitorEntity();
234                 bMEntity.setResoruceNodeName(resourceNodeName);
235                 bMEntity.setResourceName(resourceName);
236                 bMEntity = setMaster(bMEntity);
237                 bMEntity.setTimeStamp(new Date());
238                 em.persist(bMEntity);
239                 em.flush();
240             } else {
241                 // Check for other Master(s)
242                 ArrayList<BackUpMonitorEntity> masterEntities = new ArrayList<>();
243                 // Check for self.
244                 BackUpMonitorEntity selfEntity = null;
245                 // Check backup monitor entities.
246                 for (int i = 0; i < bMList.size(); i++) {
247                     BackUpMonitorEntity bMEntity = (BackUpMonitorEntity) bMList.get(i);
248                     LOGGER.info("Refreshing Entity. ");
249                     em.refresh(bMEntity);
250                     if (bMEntity.getFlag().equalsIgnoreCase("MASTER")) {
251                         masterEntities.add(bMEntity);
252                     }
253                     if (bMEntity.getResourceName().equals(resourceName)) {
254                         selfEntity = bMEntity;
255                     }
256                 }
257                 if (selfEntity != null) {
258                     LOGGER.info("Resource Name already Exists: " + resourceName);
259                     if (selfEntity.getFlag().equalsIgnoreCase("MASTER")) {
260                         // Already Master Mode.
261                         setFlag(true);
262                         LOGGER.info(resourceName + " is on Master Mode");
263                         selfEntity.setTimeStamp(new Date());
264                         selfEntity.setNotificationRecord(notificationRecord);
265                         em.persist(selfEntity);
266                         em.flush();
267                         setLastNotification(null);
268                         if (!masterEntities.contains(selfEntity)) {
269                             masterEntities.add(selfEntity);
270                         }
271                     } else {
272                         // Already Slave Mode.
273                         setFlag(false);
274                         selfEntity.setTimeStamp(new Date());
275                         selfEntity.setNotificationRecord(notificationRecord);
276                         em.persist(selfEntity);
277                         em.flush();
278                         LOGGER.info(resourceName + " is on Slave Mode");
279                     }
280                 } else {
281                     // Resource name is null -> No resource with same name.
282                     selfEntity = new BackUpMonitorEntity();
283                     selfEntity.setResoruceNodeName(resourceNodeName);
284                     selfEntity.setResourceName(resourceName);
285                     selfEntity.setTimeStamp(new Date());
286                     selfEntity = setSlave(selfEntity);
287                     setLastNotification(null);
288                     LOGGER.info("Creating: " + resourceName + " on Slave Mode");
289                     em.persist(selfEntity);
290                     em.flush();
291                 }
292                 // Correct the database if any errors and perform monitor checks.
293                 if (masterEntities.size() != 1 || !getFlag()) {
294                     // We are either not master or there are more masters or no masters.
295                     if (masterEntities.isEmpty()) {
296                         // No Masters is a problem Convert ourselves to Master.
297                         selfEntity = setMaster(selfEntity);
298                         selfEntity.setTimeStamp(new Date());
299                         selfEntity.setNotificationRecord(notificationRecord);
300                         LOGGER.info(resourceName + " changed to Master Mode - No Masters available.");
301                         em.persist(selfEntity);
302                         em.flush();
303                     } else {
304                         if (masterEntities.size() > 1) {
305                             // More Masters is a problem, Fix the issue by looking for the latest one and make others
306                             // Slave.
307                             BackUpMonitorEntity masterEntity = null;
308                             for (BackUpMonitorEntity currentEntity : masterEntities) {
309                                 if (currentEntity.getFlag().equalsIgnoreCase("MASTER")) {
310                                     if (masterEntity == null) {
311                                         masterEntity = currentEntity;
312                                     } else if (currentEntity.getTimeStamp().getTime() > masterEntity.getTimeStamp()
313                                             .getTime()) {
314                                         // False Master, Update master to slave and take currentMaster as Master.
315                                         masterEntity.setFlag("SLAVE");
316                                         masterEntity.setTimeStamp(new Date());
317                                         em.persist(masterEntity);
318                                         em.flush();
319                                         masterEntity = currentEntity;
320                                     } else {
321                                         currentEntity.setFlag("SLAVE");
322                                         currentEntity.setTimeStamp(new Date());
323                                         em.persist(currentEntity);
324                                         em.flush();
325                                     }
326                                 }
327                             }
328                             masterEntities = new ArrayList<>();
329                             masterEntities.add(masterEntity);
330                         }
331                         if (masterEntities.size() == 1) {
332                             // Correct Size, Check if Master is Latest, if not Change Master to Slave and Slave to
333                             // Master.
334                             BackUpMonitorEntity masterEntity = masterEntities.get(0);
335                             if (!masterEntity.getResourceName().equals(selfEntity.getResourceName())) {
336                                 Date currentTime = new Date();
337                                 long timeDiff = 0;
338                                 timeDiff = currentTime.getTime() - masterEntity.getTimeStamp().getTime();
339                                 if (timeDiff > (pingInterval + 1500)) {
340                                     // This is down or has an issue and we need to become Master while turning the
341                                     // Master to slave.
342                                     masterEntity.setFlag("SLAVE");
343                                     String lastNotification = null;
344                                     if (masterEntity.getNotificationRecord() != null) {
345                                         lastNotification = calculatePatch(masterEntity.getNotificationRecord());
346                                     }
347                                     setLastNotification(lastNotification);
348                                     em.persist(masterEntity);
349                                     em.flush();
350                                     // Lets Become Master.
351                                     selfEntity = setMaster(selfEntity);
352                                     LOGGER.info("Changing " + resourceName + " from slave to Master Mode");
353                                     selfEntity.setTimeStamp(new Date());
354                                     selfEntity.setNotificationRecord(notificationRecord);
355                                     em.persist(selfEntity);
356                                     em.flush();
357                                 }
358                             }
359                         } else {
360                             LOGGER.error(
361                                     "Backup Monitor Issue, Masters out of sync, This will be fixed in next interval.");
362                         }
363                     }
364                 }
365             }
366             et.commit();
367         } catch (Exception e) {
368             LOGGER.error("failed Database Operation " + e.getMessage(), e);
369             if (et.isActive()) {
370                 et.rollback();
371             }
372             throw new BackUpMonitorException(e);
373         }
374     }
375
376     private static void setNotificationRecord() throws BackUpMonitorException {
377         try {
378             notificationRecord = PolicyUtils.objectToJsonString(NotificationStore.getNotificationRecord());
379         } catch (JsonProcessingException e1) {
380             LOGGER.error("Error retrieving notification record failed. ", e1);
381             throw new BackUpMonitorException(e1);
382         }
383     }
384
385     // Calculate Patch and return String JsonPatch of the notification Delta.
386     private synchronized String calculatePatch(String oldNotificationRecord) {
387         try {
388             JsonNode notification = JsonLoader.fromString(notificationRecord);
389             JsonNode oldNotification = JsonLoader.fromString(oldNotificationRecord);
390             JsonNode patchNode = JsonDiff.asJson(oldNotification, notification);
391             LOGGER.info("Generated JSON Patch is " + patchNode.toString());
392             JsonPatch patch = JsonPatch.fromJson(patchNode);
393             try {
394                 JsonNode patched = patch.apply(oldNotification);
395                 LOGGER.info("Generated New Notification is : " + patched.toString());
396                 return patched.toString();
397             } catch (JsonPatchException e) {
398                 LOGGER.error("Error generating Patched " + e.getMessage(), e);
399                 return null;
400             }
401         } catch (IOException e) {
402             LOGGER.error("Error generating Patched " + e.getMessage(), e);
403             return null;
404         }
405     }
406
407     /**
408      * Updates Notification in the Database while Performing the health check.
409      * 
410      * @param notification
411      *            String format of notification record to store in the Database.
412      * @throws Exception
413      */
414     public synchronized void updateNotification() throws BackUpMonitorException {
415         checkDataBase();
416     }
417
418     // Take in string notification and send the record delta to Handler.
419     private static void callHandler(String notification) {
420         if (handler != null) {
421             try {
422                 PDPNotification notificationObject = PolicyUtils.jsonStringToObject(notification,
423                         StdPDPNotification.class);
424                 if (notificationObject.getNotificationType() != null) {
425                     LOGGER.info("Performing Patched notification ");
426                     try {
427                         handler.runOnNotification(notificationObject);
428                         notificationRecord = lastMasterNotification;
429                     } catch (Exception e) {
430                         LOGGER.error("Error in Clients Handler Object : " + e.getMessage(), e);
431                     }
432                 }
433             } catch (IOException e) {
434                 LOGGER.info("Error while notification Conversion " + e.getMessage(), e);
435             }
436         }
437     }
438
439     // Used to set LastMasterNotification Record.
440     private static void setLastNotification(String notification) {
441         synchronized (notificationLock) {
442             lastMasterNotification = notification;
443             if (lastMasterNotification != null && !lastMasterNotification.equals("\"notificationType\":null")) {
444                 if (lastMasterNotification.equals(notificationRecord)) {
445                     return;
446                 }
447                 callHandler(notification);
448             }
449         }
450     }
451 }