001: /* *************************************************************************
002:
003: Millstone(TM)
004: Open Sourced User Interface Library for
005: Internet Development with Java
006:
007: Millstone is a registered trademark of IT Mill Ltd
008: Copyright (C) 2000-2005 IT Mill Ltd
009:
010: *************************************************************************
011:
012: This library is free software; you can redistribute it and/or
013: modify it under the terms of the GNU Lesser General Public
014: license version 2.1 as published by the Free Software Foundation.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: *************************************************************************
026:
027: For more information, contact:
028:
029: IT Mill Ltd phone: +358 2 4802 7180
030: Ruukinkatu 2-4 fax: +358 2 4802 7181
031: 20540, Turku email: info@itmill.com
032: Finland company www: www.itmill.com
033:
034: Primary source for MillStone information and releases: www.millstone.org
035:
036: ********************************************************************** */
037:
038: package org.millstone.webadapter;
039:
040: import java.io.ByteArrayInputStream;
041: import java.io.ByteArrayOutputStream;
042: import java.io.File;
043: import java.io.FileNotFoundException;
044: import java.io.IOException;
045: import java.io.InputStream;
046: import java.lang.ref.SoftReference;
047: import java.util.Collection;
048: import java.util.Enumeration;
049: import java.util.HashMap;
050: import java.util.Iterator;
051: import java.util.LinkedList;
052: import java.util.Map;
053: import java.util.jar.JarEntry;
054: import java.util.jar.JarFile;
055:
056: /** Theme source for reading themes from a JAR archive.
057: * At this time only jar files are supported and an archive
058: * may not contain any recursive archives.
059: * @author IT Mill Ltd.
060: * @version 3.1.1
061: * @since 3.0
062: */
063: public class JarThemeSource implements ThemeSource {
064:
065: private File file;
066: private JarFile jar;
067: private Theme theme;
068: private String path;
069: private String name;
070: private WebAdapterServlet webAdapterServlet;
071: private Cache resourceCache = new Cache();
072:
073: /** Collection of subdirectory entries */
074: private Collection subdirs = new LinkedList();
075:
076: /** Creates a new instance of ThemeRepository by reading the themes
077: * from a local directory.
078: * @param file Path to the JAR archive .
079: * @param path Path inside the archive to be processed.
080: * @throws FileNotFoundException if no theme files are found
081: */
082: public JarThemeSource(File file,
083: WebAdapterServlet webAdapterServlet, String path)
084: throws ThemeException, FileNotFoundException, IOException {
085:
086: this .file = file;
087: this .jar = new JarFile(file);
088: this .theme = null;
089: this .path = path;
090: if (this .path.length() > 0 && !this .path.endsWith("/")) {
091: this .path = this .path + "/";
092: }
093: this .name = file.getName();
094: if (this .name.toLowerCase().endsWith(".jar")) {
095: this .name = this .name.substring(0, this .name.length() - 4);
096: }
097:
098: this .webAdapterServlet = webAdapterServlet;
099:
100: // Load description file
101: JarEntry entry = jar.getJarEntry(this .path
102: + Theme.DESCRIPTIONFILE);
103: if (entry != null) {
104: try {
105: this .theme = new Theme(jar.getInputStream(entry));
106: } catch (Exception e) {
107: throw new ThemeException(
108: "JarThemeSource: Failed to load '" + path
109: + "': " + e);
110: }
111:
112: // Debug info
113: if (webAdapterServlet.isDebugMode()) {
114: Log.debug("Added JarThemeSource: " + this .file + ":"
115: + this .path);
116: }
117:
118: } else {
119: // There was no description file found.
120: // Handle subdirectories recursively
121: for (Enumeration entries = jar.entries(); entries
122: .hasMoreElements();) {
123: JarEntry e = (JarEntry) entries.nextElement();
124: if (e.getName().startsWith(this .path)) {
125: if (e.getName().endsWith("/")
126: && e.getName().indexOf('/',
127: this .path.length()) == (e.getName()
128: .length() - 1)) {
129: this .subdirs.add(new JarThemeSource(this .file,
130: this .webAdapterServlet, e.getName()));
131: }
132: }
133: }
134:
135: if (this .subdirs.isEmpty()) {
136: if (webAdapterServlet.isDebugMode()) {
137: Log
138: .info("JarThemeSource: Ignoring empty JAR path: "
139: + this .file + " path: " + this .path);
140: }
141: }
142: }
143: }
144:
145: /**
146: * @see org.millstone.webadapter.ThemeSource#getXSLStreams(Theme, WebBrowser)
147: */
148: public Collection getXSLStreams(Theme theme, WebBrowser type)
149: throws ThemeException {
150: Collection xslFiles = new LinkedList();
151: // If this directory contains a theme
152: // return XSL from this theme
153: if (this .theme != null) {
154:
155: if (webAdapterServlet.isDebugMode()) {
156: Log.info("JarThemeSource: Loading XSL from: " + theme);
157: }
158:
159: // Reload the theme if JAR has been modified
160: JarEntry entry = jar.getJarEntry(this .path
161: + Theme.DESCRIPTIONFILE);
162: if (entry != null) {
163: try {
164: this .theme = new Theme(jar.getInputStream(entry));
165: } catch (IOException e) {
166: throw new ThemeException(
167: "Failed to read description: " + this .file
168: + ":" + this .path
169: + Theme.DESCRIPTIONFILE);
170: }
171: }
172:
173: Collection fileNames = theme.getFileNames(type);
174: // Add all XSL file streams
175: for (Iterator i = fileNames.iterator(); i.hasNext();) {
176: entry = jar.getJarEntry(this .path + (String) i.next());
177: try {
178: xslFiles.add(new XSLStream(entry.getName(), jar
179: .getInputStream(entry)));
180: } catch (java.io.FileNotFoundException e) {
181: throw new ThemeException("XSL File not found: "
182: + this .file + ": " + entry);
183: } catch (java.io.IOException e) {
184: throw new ThemeException(
185: "Failed to read XSL file. " + this .file
186: + ": " + entry);
187: }
188: }
189:
190: } else {
191:
192: // Handle subdirectories in archive: return the first match
193: for (Iterator i = this .subdirs.iterator(); i.hasNext();) {
194: ThemeSource source = (ThemeSource) i.next();
195: if (source.getThemes().contains(theme))
196: xslFiles.addAll(source.getXSLStreams(theme, type));
197: }
198: }
199:
200: return xslFiles;
201: }
202:
203: /** Return modication time of the jar file.
204: * @see org.millstone.webadapter.ThemeSource#getModificationTime()
205: */
206: public long getModificationTime() {
207: return this .file.lastModified();
208: }
209:
210: /**
211: * @see org.millstone.webadapter.ThemeSource#getResource(String)
212: */
213: public InputStream getResource(String resourceId)
214: throws ThemeSource.ThemeException {
215:
216: // Strip off the theme name prefix from resource id
217: if (this .theme != null && this .theme.getName() != null
218: && resourceId.startsWith(this .theme.getName() + "/")) {
219: resourceId = resourceId.substring(this .theme.getName()
220: .length() + 1);
221: }
222:
223: // Return the resource inside the jar file
224: JarEntry entry = jar.getJarEntry(resourceId);
225: if (entry != null)
226: try {
227:
228: // Try cache
229: byte[] data = (byte[]) resourceCache.get(entry);
230: if (data != null)
231: return new ByteArrayInputStream(data);
232:
233: // Read data
234: int bufSize = 1024;
235: ByteArrayOutputStream out = new ByteArrayOutputStream(
236: bufSize);
237: InputStream in = jar.getInputStream(entry);
238: byte[] buf = new byte[bufSize];
239: int n = 0;
240: while ((n = in.read(buf)) >= 0) {
241: out.write(buf, 0, n);
242: }
243: in.close();
244: data = out.toByteArray();
245:
246: // Cache data
247: resourceCache.put(entry, data);
248: return new ByteArrayInputStream(data);
249: } catch (IOException e) {
250: }
251:
252: throw new ThemeSource.ThemeException("Resource " + resourceId
253: + " not found.");
254: }
255:
256: /**
257: * @see org.millstone.webadapter.ThemeSource#getThemes()
258: */
259: public Collection getThemes() {
260: Collection themes = new LinkedList();
261: if (this .theme != null) {
262: themes.add(this .theme);
263: } else {
264: for (Iterator i = this .subdirs.iterator(); i.hasNext();) {
265: ThemeSource source = (ThemeSource) i.next();
266: themes.addAll(source.getThemes());
267: }
268: }
269: return themes;
270: }
271:
272: /**
273: * @see org.millstone.webadapter.ThemeSource#getName()
274: */
275: public String getName() {
276: if (this .theme != null) {
277: return this .theme.getName();
278: } else {
279: return this .name;
280: }
281: }
282:
283: /**
284: * @see org.millstone.webadapter.ThemeSource#getThemeByName(String)
285: */
286: public Theme getThemeByName(String name) {
287: Collection themes = this .getThemes();
288: for (Iterator i = themes.iterator(); i.hasNext();) {
289: Theme t = (Theme) i.next();
290: if (name != null && name.equals(t.getName()))
291: return t;
292: }
293: return null;
294: }
295:
296: /**
297: * @author IT Mill Ltd.
298: * @version 3.1.1
299: * @since 3.0
300: */
301: private class Cache {
302:
303: private Map data = new HashMap();
304:
305: public void put(Object key, Object value) {
306: data.put(key, new SoftReference(new CacheItem(value, key
307: .toString())));
308: }
309:
310: public Object get(Object key) {
311: SoftReference ref = (SoftReference) data.get(key);
312: if (ref != null)
313: return ((CacheItem) ref.get()).getData();
314: return null;
315: }
316:
317: public void clear() {
318: data.clear();
319: }
320: }
321:
322: /**
323: * @author IT Mill Ltd.
324: * @version 3.1.1
325: * @since 3.0
326: */
327: private class CacheItem {
328:
329: private Object data;
330: private String name;
331:
332: public CacheItem(Object data, String name) {
333: this .name = name;
334: this .data = data;
335: }
336:
337: public Object getData() {
338: return this .data;
339: };
340:
341: public void finalize() throws Throwable {
342: this.data = null;
343: this.name = null;
344: super.finalize();
345: }
346:
347: }
348:
349: }
|