|
|||||||||||||||||||
30 day Evaluation Version distributed via the Maven Jar Repository. Clover is not free. You have 30 days to evaluate it. Please visit http://www.thecortex.net/clover 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 | 58 |
public String getURL(String resourcePath)
|
243 |
{ |
|
244 | 58 |
if (_assetDir == null) |
245 | 58 |
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 |
} |
|