package railo.runtime.tag; import java.util.Iterator; import java.util.Map; import railo.commons.lang.StringUtil; import railo.runtime.exp.ApplicationException; import railo.runtime.exp.PageException; import railo.runtime.ext.tag.TagImpl; import railo.runtime.op.Caster; import railo.runtime.op.Decision; import railo.runtime.search.AddionalAttrs; import railo.runtime.search.SearchCollection; import railo.runtime.search.SearchData; import railo.runtime.search.SearchDataImpl; import railo.runtime.search.SearchEngine; import railo.runtime.search.SearchException; import railo.runtime.search.SuggestionItem; import railo.runtime.tag.util.DeprecatedUtil; import railo.runtime.type.KeyImpl; import railo.runtime.type.QueryImpl; import railo.runtime.type.Struct; import railo.runtime.type.StructImpl; import railo.runtime.type.util.KeyConstants; import railo.runtime.type.util.ListUtil; public final class Search extends TagImpl { private static final String[] EMPTY = new String[0]; private static final int SUGGESTIONS_ALWAYS = Integer.MAX_VALUE; private static final int SUGGESTIONS_NEVER = -1; private static final railo.runtime.type.Collection.Key FOUND = KeyImpl.intern("found"); private static final railo.runtime.type.Collection.Key SEARCHED = KeyImpl.intern("searched"); private static final railo.runtime.type.Collection.Key KEYWORDS = KeyImpl.intern("keywords"); private static final railo.runtime.type.Collection.Key KEYWORD_SCORE = KeyImpl.intern("keywordScore"); /** Specifies the criteria type for the search. */ private short type=SearchCollection.SEARCH_TYPE_SIMPLE; /** Specifies the maximum number of entries for index queries. If omitted, all rows are returned. */ private int maxrows=-1; /** Specifies the criteria for the search following the syntactic rules specified by type. */ private String criteria=""; /** Specifies the first row number to be retrieved. Default is 1. */ private int startrow=1; /** The logical collection name that is the target of the search operation or an external collection ** with fully qualified path. */ private SearchCollection[] collections; /** A name for the search query. */ private String name; private String[] category=EMPTY; private String categoryTree=""; private String status; private int suggestions=SUGGESTIONS_NEVER; private int contextPassages=0; private int contextBytes=300; private String contextHighlightBegin="<b>"; private String contextHighlightEnd="</b>"; private String previousCriteria; //private int spellCheckMaxLevel=10; //private String result=null; @Override public void release() { super.release(); type=SearchCollection.SEARCH_TYPE_SIMPLE; maxrows=-1; criteria=""; startrow=1; collections=null; category=EMPTY; categoryTree=""; status=null; suggestions=SUGGESTIONS_NEVER; contextPassages=0; contextBytes=300; contextHighlightBegin="<b>"; contextHighlightEnd="</b>"; previousCriteria=null; //spellCheckMaxLevel=10; //result=null; } /** set the value type * Specifies the criteria type for the search. * @param type value to set * @throws ApplicationException **/ public void setType(String type) throws ApplicationException { if(type==null) return; type=type.toLowerCase().trim(); if(type.equals("simple"))this.type=SearchCollection.SEARCH_TYPE_SIMPLE; else if(type.equals("explicit"))this.type=SearchCollection.SEARCH_TYPE_EXPLICIT; else throw new ApplicationException("attribute type of tag search has an invalid value, valid values are [simple,explicit] now is ["+type+"]"); } /** set the value maxrows * Specifies the maximum number of entries for index queries. If omitted, all rows are returned. * @param maxrows value to set **/ public void setMaxrows(double maxrows) { this.maxrows=(int) maxrows; } /** set the value criteria * Specifies the criteria for the search following the syntactic rules specified by type. * @param criteria value to set **/ public void setCriteria(String criteria) { this.criteria=criteria; } /** set the value startrow * Specifies the first row number to be retrieved. Default is 1. * @param startrow value to set **/ public void setStartrow(double startrow) { this.startrow=(int) startrow; } /** set the value collection * The logical collection name that is the target of the search operation or an external collection * with fully qualified path. * @param collection value to set * @throws PageException **/ public void setCollection(String collection) throws PageException { String[] collNames=ListUtil.toStringArrayTrim(ListUtil.listToArrayRemoveEmpty(collection,',')); collections=new SearchCollection[collNames.length]; SearchEngine se = pageContext.getConfig().getSearchEngine(); try { for(int i=0;i<collections.length;i++) { collections[i]=se.getCollectionByName(collNames[i]); } } catch (SearchException e) { collections=null; throw Caster.toPageException(e); } } /** set the value language * @param language value to set **/ public void setLanguage(String language) { DeprecatedUtil.tagAttribute(pageContext,"Search", "language"); } /** set the value external * @param external value to set * @throws ApplicationException **/ public void setExternal(boolean external) throws ApplicationException { DeprecatedUtil.tagAttribute(pageContext,"Search", "external"); } /** set the value name * A name for the search query. * @param name value to set **/ public void setName(String name) { this.name=name; } /** * @param category the category to set * @throws ApplicationException */ public void setCategory(String listCategories) { if(StringUtil.isEmpty(listCategories)) return; this.category = ListUtil.trimItems(ListUtil.listToStringArray(listCategories, ',')); } /** * @param categoryTree the categoryTree to set * @throws ApplicationException */ public void setCategorytree(String categoryTree) { if(StringUtil.isEmpty(categoryTree)) return; categoryTree=categoryTree.replace('\\', '/').trim(); if(StringUtil.startsWith(categoryTree, '/'))categoryTree=categoryTree.substring(1); if(!StringUtil.endsWith(categoryTree, '/') && categoryTree.length()>0)categoryTree+="/"; this.categoryTree = categoryTree; } /** * @param contextBytes the contextBytes to set * @throws ApplicationException */ public void setContextbytes(double contextBytes) throws ApplicationException { this.contextBytes = (int)contextBytes; } /** * @param contextHighlightBegin the contextHighlightBegin to set * @throws ApplicationException */ public void setContexthighlightbegin(String contextHighlightBegin) { this.contextHighlightBegin = contextHighlightBegin; } /** * @param contextHighlightEnd the contextHighlightEnd to set * @throws ApplicationException */ public void setContexthighlightend(String contextHighlightEnd) { this.contextHighlightEnd = contextHighlightEnd; } /** * @param contextPassages the contextPassages to set * @throws ApplicationException */ public void setContextpassages(double contextPassages) throws ApplicationException { this.contextPassages = (int)contextPassages; } /** * @param previousCriteria the previousCriteria to set * @throws ApplicationException */ public void setPreviouscriteria(String previousCriteria) throws ApplicationException { this.previousCriteria = previousCriteria; throw new ApplicationException("attribute previousCriteria for tag search is not supported yet"); // TODO impl tag attribute } /** * @param status the status to set * @throws ApplicationException */ public void setStatus(String status) { if(!StringUtil.isEmpty(status))this.status = status; } /** * @param suggestions the suggestions to set * @throws ApplicationException */ public void setSuggestions(String suggestions) throws PageException { if(StringUtil.isEmpty(suggestions))return; suggestions = suggestions.trim().toLowerCase(); if("always".equals(suggestions)) this.suggestions=SUGGESTIONS_ALWAYS; else if("never".equals(suggestions)) this.suggestions=SUGGESTIONS_NEVER; else if(Decision.isNumeric(suggestions)) { this.suggestions=Caster.toIntValue(suggestions); } else throw new ApplicationException("attribute suggestions has an invalid value ["+suggestions+"], valid values are [always,never,<positive numeric value>]"); } @Override public int doStartTag() throws PageException { //SerialNumber sn = pageContext.getConfig().getSerialNumber(); //if(sn.getVersion()==SerialNumber.VERSION_COMMUNITY) // throw new SecurityException("no access to this functionality with the "+sn.getStringVersion()+" version of railo"); final String v="VARCHAR",d="DOUBLE"; String[] cols = new String[]{"title","url","summary","score","recordssearched","key","custom1","custom2","custom3","custom4", "categoryTree","category","context","size","rank","author","type","collection"}; // TODO support context String[] types = new String[]{v,v,v,d,d,v,v,v,v,v,v,v,v,d,d,v,v,v}; SearchData data=new SearchDataImpl(suggestions); SuggestionItem item=null;// this is already here to make sure the classloader load this sinstance railo.runtime.type.Query qry=new QueryImpl(cols,types,0,"query"); SearchCollection collection; long time=System.currentTimeMillis(); AddionalAttrs.setAddionalAttrs(contextBytes,contextPassages,contextHighlightBegin,contextHighlightEnd); try { for(int i=0;i<collections.length;i++) { collection=collections[i]; startrow=collection.search(data,qry,criteria,collection.getLanguage(),type,startrow,maxrows,categoryTree,category); if(maxrows>=0 && qry.getRecordcount()>=maxrows) break; } pageContext.setVariable(name,qry); } catch(SearchException se) { throw Caster.toPageException(se); } finally{ AddionalAttrs.removeAddionalAttrs(); } time=System.currentTimeMillis()-time; Double recSearched=new Double(data.getRecordsSearched()); int len=qry.getRecordcount(); for(int i=1;i<=len;i++) { qry.setAt("recordssearched",i,recSearched); } // status if(status!=null) { Struct sct=new StructImpl(); pageContext.setVariable(status, sct); sct.set(FOUND, new Double(qry.getRecordcount())); sct.set(SEARCHED, recSearched); sct.set(KeyConstants._time, new Double(time)); // TODO impl this values Map s = data.getSuggestion(); if(s.size()>0) { String key; Iterator it = s.keySet().iterator(); Struct keywords=new StructImpl(); Struct keywordScore=new StructImpl(); sct.set(KEYWORDS, keywords); sct.set(KEYWORD_SCORE, keywordScore); Object obj; while(it.hasNext()) { key=(String) it.next(); // FUTURE add SuggestionItem as interface to public interface // the problem is a conflict between the SuggestionItem version from core and extension obj=s.get(key); if(obj instanceof SuggestionItem){ item=(SuggestionItem)obj; keywords.set(key, item.getKeywords()); keywordScore.set(key, item.getKeywordScore()); } else { Class clazz = obj.getClass(); try { keywords.set(key, clazz.getMethod("getKeywords", new Class[0]).invoke(obj, new Object[0])); keywordScore.set(key, clazz.getMethod("getKeywordScore", new Class[0]).invoke(obj, new Object[0])); } catch (Exception e) {} } } String query = data.getSuggestionQuery(); if(query!=null) { String html = StringUtil.replace(query, "<suggestion>", "<b>", false); html = StringUtil.replace(html, "</suggestion>", "</b>", false); sct.set("suggestedQueryHTML", html); String plain = StringUtil.replace(query, "<suggestion>", "", false); plain = StringUtil.replace(plain, "</suggestion>", "", false); sct.set("suggestedQuery", plain); } } //if(suggestions!=SUGGESTIONS_NEVER)sct.set("suggestedQuery", ""); //sct.set("keywords", ""); //sct.set("keywordScore", ""); } return SKIP_BODY; } @Override public int doEndTag() { return EVAL_PAGE; } }