KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > conf > DBCPDataSourceFactory


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56 package org.objectstyle.cayenne.conf;
57
58 import java.io.IOException JavaDoc;
59 import java.io.InputStream JavaDoc;
60 import java.sql.Connection JavaDoc;
61 import java.util.Properties JavaDoc;
62
63 import javax.sql.DataSource JavaDoc;
64
65 import org.apache.commons.dbcp.ConnectionFactory;
66 import org.apache.commons.dbcp.DriverManagerConnectionFactory;
67 import org.apache.commons.dbcp.PoolableConnectionFactory;
68 import org.apache.commons.dbcp.PoolingDataSource;
69 import org.apache.commons.pool.KeyedObjectPoolFactory;
70 import org.apache.commons.pool.ObjectPool;
71 import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory;
72 import org.apache.commons.pool.impl.GenericObjectPool;
73 import org.apache.log4j.Level;
74 import org.apache.log4j.Logger;
75 import org.objectstyle.cayenne.ConfigurationException;
76
77 /**
78  * An implementation of DataSourceFactory that instantiates a DataSource from Apache
79  * Commons DBCP. Configured via a properties file specified by the location hint in the
80  * modeler and under datasource attribute in cayenne.xml. Note that if location doesn't
81  * have a ".properties" extension, such extension is added automatically. Sample
82  * properties file:
83  *
84  * <pre>
85  *
86  *
87  *
88  *
89  *
90  * #
91  * # This file defines the configuration properties for Commons DBCP pool
92  * # which is used for Cayenne during some of the test cases.
93  * # For more info on setting parameters see the documentation for commons
94  * # dbcp and commons pool. The following prefixes are required:
95  * # cayenne.dbcp.&lt;param&gt; = configure the connection pool
96  * # cayenne.dbcp.ps.&lt;param&gt; = configuration for the prepared connection pools
97  * # that are associated with each pooled connections
98  *
99  *
100  * #driver class to use to connect to the database
101  * cayenne.dbcp.driverClassName=net.sourceforge.jtds.jdbc.Driver
102  *
103  * #url to the database, the parameters should be part of the connection string
104  * #and not here
105  * cayenne.dbcp.url=jdbc:jtds:sqlserver://192.168.20.2:1433/x_test2;TDS=8.0
106  *
107  * #username to use to connect to the database
108  * cayenne.dbcp.username=garyj
109  *
110  * #password to use to connect to the database
111  * cayenne.dbcp.password=somepass
112  *
113  * #maximum number of active connections
114  * cayenne.dbcp.maxActive=500
115  *
116  * #minimum number of idle connections
117  * cayenne.dbcp.minIdle=10
118  *
119  * #maximum number of active connections that can remain idle in the pool
120  * cayenne.dbcp.maxIdle=10
121  *
122  * #maximum number of milliseconds to wait for a connection to be returned to the
123  * #pool before throwing an exception when the connection is required and the pool
124  * #is exhaused of the active connections. -1 for indefinetly
125  * cayenne.dbcp.maxWait=10000
126  *
127  * #sql query to be used to validate connections from the pool. Must return
128  * #at least one row
129  * cayenne.dbcp.validationQuery=SELECT GETDATE()
130  *
131  * #should the object be validated when it is borrowed from the pool
132  * cayenne.dbcp.testOnBorrow=false
133  *
134  * #should the object be validated when it is returned to the pool
135  * cayenne.dbcp.testOnReturn=true
136  *
137  * #should the object be validated when it is idle
138  * cayenne.dbcp.testWhileIdle=true
139  *
140  * #number of milliseconds to sleep between runs of the idle object evictor thread
141  * cayenne.dbcp.timeBetweenEvictionRunsMillis=120000
142  *
143  *
144  * #number of objects to examin during each run of the idle object evictor
145  * cayenne.dbcp.numTestsPerEvictionRun=10
146  *
147  * #minimum time an object may sit idle in the pool before it is elegible for
148  * #an eviction
149  * cayenne.dbcp.minEvictableIdleTimeMillis=2000000
150  *
151  * #action to take the the pool is exhausted of all active connections
152  * #see GenericObjectPool class
153  * #this value can be set as either an int or a String the setter method
154  * #will attempt to convert the String value to it's resective representation
155  * #in the GenericObjectPool class and if successfull will use the byte
156  * #value as the config paramter to the pool. If not the default value will
157  * #be used
158  * cayenne.dbcp.whenExhaustedAction=WHEN_EXHAUSTED_GROW
159  *
160  * #The default auto-commit state of connections created by this pool
161  * caynne.dbcp.defaultAutoCommit=false
162  *
163  * #Default read only state of connections created by the pool. Can be left
164  * #as null for driver default
165  * cayenne.dbcp.defaultReadOnly=true
166  *
167  *
168  * # Default TransactionIsolation state of connections created by this pool. This can
169  * # be either a String representation of the isolation level defined in the interface
170  * # java.sql.Connection. Can be left as null for
171  * # driver default
172  * cayenne.dbcp.defaultTransactionIsolation=TRANSACTION_SERIALIZABLE
173  *
174  * #If set to true the application will be able to get access to the
175  * #actual connection object which is normally wrapped by a poolable connections
176  * cayenne.dbcp.accessToUnderlyingConnectionAllowed=true
177  *
178  * #Default catalog of connections created by this pool
179  * cayenne.dbcp.defaultCatalog=someCat
180  *
181  * #Specifies whether prepared statments should be pooled
182  * cayenne.dbcp.poolPreparedStatements=true
183  *
184  *
185  * #Controlls the maximum number of objects that can be borrowed from the pool at
186  * #one time
187  * cayenne.dbcp.ps.maxActive=500
188  *
189  * #Maximum number of idle objects in the pool
190  * cayenne.dbcp.ps.maxIdle=50
191  *
192  * #Maximum number of objects that can exist in the prepared statement pool at one time
193  * cayenne.dbcp.ps.maxTotal=600
194  *
195  *
196  * # Minimum number of milliseconds to wait for an objec the the pool of
197  * # prepared statements is exhausted and the whenExhaustedAction is set to
198  * # 1 (WHEN_EXHAUSTED_BLOCK)
199  * cayenne.dbcp.ps.maxWait=10000
200  *
201  *
202  * # Number of milliseconds an object can sit idle in the pool before it is
203  * # elegible for eviction
204  * cayenne.dbcp.ps.minEvictableIdleTimeMillis=2000000
205  *
206  *
207  * #Number of idle objects that should be examined per eviction run
208  * cayenne.dbcp.ps.numTestsPerEvictionRun=20
209  *
210  *
211  * #Specifies whether objects should be validated before they are borrowed from this pool
212  * cayenne.dbcp.ps.testOnBorrow=false
213  *
214  * #Specifies whether objects should be validated when they are returned to the pool
215  * cayenne.dbcp.ps.testOnReturn=true
216  *
217  *
218  * #Specifies whether objects should be validated in the idle eviction thread
219  * cayenne.dbcp.ps.testWhileIdle=true
220  *
221  * #Specifies the time between the runs of the eviction thread
222  * cayenne.dbcp.ps.timeBetweenEvictionRunsMillis=120000
223  *
224  * # action to take when the the pool is exhausted of all active objects.
225  * # acceptable values are strings (WHEN_EXHAUSTED_FAIL, WHEN_EXHAUSTED_BLOCK (default),
226  * # WHEN_EXHAUSTED_GROW), or their corresponding int values defined in commons-pool GenericObjectPool:
227  * cayenne.dbcp.ps.whenExhaustedAction=WHEN_EXHAUSTED_FAIL
228  *
229  *
230  *
231  *
232  *
233  * </pre>
234  *
235  * @since 1.2
236  * @author Gary Jarrel
237  */

