001    package ca.uhn.hl7v2.util;
002    
003    import java.io.File;
004    import java.io.FileNotFoundException;
005    import java.io.FileReader;
006    import java.io.FileWriter;
007    import java.io.IOException;
008    
009    import org.slf4j.Logger;
010    import 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     */
028    public 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    }