KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > outerj > daisy > frontend > components > skinsource > SkinSourceFactory


1 /*
2  * Copyright 2004 Outerthought bvba and Schaubroeck nv
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.outerj.daisy.frontend.components.skinsource;
17
18 import org.apache.excalibur.source.*;
19 import org.apache.avalon.framework.context.Contextualizable;
20 import org.apache.avalon.framework.context.Context;
21 import org.apache.avalon.framework.context.ContextException;
22 import org.apache.avalon.framework.service.ServiceManager;
23 import org.apache.avalon.framework.service.ServiceException;
24 import org.apache.avalon.framework.service.Serviceable;
25 import org.apache.avalon.framework.activity.Disposable;
26 import org.apache.avalon.framework.activity.Initializable;
27 import org.apache.avalon.framework.logger.AbstractLogEnabled;
28 import org.apache.avalon.framework.thread.ThreadSafe;
29 import org.apache.avalon.excalibur.monitor.ActiveMonitor;
30 import org.apache.avalon.excalibur.monitor.Resource;
31 import org.apache.cocoon.components.ContextHelper;
32 import org.apache.cocoon.environment.Request;
33 import org.outerj.daisy.frontend.util.AltFileResource;
34 import org.outerj.daisy.frontend.util.WikiDataDirHelper;
35 import org.outerj.daisy.frontend.WikiHelper;
36
37 import java.util.Map JavaDoc;
38 import java.util.HashSet JavaDoc;
39 import java.io.*;
40 import java.net.MalformedURLException JavaDoc;
41 import java.net.URL JavaDoc;
42 import java.beans.PropertyChangeListener JavaDoc;
43 import java.beans.PropertyChangeEvent JavaDoc;
44
45 import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
46 import EDU.oswego.cs.dl.util.concurrent.Mutex;
47
48 /**
49  * Source Factory for "daisyskin:" sources.
50  *
51  * <p>For documentation on this source, see the Daisy documentation, at the time of this
52  * writing at http://cocoondev.org/daisydocs-1_5/192.html
53  */

