Source code: com/virtuosotechnologies/asaph/xmldatabase/IndexedDatabaseFactoryImpl.java
1 /*
2 ================================================================================
3
4 FILE: IndexedDatabaseFactoryImpl.java
5
6 PROJECT:
7
8 Asaph
9
10 CONTENTS:
11
12 Implementation of IndexedDatabaseFactory
13
14 PROGRAMMERS:
15
16 Daniel Azuma (DA) <dazuma@kagi.com>
17
18 COPYRIGHT:
19
20 Copyright (C) 2003 Daniel Azuma (dazuma@kagi.com)
21
22 This program is free software; you can redistribute it and/or
23 modify it under the terms of the GNU General Public License as
24 published by the Free Software Foundation; either version 2
25 of the License, or (at your option) any later version.
26
27 This program is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public
33 License along with this program; if not, write to
34 Free Software Foundation, Inc.
35 59 Temple Place, Suite 330
36 Boston, MA 02111-1307 USA
37
38 ================================================================================
39 */
40
41
42 package com.virtuosotechnologies.asaph.xmldatabase;
43
44
45 import java.io.IOException;
46 import java.io.FileNotFoundException;
47 import java.io.File;
48 import java.io.FilenameFilter;
49 import java.io.OutputStream;
50 import java.io.FileOutputStream;
51 import java.io.InputStream;
52 import java.io.FileInputStream;
53 import java.net.URL;
54 import javax.xml.parsers.SAXParserFactory;
55 import javax.xml.parsers.SAXParser;
56 import javax.xml.parsers.ParserConfigurationException;
57 import org.xml.sax.SAXException;
58 import org.xml.sax.ErrorHandler;
59
60 import com.virtuosotechnologies.lib.util.FileUtils;
61 import com.virtuosotechnologies.lib.util.StringID;
62 import com.virtuosotechnologies.lib.xml.XMLDocumentUnparser;
63 import com.virtuosotechnologies.lib.xml.ZeroToleranceErrorHandler;
64
65 import com.virtuosotechnologies.asaph.model.Song;
66 import com.virtuosotechnologies.asaph.model.SongID;
67 import com.virtuosotechnologies.asaph.model.SongDatabase;
68 import com.virtuosotechnologies.asaph.modelutils.SongUtils;
69 import com.virtuosotechnologies.asaph.standardmodel.StandardModelFactory;
70
71
72 /**
73 * Implementation of IndexedDatabaseFactory
74 */
75 /*package*/ class IndexedDatabaseFactoryImpl
76 implements
77 IndexedDatabaseFactory
78 {
79 private static final String DTD_FILENAME = "databaseindex.dtd";
80 private static final String STR_PreferredCharacterEncoding =
81 ResourceAccess.Strings.buildString("PreferredCharacterEncoding");
82
83 private SAXParserFactory parserFactory_;
84 private SongUtils songUtils_;
85 private StandardModelFactory modelFactory_;
86 private String dtdString_;
87
88
89 /**
90 * Constructor
91 */
92 /*package*/ IndexedDatabaseFactoryImpl(
93 SongUtils songUtils,
94 StandardModelFactory modelFactory)
95 throws
96 IOException
97 {
98 songUtils_ = songUtils;
99 modelFactory_ = modelFactory;
100 parserFactory_ = SAXParserFactory.newInstance();
101 parserFactory_.setValidating(true);
102 parserFactory_.setNamespaceAware(true);
103 dtdString_ = FileUtils.readResourceCompletely(getClass(), DTD_FILENAME);
104 }
105
106
107 /**
108 * Create a new mutable indexed database rooted at the given directory.
109 *
110 * @param directory directory to create
111 * @return database created
112 * @exception IOException fatal error
113 * @exception SAXException fatal error
114 */
115 public SongDatabase createIndexedDatabase(
116 File directory)
117 throws
118 IOException,
119 SAXException
120 {
121 if (directory.exists())
122 {
123 throw new IOException(ResourceAccess.Strings.buildString(
124 "err_DirExists", directory));
125 }
126 if (!directory.mkdirs())
127 {
128 throw new IOException(ResourceAccess.Strings.buildString(
129 "err_CantCreateDir", directory));
130 }
131 File indexFile = new File(directory, IndexConstants.INDEX_FILENAME);
132 OutputStream stream = new FileOutputStream(indexFile);
133 try
134 {
135 XMLDocumentUnparser unparser = XMLDocumentUnparser.create(stream, STR_PreferredCharacterEncoding,
136 IndexConstants.XMLDATABASEINDEX_DTD, IndexConstants.INDEXROOT_ELEMENT);
137 unparser.addAttribute("xmlns", IndexConstants.XMLDATABASEINDEX_NAMESPACE);
138 unparser.startMultiLineElement(IndexConstants.INDEX_ELEMENT);
139 unparser.addAttribute(IndexConstants.INDEX_LASTID_ATTRIBUTE, "");
140 unparser.endElement(IndexConstants.INDEX_ELEMENT);
141 unparser.finishDocument(IndexConstants.INDEXROOT_ELEMENT);
142 }
143 finally
144 {
145 try
146 {
147 stream.close();
148 }
149 catch (IOException ex)
150 {
151 }
152 }
153 return openIndexedDatabase(directory);
154 }
155
156
157 /**
158 * Open a mutable indexed database backed by the filesystem at the given directory.
159 *
160 * @param directory indexed database to open
161 * @return database opened
162 * @exception IOException fatal error
163 * @exception SAXException fatal error
164 */
165 public SongDatabase openIndexedDatabase(
166 File directory)
167 throws
168 IOException,
169 SAXException
170 {
171 if (!directory.isDirectory())
172 {
173 throw new IOException(ResourceAccess.Strings.buildString(
174 "err_NotDir", directory));
175 }
176 try
177 {
178 SAXParser parser = parserFactory_.newSAXParser();
179 return new IndexedSongDatabase(songUtils_, modelFactory_, directory,
180 parser, dtdString_);
181 }
182 catch (ParserConfigurationException ex)
183 {
184 throw new IOException(ResourceAccess.Strings.buildString(
185 "err_UnexpectedException", ex));
186 }
187 }
188
189
190 /**
191 * Open an immutable indexed database backed by the given URL.
192 *
193 * @param url indexed database to open
194 * @param errorHandler handler for errors parsing the index file
195 * @return database opened
196 * @exception IOException fatal error
197 * @exception SAXException fatal error
198 */
199 public SongDatabase openIndexedDatabase(
200 URL url,
201 ErrorHandler errorHandler)
202 throws
203 IOException,
204 SAXException
205 {
206 String urlStr = url.toExternalForm();
207 if (urlStr.charAt(urlStr.length()-1) != '/')
208 {
209 urlStr += '/';
210 }
211 url = new URL(urlStr);
212 try
213 {
214 SAXParser parser = parserFactory_.newSAXParser();
215 return new IndexedSongDatabase(songUtils_, modelFactory_, url, parser,
216 dtdString_, errorHandler);
217 }
218 catch (ParserConfigurationException ex)
219 {
220 throw new IOException(ResourceAccess.Strings.buildString(
221 "err_UnexpectedException", ex));
222 }
223 }
224
225
226 /**
227 * Is the given database an indexed database?
228 *
229 * @param database database to check
230 * @return true if it is an indexed database
231 */
232 public boolean isIndexedDatabase(
233 SongDatabase database)
234 {
235 return database instanceof IndexedSongDatabase;
236 }
237
238
239 /**
240 * Update a filesystem indexed database from version 0.3 to version 0.4.
241 *
242 * @param directory indexed database to update
243 * @exception IOException fatal error
244 * @exception SAXException fatal error
245 */
246 public void updateDatabase04(
247 File directory)
248 throws
249 IOException,
250 SAXException
251 {
252 String[] contents = directory.list(
253 new FilenameFilter()
254 {
255 public boolean accept(
256 File dir,
257 String name)
258 {
259 if (!name.startsWith(IndexConstants.SONG_PREFIX) ||
260 !name.endsWith(IndexConstants.SONG_SUFFIX_OLD))
261 {
262 return false;
263 }
264 name = name.substring(IndexConstants.SONG_PREFIX.length(),
265 name.length()-IndexConstants.SONG_SUFFIX_OLD.length());
266 return StringID.isWellFormed(name);
267 }
268 });
269 if (contents == null)
270 {
271 throw new FileNotFoundException();
272 }
273
274 for (int i=0; i<contents.length; ++i)
275 {
276 File file = new File(directory, contents[i]);
277 String idStr = contents[i].substring(IndexConstants.SONG_PREFIX.length(),
278 contents[i].length()-IndexConstants.SONG_SUFFIX_OLD.length());
279 InputStream is = null;
280 OutputStream os = null;
281 try
282 {
283 SongID songID = new DummySongIDImpl(new StringID(idStr));
284 is = new FileInputStream(file);
285 Song song = modelFactory_.parseSong(FileUtils.createUTFReader(is),
286 songID, new ZeroToleranceErrorHandler());
287 is.close();
288 is = null;
289 String versionStr = modelFactory_.getSongVersion(song).getValue();
290 File file2 = new File(directory, IndexConstants.SONG_PREFIX+idStr+
291 IndexConstants.SONG_INTER+versionStr+IndexConstants.SONG_SUFFIX);
292 os = new FileOutputStream(file2);
293 modelFactory_.unparseSong(song, os, STR_PreferredCharacterEncoding);
294 os.close();
295 os = null;
296 file.delete();
297 }
298 finally
299 {
300 try
301 {
302 if (is != null)
303 {
304 is.close();
305 }
306 if (os != null)
307 {
308 os.close();
309 }
310 }
311 catch (IOException ex)
312 {
313 }
314 }
315 }
316
317 new File(directory, IndexConstants.INDEX_FILENAME_OLD).delete();
318 }
319
320
321 /**
322 * Dummy implementation of SongID
323 */
324 /*package*/ class DummySongIDImpl
325 implements SongID
326 {
327 private StringID id_;
328
329 /*package*/ DummySongIDImpl(
330 StringID id)
331 {
332 id_ = id;
333 }
334
335 public SongDatabase getDatabase()
336 {
337 return null;
338 }
339
340 public String getStringRepresentation()
341 {
342 return id_.getValue();
343 }
344
345 public boolean equals(
346 Object obj)
347 {
348 if (obj instanceof DummySongIDImpl)
349 {
350 DummySongIDImpl ssid = (DummySongIDImpl)obj;
351 return ssid.id_.equals(id_);
352 }
353 return false;
354 }
355
356 public int hashCode()
357 {
358 return id_.hashCode();
359 }
360 }
361 }