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    }