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