001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019
020package org.apache.isis.core.metamodel.services.container;
021
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Map;
025import javax.annotation.PostConstruct;
026import javax.annotation.PreDestroy;
027import com.google.common.base.Predicate;
028import org.apache.isis.applib.*;
029import org.apache.isis.applib.filter.Filter;
030import org.apache.isis.applib.filter.Filters;
031import org.apache.isis.applib.query.Query;
032import org.apache.isis.applib.query.QueryFindAllInstances;
033import org.apache.isis.applib.security.RoleMemento;
034import org.apache.isis.applib.security.UserMemento;
035import org.apache.isis.applib.services.exceprecog.ExceptionRecognizer;
036import org.apache.isis.applib.services.exceprecog.ExceptionRecognizerComposite;
037import org.apache.isis.applib.services.exceprecog.ExceptionRecognizerForType;
038import org.apache.isis.core.commons.authentication.AuthenticationSession;
039import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
040import org.apache.isis.core.commons.authentication.AuthenticationSessionProviderAware;
041import org.apache.isis.core.commons.ensure.Assert;
042import org.apache.isis.core.commons.exceptions.IsisException;
043import org.apache.isis.core.metamodel.adapter.*;
044import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
045import org.apache.isis.core.metamodel.adapter.mgr.AdapterManagerAware;
046import org.apache.isis.core.metamodel.adapter.oid.AggregatedOid;
047import org.apache.isis.core.metamodel.adapter.util.AdapterUtils;
048import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
049import org.apache.isis.core.metamodel.consent.InteractionResult;
050import org.apache.isis.core.metamodel.facets.object.viewmodel.ViewModelFacet;
051import org.apache.isis.core.metamodel.services.container.query.QueryFindByPattern;
052import org.apache.isis.core.metamodel.services.container.query.QueryFindByTitle;
053import org.apache.isis.core.metamodel.spec.ObjectSpecification;
054import org.apache.isis.core.metamodel.spec.SpecificationLoader;
055import org.apache.isis.core.metamodel.spec.SpecificationLoaderAware;
056
057public class  DomainObjectContainerDefault implements DomainObjectContainer, QuerySubmitterAware, ObjectDirtierAware, DomainObjectServicesAware, ObjectPersistorAware, SpecificationLoaderAware, AuthenticationSessionProviderAware, AdapterManagerAware, LocalizationProviderAware, ExceptionRecognizer {
058
059    private ObjectDirtier objectDirtier;
060    private ObjectPersistor objectPersistor;
061    private QuerySubmitter querySubmitter;
062    private SpecificationLoader specificationLookup;
063    private DomainObjectServices domainObjectServices;
064    private AuthenticationSessionProvider authenticationSessionProvider;
065    private AdapterManager adapterManager;
066    private LocalizationProvider localizationProvider;
067
068    public DomainObjectContainerDefault() {
069    }
070
071    // //////////////////////////////////////////////////////////////////
072    // titleOf
073    // //////////////////////////////////////////////////////////////////
074
075    @Override
076    public String titleOf(final Object domainObject) {
077        final ObjectAdapter objectAdapter = adapterManager.adapterFor(domainObject);
078        return objectAdapter.getSpecification().getTitle(objectAdapter, localizationProvider.getLocalization());
079    }
080
081    // //////////////////////////////////////////////////////////////////
082    // newInstance, disposeInstance
083    // //////////////////////////////////////////////////////////////////
084
085    @Override
086    @SuppressWarnings("unchecked")
087    public <T> T newTransientInstance(final Class<T> ofClass) {
088        final ObjectSpecification spec = getSpecificationLookup().loadSpecification(ofClass);
089        if (spec.isParented()) {
090            return newAggregatedInstance(this, ofClass);
091        } else {
092            final ObjectAdapter adapter = doCreateTransientInstance(spec);
093            return (T) adapter.getObject();
094        }
095    }
096
097    @SuppressWarnings("unchecked")
098    @Override
099    public <T extends ViewModel> T newViewModelInstance(Class<T> ofClass, String memento) {
100        final ObjectSpecification spec = getSpecificationLookup().loadSpecification(ofClass);
101        if (!spec.containsFacet(ViewModelFacet.class)) {
102            throw new IsisException("Type must be a ViewModel: " + ofClass);
103        }
104        final ObjectAdapter adapter = doCreateViewModelInstance(spec, memento);
105        if(adapter.getOid().isViewModel()) {
106            return (T)adapter.getObject();
107        } else {
108            throw new IsisException("Object instantiated but was not given a ViewModel Oid; please report as a possible defect in Isis: " + ofClass);
109        }
110    }
111
112    @Override
113    @SuppressWarnings("unchecked")
114    public <T> T newAggregatedInstance(final Object parent, final Class<T> ofClass) {
115        final ObjectSpecification spec = getSpecificationLookup().loadSpecification(ofClass);
116        if (!spec.isParented()) {
117            throw new IsisException("Type must be annotated as @Aggregated: " + ofClass);
118        }
119        final ObjectAdapter adapter = doCreateAggregatedInstance(spec, parent);
120        if (adapter.getOid() instanceof AggregatedOid) {
121            return (T) adapter.getObject();
122        } else {
123            throw new IsisException("Object instantiated but was not given a AggregatedOid (does the configured object store support aggregates?): " + ofClass);
124        }
125    }
126
127    /**
128     * Returns a new instance of the specified class that will have been
129     * persisted.
130     */
131    @Override
132    public <T> T newPersistentInstance(final Class<T> ofClass) {
133        final T newInstance = newTransientInstance(ofClass);
134        persist(newInstance);
135        return newInstance;
136    }
137
138    /**
139     * Returns a new instance of the specified class that has the same persisted
140     * state as the specified object.
141     */
142    @Override
143    public <T> T newInstance(final Class<T> ofClass, final Object object) {
144        if (isPersistent(object)) {
145            return newPersistentInstance(ofClass);
146        } else {
147            return newTransientInstance(ofClass);
148        }
149    }
150
151    /**
152     * Factored out as a potential hook method for subclasses.
153     */
154    protected ObjectAdapter doCreateTransientInstance(final ObjectSpecification spec) {
155        return getDomainObjectServices().createTransientInstance(spec);
156    }
157
158    protected ObjectAdapter doCreateViewModelInstance(final ObjectSpecification spec, final String memento) {
159        return getDomainObjectServices().createViewModelInstance(spec, memento);
160    }
161
162    private ObjectAdapter doCreateAggregatedInstance(final ObjectSpecification spec, final Object parent) {
163        final ObjectAdapter parentAdapter = getAdapterManager().getAdapterFor(parent);
164        return getDomainObjectServices().createAggregatedInstance(spec, parentAdapter);
165    }
166
167    @Override
168    public void remove(final Object persistentObject) {
169        if (persistentObject == null) {
170            throw new IllegalArgumentException("Must specify a reference for disposing an object");
171        }
172        final ObjectAdapter adapter = getAdapterManager().adapterFor(persistentObject);
173        if (!isPersistent(persistentObject)) {
174            throw new RepositoryException("Object not persistent: " + adapter);
175        }
176
177        getObjectPersistor().remove(adapter);
178    }
179
180    @Override
181    public void removeIfNotAlready(final Object object) {
182        if (!isPersistent(object)) {
183            return;
184        }
185        remove(object);
186    }
187
188
189    // //////////////////////////////////////////////////////////////////
190    // injectServicesInto
191    // //////////////////////////////////////////////////////////////////
192
193    @Override
194    public <T> T injectServicesInto(T domainObject) {
195        getDomainObjectServices().injectServicesInto(domainObject);
196        return domainObject;
197    }
198
199    // //////////////////////////////////////////////////////////////////
200    // resolve, objectChanged
201    // //////////////////////////////////////////////////////////////////
202
203    @Override
204    public void resolve(final Object parent) {
205        getDomainObjectServices().resolve(parent);
206    }
207
208    @Override
209    public void resolve(final Object parent, final Object field) {
210        getDomainObjectServices().resolve(parent, field);
211    }
212
213    @Override
214    public void objectChanged(final Object object) {
215        getObjectDirtier().objectChanged(object);
216    }
217
218    // //////////////////////////////////////////////////////////////////
219    // flush, commit
220    // //////////////////////////////////////////////////////////////////
221
222    @Override
223    public boolean flush() {
224        return getDomainObjectServices().flush();
225    }
226
227    @Override
228    public void commit() {
229        getDomainObjectServices().commit();
230    }
231
232    // //////////////////////////////////////////////////////////////////
233    // isValid, validate
234    // //////////////////////////////////////////////////////////////////
235
236    @Override
237    public boolean isValid(final Object domainObject) {
238        return validate(domainObject) == null;
239    }
240
241    @Override
242    public String validate(final Object domainObject) {
243        final ObjectAdapter adapter = getAdapterManager().adapterFor(domainObject);
244        final InteractionResult validityResult = adapter.getSpecification().isValidResult(adapter);
245        return validityResult.getReason();
246    }
247
248    // //////////////////////////////////////////////////////////////////
249    // persistence
250    // //////////////////////////////////////////////////////////////////
251
252    @Override
253    public boolean isPersistent(final Object domainObject) {
254        final ObjectAdapter adapter = getAdapterManager().adapterFor(domainObject);
255        return adapter.representsPersistent();
256    }
257
258    /**
259     * {@inheritDoc}
260     */
261    @Override
262    public void persist(final Object domainObject) {
263        final ObjectAdapter adapter = getAdapterManager().adapterFor(domainObject);
264
265        if(adapter == null) {
266            throw new PersistFailedException("Object not known to framework; instantiate using newTransientInstance(...) rather than simply new'ing up.");
267        }
268        if (adapter.isParented()) {
269            // TODO check aggregation is supported
270            return;
271        }
272        if (isPersistent(domainObject)) {
273            throw new PersistFailedException("Object already persistent; OID=" + adapter.getOid());
274        }
275        getObjectPersistor().makePersistent(adapter);
276    }
277
278    /**
279     * {@inheritDoc}
280     */
281    @Override
282    public void persistIfNotAlready(final Object object) {
283        if (isPersistent(object)) {
284            return;
285        }
286        persist(object);
287    }
288
289    // //////////////////////////////////////////////////////////////////
290    // security
291    // //////////////////////////////////////////////////////////////////
292
293    @Override
294    public UserMemento getUser() {
295        final AuthenticationSession session = getAuthenticationSessionProvider().getAuthenticationSession();
296
297        final String name = session.getUserName();
298        final List<RoleMemento> roleMementos = asRoleMementos(session.getRoles());
299
300        final UserMemento user = new UserMemento(name, roleMementos);
301        return user;
302    }
303
304    private List<RoleMemento> asRoleMementos(final List<String> roles) {
305        final List<RoleMemento> mementos = new ArrayList<RoleMemento>();
306        if (roles != null) {
307            for (final String role : roles) {
308                mementos.add(new RoleMemento(role));
309            }
310        }
311        return mementos;
312    }
313
314    // //////////////////////////////////////////////////////////////////
315    // properties
316    // //////////////////////////////////////////////////////////////////
317
318    @Override
319    public String getProperty(final String name) {
320        return getDomainObjectServices().getProperty(name);
321    }
322
323    @Override
324    public String getProperty(final String name, final String defaultValue) {
325        final String value = getProperty(name);
326        return value == null ? defaultValue : value;
327    }
328
329    @Override
330    public List<String> getPropertyNames() {
331        return getDomainObjectServices().getPropertyNames();
332    }
333
334    // //////////////////////////////////////////////////////////////////
335    // info, warn, error messages
336    // //////////////////////////////////////////////////////////////////
337
338    @Override
339    public void informUser(final String message) {
340        getDomainObjectServices().informUser(message);
341    }
342
343    @Override
344    public void raiseError(final String message) {
345        getDomainObjectServices().raiseError(message);
346    }
347
348    @Override
349    public void warnUser(final String message) {
350        getDomainObjectServices().warnUser(message);
351    }
352
353    // //////////////////////////////////////////////////////////////////
354    // allInstances
355    // //////////////////////////////////////////////////////////////////
356
357    @Override
358    public <T> List<T> allInstances(final Class<T> type, long... range) {
359        return allMatches(new QueryFindAllInstances<T>(type, range));
360    }
361
362    // //////////////////////////////////////////////////////////////////
363    // allMatches
364    // //////////////////////////////////////////////////////////////////
365    
366    @Override
367    public <T> List<T> allMatches(final Class<T> cls, final Predicate<? super T> predicate, long... range) {
368        final List<T> allInstances = allInstances(cls, range);
369        final List<T> filtered = new ArrayList<T>();
370        for (final T instance : allInstances) {
371            if (predicate.apply(instance)) {
372                filtered.add(instance);
373            }
374        }
375        return filtered;
376    }
377
378    @Deprecated
379    @Override
380    public <T> List<T> allMatches(final Class<T> cls, final Filter<? super T> filter, long... range) {
381        return allMatches(cls, Filters.asPredicate(filter), range);
382    }
383    
384    @Override
385    public <T> List<T> allMatches(final Class<T> type, final T pattern, long... range) {
386        Assert.assertTrue("pattern not compatible with type", type.isAssignableFrom(pattern.getClass()));
387        return allMatches(new QueryFindByPattern<T>(type, pattern, range));
388    }
389
390    @Override
391    public <T> List<T> allMatches(final Class<T> type, final String title, long... range) {
392        return allMatches(new QueryFindByTitle<T>(type, title, range));
393    }
394
395    @Override
396    public <T> List<T> allMatches(final Query<T> query) {
397        flush(); // auto-flush any pending changes
398        final List<ObjectAdapter> allMatching = getQuerySubmitter().allMatchingQuery(query);
399        return AdapterUtils.unwrap(allMatching);
400    }
401
402    // //////////////////////////////////////////////////////////////////
403    // firstMatch
404    // //////////////////////////////////////////////////////////////////
405
406    @Override
407    public <T> T firstMatch(final Class<T> cls, final Predicate<T> predicate) {
408        final List<T> allInstances = allInstances(cls); // Have to fetch all, as matching is done in next loop
409        for (final T instance : allInstances) {
410            if (predicate.apply(instance)) {
411                return instance;
412            }
413        }
414        return null;
415    }
416
417    @Deprecated
418    @Override
419    public <T> T firstMatch(final Class<T> cls, final Filter<T> filter) {
420        return firstMatch(cls, Filters.asPredicate(filter));
421    }
422    
423    @Override
424    public <T> T firstMatch(final Class<T> type, final T pattern) {
425        final List<T> instances = allMatches(type, pattern, 0, 1); // No need to fetch more than 1
426        return firstInstanceElseNull(instances);
427    }
428
429    @Override
430    public <T> T firstMatch(final Class<T> type, final String title) {
431        final List<T> instances = allMatches(type, title, 0, 1); // No need to fetch more than 1
432        return firstInstanceElseNull(instances);
433    }
434
435    @Override
436    @SuppressWarnings("unchecked")
437    public <T> T firstMatch(final Query<T> query) {
438        flush(); // auto-flush any pending changes
439        final ObjectAdapter firstMatching = getQuerySubmitter().firstMatchingQuery(query);
440        return (T) AdapterUtils.unwrap(firstMatching);
441    }
442
443    // //////////////////////////////////////////////////////////////////
444    // uniqueMatch
445    // //////////////////////////////////////////////////////////////////
446
447    @Override
448    public <T> T uniqueMatch(final Class<T> type, final Predicate<T> predicate) {
449        final List<T> instances = allMatches(type, predicate, 0, 2); // No need to fetch more than 2.
450        if (instances.size() > 1) {
451            throw new RepositoryException("Found more than one instance of " + type + " matching filter " + predicate);
452        }
453        return firstInstanceElseNull(instances);
454    }
455
456    @Deprecated
457    @Override
458    public <T> T uniqueMatch(final Class<T> type, final Filter<T> filter) {
459        final List<T> instances = allMatches(type, filter, 0, 2); // No need to fetch more than 2.
460        if (instances.size() > 1) {
461            throw new RepositoryException("Found more than one instance of " + type + " matching filter " + filter);
462        }
463        return firstInstanceElseNull(instances);
464    }
465    
466    @Override
467    public <T> T uniqueMatch(final Class<T> type, final T pattern) {
468        final List<T> instances = allMatches(type, pattern, 0, 2); // No need to fetch more than 2.
469        if (instances.size() > 1) {
470            throw new RepositoryException("Found more that one instance of " + type + " matching pattern " + pattern);
471        }
472        return firstInstanceElseNull(instances);
473    }
474
475    @Override
476    public <T> T uniqueMatch(final Class<T> type, final String title) {
477        final List<T> instances = allMatches(type, title, 0, 2); // No need to fetch more than 2.
478        if (instances.size() > 1) {
479            throw new RepositoryException("Found more that one instance of " + type + " with title " + title);
480        }
481        return firstInstanceElseNull(instances);
482    }
483
484    @Override
485    public <T> T uniqueMatch(final Query<T> query) {
486        final List<T> instances = allMatches(query); // No need to fetch more than 2. 
487        if (instances.size() > 1) {
488            throw new RepositoryException("Found more that one instance for query:" + query.getDescription());
489        }
490        return firstInstanceElseNull(instances);
491    }
492
493    private <T> T firstInstanceElseNull(final List<T> instances) {
494        return instances.size() == 0 ? null : instances.get(0);
495    }
496
497    
498    ///////////////////////////////////////////////////////////////
499    // ExceptionRecognizer
500    ///////////////////////////////////////////////////////////////
501
502    static class ExceptionRecognizerForConcurrencyException extends ExceptionRecognizerForType {
503        public ExceptionRecognizerForConcurrencyException() {
504            super(ConcurrencyException.class, prefix("Another user has just changed this data"));
505        }
506    }
507    static class ExceptionRecognizerForRecoverableException extends ExceptionRecognizerForType {
508        public ExceptionRecognizerForRecoverableException() {
509            super(RecoverableException.class);
510        }
511    }
512
513    private final ExceptionRecognizer recognizer = 
514            new ExceptionRecognizerComposite(
515                    new ExceptionRecognizerForConcurrencyException(),
516                    new ExceptionRecognizerForRecoverableException()
517                );
518    
519    /**
520     * Framework-provided implementation of {@link ExceptionRecognizer},
521     * which will automatically recognize any {@link org.apache.isis.applib.RecoverableException}s or
522     * any {@link ConcurrencyException}s.
523     */
524    @Override
525    public String recognize(Throwable ex) {
526        return recognizer.recognize(ex);
527    }
528    
529    ExceptionRecognizer getRecognizer() {
530        return recognizer;
531    }
532
533    @PostConstruct
534    @Override
535    public void init(Map<String, String> properties) {
536        recognizer.init(properties);
537    }
538
539    @PreDestroy
540    @Override
541    public void shutdown() {
542        recognizer.shutdown();
543    }
544
545    // //////////////////////////////////////////////////////////////////
546    // Dependencies
547    // //////////////////////////////////////////////////////////////////
548
549    protected QuerySubmitter getQuerySubmitter() {
550        return querySubmitter;
551    }
552
553    @Override
554    public void setQuerySubmitter(final QuerySubmitter querySubmitter) {
555        this.querySubmitter = querySubmitter;
556    }
557
558    protected DomainObjectServices getDomainObjectServices() {
559        return domainObjectServices;
560    }
561
562    @Override
563    public void setDomainObjectServices(final DomainObjectServices domainObjectServices) {
564        this.domainObjectServices = domainObjectServices;
565    }
566
567    protected SpecificationLoader getSpecificationLookup() {
568        return specificationLookup;
569    }
570
571    @Override
572    public void setSpecificationLookup(final SpecificationLoader specificationLookup) {
573        this.specificationLookup = specificationLookup;
574    }
575
576    protected AuthenticationSessionProvider getAuthenticationSessionProvider() {
577        return authenticationSessionProvider;
578    }
579
580    @Override
581    public void setAuthenticationSessionProvider(final AuthenticationSessionProvider authenticationSessionProvider) {
582        this.authenticationSessionProvider = authenticationSessionProvider;
583    }
584
585    protected AdapterManager getAdapterManager() {
586        return adapterManager;
587    }
588
589    @Override
590    public void setAdapterManager(final AdapterManager adapterManager) {
591        this.adapterManager = adapterManager;
592    }
593
594    protected ObjectDirtier getObjectDirtier() {
595        return objectDirtier;
596    }
597
598    @Override
599    public void setObjectDirtier(final ObjectDirtier objectDirtier) {
600        this.objectDirtier = objectDirtier;
601    }
602
603    protected ObjectPersistor getObjectPersistor() {
604        return objectPersistor;
605    }
606
607    @Override
608    public void setObjectPersistor(final ObjectPersistor objectPersistor) {
609        this.objectPersistor = objectPersistor;
610    }
611
612    @Override
613    public void setLocalizationProvider(final LocalizationProvider localizationProvider) {
614        this.localizationProvider = localizationProvider;
615    }
616
617
618
619
620}