Project

General

Profile

Download (22.5 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
package eu.etaxonomy.taxeditor.store;
10

    
11
import java.util.EnumSet;
12

    
13
import org.eclipse.core.runtime.IProgressMonitor;
14
import org.eclipse.core.runtime.jobs.Job;
15
import org.eclipse.swt.widgets.Display;
16
import org.slf4j.Marker;
17
import org.springframework.core.io.ClassPathResource;
18
import org.springframework.core.io.Resource;
19
import org.springframework.security.authentication.AuthenticationManager;
20
import org.springframework.security.core.Authentication;
21
import org.springframework.security.core.GrantedAuthority;
22
import org.springframework.security.core.context.SecurityContext;
23
import org.springframework.security.core.context.SecurityContextHolder;
24

    
25
import eu.etaxonomy.cdm.api.application.CdmApplicationException;
26
import eu.etaxonomy.cdm.api.application.CdmApplicationRemoteController;
27
import eu.etaxonomy.cdm.api.application.CdmApplicationState;
28
import eu.etaxonomy.cdm.api.application.ICdmRepository;
29
import eu.etaxonomy.cdm.api.cache.CdmServiceCacher;
30
import eu.etaxonomy.cdm.api.service.IAgentService;
31
import eu.etaxonomy.cdm.api.service.IAnnotationService;
32
import eu.etaxonomy.cdm.api.service.IClassificationService;
33
import eu.etaxonomy.cdm.api.service.ICollectionService;
34
import eu.etaxonomy.cdm.api.service.ICommonService;
35
import eu.etaxonomy.cdm.api.service.IDescriptionService;
36
import eu.etaxonomy.cdm.api.service.IDescriptiveDataSetService;
37
import eu.etaxonomy.cdm.api.service.IEntityConstraintViolationService;
38
import eu.etaxonomy.cdm.api.service.IEntityValidationService;
39
import eu.etaxonomy.cdm.api.service.IGrantedAuthorityService;
40
import eu.etaxonomy.cdm.api.service.IGroupService;
41
import eu.etaxonomy.cdm.api.service.IMarkerService;
42
import eu.etaxonomy.cdm.api.service.IMediaService;
43
import eu.etaxonomy.cdm.api.service.INameService;
44
import eu.etaxonomy.cdm.api.service.IOccurrenceService;
45
import eu.etaxonomy.cdm.api.service.IPolytomousKeyNodeService;
46
import eu.etaxonomy.cdm.api.service.IPolytomousKeyService;
47
import eu.etaxonomy.cdm.api.service.IReferenceService;
48
import eu.etaxonomy.cdm.api.service.IRegistrationService;
49
import eu.etaxonomy.cdm.api.service.IRightsService;
50
import eu.etaxonomy.cdm.api.service.IService;
51
import eu.etaxonomy.cdm.api.service.ITaxonService;
52
import eu.etaxonomy.cdm.api.service.ITermNodeService;
53
import eu.etaxonomy.cdm.api.service.ITermService;
54
import eu.etaxonomy.cdm.api.service.ITermTreeService;
55
import eu.etaxonomy.cdm.api.service.IUserService;
56
import eu.etaxonomy.cdm.api.service.IVocabularyService;
57
import eu.etaxonomy.cdm.api.service.molecular.IAmplificationService;
58
import eu.etaxonomy.cdm.api.service.molecular.IPrimerService;
59
import  eu.etaxonomy.cdm.cache.CdmRemoteCacheManager;
60
import eu.etaxonomy.cdm.config.ICdmSource;
61
import eu.etaxonomy.cdm.database.DbSchemaValidation;
62
import eu.etaxonomy.cdm.model.agent.AgentBase;
63
import eu.etaxonomy.cdm.model.common.Annotation;
64
import eu.etaxonomy.cdm.model.common.CdmBase;
65
import eu.etaxonomy.cdm.model.common.Language;
66
import eu.etaxonomy.cdm.model.description.DescriptionBase;
67
import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
68
import eu.etaxonomy.cdm.model.description.PolytomousKey;
69
import eu.etaxonomy.cdm.model.description.PolytomousKeyNode;
70
import eu.etaxonomy.cdm.model.media.Media;
71
import eu.etaxonomy.cdm.model.media.Rights;
72
import eu.etaxonomy.cdm.model.molecular.Amplification;
73
import eu.etaxonomy.cdm.model.molecular.Primer;
74
import eu.etaxonomy.cdm.model.name.Registration;
75
import eu.etaxonomy.cdm.model.name.TaxonName;
76
import eu.etaxonomy.cdm.model.occurrence.Collection;
77
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
78
import eu.etaxonomy.cdm.model.permission.CRUD;
79
import eu.etaxonomy.cdm.model.permission.Group;
80
import eu.etaxonomy.cdm.model.reference.Reference;
81
import eu.etaxonomy.cdm.model.taxon.Classification;
82
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
83
import eu.etaxonomy.cdm.model.term.DefinedTermBase;
84
import eu.etaxonomy.cdm.model.term.TermNode;
85
import eu.etaxonomy.cdm.model.term.TermTree;
86
import eu.etaxonomy.cdm.model.term.TermVocabulary;
87
import eu.etaxonomy.cdm.model.validation.EntityConstraintViolation;
88
import eu.etaxonomy.cdm.model.validation.EntityValidation;
89
import eu.etaxonomy.cdm.persistence.permission.ICdmPermissionEvaluator;
90
import eu.etaxonomy.cdm.persistence.permission.Role;
91
import eu.etaxonomy.taxeditor.io.ExportManager;
92
import eu.etaxonomy.taxeditor.io.ImportManager;
93
import eu.etaxonomy.taxeditor.model.MessagingUtils;
94
import eu.etaxonomy.taxeditor.preference.PreferencesUtil;
95
import eu.etaxonomy.taxeditor.session.ICdmEntitySessionManager;
96
import eu.etaxonomy.taxeditor.store.internal.TaxeditorStorePlugin;
97
import eu.etaxonomy.taxeditor.ui.dialog.RemotingLoginDialog;
98
import eu.etaxonomy.taxeditor.util.ProgressMonitorClientManager;
99

    
100
/**
101
 * This implementation of ICdmDataRepository depends on hibernate sessions to
102
 * store the data correctly for the current session. No state is held in this
103
 * class.
104
 *
105
 * Only methods that either get or manipulate data are exposed here. So this
106
 * class acts as a facade for the methods in cdmlib-service.
107
 *
108
 * NOTE by AM: TODO this description is outdated since we use remoting now.
109
 *
110
 * @author n.hoffmann
111
 * @created 17.03.2009
112
 */
113
public class CdmStore {
114

    
115
    public static final Resource DEFAULT_APPLICATION_CONTEXT = new ClassPathResource(
116
            "/eu/etaxonomy/cdm/editorApplicationContext.xml",
117
            TaxeditorStorePlugin.class);
118

    
119
    public static final DbSchemaValidation DEFAULT_DB_SCHEMA_VALIDATION = DbSchemaValidation.VALIDATE;
120

    
121
    protected static CdmStore instance;
122

    
123
    private static ContextManager contextManager = new ContextManager();
124

    
125
    private static LoginManager loginManager = new LoginManager();
126

    
127
    private static TermManager termManager = new TermManager();
128

    
129
    private static SearchManager searchManager = new SearchManager();
130

    
131
    private static UseObjectStore useObjectInitializer = new UseObjectStore();
132

    
133
    private static ProgressMonitorClientManager progressMonitorClientManager = new ProgressMonitorClientManager();
134

    
135
    private static CdmStoreConnector job;
136

    
137
    private Language language;
138

    
139
    private ICdmSource cdmSource;
140

    
141
    private boolean isConnected;
142

    
143
    protected static CdmStore getDefault(boolean connecting){
144
        if (instance != null && instance.isConnected()) {
145
            return instance;
146
        } else{// if (instance == null || !instance.isConnected) {
147
            if (connecting){
148
                MessagingUtils.dataSourceNotAvailableWarningDialog(instance);
149
            }else{
150
                MessagingUtils.noDataSourceWarningDialog(instance);
151
            }
152
            return null;
153
        }
154
    }
155

    
156
    protected static CdmStore getDefault(){
157
       return getDefault(false);
158
    }
159

    
160
    /**
161
     * Initialize the with the last edited datasource
162
     * @deprecated this method is not needed anymore after fully changing to remoting.
163
     * Therefore it will be removed
164
     */
165
//    @Deprecated
166
//    public static void connect() {
167
//        try {
168
//            ICdmSource cdmSource = CdmDataSourceRepository.getCurrentCdmSource();
169
//            connect(cdmSource);
170
//        } catch (Exception e) {
171
//            MessagingUtils.messageDialog("Connection to CDM Source Failed", CdmStore.class, "Could not connect to target CDM Source", e);
172
//        }
173
//    }
174

    
175
    /**
176
     * Initialize with a specific datasource
177
     *
178
     * @param datasource
179
     *            a {@link eu.etaxonomy.cdm.database.ICdmDataSource} object.
180
     */
181
    public static void connect(ICdmSource cdmSource) {
182
        connect(cdmSource, DEFAULT_DB_SCHEMA_VALIDATION,
183
                DEFAULT_APPLICATION_CONTEXT);
184
    }
185

    
186
    public static void connect(ICdmSource cdmSource, RemotingLoginDialog loginDialog) {
187
        connect(cdmSource,
188
                DEFAULT_DB_SCHEMA_VALIDATION,
189
                DEFAULT_APPLICATION_CONTEXT,
190
                loginDialog);
191
    }
192

    
193
    /**
194
     * Initialize and provide
195
     *
196
     * @param datasource
197
     * @param dbSchemaValidation
198
     * @param applicationContextBean
199
     */
200
    private static void connect(final ICdmSource cdmSource,
201
            final DbSchemaValidation dbSchemaValidation,
202
            final Resource applicationContextBean) {
203

    
204
        MessagingUtils.info("Connecting to datasource: " + cdmSource);
205

    
206
        job = new CdmStoreConnector(Display.getDefault(), cdmSource,
207
                dbSchemaValidation, applicationContextBean);
208
        job.setUser(true);
209
        job.setPriority(Job.BUILD);
210
        job.schedule();
211
    }
212

    
213
    private static void connect(final ICdmSource cdmSource,
214
            final DbSchemaValidation dbSchemaValidation,
215
            final Resource applicationContextBean,
216
            RemotingLoginDialog remotingLoginDialog) {
217
        RemotingLoginDialog loginDialog = remotingLoginDialog;
218
        if(isActive()) {
219
            // before we connect we clear the entity caches and the sessions
220
           CdmRemoteCacheManager.removeEntityCaches();
221
            if(getCurrentSessionManager(true) != null) {
222
                getCurrentSessionManager(true).disposeAll();
223
            }
224
        }
225
        MessagingUtils.info("Connecting to datasource: " + cdmSource);
226

    
227
        job = new CdmStoreConnector(Display.getDefault(),
228
                cdmSource,
229
                dbSchemaValidation,
230
                applicationContextBean);
231
        job.start(loginDialog);
232

    
233
    }
234

    
235
    public static boolean isConnecting() {
236
        return job != null && job.getState() == Job.RUNNING;
237
    }
238

    
239
    /**
240
     * Closes the current application context
241
     *
242
     * @param monitor
243
     *            a {@link org.eclipse.core.runtime.IProgressMonitor} object.
244
     */
245
    public static void close(final IProgressMonitor monitor) {
246
        Display.getDefault().asyncExec(new Runnable() {
247

    
248
            @Override
249
            public void run() {
250
                getContextManager().notifyContextAboutToStop(monitor);
251
                if ((monitor == null || (!monitor.isCanceled()) && isActive())) {
252
                    getContextManager().notifyContextStop(monitor);
253
                    instance.close();
254
                }
255
            }
256
        });
257
    }
258

    
259
    public static void close(IProgressMonitor monitor, boolean async) {
260
        if(async) {
261
            close(monitor);
262
        } else {
263
            getContextManager().notifyContextAboutToStop(monitor);
264
            if ((monitor == null || (!monitor.isCanceled()) && isActive())) {
265
                getContextManager().notifyContextStop(monitor);
266
                instance.close();
267
            }
268
        }
269
    }
270

    
271
    private void close() {
272
        this.setConnected(false);
273
        this.cdmSource = null;
274
        CdmApplicationState.dispose();
275
    }
276

    
277
    public static void setInstance(CdmApplicationRemoteController applicationController,
278
            ICdmSource cdmSource) {
279
        instance = new CdmStore(applicationController, cdmSource);
280
        CdmApplicationState.setCdmServiceCacher(new CdmServiceCacher());
281
    }
282

    
283
    protected CdmStore(CdmApplicationRemoteController repository,
284
            ICdmSource cdmSource) {
285
        CdmApplicationState.setCurrentAppConfig(repository);
286
        CdmApplicationState.setCurrentDataChangeService(new CdmUIDataChangeService());
287
        this.cdmSource = cdmSource;
288
        setConnected(true);
289
    }
290

    
291
    /**
292
     * All calls to the datastore require
293
     *
294
     * @return
295
     */
296
    private CdmApplicationRemoteController getApplicationConfiguration() {
297
        try {
298
            return CdmApplicationState.getCurrentAppConfig();
299
        } catch (Exception e) {
300
            MessagingUtils.error(CdmStore.class, e);
301
        }
302
        return null;
303
    }
304

    
305
    public static CdmApplicationRemoteController getCurrentApplicationConfiguration() {
306
        CdmStore defaultStore = getDefault();
307
        if (defaultStore != null) {
308
            return defaultStore.getApplicationConfiguration();
309
        }
310
        return null;
311
    }
312

    
313
    private ICdmEntitySessionManager getSessionManager() {
314
        return getCurrentApplicationConfiguration().getCdmEntitySessionManager();
315
    }
316

    
317
    public static ICdmEntitySessionManager getCurrentSessionManager() {
318
        return getCurrentSessionManager(false);
319
    }
320

    
321
    public static  ICdmEntitySessionManager getCurrentSessionManager(boolean connecting) {
322
        CdmStore cdmStore = getDefault(connecting);
323
        if (cdmStore != null) {
324
            return cdmStore.getSessionManager();
325
        }
326
        return null;
327
    }
328

    
329
    /**
330
     * Generic method that will scan the getters of {@link ICdmRepository} for the given service
331
     * interface. If a matching getter is found the according service implementation is returned by
332
     * invoking the getter otherwise the method returns <code>null</code>.
333
     *
334
     * @param <T>
335
     * @param serviceClass
336
     * @return the configured implementation of <code>serviceClass</code> or <code>null</code>
337
     */
338
    public static <T extends IService> T getService(Class<T> serviceClass) {
339
        T service = null;
340
        try {
341
            service = CdmApplicationState.getService(serviceClass);
342
        } catch (CdmApplicationException cae) {
343
            MessagingUtils.error(CdmStore.class, cae);
344
        }
345

    
346
        return service;
347
    }
348

    
349
    /**
350
     * @see #getService(Class)
351
     * As ICommonService is not extending IService we need a specific request here
352
     */
353
    public static ICommonService getCommonService() {
354
        return CdmApplicationState.getCommonService();
355
    }
356

    
357
    public static AuthenticationManager getAuthenticationManager() {
358
        return getCurrentApplicationConfiguration().getAuthenticationManager();
359
    }
360

    
361
    public static ICdmPermissionEvaluator getPermissionEvaluator() {
362
        return getCurrentApplicationConfiguration().getPermissionEvaluator();
363
    }
364

    
365
    /*
366
     * SECURITY RELATED CONVENIENCE METHODS
367
     */
368

    
369
    /**
370
     * @see org.springframework.security.access.PermissionEvaluator#hasPermission(org.springframework.security.core.Authentication, java.lang.Object, java.lang.Object)
371
     *
372
     * @param targetDomainObject
373
     * @param permission
374
     * @return
375
     */
376
    public static boolean currentAuthentiationHasPermission(CdmBase targetDomainObject, EnumSet<CRUD> permission){
377
        //TODO use getCurrentApplicationConfiguration().currentAuthentiationHasPermission(CdmBase targetDomainObject, Operation permission) instead
378
        SecurityContext context = SecurityContextHolder.getContext();
379
        boolean hasPermission = false;
380
        try {
381
            hasPermission = getPermissionEvaluator().hasPermission(context.getAuthentication(), targetDomainObject,
382
                    permission);
383
        } catch (org.springframework.security.access.AccessDeniedException e) {
384
            /* IGNORE */
385
        }
386
        return hasPermission;
387
    }
388

    
389
    /**
390
     * @see org.springframework.security.access.PermissionEvaluator#hasPermission(org.springframework.security.core.Authentication, java.lang.Object, java.lang.Object)
391
     *
392
     * @param targetDomainObject
393
     * @param permission
394
     * @return
395
     */
396
    public static boolean currentAuthentiationHasPermission(Class<? extends CdmBase> targetType, EnumSet<CRUD> permission){
397
        boolean hasPermission = false;
398
        try {
399
            hasPermission = getPermissionEvaluator().hasPermission(getCurrentAuthentiation(), null, targetType.getName(), permission);
400
        } catch (org.springframework.security.access.AccessDeniedException e) {
401
            /* IGNORE */
402
        }
403
        return hasPermission;
404
    }
405

    
406
    public static boolean currentAuthentiationHasOneOfRoles(Role ... roles){
407
        boolean hasPermission = false;
408
        try {
409
            hasPermission =  getPermissionEvaluator().hasOneOfRoles(getCurrentAuthentiation(), roles);
410
        } catch (org.springframework.security.access.AccessDeniedException e) {
411
            /* IGNORE */
412
        }
413
        return hasPermission;
414
    }
415

    
416
    public static Authentication getCurrentAuthentiation() {
417
        SecurityContext context = SecurityContextHolder.getContext();
418
        return context.getAuthentication();
419
    }
420

    
421
    /*
422
     * LANGUAGE
423
     */
424

    
425
    /**
426
     * Provides access to the global default language set in the application preferences.
427
     *
428
     * @return a {@link eu.etaxonomy.cdm.model.common.Language} object.
429
     */
430
    public static Language getDefaultLanguage() {
431
        if (getDefault().getLanguage() == null) {
432
            getDefault().setLanguage(PreferencesUtil.getPreferredDefaultLanguage());
433
        }
434
        return getDefault().getLanguage();
435
    }
436

    
437
    public static void setDefaultLanguage(Language language) {
438
        getDefault().setLanguage(language);
439
    }
440

    
441
    private Language getLanguage() {
442
        return language;
443
    }
444

    
445
    private void setLanguage(Language language) {
446
        this.language = language;
447
    }
448

    
449
    /*
450
     * LOGIN
451
     */
452

    
453
    public static LoginManager getLoginManager() {
454
        return loginManager;
455
    }
456

    
457
    public static ContextManager getContextManager() {
458
        return contextManager;
459
    }
460

    
461
    public static TermManager getTermManager() {
462
        return termManager;
463
    }
464

    
465
    public static SearchManager getSearchManager() {
466
        return searchManager;
467
    }
468

    
469

    
470
    public static ProgressMonitorClientManager getProgressMonitorClientManager() {
471
        return progressMonitorClientManager;
472
    }
473

    
474
    /*
475
     * IMPORT/EXPORT FACTORIES
476
     */
477

    
478
    public static ImportManager getImportManager() {
479
        return ImportManager.NewInstance(getCurrentApplicationConfiguration());
480
    }
481

    
482
    public static ExportManager getExportManager() {
483
        return ExportManager.NewInstance(getCurrentApplicationConfiguration());
484
    }
485

    
486
    /**
487
     * Whether this CdmStore is currently connected to a datasource
488
     *
489
     * @return a boolean.
490
     */
491
    public static boolean isActive() {
492
        return instance != null && instance.isConnected();
493
    }
494

    
495
    public static ICdmSource getActiveCdmSource() {
496
        if (isActive()) {
497
            return instance.getCdmSource();
498
        }
499
        return null;
500
    }
501

    
502
    private ICdmSource getCdmSource() {
503
        return cdmSource;
504
    }
505

    
506
    @SuppressWarnings("unchecked")
507
    public static <T extends CdmBase> IService<T> getService(T cdmBase){
508
        IService<T> service = null;
509
        if(cdmBase!=null){
510
            //get corresponding service
511
            if(cdmBase.isInstanceOf(Reference.class)){
512
                service = (IService<T>) getService(IReferenceService.class);
513
            }
514
            else if (cdmBase.isInstanceOf(AgentBase.class)){
515
                service = (IService<T>) getService(IAgentService.class);
516
            }
517
            else if (cdmBase instanceof TaxonName) {
518
                service = (IService<T>) getService(INameService.class);
519
            }
520
            else if (cdmBase instanceof TaxonBase) {
521
                service = (IService<T>) getService(ITaxonService.class);
522
            }
523
            else if (cdmBase instanceof SpecimenOrObservationBase) {
524
                service = (IService<T>) getService(IOccurrenceService.class);
525
            }
526
            else if (cdmBase instanceof Media) {
527
                service = (IService<T>) getService(IMediaService.class);
528
            }
529
            else if (cdmBase instanceof Collection) {
530
                service = (IService<T>) getService(ICollectionService.class);
531
            }
532
            else if (cdmBase instanceof eu.etaxonomy.cdm.model.permission.User) {
533
                service = (IService<T>) getService(IUserService.class);
534
            }
535
            else if (cdmBase instanceof Group) {
536
            	service = (IService<T>) getService(IGroupService.class);
537
            }
538
            else if (cdmBase instanceof DescriptiveDataSet) {
539
            	service = (IService<T>) getService(IDescriptiveDataSetService.class);
540
            }
541
            else if (cdmBase instanceof TermVocabulary<?>) {
542
            	service = (IService<T>) getService(IVocabularyService.class);
543
            }
544
            else if (cdmBase instanceof DefinedTermBase<?>) {
545
            	service = (IService<T>) getService(ITermService.class);
546
            }
547
            else if (cdmBase instanceof Primer) {
548
                service = (IService<T>) getService(IPrimerService.class);
549
            }
550
            else if (cdmBase instanceof Amplification) {
551
                service = (IService<T>) getService(IAmplificationService.class);
552
            }
553
            else if (cdmBase instanceof PolytomousKey) {
554
                service = (IService<T>) getService(IPolytomousKeyService.class);
555
            }
556
            else if (cdmBase instanceof PolytomousKeyNode) {
557
                service = (IService<T>) getService(IPolytomousKeyNodeService.class);
558
            }
559
            else if (cdmBase instanceof Annotation) {
560
                service = (IService<T>) getService(IAnnotationService.class);
561
            }
562
            else if (cdmBase instanceof Classification) {
563
                service = (IService<T>) getService(IClassificationService.class);
564
            }
565
            else if (cdmBase instanceof DescriptionBase<?>) {
566
                service = (IService<T>) getService(IDescriptionService.class);
567
            }
568
            else if (cdmBase instanceof EntityConstraintViolation) {
569
                service = (IService<T>) getService(IEntityConstraintViolationService.class);
570
            }
571
            else if (cdmBase instanceof EntityValidation) {
572
                service = (IService<T>) getService(IEntityValidationService.class);
573
            }
574
            else if (cdmBase instanceof TermNode) {
575
                service = (IService<T>) getService(ITermNodeService.class);
576
            }
577
            else if (cdmBase instanceof TermTree) {
578
                service = (IService<T>) getService(ITermTreeService.class);
579
            }
580
            else if (cdmBase instanceof GrantedAuthority) {
581
                service = (IService<T>) getService(IGrantedAuthorityService.class);
582
            }
583
            else if (cdmBase instanceof Marker) {
584
                service = (IService<T>) getService(IMarkerService.class);
585
            }
586
            else if (cdmBase instanceof Registration) {
587
                service = (IService<T>) getService(IRegistrationService.class);
588
            }
589
            else if (cdmBase instanceof Rights) {
590
                service = (IService<T>) getService(IRightsService.class);
591
            }
592
            else{
593
            	String message = "Service for entity of class %s not yet implemented in TaxEditor";
594
            	message = String.format(message, cdmBase.getClass().getSimpleName());
595
            	throw new RuntimeException(message);
596
            }
597
        }
598
        return service;
599
    }
600

    
601
    public boolean isConnected() {
602
        return isConnected;
603
    }
604

    
605
    public void setConnected(boolean isConnected) {
606
        this.isConnected = isConnected;
607
    }
608
}
(3-3/13)