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.runtime.rest;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026
027import lucee.commons.io.FileUtil;
028import lucee.commons.io.res.Resource;
029import lucee.commons.io.res.filter.AndResourceFilter;
030import lucee.commons.io.res.filter.ExtensionResourceFilter;
031import lucee.commons.io.res.filter.ResourceFilter;
032import lucee.commons.lang.ExceptionUtil;
033import lucee.commons.lang.mimetype.MimeType;
034import lucee.runtime.Component;
035import lucee.runtime.PageContext;
036import lucee.runtime.PageSource;
037import lucee.runtime.component.ComponentLoader;
038import lucee.runtime.config.Config;
039import lucee.runtime.config.ConfigWebImpl;
040import lucee.runtime.config.ConfigWebUtil;
041import lucee.runtime.config.Constants;
042import lucee.runtime.exp.PageException;
043import lucee.runtime.op.Caster;
044import lucee.runtime.type.Struct;
045import lucee.runtime.type.StructImpl;
046import lucee.runtime.type.util.ArrayUtil;
047import lucee.runtime.type.util.KeyConstants;
048
049public class Mapping {
050
051        private static final ResourceFilter FILTER = new AndResourceFilter(new ResourceFilter[]{
052                        new ExtensionResourceFilter(Constants.CFC_EXTENSION),
053                        new ResourceFilter() {
054                                
055                                public boolean accept(Resource res) {
056                                        return !Constants.APP_CFC.equalsIgnoreCase(res.getName());
057                                }
058                        }
059        });
060
061
062        
063        private String virtual;
064        private Resource physical;
065        private String strPhysical;
066        private boolean hidden;
067        private boolean readonly;
068        private boolean _default;
069
070
071        private List<Source> baseSources;
072        private Map<Resource,List<Source>> customSources=new HashMap<Resource, List<Source>>();
073
074        public Mapping(Config config, String virtual, String physical, boolean hidden, boolean readonly, boolean _default) {
075                if(!virtual.startsWith("/"))this.virtual="/"+virtual;
076                if(virtual.endsWith("/"))this.virtual=virtual.substring(0,virtual.length()-1);
077        else this.virtual=virtual;
078        
079                this.strPhysical=physical;
080                this.hidden=hidden;
081                this.readonly=readonly;
082                this._default=_default;
083                
084
085        if(!(config instanceof ConfigWebImpl)) return;
086        ConfigWebImpl cw=(ConfigWebImpl) config;
087                
088                this.physical=ConfigWebUtil.getExistingResource(cw.getServletContext(),physical,null,cw.getConfigDir(),FileUtil.TYPE_DIR,cw);
089                
090        }
091
092
093        private List<Source> init(PageContext pc, boolean reset) throws PageException {
094                if(reset)release();
095                
096                Resource[] locations = pc.getApplicationContext().getRestCFCLocations();
097                
098                // base source
099                if(ArrayUtil.isEmpty(locations)) {
100                        if(baseSources==null && this.physical!=null && this.physical.isDirectory()) {
101                                baseSources=_init(pc,this, this.physical);
102                        }
103                        return baseSources;
104                }
105                
106                // custom sources
107                List<Source> rtn = new ArrayList<Source>(); 
108                List<Source> list;
109                for(int i=0;i<locations.length;i++){
110                        list = customSources.get(locations[i]);
111                        if(list==null && locations[i].isDirectory()) {
112                                list=_init(pc,this, locations[i]);
113                                customSources.put(locations[i], list);
114                        }
115                        copy(list,rtn);
116                }
117                return rtn;
118        }
119        
120        private void copy(List<Source> src, List<Source> trg) { 
121                if(src==null) return;
122                Iterator<Source> it = src.iterator();
123                while(it.hasNext()){
124                        trg.add(it.next());
125                }
126        }
127
128
129        private static ArrayList<Source> _init(PageContext pc, Mapping mapping, Resource dir) throws PageException{
130                Resource[] children = dir.listResources(FILTER);
131                
132                RestSettings settings = pc.getApplicationContext().getRestSettings();
133                ArrayList<Source> sources = new ArrayList<Source>(); 
134        
135                PageSource ps;
136                Component cfc;
137                Struct meta;
138                String path;
139                for(int i=0;i<children.length;i++){
140                        try{
141                                ps = pc.toPageSource(children[i],null);
142                                cfc = ComponentLoader.loadComponent(pc, null, ps, children[i].getName(), true,true);
143                                meta = cfc.getMetaData(pc);
144                                if(Caster.toBooleanValue(meta.get(KeyConstants._rest,null),false)){
145                                        path = Caster.toString(meta.get(KeyConstants._restPath,null),null);
146                                        sources.add(new Source(mapping, cfc.getPageSource(), path));
147                                }
148                        }
149                        catch(Throwable t){
150                                ExceptionUtil.rethrowIfNecessary(t);
151                                if(!settings.getSkipCFCWithError()) throw Caster.toPageException(t);
152                        }
153                }
154                return sources;
155        }
156
157
158        public lucee.runtime.rest.Mapping duplicate(Config config,Boolean readOnly) {
159                return new Mapping(config, virtual, strPhysical, hidden, readOnly==null?this.readonly:readOnly.booleanValue(),_default); 
160        }
161        
162        /**
163         * @return the physical
164         */
165        public Resource getPhysical() {
166                return physical;
167        }
168
169
170        /**
171         * @return the virtual
172         */
173        public String getVirtual() {
174                return virtual;
175        }
176        public String getVirtualWithSlash() {
177                return virtual+"/";
178        }
179
180
181        /**
182         * @return the strPhysical
183         */
184        public String getStrPhysical() {
185                return strPhysical;
186        }
187
188
189        /**
190         * @return the hidden
191         */
192        public boolean isHidden() {
193                return hidden;
194        }
195
196
197        /**
198         * @return the readonly
199         */
200        public boolean isReadonly() {
201                return readonly;
202        }
203
204        public boolean isDefault() {
205                return _default;
206        }
207
208
209        public Result getResult(PageContext pc,String path,Struct matrix,int format,boolean hasFormatExtension, List<MimeType> accept, MimeType contentType,Result defaultValue) throws PageException {
210                List<Source> sources = init(pc,false);
211                Iterator<Source> it = sources.iterator();
212                Source src;
213                String[] arrPath,subPath;
214                int index;
215                while(it.hasNext()) {
216                        src = it.next();
217                        Struct variables=new StructImpl();
218                        arrPath = RestUtil.splitPath(path);
219                        index=RestUtil.matchPath(variables,src.getPath(),arrPath);
220                        if(index!=-1){
221                subPath=new String[(arrPath.length-1)-index];
222                System.arraycopy(arrPath, index+1, subPath, 0, subPath.length);
223                                return new Result(src,variables,subPath,matrix,format,hasFormatExtension,accept,contentType);
224                        }       
225                }
226                
227                return defaultValue;
228        }
229
230
231        public void setDefault(boolean _default) {
232                this._default=_default;
233        }
234
235
236        public void reset(PageContext pc) throws PageException {
237                init(pc, true);
238        }
239
240
241        public synchronized void release() {
242                if(baseSources!=null) {
243                        baseSources.clear();
244                        baseSources = null; 
245                }
246                customSources.clear();
247        }
248}