001    package railo.runtime.tag;
002    
003    import java.io.IOException;
004    import java.net.MalformedURLException;
005    import java.net.URL;
006    
007    import railo.commons.io.res.Resource;
008    import railo.commons.io.res.util.ResourceUtil;
009    import railo.commons.lang.StringUtil;
010    import railo.runtime.exp.ApplicationException;
011    import railo.runtime.exp.ExpressionException;
012    import railo.runtime.exp.PageException;
013    import railo.runtime.exp.SecurityException;
014    import railo.runtime.ext.tag.TagImpl;
015    import railo.runtime.op.Caster;
016    import railo.runtime.search.IndexResult;
017    import railo.runtime.search.SearchCollection;
018    import railo.runtime.search.SearchCollectionSupport;
019    import railo.runtime.search.SearchException;
020    import railo.runtime.search.SearchIndex;
021    import railo.runtime.search.lucene2.LuceneSearchCollection;
022    import railo.runtime.tag.util.DeprecatedUtil;
023    import railo.runtime.type.List;
024    import railo.runtime.type.Struct;
025    import railo.runtime.type.StructImpl;
026    
027    /**
028    * Populates collections with indexed data.
029    **/
030    public final class Index extends TagImpl {
031        
032    
033        private static final String[] EMPTY = new String[0];
034    
035    
036            public static String[] EXTENSIONS=new String[]{"htm","html","cfm","cfml","dbm","dbml"};
037        
038    
039            /** Specifies the index action. */
040            private String action;
041    
042            /** Specifies the URL path for files if type = "file" and type = "path". When the collection is 
043            **      searched with cfsearch, the pathname is automatically be prepended to filenames and returned as 
044            **      the url attribute. */
045            private String urlpath;
046    
047            /** Specifies the type of entity being indexed. Default is CUSTOM. */
048            private short type=-1;
049    
050            /** Title for collection;
051            ** Query column name for type and a valid query name;
052            ** Permits searching collections by title or displaying a separate title from the key */
053            private String title;
054    
055            private String language;
056    
057    
058            /** Specifies the comma-separated list of file extensions that CFML uses to index files if 
059            **      type = "Path". Default is HTM, HTML, CFM, CFML, DBM, DBML.
060            **      An entry of "*." returns files with no extension */
061            private String[] extensions=EXTENSIONS;
062    
063            /**  */
064            private String key;
065    
066        /** A custom field you can use to store data during an indexing operation. Specify a query column 
067        **  name for type and a query name. */
068            private String custom1;
069            private long timeout=10000;
070    
071        /** A custom field you can use to store data during an indexing operation. Usage is the same as 
072        **  for custom1. */
073        private String custom2;
074    
075        /** A custom field you can use to store data during an indexing operation. Usage is the same as 
076        **  for custom1. */
077        private String custom3;
078    
079        /** A custom field you can use to store data during an indexing operation. Usage is the same as 
080        **  for custom1. */
081        private String custom4;
082    
083            /** Specifies the name of the query against which the collection is generated. */
084            private String query;
085    
086            /** Specifies a collection name. If you are indexing an external collection external = "Yes", 
087            **      specify the collection name, including fully qualified path. */
088            private SearchCollection collection;
089    
090            /** Yes or No. Yes specifies, if type = "Path", that directories below the path specified in 
091            **      key are included in the indexing operation. */
092            private boolean recurse;
093    
094            /**  */
095            private String body;
096            private String name;
097            
098    
099            private String[] category=EMPTY;
100            private String categoryTree="";
101            private String status;
102            private String prefix;
103    
104    
105            private boolean throwontimeout=false;
106    
107            /**
108            * @see javax.servlet.jsp.tagext.Tag#release()
109            */
110            public void release()   {
111                    super.release();
112                    action=null;
113                    urlpath=null;
114                    type=-1;
115                    title=null;
116                    language=null;
117                    extensions=EXTENSIONS;
118                    key=null;
119                    
120                    custom1=null;
121                    custom2=null;
122                    custom3=null;
123                    custom4=null;
124                    
125                    query=null;
126                    collection=null;
127                    recurse=false;
128                    body=null;
129                    name=null;
130                    
131                    category=EMPTY;
132                    categoryTree="";
133                    status=null;
134                    prefix=null;
135                    timeout=10000;
136                    throwontimeout=false;
137            }
138    
139    
140            /** set the value action
141            *  Specifies the index action.
142            * @param action value to set
143            **/
144            public void setAction(String action)    {
145                    this.action=action.toLowerCase().trim();
146            }
147    
148            /** set the value urlpath
149            *  Specifies the URL path for files if type = "file" and type = "path". When the collection is 
150            *       searched with cfsearch, the pathname is automatically be prepended to filenames and returned as 
151            *       the url attribute.
152            * @param urlpath value to set
153            **/
154            public void setUrlpath(String urlpath)  {
155                    if(StringUtil.isEmpty(urlpath))return;
156                    this.urlpath=urlpath.toLowerCase().trim();
157            }
158    
159            /** set the value type
160            *  Specifies the type of entity being indexed. Default is CUSTOM.
161            * @param type value to set
162             * @throws PageException
163            **/
164            public void setType(String type) throws PageException   {
165                    if(type==null)return;
166                try {
167                this.type=SearchIndex.toType(type);
168            } 
169                catch (SearchException e) {
170                throw Caster.toPageException(e);
171            }
172            }
173            
174            /**
175             * @param timeout the timeout in seconds
176             * @throws ApplicationException 
177             */
178            public void setTimeout(double timeout) throws ApplicationException {
179                    
180                    this.timeout = (long)(timeout*1000D);
181                    if(this.timeout<0)
182                            throw new ApplicationException("attribute timeout must contain a positive number");
183                    if(timeout==0)timeout=1;
184            }
185            
186            /** set the value throwontimeout
187            *  Yes or No. Specifies how   timeout conditions are handled. If the value is Yes, an exception is 
188            *       generated to provide notification of the timeout. If the value is No, execution continues. Default is Yes.
189            * @param throwontimeout value to set
190            **/
191            public void setThrowontimeout(boolean throwontimeout) {
192                    this.throwontimeout = throwontimeout;
193            }
194    
195            
196            
197            public void setName(String name){
198                    this.name=name;
199            }
200    
201            /** set the value title
202            *  Title for collection;
203            * Query column name for type and a valid query name;
204            * Permits searching collections by title or displaying a separate title from the key
205            * @param title value to set
206            **/
207            public void setTitle(String title)      {
208                    this.title=title;
209            }
210    
211            /** set the value custom1
212            *  A custom field you can use to store data during an indexing operation. Specify a query column 
213            *       name for type and a query name.
214            * @param custom1 value to set
215            **/
216            public void setCustom1(String custom1)  {
217                    this.custom1=custom1;
218            }
219    
220            /** set the value language
221            * @param language value to set
222            **/
223            public void setLanguage(String language)        {
224                    if(StringUtil.isEmpty(language)) return;
225                    this.language=Collection.validateLanguage(language);
226            }
227    
228            /** set the value external
229            * @param external value to set
230             * @throws ApplicationException
231            **/
232            public void setExternal(boolean external) {
233                    DeprecatedUtil.tagAttribute(pageContext,"Index", "external");
234            }
235    
236            /** set the value extensions
237            * @param extensions value to set
238             * @throws PageException
239            **/
240            public void setExtensions(String extensions) throws PageException       {
241                    if(extensions==null) return;
242                    this.extensions=List.toStringArrayTrim(List.listToArray(extensions,','));
243            }
244    
245            /** set the value key
246            *  
247            * @param key value to set
248            **/
249            public void setKey(String key)  {
250                    this.key=key;
251            }
252    
253            /** set the value custom2
254            *  A custom field you can use to store data during an indexing operation. Usage is the same as 
255            *       for custom1.
256            * @param custom2 value to set
257            **/
258            public void setCustom2(String custom2)  {
259                    this.custom2=custom2;
260            }
261        
262        /**
263         * @param custom3 The custom3 to set.
264         */
265        public void setCustom3(String custom3) {
266            this.custom3 = custom3;
267        }
268    
269    
270        /**
271         * @param custom4 The custom4 to set.
272         */
273        public void setCustom4(String custom4) {
274            this.custom4 = custom4;
275        }
276    
277            /** set the value query
278            *  Specifies the name of the query against which the collection is generated.
279            * @param query value to set
280            **/
281            public void setQuery(String query)      {
282                    this.query=query;
283            }
284    
285            /** set the value collection
286            *  Specifies a collection name. If you are indexing an external collection external = "Yes", 
287            *       specify the collection name, including fully qualified path.
288            * @param collection value to set
289             * @throws PageException
290            **/
291            public void setCollection(String collection) throws PageException       {
292                    try {
293                        this.collection=pageContext.getConfig().getSearchEngine().getCollectionByName(collection.toLowerCase().trim());
294                }
295                    catch (SearchException e) {
296                throw Caster.toPageException(e);
297            }
298                      
299            }
300    
301            /** set the value recurse
302            *  Yes or No. Yes specifies, if type = "Path", that directories below the path specified in 
303            *       key are included in the indexing operation.
304            * @param recurse value to set
305            **/
306            public void setRecurse(boolean recurse) {
307                    this.recurse=recurse;
308            }
309    
310            /** set the value body
311            *  
312            * @param body value to set
313            **/
314            public void setBody(String body)        {
315                    this.body=body;
316            }
317            
318            /**
319             * @param category the category to set
320             * @throws ApplicationException 
321             */
322            public void setCategory(String listCategories)  {
323                    if(listCategories==null) return;
324                    this.category = List.trimItems(List.listToStringArray(listCategories, ','));
325            }
326    
327    
328            /**
329             * @param categoryTree the categoryTree to set
330             * @throws ApplicationException 
331             */
332            public void setCategorytree(String categoryTree) {
333                    if(categoryTree==null) return;
334                    categoryTree=categoryTree.replace('\\', '/').trim();
335                    if(StringUtil.startsWith(categoryTree, '/'))categoryTree=categoryTree.substring(1);
336                    if(!StringUtil.endsWith(categoryTree, '/') && categoryTree.length()>0)categoryTree+="/";
337                    this.categoryTree = categoryTree;
338            }
339    
340    
341            /**
342             * @param prefix the prefix to set
343             */
344            public void setPrefix(String prefix) {
345                    this.prefix = prefix;
346            }
347    
348    
349            /**
350             * @param status the status to set
351             * @throws ApplicationException 
352             */
353            public void setStatus(String status) {
354                    this.status = status;
355            }
356    
357    
358            /**
359            * @throws SecurityException
360             * @see javax.servlet.jsp.tagext.Tag#doStartTag()
361            */
362            public int doStartTag() throws PageException    {
363                // SerialNumber sn = pageContext.getConfig().getSerialNumber();
364                //if(sn.getVersion()==SerialNumber.VERSION_COMMUNITY)
365                //    throw new SecurityException("no access to this functionality with the "+sn.getStringVersion()+" version of railo");
366                
367                try {
368                        if(action.equals("purge")) doPurge();
369                        else if(action.equals("update")) doUpdate();
370                            else if(action.equals("delete")) doDelete();
371                            else if(action.equals("refresh")) doRefresh();
372                            else if(action.equals("list")) doList();
373    
374                            else throw new ApplicationException("invalid action name [" + action + "]","valid action names are [list,update, delete, purge, refresh]");
375                    } catch (Exception e) {
376                            throw Caster.toPageException(e);
377                    } 
378                    return SKIP_BODY;
379            }
380    
381            
382            /**
383             * @throws PageException
384             * @throws SearchException
385             * @throws IOException
386         * 
387         */
388        private void doRefresh() throws PageException, SearchException, IOException {
389            doPurge();
390            doUpdate();
391        }
392        
393        private void doList() throws ApplicationException, PageException {
394                    required("index",action,"name",name);
395                    pageContext.setVariable(name,((SearchCollectionSupport)collection).getIndexesAsQuery());
396            }
397    
398    
399        /**
400         * delete a collection
401             * @throws PageException
402             * @throws SearchException
403         */
404        private void doDelete() throws PageException, SearchException {
405            required("index",action,"collection",collection);
406            if(type!=SearchIndex.TYPE_CUSTOM)required("index",action,"key",key);
407            
408            // no type defined
409            if(type==-1) {
410                if(query!=null) {
411                    type=SearchIndex.TYPE_CUSTOM;
412                }
413                else {
414                    Resource file=null;
415                    try {
416                        file=ResourceUtil.toResourceExisting(pageContext,key);
417                        pageContext.getConfig().getSecurityManager().checkFileLocation(file);
418                    } 
419                    catch (ExpressionException e) {}
420    
421                    
422                    if(file!=null && file.exists() && file.isFile()) type=SearchIndex.TYPE_FILE;
423                    else if(file!=null && file.exists() && file.isDirectory()) type=SearchIndex.TYPE_PATH;
424                    else {
425                        try {
426                            new URL(key);
427                            type=SearchIndex.TYPE_URL;
428                        } catch (MalformedURLException e) {}
429                    }
430                }
431            }
432            
433            collection.deleteIndex(pageContext,key,type,query);   
434        }
435    
436        /**
437         * purge a collection
438         * @throws PageException
439         * @throws SearchException
440         */
441        private void doPurge() throws PageException, SearchException {
442            required("index",action,"collection",collection);
443            collection.purge();
444        }
445    
446        /**
447         * update a collection
448         * @throws PageException
449         * @throws SearchException
450         * @throws IOException
451         */
452        private void doUpdate() throws PageException, SearchException, IOException {
453            // check attributes
454            required("index",action,"collection",collection);
455            required("index",action,"key",key);
456            
457            if(type==-1) type=(query==null)?SearchIndex.TYPE_FILE:SearchIndex.TYPE_CUSTOM;
458            
459            if(type==SearchIndex.TYPE_CUSTOM) {
460                required("index",action,"body",body);
461                //required("index",action,"query",query);
462            }
463            IndexResult result;
464            
465            // FUTURE remove this condition
466            if(collection instanceof LuceneSearchCollection)
467                    result = ((LuceneSearchCollection)collection).index(pageContext,key,type,urlpath,title,body,language,extensions,query,recurse,categoryTree,category,timeout,custom1,custom2,custom3,custom4);
468            else
469                    result = collection.index(pageContext,key,type,urlpath,title,body,language,extensions,query,recurse,categoryTree,category,custom1,custom2,custom3,custom4);
470             if(!StringUtil.isEmpty(status))pageContext.setVariable(status,toStruct(result));
471        }
472    
473    
474            /**
475            * @see javax.servlet.jsp.tagext.Tag#doEndTag()
476            */
477            public int doEndTag()   {
478                    return EVAL_PAGE;
479            }
480    
481            private Struct toStruct(IndexResult result) {
482                    Struct sct=new StructImpl();
483                    sct.setEL("deleted",new Double(result.getCountDeleted()));
484                    sct.setEL("inserted",new Double(result.getCountInserted()));
485                    sct.setEL("updated",new Double(result.getCountUpdated()));
486                    return sct;
487            }
488    
489    }