View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.common.xml;
20  
21  import java.io.BufferedInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.net.URL;
26  import java.net.URLConnection;
27  
28  import org.apache.commons.io.IOUtils;
29  import org.apache.logging.log4j.LogManager;
30  import org.apache.logging.log4j.Logger;
31  import org.jdom2.JDOMException;
32  import org.mycore.common.MCRCache;
33  import org.mycore.common.MCRClassTools;
34  import org.mycore.common.config.MCRConfiguration2;
35  import org.mycore.common.config.MCRConfigurationDir;
36  import org.mycore.common.content.MCRContent;
37  import org.mycore.common.content.MCRURLContent;
38  
39  /**
40   * provides a cache for reading XML resources.
41   *
42   * Cache size can be configured by property
43   * <code>MCR.MCRXMLResouce.Cache.Size</code> which defaults to <code>100</code>.
44   *
45   * @author Thomas Scheffler (yagee)
46   */
47  public class MCRXMLResource {
48  
49      private static final MCRCache<String, CacheEntry> RESOURCE_CACHE = new MCRCache<>(
50          MCRConfiguration2.getInt("MCR.MCRXMLResource.Cache.Size").orElse(100),
51          "XML resources");
52  
53      private static MCRXMLResource instance = new MCRXMLResource();
54  
55      private static Logger LOGGER = LogManager.getLogger(MCRXMLResource.class);
56  
57      private MCRXMLResource() {
58      }
59  
60      /**
61       * @return singleton instance
62       */
63      public static MCRXMLResource instance() {
64          return instance;
65      }
66  
67      private static URLConnection getResourceURLConnection(String name, ClassLoader classLoader) throws IOException {
68          LOGGER.debug("Reading xml from classpath resource {}", name);
69          URL url = MCRConfigurationDir.getConfigResource(name, classLoader);
70          LOGGER.debug("Resource URL:{}", url);
71          if (url == null) {
72              return null;
73          }
74          return url.openConnection();
75      }
76  
77      private static MCRContent getDocument(URL url) {
78          return new MCRURLContent(url);
79      }
80  
81      private static void closeURLConnection(URLConnection con) throws IOException {
82          if (con == null) {
83              return;
84          }
85          con.getInputStream().close();
86      }
87  
88      public URL getURL(String name) throws IOException {
89          return getURL(name, MCRClassTools.getClassLoader());
90      }
91  
92      public URL getURL(String name, ClassLoader classLoader) throws IOException {
93          URLConnection con = getResourceURLConnection(name, classLoader);
94          if (con == null) {
95              return null;
96          }
97          try {
98              return con.getURL();
99          } finally {
100             closeURLConnection(con);
101         }
102     }
103 
104     /**
105      * Returns MCRContent using ClassLoader of MCRXMLResource class
106      *
107      * @param name
108      *            resource name
109      * @see MCRXMLResource#getResource(String, ClassLoader)
110      */
111     public MCRContent getResource(String name) throws IOException, JDOMException {
112         return getResource(name, MCRClassTools.getClassLoader());
113     }
114 
115     /**
116      * returns xml as byte array using ClassLoader of MCRXMLResource class
117      *
118      * @param name
119      *            resource name
120      * @see MCRXMLResource#getRawResource(String, ClassLoader)
121      */
122     public byte[] getRawResource(String name) throws IOException {
123         return getRawResource(name, MCRClassTools.getClassLoader());
124     }
125 
126     /**
127      * Returns MCRContent of resource.
128      *
129      * A cache is used to avoid reparsing if the source of the resource did not
130      * change.
131      *
132      * @param name
133      *            the resource name
134      * @param classLoader
135      *            a ClassLoader that should be used to locate the resource
136      * @return a parsed Document of the resource or <code>null</code> if the
137      *         resource is not found
138      * @throws IOException
139      *             if resource cannot be loaded
140      */
141     public MCRContent getResource(String name, ClassLoader classLoader) throws IOException {
142         ResourceModifiedHandle modifiedHandle = getModifiedHandle(name, classLoader, 10000);
143         CacheEntry entry = RESOURCE_CACHE.getIfUpToDate(name, modifiedHandle);
144         URL resolvedURL = modifiedHandle.getURL();
145         if (entry != null && (resolvedURL == null || entry.resourceURL.equals(resolvedURL))) {
146             LOGGER.debug("Using cached resource {}", name);
147             return entry.content;
148         }
149         if (resolvedURL == null) {
150             LOGGER.warn("Could not resolve resource: {}", name);
151             return null;
152         }
153         entry = new CacheEntry();
154         RESOURCE_CACHE.put(name, entry);
155         entry.resourceURL = resolvedURL;
156         entry.content = getDocument(entry.resourceURL);
157         return entry.content;
158     }
159 
160     public ResourceModifiedHandle getModifiedHandle(String name, ClassLoader classLoader, long checkPeriod) {
161         return new ResourceModifiedHandle(name, classLoader, checkPeriod);
162     }
163 
164     /**
165      * Returns raw XML resource as byte array. Note that no cache will be used.
166      *
167      * @param name
168      *            the resource name
169      * @param classLoader
170      *            a ClassLoader that should be used to locate the resource
171      * @return unparsed xml of the resource or <code>null</code> if the
172      *         resource is not found
173      * @throws IOException
174      *             if resource cannot be loaded
175      */
176     public byte[] getRawResource(String name, ClassLoader classLoader) throws IOException {
177         URLConnection con = getResourceURLConnection(name, classLoader);
178         if (con == null) {
179             return null;
180         }
181         try (ByteArrayOutputStream baos = new ByteArrayOutputStream(64 * 1024);
182             InputStream in = new BufferedInputStream(con.getInputStream())) {
183             IOUtils.copy(in, baos);
184             return baos.toByteArray();
185         } finally {
186             closeURLConnection(con);
187         }
188     }
189 
190     public long getLastModified(String name, ClassLoader classLoader) throws IOException {
191         URLConnection con = getResourceURLConnection(name, classLoader);
192         try {
193             return con == null ? -1 : con.getLastModified();
194         } finally {
195             closeURLConnection(con);
196         }
197     }
198 
199     public boolean exists(String name, ClassLoader classLoader) throws IOException {
200         final URLConnection resourceURLConnection = getResourceURLConnection(name, classLoader);
201         try {
202             return resourceURLConnection != null;
203         } finally {
204             closeURLConnection(resourceURLConnection);
205         }
206     }
207 
208     private static class CacheEntry {
209         URL resourceURL;
210 
211         MCRContent content;
212     }
213 
214     public static class ResourceModifiedHandle implements MCRCache.ModifiedHandle {
215         private long checkPeriod;
216 
217         private String name;
218 
219         private ClassLoader classLoader;
220 
221         private URL resolvedURL;
222 
223         public ResourceModifiedHandle(String name, ClassLoader classLoader, long checkPeriod) {
224             this.name = name;
225             this.classLoader = classLoader;
226             this.checkPeriod = checkPeriod;
227         }
228 
229         public URL getURL() {
230             return this.resolvedURL == null ? MCRConfigurationDir.getConfigResource(name, classLoader)
231                 : this.resolvedURL;
232         }
233 
234         @Override
235         public long getCheckPeriod() {
236             return checkPeriod;
237         }
238 
239         @Override
240         public long getLastModified() throws IOException {
241             URLConnection con = getResourceURLConnection(name, classLoader);
242             if (con == null) {
243                 return -1;
244             }
245             try {
246                 long lastModified = con.getLastModified();
247                 resolvedURL = con.getURL();
248                 LOGGER.debug("{} last modified: {}", name, lastModified);
249                 return lastModified;
250             } finally {
251                 closeURLConnection(con);
252             }
253         }
254 
255     }
256 }