54 public class SkinSourceFactory extends AbstractLogEnabled implements SourceFactory, Contextualizable, Serviceable,
55         Disposable, ThreadSafe, Initializable {
56     private Context context;
57     /**
58      * Map key = java.util.File object, map value = a {@link CachedEntry} object.
59      */

60     private Map JavaDoc baseSkinCache = new ConcurrentReaderHashMap();
61     /**
62      * A mutex to synchronize updates to the baseSkinCache.
63      */

64     private Mutex updateMutex = new Mutex();
65     private ActiveMonitor monitor;
66     private ServiceManager serviceManager;
67     private static final String JavaDoc BASE_SKIN_FILENAME = "baseskin.txt";
68     private File skinsDir;
69     private File skinsFallbackDir;
70
71     public void contextualize(Context context) throws ContextException {
72         this.context = context;
73     }
74
75     public void service(ServiceManager serviceManager) throws ServiceException {
76         this.serviceManager = serviceManager;
77         monitor = (ActiveMonitor)serviceManager.lookup(ActiveMonitor.ROLE);
78     }
79
80     public void initialize() throws Exception JavaDoc {
81         this.skinsDir = new File(new File(WikiDataDirHelper.getWikiDataDir(context)), "resources" + File.separator + "skins").getAbsoluteFile();
82     }
83
84     public void dispose() {
85         serviceManager.release(monitor);
86     }
87
88     public Source getSource(String JavaDoc location, Map JavaDoc parameters) throws IOException, MalformedURLException JavaDoc {
89         Request request = ContextHelper.getRequest(context);
90         initFallbackDir();
91
92         String JavaDoc[] parsedLocation = parseLocation(location);
93
94         String JavaDoc skin;
95         if (parsedLocation[0] != null) {
96             skin = parsedLocation[0];
97         } else {
98             skin = (String JavaDoc)request.getAttribute("skin");
99             if (skin == null)
100                 throw new IOException("Missing \"skin\" attribute in the Cocoon Request object.");
101         }
102
103         boolean forceFallback = parsedLocation[1] != null;
104
105         String JavaDoc path = parsedLocation[2];
106
107         File skinFallbackDir = new File(skinsFallbackDir, skin);
108         File skinFallbackResource = new File(skinFallbackDir, path);
109         File skinDir = new File(skinsDir, skin);
110         File skinResource = forceFallback ? skinFallbackResource : new File(skinDir, path);
111         File skinOriginalResource = skinResource;
112         File skinSelectedSource;
113
114         HashSet JavaDoc searchedParentSkins = null;
115
116         while ((skinSelectedSource = getExistingFile(skinResource, skinFallbackResource)) == null) {
117             String JavaDoc parentSkin = getBaseSkin(skinDir);
118             parentSkin = parentSkin == null ? getBaseSkin(skinFallbackDir) : parentSkin;
119             if (parentSkin != null) {
120                 if (searchedParentSkins != null && searchedParentSkins.contains(parentSkin))
121                     throw new IOException("Recursive skin dependency found for skin \"" + skin + "\" and base skin \"" + parentSkin + "\".");
122
123                 skinFallbackDir = new File(skinsFallbackDir, parentSkin);
124                 skinFallbackResource = new File(skinFallbackDir, path);
125                 skinDir = new File(skinsDir, parentSkin);
126                 skinResource = forceFallback ? skinFallbackResource : new File(skinDir, path);
127
128                 if (searchedParentSkins == null)
129                     searchedParentSkins = new HashSet JavaDoc();
130                 searchedParentSkins.add(parentSkin);
131             } else {
132                 // just pretend we found it at the original location
133
skinSelectedSource = skinOriginalResource;
134                 break;
135             }
136         }
137
138         // construct absolute URL
139
StringBuffer JavaDoc absoluteURL = new StringBuffer JavaDoc(150);
140         absoluteURL.append("daisyskin:/(").append(skin).append(')');
141         if (forceFallback)
142             absoluteURL.append("(webapp)");
143         absoluteURL.append(path);
144
145         return new SkinSource(skinSelectedSource, absoluteURL.toString());
146     }
147
148     private File getExistingFile(File firstChoice, File secondChoice) {
149         if (firstChoice.exists())
150             return firstChoice;
151         else if (secondChoice.exists())
152             return secondChoice;
153         else
154             return null;
155     }
156
157     private String JavaDoc[] parseLocation(String JavaDoc location) throws MalformedURLException JavaDoc {
158         if (!location.startsWith("daisyskin:"))
159             throw new MalformedURLException JavaDoc("The URL does not use the daisyskin sheme, it cannot be handled by this source implementation.");
160
161         String JavaDoc schemeSpecificPart = location.substring("daisyskin:".length());
162
163         String JavaDoc skinName;
164         String JavaDoc locationName; // wikidata or webapp
165
String JavaDoc path;
166
167         if (schemeSpecificPart.startsWith("/(")) {
168             int closeSkinNameParenPos = schemeSpecificPart.indexOf(')');
169             if (closeSkinNameParenPos == -1)
170                 throw new MalformedURLException JavaDoc("Missing closing parenthesis in: " + location);
171             skinName = schemeSpecificPart.substring(2, closeSkinNameParenPos);
172             if (skinName.trim().equals("")) {
173                 // it is allowed to have an empty skin specification (e.g. in case you
174
// only want to specify the skins dir)
175
skinName = null;
176             }
177             int pathStartPos = closeSkinNameParenPos + 1;
178             if (schemeSpecificPart.length() > closeSkinNameParenPos && schemeSpecificPart.charAt(closeSkinNameParenPos + 1) == '(') {
179                 int locationNameParenPos = schemeSpecificPart.indexOf(')', closeSkinNameParenPos + 1);
180                 if (locationNameParenPos == -1)
181                     throw new MalformedURLException JavaDoc("Missing closing parenthesis in: " + location);
182                 locationName = schemeSpecificPart.substring(closeSkinNameParenPos + 2, locationNameParenPos);
183                 if (locationName.trim().equals("")) {
184                     locationName = null;
185                 } else if (!locationName.equals("webapp")) {
186                     throw new MalformedURLException JavaDoc("The location name in the URL, when specified, should be 'webapp'.");
187                 }
188                 pathStartPos = locationNameParenPos + 1;
189             } else {
190                 locationName = null;
191             }
192             path = schemeSpecificPart.substring(pathStartPos);
193         } else {
194             skinName = null;
195             locationName = null;
196             path = schemeSpecificPart;
197         }
198
199         return new String JavaDoc[] {skinName, locationName, path};
200     }
201
202     private String JavaDoc getBaseSkin(File skinDir) throws IOException {
203         CachedEntry cachedEntry = (CachedEntry)baseSkinCache.get(skinDir);
204         if (cachedEntry != null)
205             return cachedEntry.baseSkin;
206
207         if (!skinDir.exists())
208             return null;
209
210         try {
211             updateMutex.acquire();
212         } catch (InterruptedException JavaDoc e) {
213             throw new IOException("Error doing updateMutex.acquire(): " + e.toString());
214         }
215         try {
216             cachedEntry = (CachedEntry)baseSkinCache.get(skinDir);
217             if (cachedEntry != null)
218                 return cachedEntry.baseSkin;
219
220             File baseSkinFile = new File(skinDir, BASE_SKIN_FILENAME);
221             String JavaDoc baseSkin = loadBaseSkin(baseSkinFile);
222
223             Resource fileResource;
224             try {
225                 fileResource = new AltFileResource(baseSkinFile);
226             } catch (Exception JavaDoc e) {
227                 throw new IOException("Error constructing FileResource object for " + baseSkinFile.getAbsolutePath() + ": " + e.toString());
228             }
229             fileResource.addPropertyChangeListener(new BaseSkinListener(skinDir));
230             cachedEntry = new CachedEntry(baseSkin, fileResource);
231             baseSkinCache.put(skinDir, cachedEntry);
232             monitor.addResource(fileResource);
233
234             return baseSkin;
235         } finally {
236             updateMutex.release();
237         }
238     }
239
240     static class CachedEntry {
241         public String JavaDoc baseSkin;
242         public Resource fileResource;
243
244         public CachedEntry(String JavaDoc baseSkin, Resource fileResource) {
245             this.baseSkin = baseSkin;
246             this.fileResource = fileResource;
247         }
248     }
249
250     class BaseSkinListener implements PropertyChangeListener JavaDoc {
251         private File skinDir;
252
253         public BaseSkinListener(File skinDir) {
254             this.skinDir = skinDir;
255         }
256
257         public void propertyChange(PropertyChangeEvent JavaDoc evt) {
258             if (getLogger().isDebugEnabled()) {
259                 getLogger().debug("Received change event for " + ((Resource)evt.getSource()).getResourceKey());
260             }
261
262             try {
263                 updateMutex.acquire();
264                 try {
265                     Resource fileResource = (Resource)evt.getSource();
266
267                     if (!skinDir.exists()) {
268                         baseSkinCache.remove(skinDir);
269                         monitor.removeResource(fileResource);
270                     } else {
271                         CachedEntry cachedEntry = (CachedEntry)baseSkinCache.get(skinDir);
272                         if (cachedEntry != null) {
273                             File baseSkinFile = new File(skinDir, BASE_SKIN_FILENAME);
274                             cachedEntry.baseSkin = loadBaseSkin(baseSkinFile);
275                         } else {
276                             // this situation should normally not occur
277
monitor.removeResource(fileResource);
278                         }
279                     }
280                 } finally {
281                     updateMutex.release();
282                 }
283             } catch (Exception JavaDoc e) {
284                 getLogger().error("Error processing baseskin.txt change event for " + skinDir.getAbsolutePath(), e);
285             }
286         }
287     }
288
289     private String JavaDoc loadBaseSkin(File baseSkinFile) throws IOException {
290         if (baseSkinFile.exists()) {
291             String JavaDoc baseskin = null;
292             FileReader fileReader = null;
293             try {
294                 fileReader = new FileReader(baseSkinFile);
295                 BufferedReader reader = new BufferedReader(fileReader);
296                 baseskin = reader.readLine();
297             } catch (Exception JavaDoc e) {
298                 throw new IOException("Error reading baseskin.txt file at " + baseSkinFile.getAbsolutePath());
299             } finally {
300                 if (fileReader != null)
301                     fileReader.close();
302             }
303
304             if (baseskin == null) {
305                 return null;
306             } else {
307                 baseskin = baseskin.trim();
308                 if (baseskin.length() == 0)
309                     return null;
310                 else
311                     return baseskin;
312             }
313         }
314         return null;
315     }
316
317     public void release(Source source) {
318     }
319
320     private void initFallbackDir() throws MalformedURLException JavaDoc {
321         if (skinsFallbackDir == null) {
322             synchronized(this) {
323                 if (skinsFallbackDir == null) {
324                     Request request = ContextHelper.getRequest(context);
325                     File contextDir = new File(new URL JavaDoc(WikiHelper.getDaisyContextPath(request)).getPath());
326                     skinsFallbackDir = new File(contextDir, "resources" + File.separator + "skins").getAbsoluteFile();
327                 }
328             }
329         }
330     }
331 }
332
Popular Tags