001 package railo.runtime.search; 002 003 import java.io.IOException; 004 import java.io.InputStream; 005 import java.io.OutputStream; 006 007 import org.apache.xerces.parsers.DOMParser; 008 import org.apache.xml.serialize.OutputFormat; 009 import org.apache.xml.serialize.XMLSerializer; 010 import org.w3c.dom.Document; 011 import org.w3c.dom.Element; 012 import org.w3c.dom.Node; 013 import org.w3c.dom.NodeList; 014 import org.xml.sax.InputSource; 015 import org.xml.sax.SAXException; 016 017 import railo.commons.io.IOUtil; 018 import railo.commons.io.log.LogAndSource; 019 import railo.commons.io.res.Resource; 020 import railo.commons.io.res.ResourceProvider; 021 import railo.commons.io.res.ResourcesImpl; 022 import railo.commons.lang.StringUtil; 023 import railo.runtime.Info; 024 import railo.runtime.config.Config; 025 import railo.runtime.engine.ThreadLocalPageContext; 026 import railo.runtime.exp.DatabaseException; 027 import railo.runtime.exp.PageException; 028 import railo.runtime.op.date.DateCaster; 029 import railo.runtime.type.Collection; 030 import railo.runtime.type.KeyImpl; 031 import railo.runtime.type.List; 032 import railo.runtime.type.Query; 033 import railo.runtime.type.QueryImpl; 034 import railo.runtime.type.Struct; 035 import railo.runtime.type.StructImpl; 036 import railo.runtime.type.dt.DateTime; 037 import railo.runtime.type.dt.DateTimeImpl; 038 039 /** 040 * 041 */ 042 public abstract class SearchEngineSupport implements SearchEngine { 043 044 private Resource searchFile; 045 private Resource searchDir; 046 private LogAndSource log; 047 private Document doc; 048 Struct collections=new StructImpl(); 049 050 /** 051 * @see railo.runtime.search.SearchEngine#init(railo.runtime.config.Config, railo.commons.io.res.Resource, railo.commons.io.log.LogAndSource) 052 */ 053 public void init(railo.runtime.config.Config config,Resource searchDir, LogAndSource log) throws SAXException, IOException, SearchException { 054 this.searchDir=searchDir; 055 this.searchFile=searchDir.getRealResource("search.xml"); 056 if(!searchFile.exists()) createSearchFile(searchFile); 057 058 DOMParser parser = new DOMParser(); 059 InputStream is=null; 060 try { 061 is = IOUtil.toBufferedInputStream(searchFile.getInputStream()); 062 InputSource source = new InputSource(is); 063 parser.parse(source); 064 } 065 finally { 066 IOUtil.closeEL(is); 067 } 068 doc = parser.getDocument(); 069 070 this.log=log; 071 072 readCollections(config); 073 } 074 075 /** 076 * @see railo.runtime.search.SearchEngine#getCollectionByName(java.lang.String) 077 */ 078 public final SearchCollection getCollectionByName(String name) throws SearchException { 079 Object o=collections.get(name.toLowerCase(),null); 080 if(o!=null)return (SearchCollection) o; 081 throw new SearchException("collection "+name+" is undefined"); 082 } 083 084 /** 085 * @see railo.runtime.search.SearchEngine#getCollectionsAsQuery() 086 */ 087 public final Query getCollectionsAsQuery() { 088 final String v="VARCHAR"; 089 Query query=null; 090 String[] cols = new String[]{"external","language","mapped","name","online","path","registered","lastmodified","categories","charset","created", 091 "size","doccount"}; 092 String[] types = new String[]{"BOOLEAN",v,"BOOLEAN",v,"BOOLEAN",v,v,"DATE","BOOLEAN",v,"OBJECT","DOUBLE","DOUBLE"}; 093 try { 094 query=new QueryImpl(cols,types, collections.size(),"query"); 095 } catch (DatabaseException e) { 096 query=new QueryImpl(cols, collections.size(),"query"); 097 } 098 099 Collection.Key[] keys = collections.keys(); 100 101 for(int i=0;i<keys.length;i++) { 102 try { 103 SearchCollection coll = (SearchCollection) collections.get(keys[i]); 104 query.setAt("external",i+1,Boolean.FALSE); 105 query.setAt("charset",i+1,"UTF-8"); 106 query.setAt("created",i+1,coll.created()); 107 108 query.setAt("categories",i+1,Boolean.TRUE); 109 query.setAt("language",i+1,coll.getLanguage()); 110 query.setAt("mapped",i+1,Boolean.FALSE); 111 query.setAt("name",i+1,coll.getName()); 112 query.setAt("online",i+1,Boolean.TRUE); 113 query.setAt("path",i+1,coll.getPath().getAbsolutePath()); 114 query.setAt("registered",i+1,"CF"); 115 query.setAt("lastmodified",i+1,coll.getLastUpdate()); 116 query.setAt("size",i+1,new Double(coll.getSize())); 117 query.setAt("doccount",i+1,new Double(coll.getDocumentCount())); 118 } 119 catch(PageException pe) {} 120 } 121 return query; 122 } 123 124 /** 125 * 126 * @see railo.runtime.search.SearchEngine#createCollection(java.lang.String, railo.commons.io.res.Resource, java.lang.String, boolean) 127 */ 128 public final SearchCollection createCollection(String name,Resource path, String language, boolean allowOverwrite) throws SearchException { 129 SearchCollection coll = _createCollection(name,path,language); 130 coll.create(); 131 addCollection(coll,allowOverwrite); 132 return coll; 133 } 134 135 /** 136 * Creates a new Collection, will be invoked by createCollection 137 * @param name The Name of the Collection 138 * @param path the path to store 139 * @param language The language of the collection 140 * @return New SearchCollection 141 * @throws SearchException 142 */ 143 protected abstract SearchCollection _createCollection(String name,Resource path, String language) throws SearchException; 144 145 /** 146 * adds a new Collection to the storage 147 * @param collection 148 * @param allowOverwrite if allowOverwrite is false and a collection already exist -> throw Exception 149 * @throws SearchException 150 */ 151 private final synchronized void addCollection(SearchCollection collection, boolean allowOverwrite) throws SearchException { 152 Object o = collections.get(collection.getName(),null); 153 if(!allowOverwrite && o!=null) 154 throw new SearchException("there is already a collection with name "+collection.getName()); 155 collections.setEL(collection.getName(),collection); 156 // update 157 if(o!=null) { 158 setAttributes(getCollectionElement(collection.getName()),collection); 159 } 160 // create 161 else { 162 doc.getDocumentElement().appendChild(toElement(collection)); 163 } 164 store(); 165 } 166 167 /** 168 * removes a Collection from the storage 169 * @param collection Collection to remove 170 * @throws SearchException 171 */ 172 protected final synchronized void removeCollection(SearchCollection collection) 173 throws SearchException { 174 removeCollection(collection.getName()); 175 _removeCollection(collection); 176 } 177 178 /** 179 * removes a Collection from the storage 180 * @param collection Collection to remove 181 * @throws SearchException 182 */ 183 protected abstract void _removeCollection(SearchCollection collection) throws SearchException; 184 185 /** 186 * removes a Collection from the storage 187 * @param name Name of the Collection to remove 188 * @throws SearchException 189 */ 190 private final synchronized void removeCollection(String name) throws SearchException { 191 try { 192 collections.remove(KeyImpl.init(name)); 193 doc.getDocumentElement().removeChild(getCollectionElement(name)); 194 store(); 195 } 196 catch (PageException e) { 197 throw new SearchException("can't remove collection "+name+", collection doesn't exist"); 198 } 199 } 200 201 202 /** 203 * purge a Collection 204 * @param collection Collection to purge 205 * @throws SearchException 206 */ 207 protected final synchronized void purgeCollection(SearchCollection collection) 208 throws SearchException { 209 210 purgeCollection(collection.getName()); 211 } 212 213 /** 214 * purge a Collection 215 * @param name Name of the Collection to purge 216 * @throws SearchException 217 */ 218 private final synchronized void purgeCollection(String name) throws SearchException { 219 220 //Map map=(Map)collections.get(name); 221 //if(map!=null)map.clear(); 222 Element parent = getCollectionElement(name); 223 NodeList list=parent.getChildNodes(); 224 int len=list.getLength(); 225 for(int i=len-1;i>=0;i--) { 226 parent.removeChild(list.item(i)); 227 } 228 //doc.getDocumentElement().removeChild(getCollectionElement(name)); 229 store(); 230 231 } 232 233 /** 234 * @see railo.runtime.search.SearchEngine#getDirectory() 235 */ 236 public Resource getDirectory() { 237 return searchDir; 238 } 239 240 /** 241 * @see railo.runtime.search.SearchEngine#getLogger() 242 */ 243 public LogAndSource getLogger() { 244 return log; 245 } 246 247 /** 248 * return XML Element matching collection name 249 * @param name 250 * @return matching XML Element 251 */ 252 protected final Element getCollectionElement(String name) { 253 Element root = doc.getDocumentElement(); 254 NodeList children = root.getChildNodes(); 255 int len=children.getLength(); 256 for(int i=0;i<len;i++) { 257 Node n=children.item(i); 258 if(n instanceof Element && n.getNodeName().equals("collection")) { 259 Element el = (Element)n; 260 if(el.getAttribute("name").equalsIgnoreCase(name)) return el; 261 } 262 } 263 return null; 264 } 265 266 /** 267 * @see railo.runtime.search.SearchEngine#getIndexElement(org.w3c.dom.Element, java.lang.String) 268 */ 269 public Element getIndexElement(Element collElement, String id) { 270 271 NodeList children = collElement.getChildNodes(); 272 int len=children.getLength(); 273 for(int i=0;i<len;i++) { 274 Node n=children.item(i); 275 if(n instanceof Element && n.getNodeName().equals("index")) { 276 Element el = (Element)n; 277 if(el.getAttribute("id").equals(id)) return el; 278 } 279 } 280 return null; 281 } 282 283 /** 284 * translate a collection object to a XML Element 285 * @param coll Collection to translate 286 * @return XML Element 287 */ 288 private final Element toElement(SearchCollection coll) { 289 Element el = doc.createElement("collection"); 290 setAttributes(el,coll); 291 return el; 292 } 293 294 /** 295 * translate a collection object to a XML Element 296 * @param index Index to translate 297 * @return XML Element 298 * @throws SearchException 299 */ 300 protected final Element toElement(SearchIndex index) throws SearchException { 301 Element el = doc.createElement("index"); 302 setAttributes(el,index); 303 return el; 304 } 305 306 /** 307 * sets all attributes in XML Element from Search Collection 308 * @param el 309 * @param coll 310 */ 311 private final void setAttributes(Element el,SearchCollection coll) { 312 if(el==null) return; 313 setAttribute(el,"language",coll.getLanguage()); 314 setAttribute(el,"name",coll.getName()); 315 316 String value = coll.getLastUpdate().castToString(null); 317 if(value!=null)setAttribute(el,"lastUpdate",value); 318 value=coll.getCreated().castToString(null); 319 if(value!=null)setAttribute(el,"created",value); 320 321 setAttribute(el,"path",coll.getPath().getAbsolutePath()); 322 } 323 324 /** 325 * sets all attributes in XML Element from Search Index 326 * @param el 327 * @param index 328 * @throws SearchException 329 */ 330 protected final void setAttributes(Element el,SearchIndex index) throws SearchException { 331 if(el==null) return; 332 setAttribute(el,"categoryTree",index.getCategoryTree()); 333 setAttribute(el,"category",List.arrayToList(index.getCategories(),",")); 334 setAttribute(el,"custom1",index.getCustom1()); 335 setAttribute(el,"custom2",index.getCustom2()); 336 setAttribute(el,"custom3",index.getCustom3()); 337 setAttribute(el,"custom4",index.getCustom4()); 338 setAttribute(el,"id",index.getId()); 339 setAttribute(el,"key",index.getKey()); 340 setAttribute(el,"language",index.getLanguage()); 341 setAttribute(el,"title",index.getTitle()); 342 setAttribute(el,"extensions",List.arrayToList(index.getExtensions(),",")); 343 setAttribute(el,"type",SearchIndex.toStringType(index.getType())); 344 setAttribute(el,"urlpath",index.getUrlpath()); 345 setAttribute(el,"query",index.getQuery()); 346 } 347 348 /** 349 * helper method to set a attribute 350 * @param el 351 * @param name 352 * @param value 353 */ 354 private void setAttribute(Element el, String name, String value) { 355 if(value!=null)el.setAttribute(name,value); 356 } 357 358 /** 359 * read in collections 360 * @param config 361 * @throws SearchException 362 */ 363 private void readCollections(Config config) throws SearchException { 364 Element root = doc.getDocumentElement(); 365 NodeList children = root.getChildNodes(); 366 int len=children.getLength(); 367 for(int i=0;i<len;i++) { 368 Node n=children.item(i); 369 if(n instanceof Element && n.getNodeName().equals("collection")) { 370 readCollection(config,(Element)n); 371 } 372 } 373 } 374 375 /** 376 * read in a single collection element 377 * @param config 378 * @param el 379 * @throws SearchException 380 */ 381 private final void readCollection(Config config, Element el) throws SearchException { 382 SearchCollectionPlus sc; 383 //try { 384 // Collection 385 DateTime last = DateCaster.toDateAdvanced(el.getAttribute("lastUpdate"),ThreadLocalPageContext.getTimeZone(config),null); 386 if(last==null)last=new DateTimeImpl(); 387 DateTime cre = DateCaster.toDateAdvanced(el.getAttribute("created"),ThreadLocalPageContext.getTimeZone(config),null); 388 if(cre==null)cre=new DateTimeImpl(); 389 ResourceProvider frp = ResourcesImpl.getFileResourceProvider(); 390 sc =(SearchCollectionPlus) _readCollection( 391 el.getAttribute("name"), 392 frp.getResource(el.getAttribute("path")), 393 el.getAttribute("language"), 394 last,cre 395 ); 396 collections.setEL(KeyImpl.init(sc.getName()),sc); 397 398 // Indexes 399 NodeList children = el.getChildNodes(); 400 int len=children.getLength(); 401 for(int i=0;i<len;i++) { 402 Node n=children.item(i); 403 if(n instanceof Element && n.getNodeName().equals("index")) { 404 readIndex(sc,(Element)n); 405 } 406 } 407 /*} 408 catch (PageException e) { 409 throw new SearchException(e); 410 }*/ 411 } 412 413 /** 414 * read in a single Index 415 * @param sc 416 * @param el 417 * @throws SearchException 418 * @throws PageException 419 */ 420 protected void readIndex(SearchCollectionPlus sc, Element el) throws SearchException { 421 // Index 422 SearchIndex si=new SearchIndex( 423 _attr(el,"id"), 424 _attr(el,"title"), 425 _attr(el,"key"), 426 SearchIndex.toType(_attr(el,"type")), 427 _attr(el,"query"), 428 List.listToStringArray(_attr(el,"extensions"),','), 429 _attr(el,"language"), 430 _attr(el,"urlpath"), 431 _attr(el,"categoryTree"), 432 List.listToStringArray(_attr(el,"category"),','), 433 _attr(el,"custom1"), 434 _attr(el,"custom2"), 435 _attr(el,"custom3"), 436 _attr(el,"custom4")); 437 sc.addIndex(si); 438 } 439 440 private String _attr(Element el, String attr) { 441 return StringUtil.emptyIfNull(el.getAttribute(attr)); 442 } 443 444 /** 445 * read in a existing collection 446 * @param name 447 * @param parh 448 * @param language 449 * @param count 450 * @param lastUpdate 451 * @param created 452 * @return SearchCollection 453 * @throws SearchException 454 */ 455 protected abstract SearchCollection _readCollection(String name, Resource parh, String language, DateTime lastUpdate, DateTime created) throws SearchException; 456 457 /** 458 * store loaded data to xml file 459 * @throws SearchException 460 */ 461 protected final synchronized void store() throws SearchException { 462 String[] keys=collections.keysAsString(); 463 for(int i=0;i<keys.length;i++) { 464 Element collEl = getCollectionElement(keys[i]); 465 SearchCollection sc = getCollectionByName(keys[i]); 466 setAttributes(collEl,sc); 467 } 468 469 OutputFormat format = new OutputFormat(doc, null, true); 470 format.setLineSeparator("\r\n"); 471 format.setLineWidth(72); 472 OutputStream os=null; 473 try { 474 XMLSerializer serializer = new XMLSerializer(os=IOUtil.toBufferedOutputStream(searchFile.getOutputStream()), format); 475 serializer.serialize(doc.getDocumentElement()); 476 } catch (IOException e) { 477 throw new SearchException(e); 478 } 479 finally { 480 IOUtil.closeEL(os); 481 } 482 } 483 484 /** 485 * if no search xml exist create a empty one 486 * @param searchFile 487 * @throws IOException 488 */ 489 private final static void createSearchFile(Resource searchFile) throws IOException { 490 491 searchFile.createFile(true); 492 InputStream in = new Info().getClass().getResourceAsStream("/resource/search/default.xml"); 493 IOUtil.copy(in,searchFile,true); 494 495 } 496 497 /** 498 * @see railo.runtime.search.SearchEngine#getDisplayName() 499 */ 500 public abstract String getDisplayName(); 501 }