001    package railo.runtime.tag;
002    
003    import java.util.Iterator;
004    import java.util.Map;
005    
006    import railo.commons.lang.StringUtil;
007    import railo.runtime.exp.ApplicationException;
008    import railo.runtime.exp.PageException;
009    import railo.runtime.ext.tag.TagImpl;
010    import railo.runtime.op.Caster;
011    import railo.runtime.op.Decision;
012    import railo.runtime.search.AddionalAttrs;
013    import railo.runtime.search.SearchCollection;
014    import railo.runtime.search.SearchData;
015    import railo.runtime.search.SearchDataImpl;
016    import railo.runtime.search.SearchEngine;
017    import railo.runtime.search.SearchException;
018    import railo.runtime.search.SuggestionItem;
019    import railo.runtime.tag.util.DeprecatedUtil;
020    import railo.runtime.type.KeyImpl;
021    import railo.runtime.type.QueryImpl;
022    import railo.runtime.type.Struct;
023    import railo.runtime.type.StructImpl;
024    import railo.runtime.type.util.KeyConstants;
025    import railo.runtime.type.util.ListUtil;
026    
027    public final class Search extends TagImpl {
028    
029            private static final String[] EMPTY = new String[0];
030    
031            private static final int SUGGESTIONS_ALWAYS = Integer.MAX_VALUE;
032            private static final int SUGGESTIONS_NEVER = -1;
033    
034            private static final railo.runtime.type.Collection.Key FOUND = KeyImpl.intern("found");
035            private static final railo.runtime.type.Collection.Key SEARCHED = KeyImpl.intern("searched");
036            private static final railo.runtime.type.Collection.Key KEYWORDS = KeyImpl.intern("keywords");
037            private static final railo.runtime.type.Collection.Key KEYWORD_SCORE = KeyImpl.intern("keywordScore");
038            
039            /** Specifies the criteria type for the search. */
040            private short type=SearchCollection.SEARCH_TYPE_SIMPLE;
041    
042            /** Specifies the maximum number of entries for index queries. If omitted, all rows are returned. */
043            private int maxrows=-1;
044    
045            /** Specifies the criteria for the search following the syntactic rules specified by type. */
046            private String criteria="";
047    
048            /** Specifies the first row number to be retrieved. Default is 1. */
049            private int startrow=1;
050    
051            /** The logical collection name that is the target of the search operation or an external collection 
052            **              with fully qualified path. */
053            private SearchCollection[] collections;
054    
055            /** A name for the search query. */
056            private String name;
057    
058            private String[] category=EMPTY;
059            private String categoryTree="";
060            private String status;
061            private int suggestions=SUGGESTIONS_NEVER;
062            private int contextPassages=0;
063            private int contextBytes=300;
064            private String contextHighlightBegin="<b>";
065            private String contextHighlightEnd="</b>";
066            private String previousCriteria;
067    
068            //private int spellCheckMaxLevel=10;
069            //private String result=null;
070            
071            @Override
072            public void release()   {
073                    super.release();
074                    type=SearchCollection.SEARCH_TYPE_SIMPLE;
075                    maxrows=-1;
076                    criteria="";
077                    startrow=1;
078                    collections=null;
079                    
080                    category=EMPTY;
081                    categoryTree="";
082                    status=null;
083                    suggestions=SUGGESTIONS_NEVER;
084                    contextPassages=0;
085                    contextBytes=300;
086                    contextHighlightBegin="<b>";
087                    contextHighlightEnd="</b>";
088                    previousCriteria=null;
089                    
090    
091                    //spellCheckMaxLevel=10;
092                    //result=null;
093                    
094            }
095    
096            /** set the value type
097            *  Specifies the criteria type for the search.
098            * @param type value to set
099             * @throws ApplicationException
100            **/
101            public void setType(String type) throws ApplicationException    {
102                    if(type==null) return;
103                type=type.toLowerCase().trim();
104                if(type.equals("simple"))this.type=SearchCollection.SEARCH_TYPE_SIMPLE;
105                else if(type.equals("explicit"))this.type=SearchCollection.SEARCH_TYPE_EXPLICIT;
106                else 
107                    throw new ApplicationException("attribute type of tag search has an invalid value, valid values are [simple,explicit] now is ["+type+"]");
108    
109            }
110    
111            /** set the value maxrows
112            *  Specifies the maximum number of entries for index queries. If omitted, all rows are returned.
113            * @param maxrows value to set
114            **/
115            public void setMaxrows(double maxrows)  {
116                    this.maxrows=(int) maxrows;
117            }
118    
119            /** set the value criteria
120            *  Specifies the criteria for the search following the syntactic rules specified by type.
121            * @param criteria value to set
122            **/
123            public void setCriteria(String criteria)        {
124                    this.criteria=criteria;
125            }
126    
127            /** set the value startrow
128            *  Specifies the first row number to be retrieved. Default is 1.
129            * @param startrow value to set
130            **/
131            public void setStartrow(double startrow)        {
132                    this.startrow=(int) startrow;
133            }
134    
135            /** set the value collection
136            *  The logical collection name that is the target of the search operation or an external collection 
137            *               with fully qualified path.
138            * @param collection value to set
139             * @throws PageException
140            **/
141            public void setCollection(String collection) throws PageException       {
142                    String[] collNames=ListUtil.toStringArrayTrim(ListUtil.listToArrayRemoveEmpty(collection,','));
143                collections=new SearchCollection[collNames.length];
144                SearchEngine se = pageContext.getConfig().getSearchEngine();
145                try { 
146                        for(int i=0;i<collections.length;i++) {
147                            collections[i]=se.getCollectionByName(collNames[i]);
148                        }
149                } catch (SearchException e) {
150                    collections=null;
151                    throw Caster.toPageException(e);
152                }
153            }
154    
155            /** set the value language
156            * @param language value to set
157            **/
158            public void setLanguage(String language)        {
159                    DeprecatedUtil.tagAttribute(pageContext,"Search", "language");
160            }
161    
162            /** set the value external
163            * @param external value to set
164             * @throws ApplicationException
165            **/
166            public void setExternal(boolean external) throws ApplicationException   {
167                    DeprecatedUtil.tagAttribute(pageContext,"Search", "external");
168            }
169    
170            /** set the value name
171            *  A name for the search query.
172            * @param name value to set
173            **/
174            public void setName(String name)        {
175                    this.name=name;
176            }  
177            
178            
179    
180            /**
181             * @param category the category to set
182             * @throws ApplicationException 
183             */
184            public void setCategory(String listCategories)  {
185                    if(StringUtil.isEmpty(listCategories)) return;
186                    this.category = ListUtil.trimItems(ListUtil.listToStringArray(listCategories, ','));
187            }
188    
189    
190            /**
191             * @param categoryTree the categoryTree to set
192             * @throws ApplicationException 
193             */
194            public void setCategorytree(String categoryTree) {
195                    if(StringUtil.isEmpty(categoryTree)) return;
196                    categoryTree=categoryTree.replace('\\', '/').trim();
197                    if(StringUtil.startsWith(categoryTree, '/'))categoryTree=categoryTree.substring(1);
198                    if(!StringUtil.endsWith(categoryTree, '/') && categoryTree.length()>0)categoryTree+="/";
199                    this.categoryTree = categoryTree;
200            }
201    
202            /**
203             * @param contextBytes the contextBytes to set
204             * @throws ApplicationException 
205             */
206            public void setContextbytes(double contextBytes) throws ApplicationException {
207                    this.contextBytes = (int)contextBytes;
208            }
209    
210            /**
211             * @param contextHighlightBegin the contextHighlightBegin to set
212             * @throws ApplicationException 
213             */
214            public void setContexthighlightbegin(String contextHighlightBegin) {
215                    this.contextHighlightBegin = contextHighlightBegin;
216            }
217    
218            /**
219             * @param contextHighlightEnd the contextHighlightEnd to set
220             * @throws ApplicationException 
221             */
222            public void setContexthighlightend(String contextHighlightEnd) {
223                    this.contextHighlightEnd = contextHighlightEnd;
224            }
225    
226            /**
227             * @param contextPassages the contextPassages to set
228             * @throws ApplicationException 
229             */
230            public void setContextpassages(double contextPassages) throws ApplicationException {
231                    this.contextPassages = (int)contextPassages;
232            }
233            
234            
235            
236    
237            /**
238             * @param previousCriteria the previousCriteria to set
239             * @throws ApplicationException 
240             */
241            public void setPreviouscriteria(String previousCriteria) throws ApplicationException {
242                    this.previousCriteria = previousCriteria;
243                    throw new ApplicationException("attribute previousCriteria for tag search is not supported yet");
244                    // TODO impl tag attribute
245            }
246    
247            /**
248             * @param status the status to set
249             * @throws ApplicationException 
250             */
251            public void setStatus(String status) {
252                    if(!StringUtil.isEmpty(status))this.status = status;
253            }
254    
255            /**
256             * @param suggestions the suggestions to set
257             * @throws ApplicationException 
258             */
259            public void setSuggestions(String suggestions) throws PageException {
260                    if(StringUtil.isEmpty(suggestions))return;
261                    suggestions = suggestions.trim().toLowerCase();
262                    if("always".equals(suggestions)) this.suggestions=SUGGESTIONS_ALWAYS;
263                    else if("never".equals(suggestions)) this.suggestions=SUGGESTIONS_NEVER;
264                    else if(Decision.isNumeric(suggestions)) {
265                            this.suggestions=Caster.toIntValue(suggestions);
266                    }
267                    else    
268                            throw new ApplicationException("attribute suggestions has an invalid value ["+suggestions+"], valid values are [always,never,<positive numeric value>]");
269                    
270                    
271            }
272    
273            @Override
274            public int doStartTag() throws PageException    {
275                //SerialNumber sn = pageContext.getConfig().getSerialNumber();
276                //if(sn.getVersion()==SerialNumber.VERSION_COMMUNITY)
277                //    throw new SecurityException("no access to this functionality with the "+sn.getStringVersion()+" version of railo");
278            final String v="VARCHAR",d="DOUBLE";
279            String[] cols = new String[]{"title","url","summary","score","recordssearched","key","custom1","custom2","custom3","custom4",
280                                                                    "categoryTree","category","context","size","rank","author","type","collection"};
281            
282            // TODO support context
283            String[] types = new String[]{v,v,v,d,d,v,v,v,v,v,v,v,v,d,d,v,v,v};
284            SearchData data=new SearchDataImpl(suggestions);
285            SuggestionItem item=null;// this is already here to make sure the classloader load this sinstance 
286            
287            
288            railo.runtime.type.Query qry=new QueryImpl(cols,types,0,"query");
289                    
290                SearchCollection collection;
291                long time=System.currentTimeMillis();
292                AddionalAttrs.setAddionalAttrs(contextBytes,contextPassages,contextHighlightBegin,contextHighlightEnd);
293                try {
294                        for(int i=0;i<collections.length;i++) {
295                            collection=collections[i];
296                            startrow=collection.search(data,qry,criteria,collection.getLanguage(),type,startrow,maxrows,categoryTree,category); 
297                            
298                            if(maxrows>=0 && qry.getRecordcount()>=maxrows) break;
299                        }
300                        pageContext.setVariable(name,qry);
301                }
302                catch(SearchException se) {
303                    throw Caster.toPageException(se);
304                }
305                finally{
306                    AddionalAttrs.removeAddionalAttrs();
307                }
308                
309                time=System.currentTimeMillis()-time;
310                Double recSearched=new Double(data.getRecordsSearched());
311                int len=qry.getRecordcount();
312                for(int i=1;i<=len;i++) {
313                    qry.setAt("recordssearched",i,recSearched);
314                }
315                
316                // status
317                if(status!=null) {
318                    Struct sct=new StructImpl();
319                    pageContext.setVariable(status, sct);
320                    sct.set(FOUND, new Double(qry.getRecordcount()));
321                    sct.set(SEARCHED, recSearched);
322                    sct.set(KeyConstants._time, new Double(time));
323                    
324                    // TODO impl this values
325                    
326                    Map s = data.getSuggestion();
327                    if(s.size()>0) {
328                            String key;
329                            
330                            Iterator it = s.keySet().iterator();
331                            Struct keywords=new StructImpl();
332                            Struct keywordScore=new StructImpl();
333                            sct.set(KEYWORDS, keywords);
334                            sct.set(KEYWORD_SCORE, keywordScore);
335                            Object obj;
336                            
337                            while(it.hasNext()) {
338                                    key=(String) it.next();
339                                    // FUTURE add SuggestionItem as interface to public interface
340                                    
341                                    // the problem is a conflict between the SuggestionItem version from core and extension
342                                    obj=s.get(key);
343                                    if(obj instanceof SuggestionItem){
344                                            item=(SuggestionItem)obj;
345                                            keywords.set(key, item.getKeywords());
346                                            keywordScore.set(key, item.getKeywordScore());
347                                    }
348                                    else {
349                                            Class clazz = obj.getClass();
350                                            try {
351                                                            keywords.set(key, clazz.getMethod("getKeywords", new Class[0]).invoke(obj, new Object[0]));
352                                                            keywordScore.set(key, clazz.getMethod("getKeywordScore", new Class[0]).invoke(obj, new Object[0]));
353                                                    } 
354                                            catch (Exception e) {}
355                                    }
356                                    
357                                    
358                                    
359                            }
360                            
361                            
362                            String query = data.getSuggestionQuery();
363                            if(query!=null) {
364                                    String html = StringUtil.replace(query, "<suggestion>", "<b>", false);
365                                    html = StringUtil.replace(html, "</suggestion>", "</b>", false);
366                                    sct.set("suggestedQueryHTML", html);
367                                    
368                                    String plain = StringUtil.replace(query, "<suggestion>", "", false);
369                                    plain = StringUtil.replace(plain, "</suggestion>", "", false);
370                                    sct.set("suggestedQuery", plain);
371                            }
372                            
373                            
374                    }
375                    
376                    //if(suggestions!=SUGGESTIONS_NEVER)sct.set("suggestedQuery", "");
377                    //sct.set("keywords", "");
378                    //sct.set("keywordScore", "");
379                    
380                    
381                }
382                
383                
384                    return SKIP_BODY;
385            }
386    
387            @Override
388            public int doEndTag()   {
389                    return EVAL_PAGE;
390            }
391    
392            
393    }