Change the header to SO
[so.git] / common / src / main / java / org / openecomp / mso / properties / MsoPropertiesFactory.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
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.openecomp.mso.properties;
22
23
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.Serializable;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.Map.Entry;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.locks.ReentrantReadWriteLock;
33
34 import javax.ejb.ConcurrencyManagement;
35 import javax.ejb.ConcurrencyManagementType;
36 import javax.ejb.LocalBean;
37 import javax.ejb.Schedule;
38 import javax.ejb.Singleton;
39 import javax.ws.rs.GET;
40 import javax.ws.rs.Path;
41 import javax.ws.rs.PathParam;
42 import javax.ws.rs.Produces;
43 import javax.ws.rs.core.Response;
44
45 import org.openecomp.mso.logger.MessageEnum;
46 import org.openecomp.mso.logger.MsoLogger;
47 import org.openecomp.mso.utils.CryptoUtils;
48
49 /**
50  * This EJB Singleton class returns an instance of the mso properties for a specified file.
51  * This class can handle many config at the same time and is thread safe.
52  * This instance is a copy of the one cached so it can be modified or reloaded without impacting the others class using
53  * it.
54  * The mso properties files loaded and cached here will be reloaded every X second (it's configurable with the init
55  * method)
56  * This class can be used as an EJB or can be instantiated directly as long as the EJB has been initialized for the current
57  * module. Locks are made manually and not using EJB locks to allow this.
58  *
59  *
60  */
61 @Singleton(name = "MsoPropertiesFactory")
62 @ConcurrencyManagement(ConcurrencyManagementType.BEAN)
63 @LocalBean
64 @Path("/properties")
65 public class MsoPropertiesFactory implements Serializable {
66
67         private static final long serialVersionUID = 4365495305496742113L;
68
69     protected static String prefixMsoPropertiesPath = System.getProperty ("mso.config.path");
70
71     private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.GENERAL);
72
73     // Keep a static copy of properties for global usage
74     private static final ConcurrentHashMap <String, MsoPropertiesParameters> msoPropertiesCache;
75
76     static {
77         if (prefixMsoPropertiesPath == null) {
78             // Hardcode if nothing is received
79             prefixMsoPropertiesPath = "";
80         }
81         msoPropertiesCache = new ConcurrentHashMap <String, MsoPropertiesParameters> ();
82     }
83
84     private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock ();
85
86     public MsoPropertiesFactory () {
87
88     }
89
90     private boolean isJsonFile(String propertiesFilePath) {
91         return propertiesFilePath.endsWith(".json");
92     }
93     
94     
95     private boolean isJavaPropertiesFile (String propertiesFilePath) {
96         return propertiesFilePath.endsWith(".properties");
97     }
98     
99         private MsoPropertiesParameters createObjectType (MsoPropertiesParameters msoPropParams, String propertiesFilePath) throws MsoPropertiesException, IOException {
100         
101                 try {
102                 if (this.isJavaPropertiesFile(propertiesFilePath)) {
103                         
104                         msoPropParams.msoProperties =  new MsoJavaProperties();
105                         msoPropParams.msoPropertiesType = MsoPropertiesParameters.MsoPropertiesType.JAVA_PROP;
106                  } else if (this.isJsonFile(propertiesFilePath)) {
107                          
108                         msoPropParams.msoProperties =  new MsoJsonProperties();
109                         msoPropParams.msoPropertiesType = MsoPropertiesParameters.MsoPropertiesType.JSON_PROP;
110                  } else {
111                          throw new MsoPropertiesException("Unable to load the MSO properties file because format is not recognized (only .json or .properties): " + propertiesFilePath);
112                  }
113         
114                 msoPropParams.msoProperties.loadPropertiesFile (propertiesFilePath);
115                 
116                 return msoPropParams;
117                 } finally {
118                         if (msoPropParams.msoProperties!=null) {
119                                 msoPropParams.refreshCounter = msoPropParams.msoProperties.getAutomaticRefreshInMinutes();
120                         }
121                 }
122
123     }
124     
125     /**
126      * This method is used to create a MsoProperties file cache and factory.
127          * The ID is kept in cache even if the config fails to be loaded.
128          * This is used to maintain the config ID until someone fixes the config file.
129      *
130      * @param msoPropertiesID A string representing the key of the config
131      * @param propertiesFilePath The mso properties file to load
132      *
133      * @throws MsoPropertiesException In case of issues with the mso properties loading
134      *
135      * @see MsoPropertiesFactory#getMsoJavaProperties()
136      * @see MsoPropertiesFactory#getMsoJsonProperties()
137      */
138     public void initializeMsoProperties (String msoPropertiesID,
139                                          String propertiesFilePath) throws MsoPropertiesException {
140              
141         rwl.writeLock ().lock ();
142         
143         String msoPropPath="none";
144         MsoPropertiesParameters msoPropertiesParams=new MsoPropertiesParameters();
145         try {
146                 msoPropPath = prefixMsoPropertiesPath + propertiesFilePath; 
147                 if (msoPropertiesCache.get (msoPropertiesID) != null) {
148                 throw new MsoPropertiesException ("The factory contains already an instance of this mso properties: "
149                                                   + msoPropPath);
150             }
151                 // Create the global MsoProperties object
152                 msoPropertiesParams = createObjectType(msoPropertiesParams, msoPropPath);
153
154         } catch (FileNotFoundException e) {
155             throw new MsoPropertiesException ("Unable to load the MSO properties file because it has not been found:"
156                                               + msoPropPath, e);
157
158         } catch (IOException e) {
159             throw new MsoPropertiesException ("Unable to load the MSO properties file because IOException occurs: "
160                                               + msoPropPath, e);
161         } finally {
162                 // put it in all cases, just to not forget about him and attempt a default reload
163                 msoPropertiesCache.put (msoPropertiesID, msoPropertiesParams);
164             rwl.writeLock ().unlock ();
165         }
166     }
167
168     public void removeMsoProperties (String msoPropertiesID) throws MsoPropertiesException {
169
170         rwl.writeLock ().lock ();
171         try {
172             if (MsoPropertiesFactory.msoPropertiesCache.remove (msoPropertiesID) == null) {
173                 throw new MsoPropertiesException ("Mso properties not found in cache:" + msoPropertiesID);
174             }
175         } finally {
176             rwl.writeLock ().unlock ();
177         }
178     }
179
180     /**
181      * This method clears all the configs in cache, the factory will then be free of any config.
182      * 
183      * @see MsoPropertiesFactory#initializeMsoProperties(String, String)
184      */
185     public void removeAllMsoProperties () {
186
187         rwl.writeLock ().lock ();
188         try {
189             MsoPropertiesFactory.msoPropertiesCache.clear ();
190         } finally {
191             rwl.writeLock ().unlock ();
192         }
193     }
194
195     /**
196      * THis method can be used to change the file and timer fields of an existing MSO properties file.
197      *
198      * @param msoPropertiesID The MSO properties ID
199      * @param newMsoPropPath The new file Path
200      * @throws MsoPropertiesException In case of the MSO Properties is not found in cache
201      */
202     public void changeMsoPropertiesFilePath (String msoPropertiesID,
203                                              String newMsoPropPath) throws MsoPropertiesException {
204
205         rwl.writeLock ().lock ();
206         try {
207                 MsoPropertiesParameters msoPropInCache = MsoPropertiesFactory.msoPropertiesCache.get (msoPropertiesID);
208
209             if (msoPropInCache != null) {
210                 msoPropInCache.msoProperties.propertiesFileName = prefixMsoPropertiesPath + newMsoPropPath;
211                 
212             } else {
213                 throw new MsoPropertiesException ("Mso properties not found in cache:" + msoPropertiesID);
214             }
215         } finally {
216             rwl.writeLock ().unlock ();
217         }
218     }
219
220     private AbstractMsoProperties getAndCloneProperties(String msoPropertiesID, MsoPropertiesParameters.MsoPropertiesType type) throws MsoPropertiesException {
221          rwl.readLock ().lock ();
222          try {
223                 MsoPropertiesParameters msoPropInCache = MsoPropertiesFactory.msoPropertiesCache.get (msoPropertiesID);
224              if (msoPropInCache == null) {
225                  throw new MsoPropertiesException ("Mso properties not found in cache:" + msoPropertiesID);
226              } else {
227                  if (type.equals(msoPropInCache.msoPropertiesType)) {
228                         return msoPropInCache.msoProperties.clone ();
229                  } else {
230                         throw new MsoPropertiesException ("Mso properties is not "+type.name()+" properties type:" + msoPropertiesID);
231                  }
232                 
233              }
234          } finally {
235              rwl.readLock ().unlock ();
236          }
237     }
238     
239     /**
240      * Get the MSO Properties (As Java Properties) as a copy of the mso properties cache.
241      * The object returned can therefore be modified.
242      *
243      * @return A copy of the mso properties, properties class can be empty if the file has not been read properly
244      * @throws MsoPropertiesException If the mso properties does not exist in the cache
245      */
246     public MsoJavaProperties getMsoJavaProperties (String msoPropertiesID) throws MsoPropertiesException {
247
248         return (MsoJavaProperties)getAndCloneProperties(msoPropertiesID,MsoPropertiesParameters.MsoPropertiesType.JAVA_PROP);
249     }
250     
251     /**
252      * Get the MSO Properties (As JSON Properties) as a copy of the mso properties cache.
253      * The object returned can therefore be modified.
254      *
255      * @return A copy of the mso properties, properties class can be empty if the file has not been read properly
256      * @throws MsoPropertiesException If the mso properties does not exist in the cache
257      */
258     public MsoJsonProperties getMsoJsonProperties (String msoPropertiesID) throws MsoPropertiesException {
259
260         return (MsoJsonProperties)getAndCloneProperties(msoPropertiesID,MsoPropertiesParameters.MsoPropertiesType.JSON_PROP);
261     }
262
263     /**
264      * Get all MSO Properties as a copy of the mso properties cache.
265      * The objects returned can therefore be modified.
266      *
267      * @return A List of copies of the mso properties, can be empty
268      */
269     public List <AbstractMsoProperties> getAllMsoProperties () {
270
271         List <AbstractMsoProperties> resultList = new LinkedList <AbstractMsoProperties> ();
272         rwl.readLock ().lock ();
273         try {
274
275                 for (MsoPropertiesParameters msoProp:MsoPropertiesFactory.msoPropertiesCache.values ()) {
276                         resultList.add(msoProp.msoProperties.clone());
277                 }
278             return resultList;
279
280         } finally {
281             rwl.readLock ().unlock ();
282         }
283
284     }
285
286     /**
287      * This method is not intended to be called, it's used to refresh the config automatically
288      *
289      * @return true if Properties have been reloaded, false otherwise
290      */
291     @Schedule(minute = "*/1", hour = "*", persistent = false)
292     public boolean reloadMsoProperties () {
293         AbstractMsoProperties msoPropInCache = null;
294         try {
295             if (!rwl.writeLock ().tryLock () && !rwl.writeLock ().tryLock (30L, TimeUnit.SECONDS)) {
296                 LOGGER.debug ("Busy write lock on mso properties factory, skipping the reloading");
297                 return false;
298             }
299         } catch (InterruptedException e1) {
300             LOGGER.debug ("Interrupted while trying to acquire write lock on mso properties factory, skipping the reloading");
301             Thread.currentThread ().interrupt ();
302             return false;
303         }
304         try {
305             for (Entry <String, MsoPropertiesParameters> entryMsoPropTimer : MsoPropertiesFactory.msoPropertiesCache.entrySet ()) {
306
307                 if (entryMsoPropTimer.getValue ().refreshCounter <= 1) {
308                     // It's time to reload the config
309                     msoPropInCache = MsoPropertiesFactory.msoPropertiesCache.get (entryMsoPropTimer.getKey ()).msoProperties;
310                     try {
311                         AbstractMsoProperties oldProps = msoPropInCache.clone ();
312                         msoPropInCache.reloadPropertiesFile ();
313                         entryMsoPropTimer.getValue().refreshCounter=entryMsoPropTimer.getValue().msoProperties.getAutomaticRefreshInMinutes();
314                      
315                         if (!msoPropInCache.equals (oldProps)) {
316                             LOGGER.info (MessageEnum.LOAD_PROPERTIES_SUC, msoPropInCache.getPropertiesFileName (), "", "");
317                         }
318                     } catch (FileNotFoundException ef) {
319                         LOGGER.error (MessageEnum.NO_PROPERTIES, msoPropInCache.propertiesFileName, "", "", MsoLogger.ErrorCode.PermissionError, "", ef);
320                     } catch (Exception e) {
321                         LOGGER.error (MessageEnum.LOAD_PROPERTIES_FAIL, msoPropInCache.propertiesFileName, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "", e);
322                     }
323
324                 } else {
325                         --entryMsoPropTimer.getValue().refreshCounter;
326                 }
327             }
328             return true;
329         } catch (Exception e) {
330             LOGGER.error (MessageEnum.LOAD_PROPERTIES_FAIL, "Unknown. Global issue while reloading", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "", e);
331             return false;
332         } finally {
333             rwl.writeLock ().unlock ();
334         }
335     }
336
337     /**
338      * This method can be used to known if the MSO properties instance hold is different from the one in cache
339      *
340      * @param msoPropertiesID The MSO properties ID
341      * @param oldMsoProperties The MSO Properties instance that must be compared to
342      * @return True if they are the same, false otherwise
343      * @throws MsoPropertiesException 
344      */
345     public boolean propertiesHaveChanged (String msoPropertiesID, AbstractMsoProperties oldMsoProperties) throws MsoPropertiesException {
346         rwl.readLock ().lock ();
347         try {
348                 if (MsoPropertiesFactory.msoPropertiesCache.get (msoPropertiesID) == null) {
349                         throw new MsoPropertiesException ("Mso properties not found in cache:" + msoPropertiesID);
350                 }
351                          
352                 AbstractMsoProperties msoPropInCache = MsoPropertiesFactory.msoPropertiesCache.get (msoPropertiesID).msoProperties;
353             if (oldMsoProperties != null) {
354                 return !oldMsoProperties.equals (msoPropInCache);
355             } else {
356                 return msoPropInCache != null;
357             }
358         } finally {
359             rwl.readLock ().unlock ();
360         }
361     }
362
363     @GET
364     @Path("/show")
365     @Produces("text/plain")
366     public Response showProperties () {
367
368         List <AbstractMsoProperties> listMsoProp = this.getAllMsoProperties ();
369         StringBuffer response = new StringBuffer ();
370
371         if (listMsoProp.isEmpty ()) {
372             response.append ("No file defined");
373         }
374
375         for (AbstractMsoProperties properties : listMsoProp) {
376
377                 response.append(properties.toString());
378         }
379
380         return Response.status (200).entity (response).build ();
381     }
382
383     @GET
384     @Path("/encrypt/{value}/{cryptKey}")
385     @Produces("text/plain")
386     public Response encryptProperty (@PathParam("value") String value, @PathParam("cryptKey") String cryptKey) {
387         try {
388             String encryptedValue = CryptoUtils.encrypt (value, cryptKey);
389             return Response.status (200).entity (encryptedValue).build ();
390         } catch (Exception e) {
391             LOGGER.error (MessageEnum.GENERAL_EXCEPTION_ARG, "Encryption error", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Error in encrypting property", e);
392             return Response.status (500).entity (e.getMessage ()).build ();
393         }
394     }
395 }