001    /*
002     * Copyright (c) 2002-2009, The DSpace Foundation.  All rights reserved.
003     *
004     * Redistribution and use in source and binary forms, with or without
005     * modification, are permitted provided that the following conditions are
006     * met:
007     *
008     * - Redistributions of source code must retain the above copyright
009     * notice, this list of conditions and the following disclaimer.
010     *
011     * - Redistributions in binary form must reproduce the above copyright
012     * notice, this list of conditions and the following disclaimer in the
013     * documentation and/or other materials provided with the distribution.
014     *
015     * - Neither the name of the DSpace Foundation nor the names of its
016     * contributors may be used to endorse or promote products derived from
017     * this software without specific prior written permission.
018     *
019     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
020     * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
021     * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
022     * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
023     * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
024     * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
025     * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
026     * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
027     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
028     * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
029     * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
030     * DAMAGE.
031     */
032    
033    package org.dspace.checker;
034    
035    import java.io.FileInputStream;
036    import java.io.FileNotFoundException;
037    import java.io.IOException;
038    import java.text.ParseException;
039    import java.util.Enumeration;
040    import java.util.HashMap;
041    import java.util.Iterator;
042    import java.util.List;
043    import java.util.Map;
044    import java.util.Properties;
045    import java.util.regex.Matcher;
046    import java.util.regex.Pattern;
047    
048    import org.apache.log4j.Logger;
049    import org.dspace.core.ConfigurationManager;
050    import org.dspace.core.Utils;
051    
052    /**
053     * Manages the deletion of results from the checksum history. It uses the
054     * dspace.cfg file as the default configuration file for the deletion settings
055     * and can use a different configuration file if it is passed in.
056     * 
057     * @author Jim Downing
058     * @author Grace Carpenter
059     * @author Nathan Sarr
060     * 
061     * 
062     */
063    public final class ResultsPruner
064    {
065    
066        /**
067         * Default logger.
068         */
069        private static final Logger LOG = Logger.getLogger(ResultsPruner.class);
070    
071        /**
072         * Factory method for the default results pruner configuration using
073         * dspace.cfg
074         * 
075         * @return a ResultsPruner that represent the default retention policy
076         */
077        public static ResultsPruner getDefaultPruner()
078        {
079            try
080            {
081                return getPruner(ConfigurationManager.getProperties());
082            }
083            catch (FileNotFoundException e)
084            {
085                throw new RuntimeException(
086                        "VeryExceptionalException - config file not there! ", e);
087            }
088    
089        }
090    
091        
092        /**
093         * Factory method for ResultsPruners
094         * 
095         * @param propsFile
096         *            to configure the results pruner.
097         * @return the configured results pruner.
098         * @throws FileNotFoundException
099         *             it the configuration file cannot be found.
100         */
101        public static ResultsPruner getPruner(String propsFile)
102                throws FileNotFoundException
103        {
104            Properties props = new Properties();
105            FileInputStream fin = null;
106            try
107            {
108                fin = new FileInputStream(propsFile);
109                props.load(fin);
110                
111                return getPruner(props);
112            }
113            catch (IOException e)
114            {
115                throw new RuntimeException("Problem loading properties file: "
116                        + e.getMessage(), e);
117            }
118            finally
119            {
120                if (fin != null)
121                    try
122                    {
123                        fin.close();
124                    }
125                    catch (IOException e)
126                    {
127                        LOG.warn(e);
128                    }
129            }
130        }
131        
132        /**
133         * Factory method for ResultsPruners (used to load ConfigurationManager
134         * properties.
135         * 
136         * @param props
137         * @return
138         * @throws FileNotFoundException
139         */
140        public static ResultsPruner getPruner(Properties props)
141        throws FileNotFoundException
142        {
143         
144            ResultsPruner rp = new ResultsPruner();
145            Pattern retentionPattern = Pattern
146                    .compile("checker\\.retention\\.(.*)");
147            for (Enumeration en = props.propertyNames(); en.hasMoreElements();)
148            {
149                String name = (String) en.nextElement();
150                Matcher matcher = retentionPattern.matcher(name);
151                if (!matcher.matches())
152                    continue;
153                String resultCode = matcher.group(1);
154                long duration;
155                try
156                {
157                    duration = Utils.parseDuration(props.getProperty(name));
158                }
159                catch (ParseException e)
160                {
161                    throw new RuntimeException("Problem parsing duration: "
162                            + e.getMessage(), e);
163                }
164                if ("default".equals(resultCode))
165                {
166                    rp.setDefaultDuration(duration);
167                }
168                else
169                {
170                    rp.addInterested(resultCode, duration);
171                }
172            }
173            return rp;
174            
175        }
176    
177        /** Ten years */
178        private long defaultDuration = 31536000000L;
179    
180        /**
181         * Map of retention durations, keyed by result code name
182         */
183        Map interests = new HashMap();
184    
185        /**
186         * Checksum results database Data access
187         */
188        private ChecksumResultDAO checksumResultDAO = null;
189    
190        /**
191         * Checksum history database data access.
192         */
193        private ChecksumHistoryDAO checksumHistoryDAO = null;
194    
195        /**
196         * Default Constructor
197         */
198        public ResultsPruner()
199        {
200            checksumResultDAO = new ChecksumResultDAO();
201            checksumHistoryDAO = new ChecksumHistoryDAO();
202        }
203    
204        /**
205         * Add a result and the length of time before the history with this type of
206         * result is removed from the database.
207         * 
208         * @param result
209         *            code in the database.
210         * 
211         * @param duration
212         *            before bitstreams with the specified result type in the
213         *            checksum history is removed.
214         */
215        public void addInterested(String result, long duration)
216        {
217            interests.put(result, new Long(duration));
218        }
219    
220        /**
221         * Add a result and the length of time before it is removed from the
222         * checksum history table.
223         * 
224         * @param result
225         *            code in the database.
226         * 
227         * @param duration
228         *            before bitstreams with the specified result type in the
229         *            checksum history is removed.
230         * 
231         * @throws ParseException
232         *             if the duration cannot be parsed into a long value.
233         */
234        public void addInterested(String result, String duration)
235                throws ParseException
236        {
237            addInterested(result, Utils.parseDuration(duration));
238        }
239    
240        /**
241         * The default amount of time before a result is removed.
242         * 
243         * @return the default duration.
244         */
245        public long getDefaultDuration()
246        {
247            return defaultDuration;
248        }
249    
250        /**
251         * Prunes the results retaining results as configured by the interests
252         * registered with this object.
253         * 
254         * @return number of results removed.
255         */
256        public int prune()
257        {
258            List codes = checksumResultDAO.listAllCodes();
259            for (Iterator iter = codes.iterator(); iter.hasNext();)
260            {
261                String code = (String) iter.next();
262                if (!interests.containsKey(code))
263                {
264                    interests.put(code, new Long(defaultDuration));
265                }
266    
267            }
268            return checksumHistoryDAO.prune(interests);
269        }
270    
271        /**
272         * The default duration before records are removed from the checksum history
273         * table.
274         * 
275         * @param defaultDuration
276         *            used before records are removed from the checksum history.
277         */
278        public void setDefaultDuration(long defaultDuration)
279        {
280            this.defaultDuration = defaultDuration;
281        }
282    }