Project

General

Profile

Download (22.4 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9

    
10
package eu.etaxonomy.cdm.persistence.dao.hibernate.common;
11

    
12
import java.util.ArrayList;
13
import java.util.List;
14
import java.util.UUID;
15

    
16
import org.apache.log4j.Logger;
17
import org.apache.lucene.analysis.standard.StandardAnalyzer;
18
import org.apache.lucene.queryparser.classic.ParseException;
19
import org.apache.lucene.queryparser.classic.QueryParser;
20
import org.hibernate.Criteria;
21
import org.hibernate.Query;
22
import org.hibernate.Session;
23
import org.hibernate.criterion.Criterion;
24
import org.hibernate.criterion.Order;
25
import org.hibernate.criterion.Projections;
26
import org.hibernate.criterion.Restrictions;
27
import org.hibernate.envers.query.AuditEntity;
28
import org.hibernate.envers.query.AuditQuery;
29
import org.hibernate.search.FullTextSession;
30
import org.hibernate.search.Search;
31
import org.hibernate.search.SearchFactory;
32

    
33
import eu.etaxonomy.cdm.model.common.CdmBase;
34
import eu.etaxonomy.cdm.model.common.Credit;
35
import eu.etaxonomy.cdm.model.common.DefinedTerm;
36
import eu.etaxonomy.cdm.model.common.IIdentifiableEntity;
37
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
38
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
39
import eu.etaxonomy.cdm.model.common.LSID;
40
import eu.etaxonomy.cdm.model.media.Rights;
41
import eu.etaxonomy.cdm.persistence.dao.QueryParseException;
42
import eu.etaxonomy.cdm.persistence.dao.common.IIdentifiableDao;
43
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
44
import eu.etaxonomy.cdm.persistence.query.MatchMode;
45
import eu.etaxonomy.cdm.persistence.query.OrderHint;
46

    
47

    
48
public class IdentifiableDaoBase<T extends IdentifiableEntity> extends AnnotatableDaoImpl<T> implements IIdentifiableDao<T>{
49
    @SuppressWarnings("unused")
50
    private static final Logger logger = Logger.getLogger(IdentifiableDaoBase.class);
51

    
52
    protected String defaultField = "titleCache_tokenized";
53
    protected Class<? extends T> indexedClasses[];
54

    
55

    
56

    
57
    public IdentifiableDaoBase(Class<T> type) {
58
        super(type);
59
    }
60

    
61

    
62
    @Override
63
    public List<T> findByTitle(String queryString) {
64
        return findByTitle(queryString, null);
65
    }
66

    
67
    @Override
68
    public List<T> findByTitle(String queryString, CdmBase sessionObject) {
69
        /**
70
         *  FIXME why do we need to call update in a find* method? I don't know for sure
71
         *  that this is a good idea . . .
72
         */
73
        Session session = getSession();
74
        if ( sessionObject != null ) {
75
            session.update(sessionObject);
76
        }
77
        checkNotInPriorView("IdentifiableDaoBase.findByTitle(String queryString, CdmBase sessionObject)");
78
        Criteria crit = session.createCriteria(type);
79
        crit.add(Restrictions.ilike("titleCache", queryString));
80
        List<T> results = crit.list();
81
        List<String> propertyPaths = null;
82
        defaultBeanInitializer.initializeAll(results, propertyPaths);
83
        return results;
84
    }
85

    
86
    @Override
87
    public List<T> findByTitleAndClass(String queryString, Class<T> clazz) {
88
        checkNotInPriorView("IdentifiableDaoBase.findByTitleAndClass(String queryString, Class<T> clazz)");
89
        Criteria crit = getSession().createCriteria(clazz);
90
        crit.add(Restrictions.ilike("titleCache", queryString));
91
        List<T> results = crit.list();
92
        return results;
93
    }
94

    
95
    @Override
96
    public List<T> findTitleCache(Class<? extends T> clazz, String queryString, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, MatchMode matchMode){
97

    
98
        Query query = prepareFindTitleCache(clazz, queryString, pageSize,
99
                pageNumber, matchMode, false);
100
        List<T> result = query.list();
101
        return result;
102
    }
103

    
104
    @Override
105
    public Long countTitleCache(Class<? extends T> clazz, String queryString, MatchMode matchMode){
106

    
107
        Query query = prepareFindTitleCache(clazz, queryString, null,
108
                null, matchMode, true);
109
        Long result = (Long)query.uniqueResult();
110
        return result;
111
    }
112

    
113
    /**
114
     * @param clazz filter by class - can be null to include all instances of type T
115
     * @param queryString the query string to filter by
116
     * @param pageSize
117
     * @param pageNumber
118
     * @param matchmode use a particular type of matching (can be null - defaults to exact matching)
119
     * @return
120
     */
121
    private Query prepareFindTitleCache(Class<? extends T> clazz,
122
            String queryString, Integer pageSize, Integer pageNumber,
123
            MatchMode matchMode, boolean doCount) {
124
        if(clazz == null) {
125
            clazz = type;
126
        }
127

    
128
        String what = (doCount ? "count(distinct e.titleCache)": "distinct e.titleCache");
129

    
130
        if(matchMode != null){
131
            queryString	= matchMode.queryStringFrom(queryString);
132
        }
133
        String hql = "select " + what + " from  " + clazz.getName() + " e where e.titleCache like '" + queryString + "'";
134

    
135
        Query query = getSession().createQuery(hql);
136

    
137
        if(pageSize != null && !doCount) {
138
            query.setMaxResults(pageSize);
139
            if(pageNumber != null) {
140
                query.setFirstResult(pageNumber * pageSize);
141
            }
142
        }
143
        return query;
144
    }
145

    
146

    
147
    @Override
148
    public List<T> findByTitle(Class<? extends T> clazz, String queryString, MatchMode matchmode, List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
149
        return findByParam(clazz, "titleCache", queryString, matchmode, criterion, pageSize, pageNumber, orderHints, propertyPaths);
150
    }
151

    
152
    @Override
153
    public List<T> findByReferenceTitle(Class<? extends T> clazz, String queryString, MatchMode matchmode, List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
154
        return findByParam(clazz, "title", queryString, matchmode, criterion, pageSize, pageNumber, orderHints, propertyPaths);
155
    }
156

    
157
    @Override
158
    public List<T> findByTitle(String queryString, MatchMode matchmode, int page, int pagesize, List<Criterion> criteria) {
159
        checkNotInPriorView("IdentifiableDaoBase.findByTitle(String queryString, MATCH_MODE matchmode, int page, int pagesize, List<Criterion> criteria)");
160
        Criteria crit = getSession().createCriteria(type);
161
        if (matchmode == MatchMode.EXACT) {
162
            crit.add(Restrictions.eq("titleCache", matchmode.queryStringFrom(queryString)));
163
        } else {
164
//			crit.add(Restrictions.ilike("titleCache", matchmode.queryStringFrom(queryString)));
165
            crit.add(Restrictions.like("titleCache", matchmode.queryStringFrom(queryString)));
166
        }
167
        if (pagesize >= 0) {
168
            crit.setMaxResults(pagesize);
169
        }
170
        if(criteria != null){
171
            for (Criterion criterion : criteria) {
172
                crit.add(criterion);
173
            }
174
        }
175
        crit.addOrder(Order.asc("titleCache"));
176
        int firstItem = (page - 1) * pagesize;
177
        crit.setFirstResult(firstItem);
178
        List<T> results = crit.list();
179
        List<String> propertyPaths = null;
180
        defaultBeanInitializer.initializeAll(results, propertyPaths);
181
        return results;
182
    }
183

    
184
    @Override
185
    public int countRights(T identifiableEntity) {
186
        checkNotInPriorView("IdentifiableDaoBase.countRights(T identifiableEntity)");
187
        Query query = getSession().createQuery("select count(rights) from " + type.getSimpleName() + " identifiableEntity join identifiableEntity.rights rights where identifiableEntity = :identifiableEntity");
188
        query.setParameter("identifiableEntity",identifiableEntity);
189
        return ((Long)query.uniqueResult()).intValue();
190
    }
191

    
192
    @Override
193
    public int countSources(T identifiableEntity) {
194
        checkNotInPriorView("IdentifiableDaoBase.countSources(T identifiableEntity)");
195
        Query query = getSession().createQuery("SELECT COUNT(source) FROM "+identifiableEntity.getClass().getName() + " ie JOIN ie.sources source WHERE ie = :identifiableEntity");
196
        query.setParameter("identifiableEntity", identifiableEntity);
197
        return ((Long)query.uniqueResult()).intValue();
198
    }
199

    
200
    @Override
201
    public List<Rights> getRights(T identifiableEntity, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
202
        checkNotInPriorView("IdentifiableDaoBase.getRights(T identifiableEntity, Integer pageSize, Integer pageNumber, List<String> propertyPaths)");
203
        Query query = getSession().createQuery("select rights from " + type.getSimpleName() + " identifiableEntity join identifiableEntity.rights rights where identifiableEntity = :identifiableEntity");
204
        query.setParameter("identifiableEntity",identifiableEntity);
205
        setPagingParameter(query, pageSize, pageNumber);
206
        List<Rights> results = query.list();
207
        defaultBeanInitializer.initializeAll(results, propertyPaths);
208
        return results;
209
    }
210

    
211
//    @Override  //TODO add to interface, maybe add property path
212
    public List<Credit> getCredits(T identifiableEntity, Integer pageSize, Integer pageNumber) {
213
        checkNotInPriorView("IdentifiableDaoBase.getCredits(T identifiableEntity, Integer pageSize, Integer pageNumber)");
214
        Query query = getSession().createQuery("select credits from " + type.getSimpleName() + " identifiableEntity join identifiableEntity.credits credits where identifiableEntity = :identifiableEntity");
215
        query.setParameter("identifiableEntity",identifiableEntity);
216
        setPagingParameter(query, pageSize, pageNumber);
217
        return query.list();
218
    }
219

    
220
    @Override
221
    public List<IdentifiableSource> getSources(T identifiableEntity, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
222
        checkNotInPriorView("IdentifiableDaoBase.getSources(T identifiableEntity, Integer pageSize, Integer pageNumber)");
223
        Query query = getSession().createQuery("SELECT source FROM "+ identifiableEntity.getClass().getName()+ " ie JOIN ie.sources source WHERE ie.id = :id");
224
        query.setParameter("id",identifiableEntity.getId());
225
        setPagingParameter(query, pageSize, pageNumber);
226
        @SuppressWarnings("unchecked")
227
        List<IdentifiableSource> results = query.list();
228
        defaultBeanInitializer.initializeAll(results, propertyPaths);
229
        return results;
230
    }
231

    
232
    @Override
233
    public List<T> findOriginalSourceByIdInSource(String idInSource, String idNamespace) {
234
        checkNotInPriorView("IdentifiableDaoBase.findOriginalSourceByIdInSource(String idInSource, String idNamespace)");
235
        Query query = getSession().createQuery(
236
                "Select c from " + type.getSimpleName() + " as c " +
237
                "inner join c.sources as source " +
238
                "where source.idInSource = :idInSource " +
239
                    " AND source.idNamespace = :idNamespace"
240
            );
241
        query.setString("idInSource", idInSource);
242
        query.setString("idNamespace", idNamespace);
243
        //TODO integrate reference in where
244
        return query.list();
245
    }
246

    
247
    @Override
248
    public T find(LSID lsid) {
249
        checkNotInPriorView("IdentifiableDaoBase.find(LSID lsid)");
250
        Criteria criteria = getSession().createCriteria(type);
251
        criteria.add(Restrictions.eq("lsid.authority", lsid.getAuthority()));
252
        criteria.add(Restrictions.eq("lsid.namespace", lsid.getNamespace()));
253
        criteria.add(Restrictions.eq("lsid.object", lsid.getObject()));
254

    
255
        if(lsid.getRevision() != null) {
256
            criteria.add(Restrictions.eq("lsid.revision", lsid.getRevision()));
257
        }
258

    
259
        T object = (T)criteria.uniqueResult();
260
        if(object != null) {
261
            return object;
262
        } else {
263
            AuditQuery query = getAuditReader().createQuery().forRevisionsOfEntity(type, false, true);
264
            query.add(AuditEntity.property("lsid_authority").eq(lsid.getAuthority()));
265
            query.add(AuditEntity.property("lsid_namespace").eq(lsid.getNamespace()));
266
            query.add(AuditEntity.property("lsid_object").eq(lsid.getObject()));
267

    
268
            if(lsid.getRevision() != null) {
269
                query.add(AuditEntity.property("lsid_revision").eq(lsid.getRevision()));
270
            }
271

    
272
            query.addOrder(AuditEntity.revisionNumber().asc());
273
            query.setMaxResults(1);
274
            query.setFirstResult(0);
275
            List<Object[]> objs = query.getResultList();
276
            if(objs.isEmpty()) {
277
                return null;
278
            } else {
279
                return (T)objs.get(0)[0];
280
            }
281
        }
282
    }
283

    
284
    @Override
285
    public List<UuidAndTitleCache<T>> getUuidAndTitleCache(Integer limit, String pattern){
286
        Session session = getSession();
287
        Query query = null;
288
        if (pattern != null){
289
            query = session.createQuery("select uuid, id, titleCache from " + type.getSimpleName() +" where titleCache like :pattern");
290
            pattern = pattern.replace("*", "%");
291
            pattern = pattern.replace("?", "_");
292
            pattern = pattern + "%";
293
            query.setParameter("pattern", pattern);
294
        } else {
295
            query = session.createQuery("select uuid, id, titleCache from " + type.getSimpleName() );
296
        }
297
        if (limit != null){
298
           query.setMaxResults(limit);
299
        }
300
        return getUuidAndTitleCache(query);
301
    }
302

    
303

    
304
    @Override
305
    public List<UuidAndTitleCache<T>> getUuidAndTitleCache(){
306
        return getUuidAndTitleCache(null, null);
307
    }
308

    
309
    protected <E extends IIdentifiableEntity> List<UuidAndTitleCache<E>> getUuidAndTitleCache(Query query){
310
        List<UuidAndTitleCache<E>> list = new ArrayList<UuidAndTitleCache<E>>();
311

    
312
        List<Object[]> result = query.list();
313

    
314
        for(Object[] object : result){
315
            list.add(new UuidAndTitleCache<E>((UUID) object[0],(Integer) object[1], (String) object[2]));
316
        }
317
        return list;
318
    }
319

    
320

    
321
    @Override
322
    public int countByTitle(Class<? extends T> clazz, String queryString,	MatchMode matchmode, List<Criterion> criterion) {
323
        return countByParam(clazz, "titleCache",queryString,matchmode,criterion);
324
    }
325

    
326
    @Override
327
    public int countByReferenceTitle(Class<? extends T> clazz, String queryString,	MatchMode matchmode, List<Criterion> criterion) {
328
        return countByParam(clazz, "title",queryString,matchmode,criterion);
329
    }
330

    
331
    @Override
332
    public int count(Class<? extends T> clazz, String queryString) {
333
        checkNotInPriorView("IdentifiableDaoBase.count(Class<? extends T> clazz, String queryString)");
334
        QueryParser queryParser = new QueryParser(defaultField , new StandardAnalyzer());
335

    
336
        try {
337
            org.apache.lucene.search.Query query = queryParser.parse(queryString);
338

    
339
            FullTextSession fullTextSession = Search.getFullTextSession(this.getSession());
340
            org.hibernate.search.FullTextQuery fullTextQuery = null;
341

    
342
            if(clazz == null) {
343
                fullTextQuery = fullTextSession.createFullTextQuery(query, type);
344
            } else {
345
                fullTextQuery = fullTextSession.createFullTextQuery(query, clazz);
346
            }
347

    
348
            Integer  result = fullTextQuery.getResultSize();
349
            return result;
350

    
351
        } catch (ParseException e) {
352
            throw new QueryParseException(e, queryString);
353
        }
354
    }
355

    
356
    @Override
357
    public void optimizeIndex() {
358
        FullTextSession fullTextSession = Search.getFullTextSession(getSession());
359
        SearchFactory searchFactory = fullTextSession.getSearchFactory();
360
        for(Class clazz : indexedClasses) {
361
            searchFactory.optimize(clazz); // optimize the indices ()
362
        }
363
        fullTextSession.flushToIndexes();
364
    }
365

    
366
    @Override
367
    public void purgeIndex() {
368
        FullTextSession fullTextSession = Search.getFullTextSession(getSession());
369
        for(Class clazz : indexedClasses) {
370
            fullTextSession.purgeAll(clazz); // remove all objects of type t from indexes
371
        }
372
        fullTextSession.flushToIndexes();
373
    }
374

    
375
    @Override
376
    public void rebuildIndex() {
377
        FullTextSession fullTextSession = Search.getFullTextSession(getSession());
378

    
379
        for(T t : list(null,null)) { // re-index all objects
380
            fullTextSession.index(t);
381
        }
382
        fullTextSession.flushToIndexes();
383
    }
384

    
385
    @Override
386
    public List<T> search(Class<? extends T> clazz, String queryString,	Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,List<String> propertyPaths) {
387
        checkNotInPriorView("IdentifiableDaoBase.search(Class<? extends T> clazz, String queryString, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,List<String> propertyPaths)");
388
        QueryParser queryParser = new QueryParser(defaultField, new StandardAnalyzer());
389
        List<T> results = new ArrayList<T>();
390

    
391
        try {
392
            org.apache.lucene.search.Query query = queryParser.parse(queryString);
393

    
394
            FullTextSession fullTextSession = Search.getFullTextSession(getSession());
395
            org.hibernate.search.FullTextQuery fullTextQuery = null;
396

    
397
            if(clazz == null) {
398
                fullTextQuery = fullTextSession.createFullTextQuery(query, type);
399
            } else {
400
                fullTextQuery = fullTextSession.createFullTextQuery(query, clazz);
401
            }
402

    
403
            addOrder(fullTextQuery,orderHints);
404

    
405
            if(pageSize != null) {
406
                fullTextQuery.setMaxResults(pageSize);
407
                if(pageNumber != null) {
408
                    fullTextQuery.setFirstResult(pageNumber * pageSize);
409
                } else {
410
                    fullTextQuery.setFirstResult(0);
411
                }
412
            }
413

    
414
            List<T> result = fullTextQuery.list();
415
            defaultBeanInitializer.initializeAll(result, propertyPaths);
416
            return result;
417

    
418
        } catch (ParseException e) {
419
            throw new QueryParseException(e, queryString);
420
        }
421
    }
422

    
423
    @Override
424
    public String suggestQuery(String string) {
425
        throw new UnsupportedOperationException("suggestQuery is not supported for objects of class " + type.getName());
426
    }
427

    
428
    @Override
429
    public Integer countByTitle(String queryString) {
430
        return countByTitle(queryString, null);
431
    }
432

    
433
    @Override
434
    public Integer countByTitle(String queryString, CdmBase sessionObject) {
435
        Session session = getSession();
436
        if ( sessionObject != null ) {
437
            session.update(sessionObject);
438
        }
439
        checkNotInPriorView("IdentifiableDaoBase.countByTitle(String queryString, CdmBase sessionObject)");
440
        Criteria crit = session.createCriteria(type);
441
        crit.add(Restrictions.ilike("titleCache", queryString));
442
        Integer result =  ((Number)crit.setProjection(Projections.rowCount()).uniqueResult()).intValue();
443
        return result;
444
    }
445

    
446
    @Override
447
    public Integer countByTitle(String queryString, MatchMode matchMode, List<Criterion> criteria) {
448
        checkNotInPriorView("IdentifiableDaoBase.findByTitle(String queryString, MATCH_MODE matchmode, int page, int pagesize, List<Criterion> criteria)");
449
        Criteria crit = getSession().createCriteria(type);
450
        if (matchMode == MatchMode.EXACT) {
451
            crit.add(Restrictions.eq("titleCache", matchMode.queryStringFrom(queryString)));
452
        } else {
453
//			crit.add(Restrictions.ilike("titleCache", matchmode.queryStringFrom(queryString)));
454
            crit.add(Restrictions.like("titleCache", matchMode.queryStringFrom(queryString)));
455
        }
456

    
457
        if(criteria != null){
458
            for (Criterion criterion : criteria) {
459
                crit.add(criterion);
460
            }
461
        }
462

    
463

    
464
        Integer result = ((Number)crit.setProjection(Projections.rowCount()).uniqueResult()).intValue();
465
        return result;
466
    }
467

    
468

    
469
	@Override
470
	public <S extends T> int countByIdentifier(Class<S> clazz,
471
			String identifier, DefinedTerm identifierType, MatchMode matchmode) {
472
		checkNotInPriorView("IdentifiableDaoBase.countByIdentifier(T clazz, String identifier, DefinedTerm identifierType, MatchMode matchmode)");
473

    
474
		Class<?> clazzParam = clazz == null ? type : clazz;
475
		String queryString = "SELECT count(*) FROM " + clazzParam.getSimpleName() + " as c " +
476
	                "INNER JOIN c.identifiers as ids " +
477
	                "WHERE (1=1) ";
478
		if (identifier != null){
479
			if (matchmode == null || matchmode == MatchMode.EXACT){
480
				queryString += " AND ids.identifier = '"  + identifier + "'";
481
			}else {
482
				queryString += " AND ids.identifier LIKE '" + matchmode.queryStringFrom(identifier)  + "'";
483
			}
484
		}
485
		if (identifierType != null){
486
        	queryString += " AND ids.type = :type";
487
        }
488

    
489
		Query query = getSession().createQuery(queryString);
490
        if (identifierType != null){
491
        	query.setEntity("type", identifierType);
492
        }
493

    
494
		Long c = (Long)query.uniqueResult();
495
        return c.intValue();
496
	}
497

    
498
	@Override
499
	public <S extends T> List<Object[]> findByIdentifier(
500
			Class<S> clazz, String identifier, DefinedTerm identifierType,
501
			MatchMode matchmode, boolean includeEntity,
502
			Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
503

    
504
		checkNotInPriorView("IdentifiableDaoBase.findByIdentifier(T clazz, String identifier, DefinedTerm identifierType, MatchMode matchmode, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)");
505

    
506
		Class<?> clazzParam = clazz == null ? type : clazz;
507
		String queryString = "SELECT ids.type, ids.identifier, %s FROM %s as c " +
508
                " INNER JOIN c.identifiers as ids " +
509
                " WHERE (1=1) ";
510
		queryString = String.format(queryString, (includeEntity ? "c":"c.uuid, c.titleCache") , clazzParam.getSimpleName());
511

    
512
		//Matchmode and identifier
513
		if (identifier != null){
514
			if (matchmode == null || matchmode == MatchMode.EXACT){
515
				queryString += " AND ids.identifier = '"  + identifier + "'";
516
			}else {
517
				queryString += " AND ids.identifier LIKE '" + matchmode.queryStringFrom(identifier)  + "'";
518
			}
519
		}
520
        if (identifierType != null){
521
        	queryString += " AND ids.type = :type";
522
        }
523
        //order
524
        queryString +=" ORDER BY ids.type.uuid, ids.identifier, c.uuid ";
525

    
526
		Query query = getSession().createQuery(queryString);
527

    
528
		//parameters
529
		if (identifierType != null){
530
        	query.setEntity("type", identifierType);
531
        }
532

    
533
        //paging
534
        setPagingParameter(query, pageSize, pageNumber);
535

    
536
        List<Object[]> results = query.list();
537
        //initialize
538
        if (includeEntity){
539
        	List<S> entities = new ArrayList<S>();
540
        	for (Object[] result : results){
541
        		entities.add((S)result[2]);
542
        	}
543
        	defaultBeanInitializer.initializeAll(entities, propertyPaths);
544
        }
545
        return results;
546
	}
547

    
548

    
549

    
550
}
(11-11/23)