|
|||||||||||||||||||
30 day Evaluation License registered to hlship@comcast.net Your 30 day evaluation period has expired. Please visit http://www.cenqua.com to obtain a licensed version of Clover | |||||||||||||||||||
Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
AssetExternalizerImpl.java | 9.1% | 14.3% | 62.5% | 17.4% |
|
1 | // Copyright 2004, 2005 The Apache Software Foundation | |
2 | // | |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | // you may not use this file except in compliance with the License. | |
5 | // You may obtain a copy of the License at | |
6 | // | |
7 | // http://www.apache.org/licenses/LICENSE-2.0 | |
8 | // | |
9 | // Unless required by applicable law or agreed to in writing, software | |
10 | // distributed under the License is distributed on an "AS IS" BASIS, | |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | // See the License for the specific language governing permissions and | |
13 | // limitations under the License. | |
14 | ||
15 | package org.apache.tapestry.asset; | |
16 | ||
17 | import java.io.BufferedInputStream; | |
18 | import java.io.BufferedOutputStream; | |
19 | import java.io.File; | |
20 | import java.io.FileOutputStream; | |
21 | import java.io.IOException; | |
22 | import java.io.InputStream; | |
23 | import java.io.OutputStream; | |
24 | import java.net.URL; | |
25 | import java.util.HashMap; | |
26 | import java.util.Map; | |
27 | ||
28 | import javax.servlet.ServletContext; | |
29 | ||
30 | import org.apache.commons.logging.Log; | |
31 | import org.apache.hivemind.ApplicationRuntimeException; | |
32 | import org.apache.hivemind.ClassResolver; | |
33 | import org.apache.tapestry.Tapestry; | |
34 | import org.apache.tapestry.engine.IPropertySource; | |
35 | import org.apache.tapestry.util.StringSplitter; | |
36 | ||
37 | /** | |
38 | * Implementation of the {@link org.apache.tapestry.asset.AssetExternalizer}service interface. | |
39 | * Responsible for copying assets from the classpath to an external directory that is visible to the | |
40 | * web server. The externalizer is stored inside the {@link ServletContext}as a named attribute. | |
41 | * <p> | |
42 | * The externalizer uses the name | |
43 | * <code>org.apache.tapestry.AssetExternalizer.<i>application name</i> | |
44 | * </code>. It configures | |
45 | * itself using two additional properties (searching in | |
46 | * {@link org.apache.tapestry.IEngine#getPropertySource()}. <table border=1> | |
47 | * <tr> | |
48 | * <th>Parameter</th> | |
49 | * <th>Description</th> | |
50 | * </tr> | |
51 | * <tr valign=top> | |
52 | * <td><code>org.apache.tapestry.asset.dir</code></td> | |
53 | * <td>The directory to which assets will be copied.</td> | |
54 | * </tr> | |
55 | * <tr valign=top> | |
56 | * <td><code>org.apache.tapestry.asset.URL</code></td> | |
57 | * <td>The corresponding URL for the asset directory.</td> | |
58 | * </tr> | |
59 | * </table> | |
60 | * <p> | |
61 | * If either of these parameters is null, then no externalization occurs. Private assets will still | |
62 | * be available, just less efficiently, as the application will be invoked via its servlet and, | |
63 | * ultimately, the {@link AssetService}will need to retrieve the asset. | |
64 | * <p> | |
65 | * Assets maintain thier directory structure when copied. For example, an asset with a resource path | |
66 | * of <code>/com/skunkworx/Banner.gif</code> would be copied to the file system as | |
67 | * <code><i>dir</i>/com/skunkworx/Banner.gif</code> and would have a URL of | |
68 | * <code><i>URL</i>/com/skunkworx/Banner.gif</code>. | |
69 | * <p> | |
70 | * The externalizer will create any directories as needed. | |
71 | * <p> | |
72 | * The externalizer will not overwrite existing files. When a new version of the application is | |
73 | * deployed with changed assets, there are two deployment stategies: | |
74 | * <ul> | |
75 | * <li>Delete the existing asset directory and allow the externalizer to recreate and repopulate | |
76 | * it. | |
77 | * <li>Change the asset directory and URL, allowing the old and new assets to exist side-by-side. | |
78 | * </ul> | |
79 | * <p> | |
80 | * When using the second approach, it is best to use a directory that has a version number in it, | |
81 | * for example, <code>D:/inetpub/assets/0</code> mapped to the URL <code>/assets/0</code>. When | |
82 | * a new version of the application is deployed, the trailing version number is incremented from 0 | |
83 | * to 1. | |
84 | * | |
85 | * @author Howard Lewis Ship | |
86 | */ | |
87 | ||
88 | public class AssetExternalizerImpl implements AssetExternalizer | |
89 | { | |
90 | /** @since 4.0 */ | |
91 | private Log _log; | |
92 | ||
93 | private ClassResolver _resolver; | |
94 | ||
95 | /** @since 4.0 */ | |
96 | private IPropertySource _propertySource; | |
97 | ||
98 | private File _assetDir; | |
99 | ||
100 | private String _URL; | |
101 | ||
102 | /** | |
103 | * A map from resource path (as a String) to final URL (as a String). | |
104 | */ | |
105 | ||
106 | private Map _resources = new HashMap(); | |
107 | ||
108 | private static final int BUFFER_SIZE = 2048; | |
109 | ||
110 | 27 | public void initializeService() |
111 | { | |
112 | 27 | String directory = _propertySource.getPropertyValue("org.apache.tapestry.asset.dir"); |
113 | ||
114 | 27 | if (directory == null) |
115 | 27 | return; |
116 | ||
117 | 0 | _URL = _propertySource.getPropertyValue("org.apache.tapestry.asset.URL"); |
118 | ||
119 | 0 | if (_URL == null) |
120 | 0 | return; |
121 | ||
122 | 0 | _assetDir = new File(directory); |
123 | ||
124 | 0 | _log.debug("Initialized with directory " + _assetDir + " mapped to " + _URL); |
125 | } | |
126 | ||
127 | 0 | protected void externalize(String resourcePath) throws IOException |
128 | { | |
129 | 0 | if (_log.isDebugEnabled()) |
130 | 0 | _log.debug("Externalizing " + resourcePath); |
131 | ||
132 | 0 | File file = _assetDir; |
133 | ||
134 | // Resources are always split by the unix seperator, even on Win32. | |
135 | ||
136 | 0 | StringSplitter splitter = new StringSplitter('/'); |
137 | ||
138 | 0 | String[] path = splitter.splitToArray(resourcePath); |
139 | ||
140 | // The path is expected to start with a leading slash, but the StringSplitter | |
141 | // will ignore that leading slash. | |
142 | ||
143 | 0 | for (int i = 0; i < path.length - 1; i++) |
144 | { | |
145 | // Doing it this way makes sure the path seperators are right. | |
146 | ||
147 | 0 | file = new File(file, path[i]); |
148 | } | |
149 | ||
150 | // Make sure the directories exist. | |
151 | ||
152 | 0 | file.mkdirs(); |
153 | ||
154 | 0 | file = new File(file, path[path.length - 1]); |
155 | ||
156 | // If the file exists, then assume all is well. This is OK for development, | |
157 | // but there may be multithreading (or even multiprocess) race conditions | |
158 | // around the creation of the file. | |
159 | ||
160 | 0 | if (file.exists()) |
161 | 0 | return; |
162 | ||
163 | // Get the resource and copy it to the file. | |
164 | ||
165 | 0 | URL inputURL = _resolver.getResource(resourcePath); |
166 | 0 | if (inputURL == null) |
167 | 0 | throw new IOException(Tapestry.format("missing-resource", resourcePath)); |
168 | ||
169 | 0 | InputStream in = null; |
170 | 0 | OutputStream out = null; |
171 | ||
172 | 0 | try |
173 | { | |
174 | 0 | in = new BufferedInputStream(inputURL.openStream()); |
175 | ||
176 | 0 | out = new BufferedOutputStream(new FileOutputStream(file)); |
177 | ||
178 | 0 | byte[] buffer = new byte[BUFFER_SIZE]; |
179 | ||
180 | 0 | while (true) |
181 | { | |
182 | 0 | int bytesRead = in.read(buffer, 0, BUFFER_SIZE); |
183 | 0 | if (bytesRead < 0) |
184 | 0 | break; |
185 | ||
186 | 0 | out.write(buffer, 0, bytesRead); |
187 | } | |
188 | } | |
189 | finally | |
190 | { | |
191 | 0 | close(in); |
192 | 0 | close(out); |
193 | } | |
194 | ||
195 | // The file is copied! | |
196 | } | |
197 | ||
198 | 0 | private void close(InputStream in) |
199 | { | |
200 | 0 | if (in != null) |
201 | 0 | try |
202 | { | |
203 | 0 | in.close(); |
204 | } | |
205 | catch (IOException ex) | |
206 | { | |
207 | // Ignore. | |
208 | } | |
209 | } | |
210 | ||
211 | 0 | private void close(OutputStream out) |
212 | { | |
213 | 0 | if (out != null) |
214 | ||
215 | 0 | try |
216 | { | |
217 | 0 | out.close(); |
218 | } | |
219 | catch (IOException ex) | |
220 | { | |
221 | // Ignore | |
222 | } | |
223 | } | |
224 | ||
225 | /** | |
226 | * Gets the URL to a private resource. If the resource was previously copied out of the | |
227 | * classpath, the previously generated URL is returned. | |
228 | * <p> | |
229 | * If the asset directory and URL are not configured, then returns null. | |
230 | * <p> | |
231 | * Otherwise, the asset is copied out to the asset directory, the URL is constructed (and | |
232 | * recorded for later) and the URL is returned. | |
233 | * <p> | |
234 | * This method is not explicitly synchronized but should work multi-threaded. It synchronizes on | |
235 | * the internal <code>Map</code> used to map resource paths to URLs. | |
236 | * | |
237 | * @param resourcePath | |
238 | * The full path of the resource within the classpath. This is expected to include a | |
239 | * leading slash. For example: <code>/com/skunkworx/Banner.gif</code>. | |
240 | */ | |
241 | ||
242 | 77 | public String getURL(String resourcePath) |
243 | { | |
244 | 77 | if (_assetDir == null) |
245 | 77 | return null; |
246 | ||
247 | 0 | synchronized (_resources) |
248 | { | |
249 | 0 | String result = (String) _resources.get(resourcePath); |
250 | ||
251 | 0 | if (result != null) |
252 | 0 | return result; |
253 | ||
254 | 0 | try |
255 | { | |
256 | 0 | externalize(resourcePath); |
257 | } | |
258 | catch (IOException ex) | |
259 | { | |
260 | 0 | throw new ApplicationRuntimeException(Tapestry.format( |
261 | "AssetExternalizer.externalize-failure", | |
262 | resourcePath, | |
263 | _assetDir), ex); | |
264 | } | |
265 | ||
266 | 0 | result = _URL + resourcePath; |
267 | ||
268 | 0 | _resources.put(resourcePath, result); |
269 | ||
270 | 0 | return result; |
271 | } | |
272 | } | |
273 | ||
274 | /** @since 4.0 */ | |
275 | 27 | public void setLog(Log log) |
276 | { | |
277 | 27 | _log = log; |
278 | } | |
279 | ||
280 | /** @since 4.0 */ | |
281 | 27 | public void setClassResolver(ClassResolver resolver) |
282 | { | |
283 | 27 | _resolver = resolver; |
284 | } | |
285 | ||
286 | /** since 4.0 */ | |
287 | 27 | public void setPropertySource(IPropertySource propertySource) |
288 | { | |
289 | 27 | _propertySource = propertySource; |
290 | } | |
291 | } |
|