PageRenderTime 52ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/java/src/org/jpublish/repository/filesystem/ExtendedFileSystemRepository.java

http://jpublish.googlecode.com/
Java | 459 lines | 224 code | 72 blank | 163 comment | 42 complexity | c8fa81635702fb468ff24785cb33253c MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. /*
  2. * Copyright 2004-2007 the original author or authors.
  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. */
  17. package org.jpublish.repository.filesystem;
  18. import com.anthonyeden.lib.config.Configuration;
  19. import com.anthonyeden.lib.config.ConfigurationException;
  20. import com.anthonyeden.lib.config.XMLConfiguration;
  21. import com.anthonyeden.lib.util.IOUtilities;
  22. import com.atlassian.util.profiling.UtilTimerStack;
  23. import org.apache.commons.logging.Log;
  24. import org.apache.commons.logging.LogFactory;
  25. import org.jpublish.*;
  26. import org.jpublish.action.ActionManager;
  27. import org.jpublish.action.ActionNotFoundException;
  28. import org.jpublish.util.*;
  29. import org.jpublish.view.ViewRenderer;
  30. import java.io.*;
  31. import java.util.Iterator;
  32. import java.util.List;
  33. import java.util.Locale;
  34. /**
  35. * The ExtendedFileSystemRepository allows actions to be bound to content
  36. * elements through XML configuration files. Only actions bound to dynamic
  37. * content elements will be executed.
  38. * <p/>
  39. * <p>Note: Actions attached to content elements cannot cause an HTTP redirect.
  40. * The "redirect" value will be ignored.</p>
  41. * <p/>
  42. * Florin - refactoring the code for speed and reimplementeing the cache
  43. *
  44. * @author Anthony Eden
  45. * @author <a href="mailto:florin.patrascu@gmail.com">Florin T.PATRASCU</a>
  46. */
  47. public class ExtendedFileSystemRepository extends AbstractFileSystemRepository implements Configurable {
  48. private static final Log log = LogFactory.getLog(ExtendedFileSystemRepository.class);
  49. private static final String EMPTY_STRING = "";
  50. private static final String DEFAULT_CACHE_NAME = "default";
  51. private static final String XML_TYPE = ".xml";
  52. private JPublishCache cache = null;
  53. private String cacheName = DEFAULT_CACHE_NAME;
  54. private String configurationDirectoryName = "config";
  55. private static final String DOT = ".";
  56. private static final String PROPERTY_NAME = "name";
  57. private static final String PROPERTY_VALUE = "value";
  58. private static final String PROPERTY_LOCALE = "locale";
  59. //private Map configCache = new HashMap();
  60. /**
  61. * Get the content from the given path. Implementations of this method
  62. * should NOT merge the content using view renderer.
  63. *
  64. * @param path The relative content path
  65. * @return The content as a String
  66. * @throws Exception Any Exception
  67. */
  68. public String get(String path) throws Exception {
  69. if (log.isDebugEnabled())
  70. log.debug("Getting static content element.");
  71. //log.info("get: "+path);
  72. return loadContent(path);
  73. }
  74. /**
  75. * Get the content from the given path and merge it with the given
  76. * context. Any actions attached to the content will be executed
  77. * first.
  78. *
  79. * @param path The content path
  80. * @param context The current context
  81. * @return The content as a String
  82. * @throws Exception Any Exception
  83. */
  84. public String get(String path, JPublishContext context) throws Exception {
  85. UtilTimerStack.push( " ==> /"+path);
  86. executeActions(path, context);
  87. if (log.isDebugEnabled())
  88. log.debug("Getting dynamic content element for path " + path);
  89. StringWriter writer = null;
  90. BufferedReader reader = null;
  91. Reader in = null;
  92. try {
  93. in = new StringReader(loadContent(path));
  94. reader = new BufferedReader(in);
  95. writer = new StringWriter();
  96. String name = PathUtilities.makeRepositoryURI(getName(), path);
  97. ViewRenderer renderer = siteContext.getViewRenderer();
  98. renderer.render(context, name, reader, writer);
  99. return writer.toString();
  100. } catch (FileNotFoundException e) {
  101. log.error(e.getMessage());
  102. throw new FileNotFoundException("File not found: " + path);
  103. } finally {
  104. IOUtilities.close(in);
  105. IOUtilities.close(reader);
  106. IOUtilities.close(writer);
  107. UtilTimerStack.pop( " ==> /"+path);
  108. }
  109. }
  110. public void remove(String path) throws Exception {
  111. pathToFile(path).delete();
  112. }
  113. /**
  114. * Make the directory for the specified path. Parent directories
  115. * will also be created if they do not exist.
  116. *
  117. * @param path The directory path
  118. */
  119. public void makeDirectory(String path) {
  120. File file = new File(getRealRoot(), path);
  121. file.mkdirs();
  122. }
  123. /**
  124. * Remove the directory for the specified path. The directory
  125. * must be empty.
  126. *
  127. * @param path The path
  128. * @throws Exception
  129. */
  130. public void removeDirectory(String path) throws Exception {
  131. log.info("Remove directory: " + path);
  132. File file = new File(getRealRoot(), path);
  133. if (log.isDebugEnabled())
  134. log.debug("Deleting file: " + file.getAbsolutePath());
  135. if (file.isDirectory()) {
  136. file.delete();
  137. } else {
  138. throw new Exception("Path is not a directory: " + path);
  139. }
  140. }
  141. /**
  142. * Get the given content as an InputStream. The InputStream will
  143. * return the raw content data.
  144. *
  145. * @param path The path to the content
  146. * @return The InputStream
  147. * @throws Exception
  148. */
  149. public InputStream getInputStream(String path) throws Exception {
  150. return new FileInputStream(pathToFile(path));
  151. }
  152. /**
  153. * Get an OutputStream for writing content to the given path.
  154. *
  155. * @param path The path to the content
  156. * @return The OutputStream
  157. * @throws Exception
  158. */
  159. public OutputStream getOutputStream(String path) throws Exception {
  160. if (!isWriteAllowed()) {
  161. throw new SecurityException("Writing not allowed");
  162. }
  163. return new FileOutputStream(pathToFile(path));
  164. }
  165. /**
  166. * Get the last modified time in milliseconds for the given path.
  167. *
  168. * @param path The content path
  169. * @return The last modified time in milliseconds
  170. * @throws Exception Any Exception
  171. */
  172. public long getLastModified(String path) throws Exception {
  173. return pathToFile(path).lastModified();
  174. }
  175. /**
  176. * Return the configuration directory name.
  177. *
  178. * @return The configuration directory name
  179. */
  180. public String getConfigurationDirectoryName() {
  181. return configurationDirectoryName;
  182. }
  183. /**
  184. * Set the name of the configuration directory used to locate XML
  185. * configuration files for a given path. The default value is "config".
  186. * <p/>
  187. * <p>Example: using the default value "config", a request for
  188. * <code>/site/test.html</code> would look for the configuration file as
  189. * <code>/site/config/test.xml</code>.</p>
  190. *
  191. * @param configurationDirectoryName The new configuration directory name
  192. */
  193. public void setConfigurationDirectoryName(String configurationDirectoryName) {
  194. if (configurationDirectoryName != null) {
  195. this.configurationDirectoryName = configurationDirectoryName;
  196. }
  197. }
  198. /**
  199. * Load the repository's configuration from the given configuration
  200. * object.
  201. *
  202. * @param configuration The configuration object
  203. * @throws Exception
  204. */
  205. public void loadConfiguration(Configuration configuration) throws Exception {
  206. this.name = configuration.getAttribute(PROPERTY_NAME);
  207. setRoot(configuration.getChildValue("root"));
  208. setCache(configuration.getChildValue("cache"));
  209. setWriteAllowed(configuration.getChildValue("write-allowed"));
  210. setConfigurationDirectoryName(configuration.getChildValue("config-dir"));
  211. }
  212. private synchronized void setCache(String cacheName) {
  213. this.cacheName = cacheName;
  214. JPublishCacheManager jPublishCacheManager = siteContext.getJPublishCacheManager();
  215. cache = jPublishCacheManager.getCache(cacheName);
  216. }
  217. /**
  218. * Get an Iterator of paths which are known to the repository.
  219. *
  220. * @return An iterator of paths
  221. * @throws Exception
  222. */
  223. public Iterator getPaths() throws Exception {
  224. return getPaths(EMPTY_STRING);
  225. }
  226. /**
  227. * Get an Iterator of paths which are known to the repository, starting
  228. * from the specified base path.
  229. *
  230. * @param base The base path
  231. * @return An iterator of paths
  232. * @throws Exception
  233. */
  234. public Iterator getPaths(String base) throws Exception {
  235. return new FileSystemPathIterator(new BreadthFirstFileTreeIterator(pathToFile(base)), this);
  236. }
  237. /**
  238. * Load the content from the given path.
  239. *
  240. * @param path The path
  241. * @return The String
  242. */
  243. private String loadContent(String path) throws Exception {
  244. CacheEntry cacheEntry = (CacheEntry) cache.get(path);
  245. long fileTimeStamp = pathToFile(path).lastModified();
  246. if (cacheEntry == null || cacheEntry.getLastModified() != fileTimeStamp) {
  247. BufferedReader reader = new BufferedReader(new InputStreamReader(getInputStream(path)));// [florin], getInputEncoding(path)));
  248. cacheEntry = new CacheEntry(FileCopyUtils.copyToString(reader), fileTimeStamp);
  249. cache.put(path, cacheEntry);
  250. //log.info(path + " loaded ...");
  251. }
  252. return (String) cacheEntry.getObject();
  253. }
  254. /**
  255. * Get the File to the content using the path.
  256. *
  257. * @param path The content path
  258. * @return A File object
  259. */
  260. public File pathToFile(String path) {
  261. return new File(getRealRoot(), path);
  262. // not sure why I wanted to do this...but for the moment
  263. // I am going to use the same system as the FileSystemRepository
  264. // for determining the content File object. -AE
  265. //return new File(new File(getRealRoot(), "data"), path);
  266. }
  267. /**
  268. * The path to the config is determined by removing the last suffix
  269. * from the path and adding <code>.xml</code> to the path and then
  270. * appending the path to <code><i>root</i>/conf</code>.
  271. *
  272. * @param path The path
  273. * @return The File
  274. */
  275. private File pathToConfig(String path) {
  276. int dotIndex = path.lastIndexOf(DOT);
  277. if (dotIndex > 0) {
  278. path = path.substring(0, dotIndex);
  279. }
  280. return new File(
  281. new File(getRealRoot(), getConfigurationDirectoryName()),
  282. path + XML_TYPE);
  283. }
  284. /**
  285. * Execute all actions for the given path.
  286. * Load the properties file and add the properties to the page definition
  287. * [florin]
  288. *
  289. * @param path The path
  290. * @param context The context
  291. * @throws Exception Any Exception
  292. */
  293. private void executeActions(String path, JPublishContext context) throws Exception {
  294. ActionManager actionManager = siteContext.getActionManager();
  295. String configFileKey;
  296. int dotIndex = path.lastIndexOf(DOT);
  297. if (dotIndex > 0) {
  298. configFileKey = path.substring(0, dotIndex) + XML_TYPE;
  299. } else {
  300. configFileKey = path + XML_TYPE;
  301. }
  302. // locate the configuration file
  303. File configFile = pathToConfig(path);
  304. if (!configFile.exists()) {
  305. return;
  306. }
  307. // load the configuration object, check the cache first
  308. Configuration configuration;
  309. CacheEntry cacheEntry = (CacheEntry) cache.get(configFileKey);
  310. long fileTimeStamp = configFile.lastModified();
  311. try {
  312. if (cacheEntry == null || cacheEntry.getLastModified() != fileTimeStamp) {
  313. configuration = new XMLConfiguration(configFileKey, configFile);
  314. configuration.getLocation().setSourceId(configFileKey);
  315. cacheEntry = new CacheEntry(configuration, fileTimeStamp);
  316. cache.put(configFileKey, cacheEntry);
  317. //log.info(configFileKey + " loaded ...");
  318. }
  319. configuration = (Configuration) cacheEntry.getObject();
  320. } catch (ConfigurationException e) {
  321. e.printStackTrace();
  322. throw new JPublishException("cannot load the Configuration for: " + configFileKey);
  323. } catch (JPublishCacheException e) {
  324. e.printStackTrace();
  325. throw new JPublishCacheException("cache refers to a null config object. Required by: " + configFileKey);
  326. }
  327. Page page = context.getPage();
  328. List properties = configuration.getChildren("property");
  329. if (properties != null) {
  330. Iterator propertyElements = properties.iterator();
  331. while (propertyElements.hasNext()) {
  332. Configuration propertyElement = (Configuration) propertyElements.next();
  333. String name = propertyElement.getAttribute(PROPERTY_NAME);
  334. String v = propertyElement.getAttribute(PROPERTY_VALUE);
  335. String value = v == null ? propertyElement.getValue() : v;
  336. String l = propertyElement.getAttribute(PROPERTY_LOCALE);
  337. if (name != null && name.trim().length() > 0) {
  338. if (l != null) {
  339. try {
  340. page.setProperty(name, value, new Locale(l));
  341. } catch (Exception e) {
  342. throw new Exception(path + ", invalid locale: " + l + ", for element: " + name + ", value: " + value);
  343. }
  344. } else {
  345. page.setProperty(name, value);
  346. }
  347. } else {
  348. throw new Exception("attempt to define a null property name from: " + configFileKey);
  349. }
  350. }
  351. }
  352. // execute all content actions
  353. List actions = configuration.getChildren("content-action");
  354. if (actions != null) {
  355. Iterator contentActionElements = actions.iterator();
  356. while (contentActionElements.hasNext()) {
  357. Configuration contentActionElement = (Configuration) contentActionElements.next();
  358. String actionName = contentActionElement.getAttribute(PROPERTY_NAME);
  359. if (actionName != null && actionName.trim().length() > 0) {
  360. actionManager.execute(actionName, context, contentActionElement);
  361. } else {
  362. throw new ActionNotFoundException("Action: " + actionName + ", not found. Defined in: " + configFileKey);
  363. }
  364. }
  365. }
  366. }
  367. /**
  368. * interim solution for using a dual cache; Florin
  369. *
  370. * @param context
  371. */
  372. public synchronized void clearCache(JPublishContext context) {
  373. try {
  374. if (cache != null) {
  375. if (context != null) {
  376. context.put("old.cache.size", new Integer(cache.getKeys().size()));
  377. }
  378. cache.clear();
  379. }
  380. } catch (JPublishCacheException e) {
  381. e.printStackTrace();
  382. }
  383. }
  384. public String getCacheName() {
  385. return cacheName;
  386. }
  387. }