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