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}