|
|||||||||||||||||||
| Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
| DiskPersistenceListener.java | 57,5% | 69,8% | 70% | 67,2% |
|
||||||||||||||
| 1 |
/*
|
|
| 2 |
* Copyright (c) 2002-2003 by OpenSymphony
|
|
| 3 |
* All rights reserved.
|
|
| 4 |
*/
|
|
| 5 |
package com.opensymphony.oscache.plugins.diskpersistence;
|
|
| 6 |
|
|
| 7 |
import com.opensymphony.oscache.base.Config;
|
|
| 8 |
import com.opensymphony.oscache.base.persistence.CachePersistenceException;
|
|
| 9 |
import com.opensymphony.oscache.base.persistence.PersistenceListener;
|
|
| 10 |
import com.opensymphony.oscache.web.ServletCacheAdministrator;
|
|
| 11 |
|
|
| 12 |
import org.apache.commons.logging.Log;
|
|
| 13 |
import org.apache.commons.logging.LogFactory;
|
|
| 14 |
|
|
| 15 |
import java.io.*;
|
|
| 16 |
|
|
| 17 |
import java.util.Set;
|
|
| 18 |
|
|
| 19 |
import javax.servlet.jsp.PageContext;
|
|
| 20 |
|
|
| 21 |
/**
|
|
| 22 |
* Persist the cache data to disk.
|
|
| 23 |
*
|
|
| 24 |
* The code in this class is totally not thread safe it is the resonsibility
|
|
| 25 |
* of the cache using this persistence listener to handle the concurrency.
|
|
| 26 |
*
|
|
| 27 |
* @version $Revision: 1.2 $
|
|
| 28 |
* @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
|
|
| 29 |
* @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
|
|
| 30 |
* @author <a href="mailto:chris@swebtec.com">Chris Miller</a>
|
|
| 31 |
*/
|
|
| 32 |
public final class DiskPersistenceListener implements PersistenceListener, Serializable { |
|
| 33 |
protected final static String CACHE_PATH_KEY = "cache.path"; |
|
| 34 |
|
|
| 35 |
/**
|
|
| 36 |
* File extension for disk cache file
|
|
| 37 |
*/
|
|
| 38 |
private final static String CACHE_EXTENSION = "cache"; |
|
| 39 |
|
|
| 40 |
/**
|
|
| 41 |
* The directory that cache groups are stored under
|
|
| 42 |
*/
|
|
| 43 |
private final static String GROUP_DIRECTORY = "__groups__"; |
|
| 44 |
|
|
| 45 |
/**
|
|
| 46 |
* Sub path name for application cache
|
|
| 47 |
*/
|
|
| 48 |
private final static String APPLICATION_CACHE_SUBPATH = "application"; |
|
| 49 |
|
|
| 50 |
/**
|
|
| 51 |
* Sub path name for session cache
|
|
| 52 |
*/
|
|
| 53 |
private final static String SESSION_CACHE_SUBPATH = "session"; |
|
| 54 |
|
|
| 55 |
/**
|
|
| 56 |
* Property to get the temporary working directory of the servlet container.
|
|
| 57 |
*/
|
|
| 58 |
private static final String CONTEXT_TMPDIR = "javax.servlet.context.tempdir"; |
|
| 59 |
private static transient final Log log = LogFactory.getLog(DiskPersistenceListener.class); |
|
| 60 |
|
|
| 61 |
/**
|
|
| 62 |
* Base path where the disk cache reside.
|
|
| 63 |
*/
|
|
| 64 |
private File cachePath = null; |
|
| 65 |
private File contextTmpDir;
|
|
| 66 |
|
|
| 67 |
/**
|
|
| 68 |
* Root path for disk cache
|
|
| 69 |
*/
|
|
| 70 |
private String root = null; |
|
| 71 |
|
|
| 72 |
/**
|
|
| 73 |
* Get the physical cache path on disk.
|
|
| 74 |
*
|
|
| 75 |
* @return A file representing the physical cache location.
|
|
| 76 |
*/
|
|
| 77 | 34 |
public File getCachePath() {
|
| 78 | 34 |
return cachePath;
|
| 79 |
} |
|
| 80 |
|
|
| 81 |
/**
|
|
| 82 |
* Verify if a group exists in the cache
|
|
| 83 |
*
|
|
| 84 |
* @param group The group name to check
|
|
| 85 |
* @return True if it exists
|
|
| 86 |
* @throws CachePersistenceException
|
|
| 87 |
*/
|
|
| 88 | 0 |
public boolean isGroupStored(String group) throws CachePersistenceException { |
| 89 | 0 |
try {
|
| 90 | 0 |
File file = getCacheGroupFile(group); |
| 91 |
|
|
| 92 | 0 |
return file.exists();
|
| 93 |
} catch (Exception e) {
|
|
| 94 | 0 |
throw new CachePersistenceException("Unable verify group '" + group + "' exists in the cache: " + e); |
| 95 |
} |
|
| 96 |
} |
|
| 97 |
|
|
| 98 |
/**
|
|
| 99 |
* Verify if an object is currently stored in the cache
|
|
| 100 |
*
|
|
| 101 |
* @param key The object key
|
|
| 102 |
* @return True if it exists
|
|
| 103 |
* @throws CachePersistenceException
|
|
| 104 |
*/
|
|
| 105 | 0 |
public boolean isStored(String key) throws CachePersistenceException { |
| 106 | 0 |
try {
|
| 107 | 0 |
File file = getCacheFile(key); |
| 108 |
|
|
| 109 | 0 |
return file.exists();
|
| 110 |
} catch (Exception e) {
|
|
| 111 | 0 |
throw new CachePersistenceException("Unable verify id '" + key + "' is stored in the cache: " + e); |
| 112 |
} |
|
| 113 |
} |
|
| 114 |
|
|
| 115 |
/**
|
|
| 116 |
* Clears the whole cache directory, starting from the root
|
|
| 117 |
*
|
|
| 118 |
* @throws CachePersistenceException
|
|
| 119 |
*/
|
|
| 120 | 6 |
public void clear() throws CachePersistenceException { |
| 121 | 6 |
clear(root); |
| 122 |
} |
|
| 123 |
|
|
| 124 |
/**
|
|
| 125 |
* Initialises this <tt>DiskPersistenceListener</tt> using the supplied
|
|
| 126 |
* configuration.
|
|
| 127 |
*
|
|
| 128 |
* @param config The OSCache configuration
|
|
| 129 |
*/
|
|
| 130 | 34 |
public PersistenceListener configure(Config config) {
|
| 131 | 34 |
String sessionId = null;
|
| 132 | 34 |
int scope = 0;
|
| 133 | 34 |
initFileCaching(config.getProperty(CACHE_PATH_KEY)); |
| 134 |
|
|
| 135 | 34 |
if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID) != null) { |
| 136 | 0 |
sessionId = config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID); |
| 137 |
} |
|
| 138 |
|
|
| 139 | 34 |
if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE) != null) { |
| 140 | 0 |
scope = Integer.parseInt(config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE)); |
| 141 |
} |
|
| 142 |
|
|
| 143 | 34 |
StringBuffer root = new StringBuffer(getCachePath().getPath());
|
| 144 | 34 |
root.append("/");
|
| 145 | 34 |
root.append(getPathPart(scope)); |
| 146 |
|
|
| 147 | 34 |
if ((sessionId != null) && (sessionId.length() > 0)) { |
| 148 | 0 |
root.append("/");
|
| 149 | 0 |
root.append(sessionId); |
| 150 |
} |
|
| 151 |
|
|
| 152 | 34 |
this.root = root.toString();
|
| 153 | 34 |
this.contextTmpDir = (File) config.get(ServletCacheAdministrator.HASH_KEY_CONTEXT_TMPDIR);
|
| 154 |
|
|
| 155 | 34 |
return this; |
| 156 |
} |
|
| 157 |
|
|
| 158 |
/**
|
|
| 159 |
* Delete a single cache entry.
|
|
| 160 |
*
|
|
| 161 |
* @param key The object key to delete
|
|
| 162 |
* @throws CachePersistenceException
|
|
| 163 |
*/
|
|
| 164 | 0 |
public void remove(String key) throws CachePersistenceException { |
| 165 | 0 |
File file = getCacheFile(key); |
| 166 | 0 |
remove(file); |
| 167 |
} |
|
| 168 |
|
|
| 169 |
/**
|
|
| 170 |
* Deletes an entire group from the cache.
|
|
| 171 |
*
|
|
| 172 |
* @param groupName The name of the group to delete
|
|
| 173 |
* @throws CachePersistenceException
|
|
| 174 |
*/
|
|
| 175 | 0 |
public void removeGroup(String groupName) throws CachePersistenceException { |
| 176 | 0 |
File file = getCacheGroupFile(groupName); |
| 177 | 0 |
remove(file); |
| 178 |
} |
|
| 179 |
|
|
| 180 |
/**
|
|
| 181 |
* Retrieve an object from the disk
|
|
| 182 |
*
|
|
| 183 |
* @param key The object key
|
|
| 184 |
* @return The retrieved object
|
|
| 185 |
* @throws CachePersistenceException
|
|
| 186 |
*/
|
|
| 187 | 269 |
public Object retrieve(String key) throws CachePersistenceException { |
| 188 | 269 |
return retrieve(getCacheFile(key));
|
| 189 |
} |
|
| 190 |
|
|
| 191 |
/**
|
|
| 192 |
* Retrieves a group from the cache, or <code>null</code> if the group
|
|
| 193 |
* file could not be found.
|
|
| 194 |
*
|
|
| 195 |
* @param groupName The name of the group to retrieve.
|
|
| 196 |
* @return A <code>Set</code> containing keys of all of the cache
|
|
| 197 |
* entries that belong to this group.
|
|
| 198 |
* @throws CachePersistenceException
|
|
| 199 |
*/
|
|
| 200 | 77 |
public Set retrieveGroup(String groupName) throws CachePersistenceException { |
| 201 | 77 |
File groupFile = getCacheGroupFile(groupName); |
| 202 |
|
|
| 203 | 77 |
try {
|
| 204 | 77 |
return (Set) retrieve(groupFile);
|
| 205 |
} catch (ClassCastException e) {
|
|
| 206 | 0 |
throw new CachePersistenceException("Group file " + groupFile + " was not persisted as a Set: " + e); |
| 207 |
} |
|
| 208 |
} |
|
| 209 |
|
|
| 210 |
/**
|
|
| 211 |
* Stores an object in cache
|
|
| 212 |
*
|
|
| 213 |
* @param key The object's key
|
|
| 214 |
* @param obj The object to store
|
|
| 215 |
* @throws CachePersistenceException
|
|
| 216 |
*/
|
|
| 217 | 161 |
public void store(String key, Object obj) throws CachePersistenceException { |
| 218 | 161 |
File file = getCacheFile(key); |
| 219 | 161 |
store(file, obj); |
| 220 |
} |
|
| 221 |
|
|
| 222 |
/**
|
|
| 223 |
* Stores a group in the persistent cache. This will overwrite any existing
|
|
| 224 |
* group with the same name
|
|
| 225 |
*/
|
|
| 226 | 68 |
public void storeGroup(String groupName, Set group) throws CachePersistenceException { |
| 227 | 68 |
File groupFile = getCacheGroupFile(groupName); |
| 228 | 68 |
store(groupFile, group); |
| 229 |
} |
|
| 230 |
|
|
| 231 |
/**
|
|
| 232 |
* Allows to translate to the temp dir of the servlet container if cachePathStr
|
|
| 233 |
* is javax.servlet.context.tempdir.
|
|
| 234 |
*
|
|
| 235 |
* @param cachePathStr Cache path read from the properties file.
|
|
| 236 |
* @return Adjusted cache path
|
|
| 237 |
*/
|
|
| 238 | 0 |
protected String adjustFileCachePath(String cachePathStr) {
|
| 239 | 0 |
if (cachePathStr.compareToIgnoreCase(CONTEXT_TMPDIR) == 0) {
|
| 240 | 0 |
cachePathStr = contextTmpDir.getAbsolutePath(); |
| 241 |
} |
|
| 242 |
|
|
| 243 | 0 |
return cachePathStr;
|
| 244 |
} |
|
| 245 |
|
|
| 246 |
/**
|
|
| 247 |
* Set caching to file on or off.
|
|
| 248 |
* If the <code>cache.path</code> property exists, we assume file caching is turned on.
|
|
| 249 |
* By the same token, to turn off file caching just remove this property.
|
|
| 250 |
*/
|
|
| 251 | 34 |
protected void initFileCaching(String cachePathStr) { |
| 252 | 34 |
if (cachePathStr != null) { |
| 253 | 34 |
cachePath = new File(cachePathStr);
|
| 254 |
|
|
| 255 | 34 |
try {
|
| 256 | 34 |
if (!cachePath.exists()) {
|
| 257 | 2 |
if (log.isInfoEnabled()) {
|
| 258 | 2 |
log.info("cache.path '" + cachePathStr + "' does not exist, creating"); |
| 259 |
} |
|
| 260 |
|
|
| 261 | 2 |
cachePath.mkdirs(); |
| 262 |
} |
|
| 263 |
|
|
| 264 | 34 |
if (!cachePath.isDirectory()) {
|
| 265 | 0 |
log.error("cache.path '" + cachePathStr + "' is not a directory"); |
| 266 | 0 |
cachePath = null;
|
| 267 | 34 |
} else if (!cachePath.canWrite()) { |
| 268 | 0 |
log.error("cache.path '" + cachePathStr + "' is not a writable location"); |
| 269 | 0 |
cachePath = null;
|
| 270 |
} |
|
| 271 |
} catch (Exception e) {
|
|
| 272 | 0 |
log.error("cache.path '" + cachePathStr + "' could not be used", e); |
| 273 | 0 |
cachePath = null;
|
| 274 |
} |
|
| 275 |
} else {
|
|
| 276 |
// Use default value
|
|
| 277 |
} |
|
| 278 |
} |
|
| 279 |
|
|
| 280 | 0 |
protected void remove(File file) throws CachePersistenceException { |
| 281 | 0 |
try {
|
| 282 |
// Loop until we are able to delete (No current read).
|
|
| 283 |
// The cache must ensure that there are never two concurrent threads
|
|
| 284 |
// doing write (store and delete) operations on the same item.
|
|
| 285 |
// Delete only should be enough but file.exists prevents infinite loop
|
|
| 286 | 0 |
while (!file.delete() && file.exists()) {
|
| 287 |
; |
|
| 288 |
} |
|
| 289 |
} catch (Exception e) {
|
|
| 290 | 0 |
throw new CachePersistenceException("Unable to remove '" + file + "' from the cache: " + e); |
| 291 |
} |
|
| 292 |
} |
|
| 293 |
|
|
| 294 |
/**
|
|
| 295 |
* Stores an object using the supplied file object
|
|
| 296 |
*
|
|
| 297 |
* @param file The file to use for storing the object
|
|
| 298 |
* @param obj the object to store
|
|
| 299 |
* @throws CachePersistenceException
|
|
| 300 |
*/
|
|
| 301 | 229 |
protected void store(File file, Object obj) throws CachePersistenceException { |
| 302 |
// check if the directory structure required exists and create it if it doesn't
|
|
| 303 | 229 |
File filepath = new File(file.getParent());
|
| 304 |
|
|
| 305 | 229 |
try {
|
| 306 | 229 |
if (!filepath.exists()) {
|
| 307 | 10 |
filepath.mkdirs(); |
| 308 |
} |
|
| 309 |
} catch (Exception e) {
|
|
| 310 | 0 |
throw new CachePersistenceException("Unable to create the directory " + filepath); |
| 311 |
} |
|
| 312 |
|
|
| 313 |
// Loop until we are able to delete (No current read).
|
|
| 314 |
// The cache must ensure that there are never two concurrent threads
|
|
| 315 |
// doing write (store and delete) operations on the same item.
|
|
| 316 |
// Delete only should be enough but file.exists prevents infinite loop
|
|
| 317 | 229 |
while (file.exists() && !file.delete()) {
|
| 318 |
; |
|
| 319 |
} |
|
| 320 |
|
|
| 321 |
// Write the object to disk
|
|
| 322 | 229 |
FileOutputStream fout = null;
|
| 323 | 229 |
ObjectOutputStream oout = null;
|
| 324 |
|
|
| 325 | 229 |
try {
|
| 326 | 229 |
fout = new FileOutputStream(file);
|
| 327 | 229 |
oout = new ObjectOutputStream(fout);
|
| 328 | 229 |
oout.writeObject(obj); |
| 329 | 229 |
oout.flush(); |
| 330 |
} catch (Exception e) {
|
|
| 331 | 0 |
throw new CachePersistenceException("Unable to write '" + file + "' in the cache. Exception: " + e.getClass().getName() + ", Message: " + e.getMessage()); |
| 332 |
} finally {
|
|
| 333 | 229 |
try {
|
| 334 | 229 |
fout.close(); |
| 335 |
} catch (Exception e) {
|
|
| 336 |
} |
|
| 337 |
|
|
| 338 | 229 |
try {
|
| 339 | 229 |
oout.close(); |
| 340 |
} catch (Exception e) {
|
|
| 341 |
} |
|
| 342 |
} |
|
| 343 |
} |
|
| 344 |
|
|
| 345 |
/**
|
|
| 346 |
* Build fully qualified cache file name specifying a cache entry key.
|
|
| 347 |
*
|
|
| 348 |
* @param key Cache Entry Key.
|
|
| 349 |
* @return File reference.
|
|
| 350 |
*/
|
|
| 351 | 430 |
private File getCacheFile(String key) {
|
| 352 | 430 |
if ((key == null) || (key.length() == 0)) { |
| 353 | 0 |
throw new IllegalArgumentException("Invalid key '" + key + "' specified to getCacheFile."); |
| 354 |
} |
|
| 355 |
|
|
| 356 | 430 |
char[] chars = key.toCharArray();
|
| 357 | 430 |
char[] fileChars = new char[chars.length]; |
| 358 |
|
|
| 359 | 430 |
for (int i = 0; i < chars.length; i++) { |
| 360 | 6324 |
char c = chars[i];
|
| 361 |
|
|
| 362 | 6324 |
switch (c) {
|
| 363 |
case '.':
|
|
| 364 |
case '/':
|
|
| 365 |
case '\\':
|
|
| 366 |
case ' ':
|
|
| 367 |
case ':':
|
|
| 368 |
case ';':
|
|
| 369 |
case '"': |
|
| 370 |
case '\'':
|
|
| 371 | 728 |
fileChars[i] = '_'; |
| 372 | 728 |
break;
|
| 373 |
default:
|
|
| 374 | 5596 |
fileChars[i] = c; |
| 375 |
} |
|
| 376 |
} |
|
| 377 |
|
|
| 378 | 430 |
File file = new File(root, new String(fileChars) + "." + CACHE_EXTENSION); |
| 379 |
|
|
| 380 | 430 |
return file;
|
| 381 |
} |
|
| 382 |
|
|
| 383 |
/**
|
|
| 384 |
* Builds a fully qualified file name that specifies a cache group entry.
|
|
| 385 |
*
|
|
| 386 |
* @param group The name of the group
|
|
| 387 |
* @return A File reference
|
|
| 388 |
*/
|
|
| 389 | 145 |
private File getCacheGroupFile(String group) {
|
| 390 | 145 |
int AVERAGE_PATH_LENGTH = 30;
|
| 391 |
|
|
| 392 | 145 |
if ((group == null) || (group.length() == 0)) { |
| 393 | 0 |
throw new IllegalArgumentException("Invalid group '" + group + "' specified to getCacheGroupFile."); |
| 394 |
} |
|
| 395 |
|
|
| 396 | 145 |
StringBuffer path = new StringBuffer(AVERAGE_PATH_LENGTH);
|
| 397 |
|
|
| 398 |
// Build a fully qualified file name for this group
|
|
| 399 | 145 |
path.append(GROUP_DIRECTORY).append('/');
|
| 400 | 145 |
path.append(group).append('.').append(CACHE_EXTENSION);
|
| 401 |
|
|
| 402 | 145 |
return new File(root, path.toString()); |
| 403 |
} |
|
| 404 |
|
|
| 405 |
/**
|
|
| 406 |
* This allows to persist different scopes in different path in the case of
|
|
| 407 |
* file caching.
|
|
| 408 |
*
|
|
| 409 |
* @param scope Cache scope.
|
|
| 410 |
* @return The scope subpath
|
|
| 411 |
*/
|
|
| 412 | 34 |
private String getPathPart(int scope) { |
| 413 | 34 |
if (scope == PageContext.SESSION_SCOPE) {
|
| 414 | 0 |
return SESSION_CACHE_SUBPATH;
|
| 415 |
} else {
|
|
| 416 | 34 |
return APPLICATION_CACHE_SUBPATH;
|
| 417 |
} |
|
| 418 |
} |
|
| 419 |
|
|
| 420 |
/**
|
|
| 421 |
* Clears a whole directory, starting from the specified
|
|
| 422 |
* directory
|
|
| 423 |
*
|
|
| 424 |
* @param baseDirName The root directory to delete
|
|
| 425 |
* @throws CachePersistenceException
|
|
| 426 |
*/
|
|
| 427 | 6 |
private void clear(String baseDirName) throws CachePersistenceException { |
| 428 | 6 |
File baseDir = new File(baseDirName);
|
| 429 | 6 |
File[] fileList = baseDir.listFiles(); |
| 430 |
|
|
| 431 | 6 |
try {
|
| 432 | 6 |
if (fileList != null) { |
| 433 |
// Loop through all the files and directory to delete them
|
|
| 434 | 6 |
for (int count = 0; count < fileList.length; count++) { |
| 435 | 6 |
if (fileList[count].isFile()) {
|
| 436 | 6 |
fileList[count].delete(); |
| 437 |
} else {
|
|
| 438 |
// Make a recursive call to delete the directory
|
|
| 439 | 0 |
clear(fileList[count].toString()); |
| 440 | 0 |
fileList[count].delete(); |
| 441 |
} |
|
| 442 |
} |
|
| 443 |
} |
|
| 444 |
|
|
| 445 |
// Delete the root directory
|
|
| 446 | 6 |
baseDir.delete(); |
| 447 |
} catch (Exception e) {
|
|
| 448 | 0 |
throw new CachePersistenceException("Unable to clear the cache directory"); |
| 449 |
} |
|
| 450 |
} |
|
| 451 |
|
|
| 452 |
/**
|
|
| 453 |
* Retrives a serialized object from the supplied file, or returns
|
|
| 454 |
* <code>null</code> if the file does not exist.
|
|
| 455 |
*
|
|
| 456 |
* @param file The file to deserialize
|
|
| 457 |
* @return The deserialized object
|
|
| 458 |
* @throws CachePersistenceException
|
|
| 459 |
*/
|
|
| 460 | 346 |
private Object retrieve(File file) throws CachePersistenceException { |
| 461 | 346 |
Object readContent = null;
|
| 462 | 346 |
boolean fileExist;
|
| 463 |
|
|
| 464 | 346 |
try {
|
| 465 | 346 |
fileExist = file.exists(); |
| 466 |
} catch (Exception e) {
|
|
| 467 | 0 |
throw new CachePersistenceException("Unable to verify if " + file + " exists: " + e); |
| 468 |
} |
|
| 469 |
|
|
| 470 |
// Read the file if it exists
|
|
| 471 | 346 |
if (fileExist) {
|
| 472 | 220 |
BufferedInputStream in = null;
|
| 473 | 220 |
ObjectInputStream oin = null;
|
| 474 |
|
|
| 475 | 220 |
try {
|
| 476 | 220 |
in = new BufferedInputStream(new FileInputStream(file)); |
| 477 | 220 |
oin = new ObjectInputStream(in);
|
| 478 | 220 |
readContent = oin.readObject(); |
| 479 |
} catch (Exception e) {
|
|
| 480 |
// We expect this exception to occur.
|
|
| 481 |
// This is when the item will be invalidated (written or deleted)
|
|
| 482 |
// during read.
|
|
| 483 |
// The cache has the logic to retry reading.
|
|
| 484 | 0 |
throw new CachePersistenceException("Unable to read '" + file.getAbsolutePath() + "' from the cache: " + e); |
| 485 |
} finally {
|
|
| 486 | 220 |
try {
|
| 487 | 220 |
oin.close(); |
| 488 |
} catch (Exception ex) {
|
|
| 489 |
} |
|
| 490 |
|
|
| 491 | 220 |
try {
|
| 492 | 220 |
in.close(); |
| 493 |
} catch (Exception ex) {
|
|
| 494 |
} |
|
| 495 |
} |
|
| 496 |
} |
|
| 497 |
|
|
| 498 | 346 |
return readContent;
|
| 499 |
} |
|
| 500 |
} |
|
| 501 |
|
|
||||||||||