1 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 ; 38 import java.util.HashSet ; 39 import java.io.*; 40 import java.net.MalformedURLException ; 41 import java.net.URL ; 42 import java.beans.PropertyChangeListener ; 43 import java.beans.PropertyChangeEvent ; 44 45 import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap; 46 import EDU.oswego.cs.dl.util.concurrent.Mutex; 47 48 54 public class SkinSourceFactory extends AbstractLogEnabled implements SourceFactory, Contextualizable, Serviceable, 55 Disposable, ThreadSafe, Initializable { 56 private Context context; 57 60 private Map baseSkinCache = new ConcurrentReaderHashMap(); 61 64 private Mutex updateMutex = new Mutex(); 65 private ActiveMonitor monitor; 66 private ServiceManager serviceManager; 67 private static final String 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 { 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 location, Map parameters) throws IOException, MalformedURLException { 89 Request request = ContextHelper.getRequest(context); 90 initFallbackDir(); 91 92 String [] parsedLocation = parseLocation(location); 93 94 String skin; 95 if (parsedLocation[0] != null) { 96 skin = parsedLocation[0]; 97 } else { 98 skin = (String )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 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 searchedParentSkins = null; 115 116 while ((skinSelectedSource = getExistingFile(skinResource, skinFallbackResource)) == null) { 117 String 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 (); 130 searchedParentSkins.add(parentSkin); 131 } else { 132 skinSelectedSource = skinOriginalResource; 134 break; 135 } 136 } 137 138 StringBuffer absoluteURL = new StringBuffer (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 [] parseLocation(String location) throws MalformedURLException { 158 if (!location.startsWith("daisyskin:")) 159 throw new MalformedURLException ("The URL does not use the daisyskin sheme, it cannot be handled by this source implementation."); 160 161 String schemeSpecificPart = location.substring("daisyskin:".length()); 162 163 String skinName; 164 String locationName; String path; 166 167 if (schemeSpecificPart.startsWith("/(")) { 168 int closeSkinNameParenPos = schemeSpecificPart.indexOf(')'); 169 if (closeSkinNameParenPos == -1) 170 throw new MalformedURLException ("Missing closing parenthesis in: " + location); 171 skinName = schemeSpecificPart.substring(2, closeSkinNameParenPos); 172 if (skinName.trim().equals("")) { 173 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 ("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 ("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 [] {skinName, locationName, path}; 200 } 201 202 private String 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 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 baseSkin = loadBaseSkin(baseSkinFile); 222 223 Resource fileResource; 224 try { 225 fileResource = new AltFileResource(baseSkinFile); 226 } catch (Exception 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 baseSkin; 242 public Resource fileResource; 243 244 public CachedEntry(String baseSkin, Resource fileResource) { 245 this.baseSkin = baseSkin; 246 this.fileResource = fileResource; 247 } 248 } 249 250 class BaseSkinListener implements PropertyChangeListener { 251 private File skinDir; 252 253 public BaseSkinListener(File skinDir) { 254 this.skinDir = skinDir; 255 } 256 257 public void propertyChange(PropertyChangeEvent 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 monitor.removeResource(fileResource); 278 } 279 } 280 } finally { 281 updateMutex.release(); 282 } 283 } catch (Exception e) { 284 getLogger().error("Error processing baseskin.txt change event for " + skinDir.getAbsolutePath(), e); 285 } 286 } 287 } 288 289 private String loadBaseSkin(File baseSkinFile) throws IOException { 290 if (baseSkinFile.exists()) { 291 String 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 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 { 321 if (skinsFallbackDir == null) { 322 synchronized(this) { 323 if (skinsFallbackDir == null) { 324 Request request = ContextHelper.getRequest(context); 325 File contextDir = new File(new URL (WikiHelper.getDaisyContextPath(request)).getPath()); 326 skinsFallbackDir = new File(contextDir, "resources" + File.separator + "skins").getAbsoluteFile(); 327 } 328 } 329 } 330 } 331 } 332 | Popular Tags |