001package ca.uhn.hl7v2.util;
002
003import java.io.File;
004import java.io.FileNotFoundException;
005import java.io.FileReader;
006import java.io.FileWriter;
007import java.io.IOException;
008
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * <p>
014 * Creates unique message IDs.  IDs are stored in a file called {@link Home#getHomeDirectory() hapi.home}/id_file for persistence
015 * across JVM sessions.  Note that if one day you run the JVM with a new working directory,
016 * you must move or copy id_file into this directory so that new ID numbers will begin
017 * with the last one used, rather than starting over again.
018 * </p>
019 * <p>
020 * Note that as of HAPI 2.0, by default this class will not fail even if the id_file can
021 * not be read/written. In this case, HAPI will try to fail gracefully by simply generating
022 * a numeric sequence starting at zero. This behaviour can be overwritten using 
023 * {@link #NEVER_FAIL_PROPERTY}
024 * </p>
025 * 
026 * @author Neal Acharya
027 */
028public class MessageIDGenerator {
029    
030        private static final Logger ourLog = LoggerFactory.getLogger(MessageIDGenerator.class.getName());
031    private static MessageIDGenerator messageIdGenerator;
032    
033    /**
034     * Contains the complete path to the default ID file, which is a plain text file containing
035     * the number corresponding to the last generated ID
036     */
037    public final static String DEFAULT_ID_FILE = Home.getHomeDirectory().getAbsolutePath() + "/id_file";
038    
039    /**
040     * System property key which indicates that this class should never fail. If this
041     * system property is set to false (default is true), as in the following code:<br>
042     * <code>System.setProperty(MessageIDGenerator.NEVER_FAIL_PROPERTY, Boolean.FALSE.toString());</code><br>
043     * this class will fail if the underlying disk file can not be
044     * read or written. This means you are roughly guaranteed a unique
045     * ID number between JVM sessions (barring the file getting lost or corrupted). 
046     */
047    public static final String NEVER_FAIL_PROPERTY = MessageIDGenerator.class.getName() + "_NEVER_FAIL_PROPERTY";
048    
049    private long id;
050    private FileWriter fileW;
051    
052    /**
053     * Constructor
054     * Creates an instance of the class
055     * Its reads an id (longint#) from an external file, if one is not present then the external file
056     * is created and initialized to zero.
057     * This id is stored into the private field of id.
058     */
059    private  MessageIDGenerator() throws IOException {
060        initialize();
061    }//end constructor code
062
063    
064        /**
065         * Force the generator to re-load the ID file and initialize itself.
066         * 
067         * This method is mostly provided as a convenience to unit tests, and does
068         * not normally need to be called.
069         */
070    void initialize() throws IOException {
071        id = 0;
072        
073                /*check external file for the last value unique id value generated by
074        this class*/
075        try{
076            // We should check to see if the external file for storing the unique ids exists
077            File extFile = new File(DEFAULT_ID_FILE);
078            if (extFile.createNewFile()== true){
079                /*there was no existing file so a new one has been created with createNewFile method.  The
080                file is stored at  <hapi.home>/id_file.txt */
081                // We can simply initialize the private id field to zero
082                id = 0;
083                
084            }//end if
085            else{
086                /*The file does exist which is why we received false from the
087                createNewFile method. We should now try to read from this file*/
088                FileReader fileR = new FileReader(DEFAULT_ID_FILE);
089                char[] charArray = new char[100];
090                int e = fileR.read(charArray);
091                if (e <= 0){
092                    /*We know the file exists but it has no value stored in it. So at this point we can simply initialize the
093                    private id field to zero*/
094                    id = 0;
095                }//end if
096                else{
097                    /* Here we know that the file exists and has a stored value. we should read this value and set the
098                    private id field to it*/
099                    String idStr = String.valueOf(charArray);
100                    String idStrTrim = idStr.trim();
101                    
102                    try {
103                        id = Long.parseLong(idStrTrim);
104                    } catch (NumberFormatException nfe) {
105                        ourLog.warn("Failed to parse message ID file value \"" + idStrTrim + "\". Defaulting to 0.");
106                    }
107                    
108                }//end else
109                //Fix for bug 1100881:  Close the file after writing.
110                fileR.close();
111            }//end else
112        } catch (FileNotFoundException e) {
113            ourLog.error("Failed to locate message ID file. Message was: {}", e.getMessage());
114        } catch (IOException e) {
115                if (Boolean.TRUE.equals(System.getProperty(NEVER_FAIL_PROPERTY, Boolean.TRUE.toString()))) {
116                        ourLog.warn("Could not retrieve message ID file, going to default to ID of 0. Message was: {}", e.getMessage());
117                        id = 0;
118                        return;
119                } else {
120                        throw e;
121                }
122        }
123        }
124    
125    /**
126     * Synchronized method used to return the single (static) instance of the class
127     */
128    public static synchronized MessageIDGenerator getInstance() throws IOException {
129        if (messageIdGenerator == null)
130            messageIdGenerator = new MessageIDGenerator();
131        return messageIdGenerator;
132    }//end method
133    
134    /**
135     * Synchronized method used to return the incremented id value
136     */
137    public synchronized String getNewID() throws IOException{
138        try {
139                //increment the private field
140                id = id + 1;
141                //write the id value to the file
142                String idStr = String.valueOf(id);
143
144                //create an instance of the Filewriter Object pointing to "C:\\extfiles\\Idfile.txt"
145                fileW = new FileWriter(DEFAULT_ID_FILE, false);
146                fileW.write(idStr);
147                fileW.flush();
148                fileW.close();
149        } catch (FileNotFoundException e) {
150                if (Boolean.TRUE.equals(System.getProperty(NEVER_FAIL_PROPERTY, Boolean.TRUE.toString()))) {
151                        ourLog.info("Failed to create message ID file. Message was: {}", e.getMessage());
152                        fileW = null;
153                }
154        } catch (IOException e) {
155                if (Boolean.TRUE.equals(System.getProperty(NEVER_FAIL_PROPERTY, Boolean.TRUE.toString()))) {
156                        ourLog.debug("Failed to create message ID file. Message was: {}", e.getMessage());
157                        fileW = null;
158                } else {
159                        throw e;
160                }
161        }
162        return String.valueOf(id);
163    }//end method
164    
165}