001: /*
002: * $Id: Resource.java 485027 2006-12-09 18:47:40Z ehillenius $ $Revision: 485027 $
003: * $Date: 2006-12-09 19:47:40 +0100 (Sat, 09 Dec 2006) $
004: *
005: * ==============================================================================
006: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
007: * use this file except in compliance with the License. You may obtain a copy of
008: * the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015: * License for the specific language governing permissions and limitations under
016: * the License.
017: */
018: package wicket;
019:
020: import java.io.OutputStream;
021: import java.net.SocketException;
022: import java.util.Map;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026:
027: import wicket.util.io.Streams;
028: import wicket.util.resource.IResourceStream;
029: import wicket.util.time.Time;
030: import wicket.util.value.ValueMap;
031:
032: /**
033: * A Resource is something that implements IResourceListener and provides a
034: * getResource() method which returns the raw IResource to be rendered back to
035: * the client browser.
036: * <p>
037: * Resources themselves do not currently have URLs. Instead, they are referred
038: * to by components that have URLs.
039: * <p>
040: * Resources can be shared throughout an application by adding them to
041: * Application with addResource(Class scope, String name) or addResource(String
042: * name). A resource added in such a way is a named resource and is accessible
043: * throughout the application via Application.getResource(Class scope, String
044: * name) or Application.getResource(String name). The ResourceReference class
045: * enables easy access to such resources in a way that is light on clusters.
046: * <p>
047: * While resources can be shared between components, it is important to
048: * emphasize that components <i>cannot </i> be shared among containers. For
049: * example, you can create a button image resource with new
050: * DefaultButtonImageResource(...) and store that in the Application with
051: * addResource(). You can then assign that logical resource via
052: * ResourceReference to several ImageButton components. While the button image
053: * resource can be shared between components like this, the ImageButton
054: * components in this example are like all other components in Wicket and cannot
055: * be shared.
056: *
057: * @author Jonathan Locke
058: * @author Johan Compagner
059: * @author Gili Tzabari
060: * @author Igor Vaynberg
061: */
062: public abstract class Resource implements IResourceListener {
063: private static final long serialVersionUID = 1L;
064:
065: /** Logger */
066: private static final Log log = LogFactory.getLog(Resource.class);
067:
068: /** True if this resource can be cached */
069: private boolean cacheable;
070:
071: /**
072: * ThreadLocal to keep any parameters associated with the request for this
073: * resource
074: */
075: private static final ThreadLocal parameters = new ThreadLocal();
076:
077: /**
078: * Constructor
079: */
080: protected Resource() {
081: // By default all resources are cacheable
082: cacheable = true;
083: }
084:
085: /**
086: * @return Gets the resource to render to the requester
087: */
088: public abstract IResourceStream getResourceStream();
089:
090: /**
091: * @return boolean True or False if this resource is cacheable
092: */
093: public final boolean isCacheable() {
094: return cacheable;
095: }
096:
097: /**
098: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
099: *
100: * Called when a resource is requested.
101: */
102: public final void onResourceRequested() {
103: try {
104: // Get request cycle
105: final RequestCycle cycle = RequestCycle.get();
106:
107: // Fetch resource from subclass if necessary
108: IResourceStream resourceStream = init();
109:
110: // Get servlet response to use when responding with resource
111: final Response response = cycle.getResponse();
112:
113: // Configure response with content type of resource
114: response.setContentType(resourceStream.getContentType());
115: response.setContentLength((int) resourceStream.length());
116:
117: if (isCacheable()) {
118: // Don't set this above setContentLength call above.
119: // The call above could create and set the last modified time.
120: response.setLastModifiedTime(resourceStream
121: .lastModifiedTime());
122: } else {
123: response.setLastModifiedTime(Time.valueOf(-1));
124: }
125: configureResponse(response);
126:
127: // Respond with resource
128: respond(resourceStream, response);
129: } finally {
130: // Really really really make sure parameters are cleared to appease
131: // Johan
132: parameters.set(null);
133: }
134: }
135:
136: /**
137: * Should this resource be cacheable, so will it set the last modified and
138: * the some cache headers in the response.
139: *
140: * @param cacheable
141: * boolean if the lastmodified and cache headers must be set.
142: * @return this
143: */
144: public final Resource setCacheable(boolean cacheable) {
145: this .cacheable = cacheable;
146: return this ;
147: }
148:
149: /**
150: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
151: *
152: * @param parameters
153: * Map of query parameters that paramterize this resource
154: */
155: public final void setParameters(final Map parameters) {
156: if (parameters == null) {
157: Resource.parameters.set(null);
158: } else {
159: Resource.parameters.set(new ValueMap(parameters));
160: }
161: }
162:
163: /**
164: * Allows implementations to do configure the response, like setting headers
165: * etc.
166: *
167: * @param response
168: * the respone
169: */
170: protected void configureResponse(final Response response) {
171: }
172:
173: /**
174: * @return Any query parameters associated with the request for this
175: * resource
176: */
177: protected ValueMap getParameters() {
178: if (parameters.get() == null) {
179: setParameters(RequestCycle.get().getRequest()
180: .getParameterMap());
181: }
182: return (ValueMap) parameters.get();
183: }
184:
185: /**
186: * Sets any loaded resource to null, thus forcing a reload on the next
187: * request.
188: */
189: protected void invalidate() {
190: }
191:
192: /**
193: * Set resource field by calling subclass
194: *
195: * @return The resource stream for the current request
196: */
197: private final IResourceStream init() {
198: IResourceStream stream = getResourceStream();
199:
200: if (stream == null) {
201: throw new WicketRuntimeException(
202: "Could not get resource stream");
203: }
204: return stream;
205: }
206:
207: /**
208: * Respond with resource
209: *
210: * @param resourceStream
211: * The current resourcestream of the resource
212: * @param response
213: * The response to write to
214: */
215: private final void respond(final IResourceStream resourceStream,
216: final Response response) {
217: try {
218: final OutputStream out = response.getOutputStream();
219: try {
220: Streams.copy(resourceStream.getInputStream(), out);
221: } finally {
222: resourceStream.close();
223: out.flush();
224: }
225: } catch (Exception e) {
226: // FIXME this doesn't catch all. For instance, Jetty (6/ NIO) on
227: // Unix like platforms will not be recogninzed as exceptions
228: // that should be ignored
229:
230: Throwable throwable = e;
231: boolean ignoreException = false;
232: while (throwable != null) {
233: if (throwable instanceof SocketException) {
234: String message = throwable.getMessage();
235: ignoreException = message != null
236: && (message
237: .indexOf("Connection reset by peer") != -1 || message
238: .indexOf("Software caused connection abort") != -1);
239: } else {
240: ignoreException = throwable.getClass().getName()
241: .indexOf("ClientAbortException") >= 0;
242: if (ignoreException) {
243: if (log.isDebugEnabled()) {
244: log
245: .debug(
246: "Socket exception ignored for sending Resource "
247: + "response to client (ClientAbort)",
248: e);
249: }
250: break;
251: }
252: }
253: throwable = throwable.getCause();
254: }
255: if (!ignoreException) {
256: throw new WicketRuntimeException(
257: "Unable to render resource stream "
258: + resourceStream, e);
259: }
260: }
261: }
262: }
|