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 }