238 public class DBCPDataSourceFactory implements DataSourceFactory {
239
240     private static final Logger logger = Logger.getLogger(DBCPDataSourceFactory.class);
241
242     /**
243      * Suffix of the properties file
244      */

245     private static final String JavaDoc SUFFIX = ".properties";
246
247     /**
248      * All the properties in the configuration properties file should be prefixed with
249      * this prefix, namely cayenne.dbcp. The config parameter as set out in commons dbcp
250      * configuration should follow this prefix.
251      */

252     public static final String JavaDoc PROPERTY_PREFIX = "cayenne.dbcp.";
253
254     /**
255      * The the properties in the configuration file, related to Prepared Statement pooling
256      * and used to configure <code>KeyedObjectPoolFactory</code> are followed by this
257      * prefix. This constants is set to PROPERTY_PREFIX + ps.
258      */

259     public static final String JavaDoc PS_PROPERTY_PREFIX = PROPERTY_PREFIX + "ps.";
260
261     protected Configuration parentConfiguration;
262
263     public void initializeWithParentConfiguration(Configuration parentConfiguration) {
264         this.parentConfiguration = parentConfiguration;
265     }
266
267     /**
268      * Returns a <code>DataSource</code> which is an instance of DBCP's <code>
269      * PoolingDataSource</code>
270      * class
271      *
272      * @return <code>DataSource</code>
273      * @throws Exception
274      */

275     public DataSource JavaDoc getDataSource(String JavaDoc location) throws Exception JavaDoc {
276         return getDataSource(location, Level.DEBUG);
277     }
278
279     /**
280      * Creates a DBCP <code>PoolingDataSource</code>
281      *
282      * @return <code>DataSource</code> which is an instance of
283      * <code>PoolingDataSource</code>
284      * @throws Exception
285      */

286     public DataSource JavaDoc getDataSource(String JavaDoc location, Level logLevel) throws Exception JavaDoc {
287
288         if (!location.endsWith(SUFFIX)) {
289             location = location.concat(SUFFIX);
290         }
291
292         logger.log(logLevel, "Loading DBCP properties from " + location);
293
294         Properties JavaDoc properties = loadProperties(location);
295         logger.log(logLevel, "Loaded DBCP properties: " + properties);
296
297         loadDriverClass(properties, logLevel);
298
299         // build and assemble parts of DBCP DataSource...
300
ConnectionFactory factory = createConnectionFactory(properties);
301         KeyedObjectPoolFactory statementPool = createStatementPool(properties);
302
303         GenericObjectPool.Config config = createPoolConfig(properties);
304
305         // PoolableConnectionFactory properties
306
String JavaDoc validationQuery = stringProperty(properties, "validationQuery");
307         boolean defaultReadOnly = booleanProperty(properties, "defaultReadOnly", false);
308         boolean defaultAutoCommit = booleanProperty(
309                 properties,
310                 "defaultAutoCommit",
311                 false);
312         int defaultTransactionIsolation = defaultTransactionIsolation(
313                 properties,
314                 "defaultTransactionIsolation",
315                 Connection.TRANSACTION_SERIALIZABLE);
316         String JavaDoc defaultCatalog = stringProperty(properties, "defaultCatalog");
317
318         // a side effect of PoolableConnectionFactory constructor call is that newly
319
// created factory object is assigned to "connectionPool", which is definitely a
320
// very confusing part of DBCP - new object is not visibly assigned to anything,
321
// still it is preserved...
322
ObjectPool connectionPool = new GenericObjectPool(null, config);
323         new PoolableConnectionFactory(
324                 factory,
325                 connectionPool,
326                 statementPool,
327                 validationQuery,
328                 defaultReadOnly ? Boolean.TRUE : Boolean.FALSE,
329                 defaultAutoCommit,
330                 defaultTransactionIsolation,
331                 defaultCatalog,
332                 null);
333
334         PoolingDataSource dataSource = new PoolingDataSource(connectionPool);
335         dataSource.setAccessToUnderlyingConnectionAllowed(booleanProperty(
336                 properties,
337                 "accessToUnderlyingConnectionAllowed",
338                 false));
339
340         return dataSource;
341     }
342
343     /**
344      * Loads driver class into driver manager.
345      */

346     void loadDriverClass(Properties JavaDoc properties, Level level) throws Exception JavaDoc {
347         String JavaDoc driver = stringProperty(properties, "driverClassName");
348         logger.log(level, "loading JDBC driver class: " + driver);
349
350         if (driver == null) {
351             throw new NullPointerException JavaDoc("No value for required property: "
352                     + PROPERTY_PREFIX
353                     + "driverClassName");
354         }
355         Class.forName(driver);
356     }
357
358     KeyedObjectPoolFactory createStatementPool(Properties JavaDoc properties) throws Exception JavaDoc {
359
360         if (!booleanProperty(properties, "poolPreparedStatements", false)) {
361             return null;
362         }
363
364         // the GenericKeyedObjectPool.Config object isn't used because
365
// although it has provision for the maxTotal parameter when
366
// passed to the GenericKeyedObjectPoolFactory constructor
367
// this parameter is not being properly set as a default for
368
// creating prepared statement pools
369

370         int maxActive = intProperty(
371                 properties,
372                 "ps.maxActive",
373                 GenericObjectPool.DEFAULT_MAX_ACTIVE);
374         byte whenExhaustedAction = whenExhaustedAction(
375                 properties,
376                 "ps.whenExhaustedAction",
377                 GenericObjectPool.DEFAULT_WHEN_EXHAUSTED_ACTION);
378
379         long maxWait = longProperty(
380                 properties,
381                 "ps.maxWait",
382                 GenericObjectPool.DEFAULT_MAX_WAIT);
383
384         int maxIdle = intProperty(
385                 properties,
386                 "ps.maxIdle",
387                 GenericObjectPool.DEFAULT_MAX_IDLE);
388
389         int maxTotal = intProperty(properties, "ps.maxTotal", 1);
390
391         boolean testOnBorrow = booleanProperty(
392                 properties,
393                 "ps.testOnBorrow",
394                 GenericObjectPool.DEFAULT_TEST_ON_BORROW);
395         boolean testOnReturn = booleanProperty(
396                 properties,
397                 "ps.testOnReturn",
398                 GenericObjectPool.DEFAULT_TEST_ON_RETURN);
399
400         long timeBetweenEvictionRunsMillis = longProperty(
401                 properties,
402                 "ps.timeBetweenEvictionRunsMillis",
403                 GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
404         int numTestsPerEvictionRun = intProperty(
405                 properties,
406                 "ps.numTestsPerEvictionRun",
407                 GenericObjectPool.DEFAULT_NUM_TESTS_PER_EVICTION_RUN);
408
409         long minEvictableIdleTimeMillis = longProperty(
410                 properties,
411                 "ps.minEvictableIdleTimeMillis",
412                 GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS);
413
414         boolean testWhileIdle = booleanProperty(
415                 properties,
416                 "ps.testWhileIdle",
417                 GenericObjectPool.DEFAULT_TEST_WHILE_IDLE);
418
419         return new GenericKeyedObjectPoolFactory(
420                 null,
421                 maxActive,
422                 whenExhaustedAction,
423                 maxWait,
424                 maxIdle,
425                 maxTotal,
426                 testOnBorrow,
427                 testOnReturn,
428                 timeBetweenEvictionRunsMillis,
429                 numTestsPerEvictionRun,
430                 minEvictableIdleTimeMillis,
431                 testWhileIdle);
432     }
433
434     ConnectionFactory createConnectionFactory(Properties JavaDoc properties) {
435         String JavaDoc url = stringProperty(properties, "url");
436         String JavaDoc userName = stringProperty(properties, "username");
437         String JavaDoc password = stringProperty(properties, "password");
438
439         // sanity check
440
if (url == null) {
441             throw new NullPointerException JavaDoc("No value for required property: "
442                     + PROPERTY_PREFIX
443                     + "url");
444         }
445
446         return new DriverManagerConnectionFactory(url, userName, password);
447     }
448
449     GenericObjectPool.Config createPoolConfig(Properties JavaDoc properties) throws Exception JavaDoc {
450         GenericObjectPool.Config config = new GenericObjectPool.Config();
451
452         config.maxIdle = intProperty(
453                 properties,
454                 "maxIdle",
455                 GenericObjectPool.DEFAULT_MAX_IDLE);
456         config.minIdle = intProperty(
457                 properties,
458                 "minIdle",
459                 GenericObjectPool.DEFAULT_MIN_IDLE);
460         config.maxActive = intProperty(
461                 properties,
462                 "maxActive",
463                 GenericObjectPool.DEFAULT_MAX_ACTIVE);
464         config.maxWait = longProperty(
465                 properties,
466                 "maxWait",
467                 GenericObjectPool.DEFAULT_MAX_WAIT);
468
469         config.testOnBorrow = booleanProperty(
470                 properties,
471                 "testOnBorrow",
472                 GenericObjectPool.DEFAULT_TEST_ON_BORROW);
473         config.testOnReturn = booleanProperty(
474                 properties,
475                 "testOnReturn",
476                 GenericObjectPool.DEFAULT_TEST_ON_RETURN);
477         config.testWhileIdle = booleanProperty(
478                 properties,
479                 "testWhileIdle",
480                 GenericObjectPool.DEFAULT_TEST_WHILE_IDLE);
481
482         config.timeBetweenEvictionRunsMillis = longProperty(
483                 properties,
484                 "timeBetweenEvictionRunsMillis",
485                 GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
486         config.numTestsPerEvictionRun = intProperty(
487                 properties,
488                 "numTestsPerEvictionRun",
489                 GenericObjectPool.DEFAULT_NUM_TESTS_PER_EVICTION_RUN);
490         config.minEvictableIdleTimeMillis = longProperty(
491                 properties,
492                 "minEvictableIdleTimeMillis",
493                 GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS);
494
495         config.whenExhaustedAction = whenExhaustedAction(
496                 properties,
497                 "whenExhaustedAction",
498                 GenericObjectPool.DEFAULT_WHEN_EXHAUSTED_ACTION);
499
500         return config;
501     }
502
503     Properties JavaDoc loadProperties(String JavaDoc location) throws IOException JavaDoc {
504
505         Properties JavaDoc properties = new Properties JavaDoc();
506         InputStream JavaDoc in = getInputStream(location);
507         if (in == null) {
508             throw new ConfigurationException("DBCP properties file not found: "
509                     + location);
510         }
511
512         try {
513             properties.load(in);
514         }
515         finally {
516             try {
517                 in.close();
518             }
519             catch (IOException JavaDoc ignore) {
520             }
521         }
522
523         return properties;
524     }
525
526     int defaultTransactionIsolation(
527             Properties JavaDoc properties,
528             String JavaDoc property,
529             int defaultValue) {
530
531         String JavaDoc value = stringProperty(properties, property);
532
533         if (value == null) {
534             return defaultValue;
535         }
536
537         // try int...
538
try {
539             return Integer.parseInt(value);
540         }
541         catch (NumberFormatException JavaDoc nfex) {
542             // try symbolic
543
try {
544                 return Connection JavaDoc.class.getField(value).getInt(null);
545             }
546             catch (Throwable JavaDoc th) {
547                 throw new ConfigurationException(
548                         "Invalid 'defaultTransactionIsolation': " + value);
549             }
550         }
551     }
552
553     byte whenExhaustedAction(Properties JavaDoc properties, String JavaDoc property, byte defaultValue)
554             throws Exception JavaDoc {
555
556         String JavaDoc value = stringProperty(properties, property);
557
558         if (value == null) {
559             return defaultValue;
560         }
561
562         // try byte...
563
try {
564             return Byte.parseByte(value);
565         }
566         catch (NumberFormatException JavaDoc nfex) {
567             // try symbolic
568
try {
569                 return GenericObjectPool.class.getField(value).getByte(null);
570             }
571             catch (Throwable JavaDoc th) {
572                 throw new ConfigurationException("Invalid 'whenExhaustedAction': "
573                         + value);
574             }
575         }
576     }
577
578     String JavaDoc stringProperty(Properties JavaDoc properties, String JavaDoc property) {
579         return properties.getProperty(PROPERTY_PREFIX + property);
580     }
581
582     boolean booleanProperty(Properties JavaDoc properties, String JavaDoc property, boolean defaultValue) {
583         String JavaDoc value = stringProperty(properties, property);
584         return (value != null) ? "true".equalsIgnoreCase(stringProperty(
585                 properties,
586                 property)) : defaultValue;
587     }
588
589     int intProperty(Properties JavaDoc properties, String JavaDoc property, int defaultValue) {
590         String JavaDoc value = stringProperty(properties, property);
591
592         try {
593             return (value != null) ? Integer.parseInt(value) : defaultValue;
594         }
595         catch (NumberFormatException JavaDoc nfex) {
596             return defaultValue;
597         }
598     }
599
600     long longProperty(Properties JavaDoc properties, String JavaDoc property, long defaultValue) {
601         String JavaDoc value = stringProperty(properties, property);
602         try {
603             return (value != null) ? Long.parseLong(value) : defaultValue;
604         }
605         catch (NumberFormatException JavaDoc nfex) {
606             return defaultValue;
607         }
608     }
609
610     byte byteProperty(Properties JavaDoc properties, String JavaDoc property, byte defaultValue) {
611         String JavaDoc value = stringProperty(properties, property);
612         try {
613             return (value != null) ? Byte.parseByte(value) : defaultValue;
614         }
615         catch (NumberFormatException JavaDoc nfex) {
616             return defaultValue;
617         }
618     }
619
620     /**
621      * Returns an input stream for the file corresponding to location.
622      */

623     InputStream JavaDoc getInputStream(String JavaDoc location) {
624         if (this.parentConfiguration == null) {
625             throw new ConfigurationException(
626                     "No parent Configuration set - cannot continue.");
627         }
628
629         return this.parentConfiguration.getResourceLocator().findResourceStream(location);
630     }
631 }
Popular Tags