001package org.intellimate.izou.sdk.properties; 002 003import org.intellimate.izou.sdk.Context; 004import org.intellimate.izou.sdk.contentgenerator.EventListener; 005import org.intellimate.izou.sdk.events.CommonEvents; 006import org.intellimate.izou.sdk.util.AddOnModule; 007import org.intellimate.izou.system.file.ReloadableFile; 008 009import java.io.*; 010import java.nio.channels.FileChannel; 011import java.nio.channels.FileLock; 012import java.nio.channels.OverlappingFileLockException; 013import java.util.Properties; 014import java.util.function.Consumer; 015 016/** 017 * EventPropertiesManager manages all events written in the local_events.properties file. You can register (add) 018 * events to the file, and get events from the file. The file pretty much serves as a hub for event IDs. 019 */ 020public class EventPropertiesAssistant extends AddOnModule implements ReloadableFile { 021 022 /** 023 * The path to the local_events.properties file 024 */ 025 private final String eventPropertiesPath = getContext().getFiles().getPropertiesLocation() + File.separator + 026 "local_events.properties"; 027 private Properties properties; 028 029 /** 030 * Creates a new EventPropertiesAssistant 031 * @param context the context to use 032 * @param id the id of the addon 033 */ 034 public EventPropertiesAssistant(Context context, String id) { 035 super(context, id); 036 properties = new Properties(); 037 try { 038 createIzouPropertiesFiles(); 039 reloadFile(null); 040 } catch (IOException e) { 041 context.getLogger().error("Unable to initialize local_events.properties file", e); 042 } 043 044 try { 045 getContext().getFiles().registerFileDir(getContext().getFiles().getPropertiesLocation().toPath(), getID(), this); 046 } catch (IOException e) { 047 error("Unable to register EventPropertiesAssistant with file manager " + 048 "(file reload service)", e); 049 } 050 051 registerStandardEvents(); 052 } 053 054 /** 055 * registers the standard-events 056 */ 057 private void registerStandardEvents() { 058 CommonEvents.Descriptors.stopListener(this).ifPresent(this::registerEventListener); 059 CommonEvents.Presence.generalLeavingListener(this).ifPresent(this::registerEventListener); 060 CommonEvents.Presence.generalListener(this).ifPresent(this::registerEventListener); 061 CommonEvents.Presence.leavingListener(this).ifPresent(this::registerEventListener); 062 CommonEvents.Presence.presenceListener(this).ifPresent(this::registerEventListener); 063 CommonEvents.Presence.strictLeavingListener(this).ifPresent(this::registerEventListener); 064 CommonEvents.Presence.strictListener(this).ifPresent(this::registerEventListener); 065 CommonEvents.Response.fullResponseListener(this).ifPresent(this::registerEventListener); 066 CommonEvents.Response.majorResponseListener(this).ifPresent(this::registerEventListener); 067 CommonEvents.Response.minorResponseListener(this).ifPresent(this::registerEventListener); 068 CommonEvents.Type.notificationListener(this).ifPresent(this::registerEventListener); 069 CommonEvents.Type.responseListener(this).ifPresent(this::registerEventListener); 070 } 071 072 private void createIzouPropertiesFiles() throws IOException { 073 String propertiesPath = getContext().getFiles().getPropertiesLocation() + File.separator + 074 "local_events.properties"; 075 076 File file = new File(propertiesPath); 077 BufferedWriter bufferedWriterInit = null; 078 try { 079 if (!file.exists()) { 080 file.createNewFile(); 081 bufferedWriterInit = new BufferedWriter(new FileWriter(propertiesPath)); 082 bufferedWriterInit.write("# You can use this file to store an event ID with a key, or shortcut, " + 083 " so that others can easily access and\n# fire it using the key"); 084 } 085 } catch (IOException e) { 086 //error("unable to create the local_events file", e); 087 } finally { 088 if(bufferedWriterInit != null) 089 bufferedWriterInit.close(); 090 } 091 } 092 093 /** 094 * gets the String containing the Properties-Path 095 * @return a String 096 */ 097 public String getEventPropertiesPath() { 098 return eventPropertiesPath; 099 } 100 101 /** 102 * Gets the full event ID associated with the key {@code key} 103 * 104 * @param key the key of the full event ID 105 * @return the complete the event ID, or null if none is found 106 */ 107 public String getEventID(String key) { 108 return (String) properties.get(key); 109 } 110 111 /** 112 * Registers or adds an event to the local_events.properties file with the informations found in the EventListener 113 * 114 * @param eventListener the eventListener to add 115 */ 116 public void registerEventListener(EventListener eventListener) { 117 registerEventID(eventListener.getDescription(), 118 eventListener.getDescriptorID(), eventListener.getDescriptor()); 119 } 120 121 /** 122 * Registers or adds an event to the local_events.properties file 123 * 124 * @param description a simple description of the Event 125 * @param key the key with which to store the event ID 126 * @param value the complete event ID 127 */ 128 public void registerEventID(String description, String key, String value) { 129 BufferedWriter bufferedWriter; 130 FileOutputStream out = null; 131 try { 132 out = new FileOutputStream(eventPropertiesPath, true); 133 bufferedWriter = new BufferedWriter(new OutputStreamWriter(out)); 134 doWithLock(out.getChannel(), lock -> { 135 unlockedReloadFile(); 136 if (getEventID(key) != null) { 137 return; 138 } 139 try { 140 bufferedWriter.write("\n\n" + key + "_DESCRIPTION = " + description + "\n" + key + " = " + value); 141 bufferedWriter.flush(); 142 } catch (IOException e) { 143 e.printStackTrace(); 144 } 145 }); 146 } catch (FileNotFoundException e) { 147 error("Unable find file", e); 148 } finally { 149 try { 150 if (out != null) { 151 out.close(); 152 } 153 } catch (IOException e) { 154 error("Unable to close lock", e); 155 } 156 } 157 } 158 159 /** 160 * executes with a lock 161 * @param channel the channel where the lock is acquired from 162 * @param consumer the consumer to execute 163 */ 164 private void doWithLock(FileChannel channel, Consumer<FileLock> consumer) { 165 FileLock lock = null; 166 try { 167 while (lock == null) { 168 try { 169 lock = channel.tryLock(); 170 } catch (OverlappingFileLockException e) { 171 Thread.sleep(500); 172 } 173 } 174 consumer.accept(lock); 175 } catch (IOException | InterruptedException e) { 176 error("Unable to write", e); 177 } finally { 178 try { 179 if (lock != null) { 180 lock.release(); 181 } 182 } catch (IOException e) { 183 error("Unable to close lock", e); 184 } 185 } 186 } 187 188 /** 189 * Unregisters or deletes an event from the local_events.properties file 190 * 191 * @param eventKey the key under which the complete event ID is stored in the properties file 192 */ 193 public void unregisterEventID(String eventKey) { 194 properties.remove(eventKey + "_DESCRIPTION"); 195 properties.remove(eventKey); 196 197 FileOutputStream out = null; 198 BufferedReader reader = null; 199 BufferedWriter writer = null; 200 201 try { 202 out = new FileOutputStream(eventPropertiesPath, true); 203 204 final File tempFile = new File(eventPropertiesPath + "temp.properties"); 205 final BufferedReader readerFinal = new BufferedReader(new FileReader(eventPropertiesPath)); 206 final BufferedWriter writerFinal = new BufferedWriter(new FileWriter(tempFile)); 207 208 doWithLock(out.getChannel(), lock -> { 209 unlockedReloadFile(); 210 if (getEventID(eventKey) != null) { 211 return; 212 } 213 214 try { 215 String currentLine = readerFinal.readLine(); 216 while(currentLine != null) { 217 String trimmedLine = currentLine.trim(); 218 if(trimmedLine.equals(eventKey + "_DESCRIPTION") || trimmedLine.equals(eventKey)) continue; 219 writerFinal.write(currentLine + System.getProperty("line.separator")); 220 currentLine = readerFinal.readLine(); 221 } 222 } catch (IOException e) { 223 e.printStackTrace(); 224 } 225 }); 226 227 reader = readerFinal; 228 writer = writerFinal; 229 tempFile.renameTo(new File(eventPropertiesPath)); 230 } catch (IOException e) { 231 error("Unable find file", e); 232 } finally { 233 try { 234 if (out != null) { 235 out.close(); 236 } 237 if (writer != null) { 238 writer.close(); 239 } 240 if (reader != null) { 241 reader.close(); 242 } 243 } catch (IOException e) { 244 error("Unable to close lock", e); 245 } 246 } 247 } 248 249 private void unlockedReloadFile() { 250 Properties tmpProperties = new Properties(); 251 BufferedReader in = null; 252 try { 253 File eventFile = new File(eventPropertiesPath); 254 FileInputStream fileInputStream = new FileInputStream(eventFile); 255 in = new BufferedReader(new InputStreamReader(fileInputStream, "UTF8")); 256 tmpProperties.load(in); 257 this.properties = tmpProperties; 258 } catch(IOException e) { 259 error("Error while trying to load local_events.properties", e); 260 } finally { 261 if (in != null) { 262 try { 263 in.close(); 264 } catch (IOException e) { 265 error("Unable to close input stream", e); 266 } 267 } 268 } 269 } 270 271 @Override 272 public void reloadFile(String eventType) { 273 try (FileOutputStream outputStream = new FileOutputStream(eventPropertiesPath, true)) { 274 doWithLock(outputStream.getChannel(), lock -> unlockedReloadFile()); 275 } catch (IOException e) { 276 error("Unable to reload local_events.properties file", e); 277 } 278 } 279}