001    /*
002     * 
003     * $Revision: 14354 $ $Date: 2008-11-07 15:23:02 +0100 (Fr, 07 Nov 2008) $
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, in a file called gpl.txt or license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     */
023    
024    package org.mycore.datamodel.ifs;
025    
026    import java.io.InputStream;
027    import java.io.OutputStream;
028    import java.text.DateFormat;
029    import java.text.SimpleDateFormat;
030    import java.util.Date;
031    import java.util.Random;
032    
033    import org.mycore.common.MCRConfiguration;
034    import org.mycore.common.MCRException;
035    import org.mycore.common.MCRPersistenceException;
036    import org.mycore.common.MCRUtils;
037    
038    /**
039     * Stores the content of MCRFiles in a persistent datastore. This can be a
040     * filesystem, IBM Content Manager, video streaming servers like IBM
041     * VideoCharger or Real Server, depending on the class that implements this
042     * interface. The MCRContentStore provides methods to store, delete and retrieve
043     * content. It uses a storage ID and the store ID to identify the place where
044     * the content of a file is stored.
045     * 
046     * @author Frank Lützenkirchen
047     * @version $Revision: 14354 $ $Date: 2008-11-07 15:23:02 +0100 (Fr, 07 Nov 2008) $
048     */
049    public abstract class MCRContentStore {
050        /** The unique store ID for this MCRContentStore implementation */
051        protected String storeID;
052    
053        /** The prefix of all properties in mycore.properties for this store */
054        protected String prefix;
055    
056        /** The depth of slot subdirectories to build */
057        protected int slotDirDepth;
058    
059        /** Default constructor * */
060        public MCRContentStore() {
061        }
062    
063        /**
064         * Initializes the store and sets its unique store ID. MCRFiles must
065         * remember this ID to indentify the store that holds their file content.
066         * The store ID is set by MCRContentStoreFactory when a new store instance
067         * is built. Subclasses should override this method.
068         * 
069         * @param storeID
070         *            the non-null unique store ID for this store instance
071         */
072        public void init(String storeID) {
073            this.storeID = storeID;
074            this.prefix = "MCR.IFS.ContentStore." + storeID + ".";
075            this.slotDirDepth = MCRConfiguration.instance().getInt(prefix + "SlotDirDepth", 2);
076        }
077    
078        /**
079         * Returns the unique store ID that was set for this store instance
080         * 
081         * @return the unique store ID that was set for this store instance
082         */
083        public String getID() {
084            return storeID;
085        }
086    
087        /**
088         * Stores the content of an MCRFile by reading from an
089         * MCRContentInputStream. Returns a StorageID to indentify the place where
090         * the content was stored.
091         * 
092         * @param file
093         *            the MCRFile thats content is to be stored
094         * @param source
095         *            the ContentInputStream where the file content is read from
096         * @return an ID that indentifies the place where the content was stored
097         */
098        public String storeContent(MCRFileReader file, MCRContentInputStream source) throws MCRPersistenceException {
099            try {
100                return doStoreContent(file, source);
101            } catch (Exception exc) {
102                if (!(exc instanceof MCRException)) {
103                    StringBuffer msg = new StringBuffer();
104                    msg.append("Could not store content of file [");
105                    msg.append(file.getPath()).append("] in store [");
106                    msg.append(storeID).append("]");
107                    throw new MCRPersistenceException(msg.toString(), exc);
108                }
109                throw (MCRException) exc;
110            }
111        }
112    
113        /**
114         * Stores the content of an MCRFile by reading from an
115         * MCRContentInputStream. Returns a StorageID to indentify the place where
116         * the content was stored.
117         * 
118         * @param file
119         *            the MCRFile thats content is to be stored
120         * @param source
121         *            the ContentInputStream where the file content is read from
122         * @return an ID that indentifies the place where the content was stored
123         */
124        protected abstract String doStoreContent(MCRFileReader file, MCRContentInputStream source) throws Exception;
125    
126        /**
127         * Deletes the content of an MCRFile object that is stored under the given
128         * Storage ID in this store instance.
129         * 
130         * @param storageID
131         *            the storage ID of the MCRFile object
132         */
133        public void deleteContent(String storageID) throws MCRException {
134            try {
135                doDeleteContent(storageID);
136            } catch (Exception exc) {
137                if (!(exc instanceof MCRException)) {
138                    StringBuffer msg = new StringBuffer();
139                    msg.append("Could not delete content of file with storage ID [");
140                    msg.append(storageID).append("] in store [");
141                    msg.append(storeID).append("]");
142                    throw new MCRPersistenceException(msg.toString(), exc);
143                }
144                throw (MCRException) exc;
145            }
146        }
147    
148        /**
149         * Deletes the content of an MCRFile object that is stored under the given
150         * Storage ID in this store instance.
151         * 
152         * @param storageID
153         *            the storage ID of the MCRFile object
154         */
155        protected abstract void doDeleteContent(String storageID) throws Exception;
156    
157        /**
158         * Retrieves the content of an MCRFile to an OutputStream. Uses the
159         * StorageID to indentify the place where the file content was stored in
160         * this store instance.
161         * 
162         * @param file
163         *            the MCRFile thats content should be retrieved
164         * @param target
165         *            the OutputStream to write the file content to
166         * @deprecated
167         *        use doRetrieveContent(MCRFileReader file) instead
168         */
169        public void retrieveContent(MCRFileReader file, OutputStream target) throws MCRException {
170            InputStream in = null;
171            try {
172                in = retrieveContent(file);
173                MCRUtils.copyStream(in, target);
174            } finally {
175                if (in != null) {
176                    try {
177                        in.close();
178                    } catch (Exception ignored) {
179                    }
180                }
181            }
182        }
183    
184        /**
185         * Retrieves the content of an MCRFile to an OutputStream. Uses the
186         * StorageID to indentify the place where the file content was stored in
187         * this store instance.
188         * 
189         * @param file
190         *          the MCRFile thats content should be retrieved
191         * @param target
192         *          the OutputStream to write the file content to
193         * @deprecated 
194         *          use doRetrieveContent(MCRFileReader file) instead
195         */
196        protected abstract void doRetrieveContent(MCRFileReader file, OutputStream target) throws Exception;
197    
198        /**
199         * Retrieves the content of an MCRFile. Uses the
200         * StorageID to indentify the place where the file content was stored in
201         * this store instance.
202         * 
203         * @param file
204         *            the MCRFile thats content should be retrieved
205         * @since 1.3
206         */
207        protected abstract InputStream doRetrieveContent(MCRFileReader file) throws Exception;
208    
209        /**
210         * Retrieves the content of an MCRFile as an InputStream.
211         * 
212         * @param file
213         *            the MCRFile thats content should be retrieved
214         */
215        public InputStream retrieveContent(MCRFileReader file) throws MCRException {
216            try {
217                return doRetrieveContent(file);
218            } catch (Exception exc) {
219                if (!(exc instanceof MCRException)) {
220                    StringBuffer msg = new StringBuffer();
221                    msg.append("Could not retrieve content of file with storage ID [");
222                    msg.append(file.getStorageID()).append("] in store [");
223                    msg.append(storeID).append("]");
224                    throw new MCRPersistenceException(msg.toString(), exc);
225                }
226                throw (MCRException) exc;
227            }
228        }
229    
230        /** DateFormat used to construct new unique IDs based on timecode */
231        protected static DateFormat formatter = new SimpleDateFormat("yyMMdd-HHmmss-SSS");
232    
233        /**
234         * Constructs a new unique ID for storing content
235         */
236        protected static synchronized String buildNextID(MCRFileReader file) {
237            StringBuffer sb = new StringBuffer();
238    
239            sb.append(buildNextTimestamp());
240            sb.append("-").append(file.getID());
241    
242            if (file.getExtension().length() > 0) {
243                sb.append(".").append(file.getExtension());
244            }
245    
246            return sb.toString();
247        }
248    
249        /** The last timestamp that was constructed */
250        protected static String lastTimestamp = null;
251    
252        /**
253         * Helper method for constructing a unique storage ID from a timestamp.
254         */
255        protected static synchronized String buildNextTimestamp() {
256            String ts = null;
257    
258            do {
259                ts = formatter.format(new Date());
260            } while (ts.equals(lastTimestamp));
261    
262            return (lastTimestamp = ts);
263        }
264    
265        /**
266         * Some content store implementations store the file's content in a
267         * hierarchical directory structure of the server's filesystem. Such stores
268         * use a directory that contains 100 subdirectories with each 100
269         * subsubdirectories, so that the internal directory operations will scale
270         * well for large file collections. The depth of this subdirectory structure
271         * can be set by the property SlotDirDepth, default is 2.
272         * 
273         * This helper method randomly chooses the "slot directory" to be used for
274         * the next storage.
275         * 
276         * @return directory names between "00" and "99" that are the "slot" where
277         *         to store the file's content in the filesystem.
278         */
279        protected String[] buildSlotPath() {
280            Random random = new Random();
281            String[] slots = new String[slotDirDepth];
282    
283            for (int i = 0; i < slotDirDepth; i++) {
284                String slot = String.valueOf(random.nextInt(100));
285                if (slot.length() < 2)
286                    slot = "0" + slot;
287                slots[i] = slot;
288            }
289    
290            return slots;
291        }
292    }