001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.commons.io.res.type.cache; 020 021import java.io.ByteArrayInputStream; 022import java.io.ByteArrayOutputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026 027import lucee.commons.io.ModeUtil; 028import lucee.commons.io.res.ContentType; 029import lucee.commons.io.res.Resource; 030import lucee.commons.io.res.ResourceMetaData; 031import lucee.commons.io.res.ResourceProvider; 032import lucee.commons.io.res.util.ResourceSupport; 033import lucee.commons.io.res.util.ResourceUtil; 034import lucee.runtime.exp.PageRuntimeException; 035import lucee.runtime.op.Caster; 036import lucee.runtime.type.Struct; 037 038 039/** 040 * a ram resource 041 */ 042public final class CacheResource extends ResourceSupport implements ResourceMetaData { 043 044 private final CacheResourceProvider provider; 045 046 private final String parent; 047 private final String name; 048 //private CacheResourceCore _core; 049 050 CacheResource(CacheResourceProvider provider, String path) { 051 this.provider=provider; 052 if(path.equals("/")) { 053 this.parent=null; 054 this.name=""; 055 } 056 else { 057 String[] pn = ResourceUtil.translatePathName(path); 058 this.parent=pn[0]; 059 this.name=pn[1]; 060 } 061 } 062 063 private CacheResource(CacheResourceProvider provider, String parent,String name) { 064 this.provider=provider; 065 this.parent=parent ; 066 this.name=name; 067 } 068 069 private CacheResourceCore getCore() { 070 return provider.getCore(parent,name); 071 } 072 073 private void removeCore() throws IOException { 074 provider.removeCore(parent,name); 075 } 076 077 private CacheResourceCore createCore(int type) throws IOException { 078 return provider.createCore(parent,name,type); 079 } 080 081 082 private void touch() { 083 provider.touch(parent,name); 084 } 085 086 @Override 087 public String getPath() { 088 return provider.getScheme().concat("://").concat(getInnerPath()); 089 } 090 private String getInnerPath() { 091 if(parent==null) return "/"; 092 return parent.concat(name); 093 } 094 095 @Override 096 public String getName() { 097 return name; 098 } 099 100 @Override 101 public String getParent() { 102 if(isRoot()) return null; 103 return provider.getScheme().concat("://").concat(ResourceUtil.translatePath(parent, true, false)); 104 } 105 106 @Override 107 public boolean isReadable() { 108 return ModeUtil.isReadable(getMode()); 109 } 110 111 @Override 112 public boolean isWriteable() { 113 return ModeUtil.isWritable(getMode()); 114 } 115 116 @Override 117 public void remove(boolean force) throws IOException { 118 if(isRoot()) 119 throw new IOException("can't remove root resource ["+getPath()+"]"); 120 121 provider.read(this); 122 CacheResourceCore core = getCore(); 123 if(core==null) 124 throw new IOException("can't remove resource ["+getPath()+"],resource does not exist"); 125 126 Resource[] children = listResources(); 127 if(children!=null && children.length>0) { 128 if(!force) { 129 throw new IOException("can't delete directory ["+getPath()+"], directory is not empty"); 130 } 131 for(int i=0;i<children.length;i++) { 132 children[i].remove(true); 133 } 134 } 135 removeCore(); 136 } 137 138 @Override 139 public boolean exists() { 140 try { 141 provider.read(this); 142 } catch (IOException e) { 143 return true; 144 } 145 return getCore()!=null; 146 } 147 148 @Override 149 public Resource getParentResource() { 150 return getParentRamResource(); 151 } 152 153 154 private CacheResource getParentRamResource() { 155 if(isRoot()) return null; 156 return new CacheResource(provider,parent); 157 } 158 159 public Resource getRealResource(String realpath) { 160 realpath=ResourceUtil.merge(getInnerPath(), realpath); 161 if(realpath.startsWith("../"))return null; 162 163 return new CacheResource(provider,realpath); 164 } 165 166 @Override 167 public boolean isAbsolute() { 168 return true; 169 } 170 171 @Override 172 public boolean isDirectory() { 173 return exists() && getCore().getType()==CacheResourceCore.TYPE_DIRECTORY; 174 } 175 176 @Override 177 public boolean isFile() { 178 return exists() && getCore().getType()==CacheResourceCore.TYPE_FILE; 179 } 180 181 @Override 182 public long lastModified() { 183 if(!exists()) return 0; 184 return getCore().getLastModified(); 185 } 186 187 @Override 188 public long length() { 189 if(!exists()) return 0; 190 byte[] data= getCore().getData(); 191 if(data==null) return 0; 192 return data.length; 193 } 194 195 @Override 196 public String[] list() { 197 if(!exists()) return null; 198 199 CacheResourceCore core = getCore(); 200 if(core.getType()!=CacheResourceCore.TYPE_DIRECTORY) 201 return null; 202 203 try { 204 return provider.getChildNames(getInnerPath()); 205 } catch (IOException e) { 206 throw new PageRuntimeException(Caster.toPageException(e)); 207 } 208 } 209 210 @Override 211 public Resource[] listResources() { 212 String[] list = list(); 213 if(list==null)return null; 214 215 Resource[] children=new Resource[list.length]; 216 String p=getInnerPath(); 217 if(!isRoot())p=p.concat("/"); 218 for(int i=0;i<children.length;i++) { 219 children[i]=new CacheResource(provider,p,list[i]); 220 } 221 return children; 222 } 223 224 @Override 225 public boolean setLastModified(long time) { 226 if(!exists()) return false; 227 getCore().setLastModified(time); 228 return true; 229 } 230 231 @Override 232 public boolean setReadOnly() { 233 return setWritable(false); 234 } 235 236 @Override 237 public void createFile(boolean createParentWhenNotExists) throws IOException { 238 ResourceUtil.checkCreateFileOK(this,createParentWhenNotExists); 239 provider.lock(this); 240 try { 241 createCore(CacheResourceCore.TYPE_FILE); 242 } 243 finally { 244 provider.unlock(this); 245 } 246 } 247 248 249 @Override 250 public void createDirectory(boolean createParentWhenNotExists) throws IOException { 251 ResourceUtil.checkCreateDirectoryOK(this,createParentWhenNotExists); 252 provider.lock(this); 253 try { 254 createCore(CacheResourceCore.TYPE_DIRECTORY); 255 } 256 finally { 257 provider.unlock(this); 258 } 259 260 } 261 262 @Override 263 public InputStream getInputStream() throws IOException { 264 ResourceUtil.checkGetInputStreamOK(this); 265 266 provider.lock(this); 267 CacheResourceCore core = getCore(); 268 269 byte[] data = core.getData(); 270 if(data==null)data=new byte[0]; 271 provider.unlock(this); 272 return new ByteArrayInputStream(data); 273 } 274 275 public OutputStream getOutputStream(boolean append) throws IOException { 276 ResourceUtil.checkGetOutputStreamOK(this); 277 provider.lock(this); 278 return new CacheOutputStream(this,append); 279 } 280 281 public ContentType getContentType() { 282 return ResourceUtil.getContentType(this); 283 } 284 285 @Override 286 public ResourceProvider getResourceProvider() { 287 return provider; 288 } 289 @Override 290 public String toString() { 291 return getPath(); 292 } 293 294 295 /** 296 * This is useed by the MemoryResource too write back data to, that are written to outputstream 297 */ 298 class CacheOutputStream extends ByteArrayOutputStream { 299 300 private CacheResource res; 301 private boolean append; 302 303 /** 304 * Constructor of the class 305 * @param res 306 */ 307 public CacheOutputStream(CacheResource res, boolean append) { 308 this.append=append; 309 this.res=res; 310 } 311 312 @Override 313 public void close() throws IOException { 314 try { 315 super.close(); 316 CacheResourceCore core = res.getCore(); 317 if(core==null)core=res.createCore(CacheResourceCore.TYPE_FILE); 318 else core.setLastModified(System.currentTimeMillis()); 319 core.setData(this.toByteArray(),append); 320 touch(); 321 } 322 finally { 323 res.getResourceProvider().unlock(res); 324 } 325 } 326 } 327 328 @Override 329 public boolean setReadable(boolean value) { 330 if(!exists())return false; 331 try { 332 setMode(ModeUtil.setReadable(getMode(), value)); 333 return true; 334 } catch (IOException e) { 335 return false; 336 } 337 338 } 339 340 @Override 341 public boolean setWritable(boolean value) { 342 if(!exists())return false; 343 try { 344 setMode(ModeUtil.setWritable(getMode(), value)); 345 return true; 346 } catch (IOException e) { 347 return false; 348 } 349 } 350 351 private boolean isRoot() { 352 return parent==null; 353 } 354 355 public int getMode() { 356 if(!exists())return 0; 357 return getCore().getMode(); 358 } 359 360 public void setMode(int mode) throws IOException { 361 if(!exists())throw new IOException("can't set mode on resource ["+this+"], resource does not exist"); 362 getCore().setMode(mode); 363 } 364 @Override 365 public boolean getAttribute(short attribute) { 366 if(!exists())return false; 367 return (getCore().getAttributes()&attribute)>0; 368 } 369 @Override 370 public void setAttribute(short attribute, boolean value) throws IOException { 371 if(!exists())throw new IOException("can't get attributes on resource ["+this+"], resource does not exist"); 372 int attr = getCore().getAttributes(); 373 if(value) { 374 if((attr&attribute)==0) attr+=attribute; 375 } 376 else { 377 if((attr&attribute)>0) attr-=attribute; 378 } 379 getCore().setAttributes(attr); 380 } 381 382 public Struct getMetaData() { 383 return provider.getMeta(parent,name); 384 } 385 386}