001 /* 002 * mesopotamia @mesopotamia.version@ 003 * Multilingual parser and repository. 004 * Copyright (C) 2005 Hammurapi Group 005 * 006 * This program is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2 of the License, or (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software 018 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 019 * 020 * URL: http://http://www.hammurapi.biz 021 * e-Mail: support@hammurapi.biz 022 */ 023 package org.mesopotamia; 024 025 import gnu.trove.TIntObjectHashMap; 026 027 import java.lang.ref.Reference; 028 import java.lang.ref.SoftReference; 029 import java.lang.reflect.InvocationTargetException; 030 import java.security.MessageDigest; 031 import java.security.NoSuchAlgorithmException; 032 import java.sql.Connection; 033 import java.sql.ResultSet; 034 import java.sql.SQLException; 035 import java.util.Collection; 036 import java.util.Collections; 037 import java.util.HashMap; 038 import java.util.Iterator; 039 import java.util.Map; 040 import java.util.Timer; 041 import java.util.logging.Level; 042 import java.util.logging.Logger; 043 044 import org.mesopotamia.sql.ErrorMessageImpl; 045 import org.mesopotamia.sql.LanguageImpl; 046 import org.mesopotamia.sql.MesopotamiaEngine; 047 import org.mesopotamia.sql.RepositoryImpl; 048 049 import biz.hammurapi.config.Context; 050 import biz.hammurapi.convert.ConverterClosure; 051 import biz.hammurapi.convert.ConvertingService; 052 import biz.hammurapi.persistence.StringStorage; 053 import biz.hammurapi.sql.IdentityGenerator; 054 import biz.hammurapi.sql.IdentityManager; 055 import biz.hammurapi.sql.IdentityRetriever; 056 import biz.hammurapi.sql.RowProcessor; 057 import biz.hammurapi.sql.SQLExceptionEx; 058 import biz.hammurapi.sql.SQLProcessor; 059 import biz.hammurapi.sql.Transaction; 060 import biz.hammurapi.util.ExceptionSink; 061 import biz.hammurapi.util.Worker; 062 063 064 /** 065 * Central class which is responsible for 066 * managing languages and repositories. 067 * @author Pavel Vlasov 068 * @revision $Revision: 1.2 $ 069 */ 070 public class RepositoryFactory implements ExceptionSink { 071 // private static final MeasurementCategory mc=MeasurementCategoryFactory.getCategory(RepositoryFactory.class); 072 private static final Logger logger = Logger.getLogger(RepositoryFactory.class.getName()); 073 074 final ConverterClosure toIntegerConverter=new ConverterClosure() { 075 076 public Object convert(Object source) { 077 return ConvertingService.convert(source, Integer.class); 078 } 079 080 }; 081 082 /** 083 * Converts "LEVEL_ID" column obtained from context into integer 084 */ 085 final ConverterClosure levelIdToIntegerConverter=new ConverterClosure() { 086 087 public Object convert(Object source) { 088 if (source instanceof Context) { 089 return ConvertingService.convert(((Context) source).get("LEVEL_ID"), Integer.class); 090 } 091 return null; 092 } 093 094 }; 095 096 int chunkSize=250; // TODO - read from configuration table. 097 098 /** 099 * Responsible for retrieving unique keys from the database. 100 */ 101 private IdentityManager identityManager; 102 103 /** 104 * Used in engine constructor and to perform transactional operations. 105 */ 106 private SQLProcessor processor; 107 108 /** 109 * Primary object to access the database. 110 */ 111 private MesopotamiaEngine engine; 112 113 private Map<String, Object> globalParameters=new HashMap<String, Object>(); 114 115 private Timer timer; 116 117 private ClassLoader classLoader; 118 119 public Object getGlobalParameter(String name) { 120 return globalParameters.get(name); 121 } 122 123 /** 124 * @param processor 125 * @param identityManager 126 * @throws SQLException 127 * @param timer Timer to schedule cache cleaning tasks. If null then internal timer is created. 128 */ 129 public RepositoryFactory( 130 SQLProcessor processor, 131 Worker worker, 132 Timer timer, 133 StringStorage stringStorage, 134 ClassLoader clsLoader) throws SQLException { 135 136 super(); 137 this.processor = processor; 138 this.engine=new MesopotamiaEngine(processor); 139 this.worker=worker; 140 this.timer = timer; 141 this.classLoader = clsLoader==null ? getClass().getClassLoader() : clsLoader; 142 if (timer==null) { 143 this.timer = new Timer(true); 144 } 145 146 this.stringStorage = stringStorage; 147 148 engine.processGlobalParameter( 149 new RowProcessor() { 150 151 public boolean process(ResultSet rs) throws SQLException { 152 String value=rs.getString("PARAMETER_VALUE"); 153 try { 154 if (value==null || value.trim().length()==0) { 155 globalParameters.put( 156 rs.getString("NAME"), 157 classLoader.loadClass(rs.getString("CLASS_NAME")).newInstance()); 158 } else { 159 globalParameters.put( 160 rs.getString("NAME"), 161 classLoader.loadClass(rs.getString("CLASS_NAME")) 162 .getConstructor(new Class[] {String.class}) 163 .newInstance(new Object[] {value})); 164 165 } 166 } catch (InstantiationException e) { 167 throw new SQLExceptionEx("Cannot instantiate parameter '"+rs.getString("NAME")+"'", e); 168 } catch (IllegalAccessException e) { 169 throw new SQLExceptionEx("Cannot instantiate parameter '"+rs.getString("NAME")+"'", e); 170 } catch (ClassNotFoundException e) { 171 throw new SQLExceptionEx("Cannot instantiate parameter '"+rs.getString("NAME")+"'", e); 172 } catch (SecurityException e) { 173 throw new SQLExceptionEx("Cannot instantiate parameter '"+rs.getString("NAME")+"'", e); 174 } catch (SQLException e) { 175 throw new SQLExceptionEx("Cannot instantiate parameter '"+rs.getString("NAME")+"'", e); 176 } catch (InvocationTargetException e) { 177 throw new SQLExceptionEx("Cannot instantiate parameter '"+rs.getString("NAME")+"'", e); 178 } catch (NoSuchMethodException e) { 179 throw new SQLExceptionEx("Cannot instantiate parameter '"+rs.getString("NAME")+"'", e); 180 } 181 return true; 182 } 183 184 }); 185 186 187 this.identityManager = (IdentityManager) getGlobalParameter("IdentityManager"); 188 this.chunkSize = ((Integer) getGlobalParameter("SharedTextChunkSize")).intValue(); 189 190 engine.processLanguage( 191 new RowProcessor() { 192 193 public boolean process(ResultSet rs) throws SQLException { 194 LanguageImpl li=new LanguageImpl(rs); 195 languages.put(new Language(li), new RepositoryLanguage(RepositoryFactory.this, li, classLoader)); 196 return true; 197 } 198 199 }); 200 } 201 202 public MesopotamiaEngine getEngine() { 203 return engine; 204 } 205 206 public IdentityManager getIdentityManager() { 207 return identityManager; 208 } 209 210 public SQLProcessor getProcessor() { 211 return processor; 212 } 213 214 public Repository createRepository(String name) throws MesopotamiaException { 215 try { 216 final RepositoryImpl ri=new RepositoryImpl(true); 217 ri.setName(name); 218 processor.executeTransaction(new Transaction() { 219 220 public boolean execute(SQLProcessor processor) throws SQLException { 221 if (identityManager instanceof IdentityGenerator) { 222 ri.setId(((IdentityGenerator) identityManager).generate(processor.getConnection(), "REPOSITORY")); 223 } 224 new MesopotamiaEngine(processor).insertRepository(ri); 225 if (identityManager instanceof IdentityRetriever) { 226 ri.setId(((IdentityRetriever) identityManager).retrieve(processor.getConnection())); 227 } 228 return true; 229 } 230 231 }); 232 return new Repository(this, ri); 233 } catch (SQLException e) { 234 throw new MesopotamiaException("Cannot create repository", e); 235 } 236 } 237 238 public Repository getRepository(int id) throws MesopotamiaException { 239 synchronized (repositoryMap) { 240 Repository ret=(Repository) repositoryMap.get(id); 241 242 if (ret!=null) { 243 return ret; 244 } 245 246 try { 247 org.mesopotamia.sql.Repository ri=engine.getRepository(id); 248 if (ri==null) { 249 throw new MesopotamiaException("Invalid repository id: " + id); 250 } 251 252 ret = new Repository(this, ri); 253 repositoryMap.put(ret.getId(), ret); 254 return ret; 255 } catch (SQLException e) { 256 throw new MesopotamiaException("Cannot load repository", e); 257 } 258 } 259 } 260 261 private Map<Language, RepositoryLanguage> languages=new HashMap<Language, RepositoryLanguage>(); 262 263 public RepositoryLanguage getRepositoryLanguage(Language language) { 264 return languages.get(language); 265 } 266 267 public Collection<RepositoryLanguage> getRepositoryLanguages() { 268 return Collections.unmodifiableCollection(languages.values()); 269 } 270 271 272 /** 273 * Returns message digest by name. 274 * @param algorithm 275 * @return 276 * @throws MesopotamiaException 277 */ 278 public MessageDigest getMessageDigest(String algorithm) throws MesopotamiaException { 279 try { 280 MessageDigest ret = MessageDigest.getInstance(algorithm); 281 return ret; 282 } catch (NoSuchAlgorithmException e) { 283 throw new MesopotamiaException("Invalid digest algorithm: "+algorithm, e); 284 } 285 } 286 287 RepositoryLanguage getRepositoryLanguageByTokenTypeId(int tokenTypeId) { 288 Iterator<RepositoryLanguage> lit=languages.values().iterator(); 289 while (lit.hasNext()) { 290 RepositoryLanguage rl=lit.next(); 291 if (rl.belongsTo(tokenTypeId)) { 292 return rl; 293 } 294 } 295 return null; 296 } 297 298 private Worker worker; 299 300 /** 301 * Processes job 302 * @param job 303 */ 304 public void process(Runnable job) { 305 if (job!=null) { 306 if (job instanceof MesopotamiaJob) { 307 ((MesopotamiaJob) job).setFactory(this); 308 } 309 310 if (worker==null || !worker.post(job)) { 311 job.run(); 312 } 313 } 314 } 315 316 /** 317 * Override if needed. 318 */ 319 public void consume(Object source, Exception e) { 320 logger.log(Level.SEVERE, "Exception in "+source+": "+e, e); 321 } 322 323 private class SoftIntObjectMap extends TIntObjectHashMap { 324 /** 325 * 326 */ 327 private static final long serialVersionUID = 5449550690446482445L; 328 329 public Object put(int key, Object value) { 330 return super.put(key, new SoftReference<Object>(value)); 331 } 332 333 public Object get(int key) { 334 Reference<?> ref = (Reference<?>) super.get(key); 335 if (ref==null) { 336 return null; 337 } 338 339 Object obj = ref.get(); 340 if (obj==null) { 341 remove(key); 342 } 343 344 return obj; 345 } 346 } 347 348 private SoftIntObjectMap scanMap=new SoftIntObjectMap(); 349 350 void addScan(Scan scan) { 351 synchronized (scanMap) { 352 scanMap.put(scan.getId(), scan); 353 } 354 } 355 356 public Scan getScan(int id) throws MesopotamiaException { 357 synchronized (scanMap) { 358 Scan ret = (Scan) scanMap.get(id); 359 360 if (ret!=null) { 361 return ret; 362 } 363 364 try { 365 org.mesopotamia.sql.Scan si=getEngine().getScan(id); 366 if (si==null) { 367 return null; 368 } 369 370 Repository repo=getRepository(si.getRepository()); 371 ret=new Scan(repo, si); 372 addScan(ret); 373 return ret; 374 } catch (SQLException e) { 375 throw new MesopotamiaException("Cannot load scan", e); 376 } 377 } 378 } 379 380 private SoftIntObjectMap repositoryMap=new SoftIntObjectMap(); 381 382 private StringStorage stringStorage; 383 384 public StringStorage getStringStorage() { 385 return stringStorage; 386 } 387 388 Timer getTimer() { 389 return timer; 390 } 391 392 /** 393 * Helper method for loaders. 394 * @param scanId 395 * @param sourceUnitId 396 * @param errorType 397 * @param errorMessage 398 * @return Message record ID. 399 * @throws SQLException 400 */ 401 public int storeErrorMessage(final int scanId, final Integer sourceUnitId, final String errorType, final String errorMessage) { 402 try { 403 final ErrorMessageImpl emi=new ErrorMessageImpl(true); 404 getProcessor().executeTransaction( 405 new Transaction() { 406 407 public boolean execute(SQLProcessor processor) throws SQLException { 408 Connection con = processor.getConnection(); 409 IdentityManager identityManager = getIdentityManager(); 410 if (identityManager instanceof IdentityGenerator) { 411 emi.setId(((IdentityGenerator) identityManager).generate(con, "ERROR_MESSAGE")); 412 } 413 414 emi.setErrorType(errorType); 415 emi.setMessageText(errorMessage); 416 emi.setSourceUnitId(sourceUnitId); 417 emi.setScanId(new Integer(scanId)); 418 419 new MesopotamiaEngine(processor).insertErrorMessage(emi); 420 421 if (identityManager instanceof IdentityRetriever) { 422 emi.setId(((IdentityRetriever) identityManager).retrieve(con)); 423 } 424 return true; 425 } 426 427 }); 428 return emi.getId(); 429 } catch (Exception e) { 430 consume(this, e); 431 return -1; 432 } 433 } 434 435 public int storeErrorMessage(final int scanId, final Integer sourceUnitId, final Throwable e) { 436 Throwable rootCause=e; 437 while (rootCause.getCause()!=null) { 438 rootCause=rootCause.getCause(); 439 } 440 441 return storeErrorMessage( 442 scanId, 443 sourceUnitId, 444 rootCause.getClass().getName(), 445 ""+rootCause.getMessage()); 446 447 } 448 449 /** 450 * Deletes source unit from the database. 451 * @param id Source unit ID. 452 * @throws MesopotamiaException 453 */ 454 public void deleteSourceUnit(int id) throws MesopotamiaException { 455 try { 456 engine.deleteSourceUnitScanBySourceUnit(id); 457 engine.deleteSourceUnit(id); 458 } catch (SQLException e) { 459 throw new MesopotamiaException(e); 460 } 461 } 462 463 }