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.datasource;
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.IOUtil;
028import lucee.commons.io.ModeUtil;
029import lucee.commons.io.res.Resource;
030import lucee.commons.io.res.ResourceProvider;
031import lucee.commons.io.res.type.datasource.DatasourceResourceProvider.ConnectionData;
032import lucee.commons.io.res.util.ResourceSupport;
033import lucee.commons.io.res.util.ResourceUtil;
034import lucee.commons.lang.ExceptionUtil;
035import lucee.commons.lang.StringUtil;
036import lucee.runtime.exp.PageException;
037import lucee.runtime.exp.PageRuntimeException;
038import lucee.runtime.type.util.ArrayUtil;
039
040public final class DatasourceResource extends ResourceSupport {
041
042        
043
044        private DatasourceResourceProvider provider;
045        private String parent;
046        private String name;
047        private ConnectionData data;
048        private int fullPathHash;
049        private int pathHash;
050        
051        /**
052         * Constructor of the class
053         * @param provider
054         * @param data
055         * @param path
056         */
057        DatasourceResource(DatasourceResourceProvider provider, ConnectionData data,String path) {
058                this.provider=provider;
059                this.data=data;
060                if("/".equals(path)) {
061                        this.parent=null;
062                        this.name="";
063                }
064                else {
065                        String[] pn = ResourceUtil.translatePathName(path);
066                        this.parent=pn[0];
067                        this.name=pn[1];
068                }
069        }
070
071
072        private int fullPathHash() {
073                if(fullPathHash==0) fullPathHash=getInnerPath().hashCode();
074                return fullPathHash;
075        }
076        
077        private int pathHash() {
078                if(pathHash==0 && parent!=null) pathHash=parent.hashCode();
079                return pathHash;
080        }
081
082        private Attr attr() {
083                return provider.getAttr(data,fullPathHash(),parent,name);
084        }
085
086
087        private boolean isRoot() {
088                return parent==null;
089        }
090        
091        @Override
092        public void createDirectory(boolean createParentWhenNotExists) throws IOException {
093                ResourceUtil.checkCreateDirectoryOK(this,createParentWhenNotExists);
094                provider.create(data,fullPathHash(),pathHash(),parent,name,Attr.TYPE_DIRECTORY);
095                
096        }
097
098        @Override
099        public void createFile(boolean createParentWhenNotExists) throws IOException {
100                ResourceUtil.checkCreateFileOK(this,createParentWhenNotExists);
101                provider.create(data,fullPathHash(),pathHash(),parent,name,Attr.TYPE_FILE);
102        }
103        
104        @Override
105        public void remove(boolean force) throws IOException {
106                ResourceUtil.checkRemoveOK(this);
107                if(isRoot()) 
108                        throw new IOException("can't remove root resource ["+getPath()+"]");
109
110                
111                Resource[] children = listResources();
112                if(children!=null && children.length>0) {
113                        if(!force) {
114                                throw new IOException("can't delete directory ["+getPath()+"], directory is not empty");
115                        }
116                        for(int i=0;i<children.length;i++) {
117                                children[i].remove(true);
118                        }
119                }
120                provider.delete(data,fullPathHash(),parent,name);
121        }
122
123        @Override
124        public boolean exists() {
125                return attr().exists();
126        }
127
128        @Override
129        public InputStream getInputStream() throws IOException {
130                ResourceUtil.checkGetInputStreamOK(this);
131                return provider.getInputStream(data,fullPathHash(),parent,name);
132        }
133
134        @Override
135        public int getMode() {
136                return attr().getMode();
137        }
138
139        @Override
140        public String getName() {
141                return name;
142        }
143
144        @Override
145        public OutputStream getOutputStream(boolean append) throws IOException {
146                ResourceUtil.checkGetOutputStreamOK(this);
147                byte[] barr=null;
148                
149                if(append && !provider.concatSupported(data) && isFile()){
150                        try{
151                                ByteArrayOutputStream baos = new ByteArrayOutputStream();
152                                IOUtil.copy(getInputStream(), baos,true,true);
153                                barr = baos.toByteArray();
154                        }
155                        catch(Throwable t){
156                ExceptionUtil.rethrowIfNecessary(t);
157                        }
158                }
159                
160                OutputStream os = provider.getOutputStream(data,fullPathHash(),pathHash(),parent,name,append);
161                if(!ArrayUtil.isEmpty(barr))IOUtil.copy(new ByteArrayInputStream(barr), os,true,false);
162                return os;
163        }
164
165        @Override
166        public String getParent() {
167                if(isRoot()) return null;
168                String p = (StringUtil.isEmpty(parent))?"/":parent;
169                return provider.getScheme().concat("://").concat(data.key()).concat(ResourceUtil.translatePath(p, true, false));
170                
171        }
172
173        @Override
174        public Resource getParentResource() {
175                return getParentDatasourceResource();
176        }
177        private DatasourceResource getParentDatasourceResource() {
178                if(isRoot()) return null;
179                return new DatasourceResource(provider,data,parent);
180        }
181
182        @Override
183        public String getPath() {
184                return provider.getScheme().concat("://").concat(data.key()).concat(getInnerPath());
185        }
186        private String getInnerPath() {
187                if(parent==null) return "/";
188                return parent.concat(name);
189        }
190
191        @Override
192        public Resource getRealResource(String realpath) {
193                realpath=ResourceUtil.merge(getInnerPath(), realpath);
194                if(realpath.startsWith("../"))return null;
195
196                return new DatasourceResource(provider,data,realpath);
197        }
198
199        @Override
200        public ResourceProvider getResourceProvider() {
201                return provider;
202        }
203
204        @Override
205        public boolean isAbsolute() {
206                return true;
207        }
208
209        @Override
210        public boolean isDirectory() {
211                return attr().isDirectory();
212        }
213        
214        @Override
215        public boolean isFile() {
216                return attr().isFile();
217        }
218
219        @Override
220        public boolean isReadable() {
221                return ModeUtil.isReadable(getMode());
222        }
223
224        @Override
225        public boolean isWriteable() {
226                return ModeUtil.isWritable(getMode());
227        }
228
229        @Override
230        public long lastModified() {
231                return attr().getLastModified();
232        }
233
234        @Override
235        public long length() {
236                return attr().size();
237        }
238
239        @Override
240        public Resource[] listResources() {
241                if(!attr().isDirectory())return null;
242                
243                String path;
244                if(parent==null) path= "/";
245                else path=parent.concat(name).concat("/");
246                
247                
248                Attr[] children=null;
249                try {
250                        children = provider.getAttrs(data,path.hashCode(),path);
251                } catch (PageException e) {
252                        throw new PageRuntimeException(e);
253                }
254                if(children==null) return new Resource[0];
255                Resource[] attrs = new Resource[children.length];
256                for(int i=0;i<children.length;i++) {
257                        // TODO optimieren, alle attr mitgeben
258                        attrs[i]=new DatasourceResource(provider,data,path+children[i].getName());
259                }
260                return attrs;
261        }
262
263        @Override
264        public boolean setLastModified(long time) {
265                if(!exists()) return false;
266                return provider.setLastModified(data,fullPathHash(),parent,name,time);
267        }
268
269        @Override
270        public void setMode(int mode) throws IOException {
271                if(!exists())throw new IOException("can't set mode on resource ["+this+"], resource does not exist");
272                provider.setMode(data,fullPathHash(),parent,name,mode);
273        }
274
275
276        @Override
277        public void moveTo(Resource dest) throws IOException {
278                super.moveTo(dest);// TODO
279        }
280        
281        
282        @Override
283        public boolean setReadable(boolean readable) {
284                if(!exists())return false;
285                try {
286                        setMode(ModeUtil.setReadable(getMode(), readable));
287                        return true;
288                } catch (IOException e) {
289                        return false;
290                }
291        }
292
293        @Override
294        public boolean setWritable(boolean writable) {
295                if(!exists())return false;
296                try {
297                        setMode(ModeUtil.setWritable(getMode(), writable));
298                        return true;
299                } catch (IOException e) {
300                        return false;
301                }
302        }
303        
304        @Override
305        public String toString() {
306                return getPath();
307        }
308
309}