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.spec.feature;
021
022import java.util.List;
023import java.util.Map;
024
025import com.google.common.base.Function;
026import com.google.common.base.Predicate;
027import com.google.common.base.Strings;
028import com.google.common.collect.Lists;
029import com.google.common.collect.Maps;
030
031import org.apache.isis.applib.annotation.When;
032import org.apache.isis.applib.annotation.Where;
033import org.apache.isis.applib.filter.Filter;
034import org.apache.isis.core.commons.authentication.AuthenticationSession;
035import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
036import org.apache.isis.core.metamodel.consent.Consent;
037import org.apache.isis.core.metamodel.facets.hide.HiddenFacet;
038import org.apache.isis.core.metamodel.facets.members.order.MemberOrderFacet;
039import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
040import org.apache.isis.core.metamodel.specloader.specimpl.ContributeeMember;
041
042/**
043 * Provides reflective access to a field on a domain object.
044 */
045public interface ObjectAssociation extends ObjectMember, CurrentHolder {
046
047    /**
048     * Get the name for the business key, if one has been specified.
049     */
050    String getBusinessKeyName();
051
052    /**
053     * Return the default for this property.
054     */
055    ObjectAdapter getDefault(ObjectAdapter adapter);
056
057    /**
058     * Set the property to it default references/values.
059     */
060    public void toDefault(ObjectAdapter target);
061
062    /**
063     * Whether there are any choices provided (eg <tt>choicesXxx</tt> supporting
064     * method) for the association.
065     */
066    public boolean hasChoices();
067    /**
068     * Returns a list of possible references/values for this field, which the
069     * user can choose from.
070     */
071    public ObjectAdapter[] getChoices(ObjectAdapter object);
072
073
074    /**
075     * Whether there are any auto-complete provided (eg <tt>autoCompleteXxx</tt> supporting
076     * method) for the association.
077     */
078    public boolean hasAutoComplete();
079    /**
080     * Returns a list of possible references/values for this field, which the
081     * user can choose from, based on the provided search argument.
082     */
083    public ObjectAdapter[] getAutoComplete(ObjectAdapter object, String searchArg);
084
085    int getAutoCompleteMinLength();
086
087    /**
088     * Returns true if calculated from other data in the object, that is, should
089     * not be persisted.
090     */
091    boolean isNotPersisted();
092
093    /**
094     * Returns <code>true</code> if this field on the specified object is deemed
095     * to be empty, or has no content.
096     */
097    boolean isEmpty(ObjectAdapter target);
098
099    /**
100     * Determines if this field must be complete before the object is in a valid
101     * state
102     */
103    boolean isMandatory();
104
105
106    // //////////////////////////////////////////////////////
107    // Functions
108    // //////////////////////////////////////////////////////
109
110    public static class Functions {
111        private Functions(){}
112        
113        public static Function<ObjectAssociation, String> toName() {
114            return new Function<ObjectAssociation, String>() {
115                @Override
116                public String apply(final ObjectAssociation oa) {
117                    return oa.getName();
118                }
119            };
120        }
121
122        public static Function<ObjectAssociation, String> toId() {
123            return new Function<ObjectAssociation, String>() {
124                @Override
125                public String apply(final ObjectAssociation oa) {
126                    return oa.getId();
127                }
128            };
129        }
130    }
131    
132    // //////////////////////////////////////////////////////
133    // Predicates
134    // //////////////////////////////////////////////////////
135
136    public static class Predicates {
137        private Predicates(){}
138        
139        public static Predicate<ObjectAssociation> being(final Contributed contributed) {
140            return new Predicate<ObjectAssociation>(){
141                @Override
142                public boolean apply(final ObjectAssociation t) {
143                    return contributed.isIncluded() || 
144                           !(t instanceof ContributeeMember);
145                }
146            };
147        }
148
149        /**
150         * Only fields that are for properties (ie 1:1 associations)
151         */
152        public final static Predicate<ObjectAssociation> PROPERTIES =
153                org.apache.isis.applib.filter.Filters.asPredicate(Filters.PROPERTIES);
154
155        /**
156         * Only fields that are for reference properties (ie 1:1 associations)
157         */
158        public final static Predicate<ObjectAssociation> REFERENCE_PROPERTIES =
159                org.apache.isis.applib.filter.Filters.asPredicate(Filters.REFERENCE_PROPERTIES);
160
161        /**
162         * Only fields that are for properties (ie 1:1 associations)
163         */
164        public final static Predicate<ObjectAssociation> WHERE_VISIBLE_IN_COLLECTION_TABLE =
165                org.apache.isis.applib.filter.Filters.asPredicate(Filters.WHERE_VISIBLE_IN_COLLECTION_TABLE);
166
167        /**
168         * Only fields that are for properties (ie 1:1 associations)
169         */
170        public final static Predicate<ObjectAssociation> WHERE_VISIBLE_IN_STANDALONE_TABLE =
171                org.apache.isis.applib.filter.Filters.asPredicate(Filters.WHERE_VISIBLE_IN_STANDALONE_TABLE);
172
173        /**
174         * All fields (that is, excludes out nothing).
175         */
176        public final static Predicate<ObjectAssociation> ALL =
177                org.apache.isis.applib.filter.Filters.asPredicate(Filters.ALL);
178
179        /**
180         * Only fields that are for collections (ie 1:m associations)
181         */
182        public final static Predicate<ObjectAssociation> COLLECTIONS =
183                org.apache.isis.applib.filter.Filters.asPredicate(Filters.COLLECTIONS);
184
185        /**
186         * Only properties that are visible statically, ie have not been
187         * unconditionally hidden at compile time.
188         */
189        public static final Predicate<ObjectAssociation> VISIBLE_AT_LEAST_SOMETIMES =
190                org.apache.isis.applib.filter.Filters.asPredicate(Filters.VISIBLE_AT_LEAST_SOMETIMES);
191
192        public static final Predicate<ObjectAssociation> staticallyVisible(final Where context) {
193            return org.apache.isis.applib.filter.Filters.asPredicate(Filters.staticallyVisible(context));
194        }
195
196        public static final Predicate<ObjectAssociation> dynamicallyVisible(final AuthenticationSession session, final ObjectAdapter target, final Where where) {
197            return org.apache.isis.applib.filter.Filters.asPredicate(Filters.dynamicallyVisible(session, target, where));
198        }
199
200        public static final Predicate<ObjectAssociation> enabled(final AuthenticationSession session, final ObjectAdapter adapter, final Where where) {
201            return org.apache.isis.applib.filter.Filters.asPredicate(Filters.enabled(session, adapter, where));
202        }
203
204    }
205    
206    // //////////////////////////////////////////////////////
207    // Filters
208    // //////////////////////////////////////////////////////
209
210    public static class Filters {
211
212        private Filters() {
213        }
214
215        /**
216         * Filters only fields that are for properties (ie 1:1 associations)
217         *
218         * @deprecated -use {@link com.google.common.base.Predicate equivalent}
219         */
220        @Deprecated
221        public final static Filter<ObjectAssociation> PROPERTIES = new Filter<ObjectAssociation>() {
222            @Override
223            public boolean accept(final ObjectAssociation association) {
224                return association.isOneToOneAssociation();
225            }
226        };
227
228        /**
229         * Filters only fields that are for reference properties (ie 1:1 associations)
230         *
231         * @deprecated -use {@link com.google.common.base.Predicate equivalent}
232         */
233        @Deprecated
234        public final static Filter<ObjectAssociation> REFERENCE_PROPERTIES = new Filter<ObjectAssociation>() {
235            @Override
236            public boolean accept(final ObjectAssociation association) {
237                return association.isOneToOneAssociation() && 
238                       !association.getSpecification().containsDoOpFacet(ValueFacet.class);
239            }
240        };
241        
242        /**
243         * Filters only fields that are for properties (ie 1:1 associations)
244         *
245         * @deprecated -use {@link com.google.common.base.Predicate equivalent}
246         */
247        @Deprecated
248        public final static Filter<ObjectAssociation> WHERE_VISIBLE_IN_COLLECTION_TABLE = new Filter<ObjectAssociation>() {
249            @Override
250            public boolean accept(final ObjectAssociation association) {
251                final HiddenFacet hiddenFacet = association.getFacet(HiddenFacet.class);
252                return hiddenFacet == null || !hiddenFacet.where().inParentedTable();
253            }
254        };
255
256        /**
257         * Filters only fields that are for properties (ie 1:1 associations)
258         *
259         * @deprecated -use {@link com.google.common.base.Predicate equivalent}
260         */
261        @Deprecated
262        public final static Filter<ObjectAssociation> WHERE_VISIBLE_IN_STANDALONE_TABLE = new Filter<ObjectAssociation>() {
263            @Override
264            public boolean accept(final ObjectAssociation association) {
265                final HiddenFacet hiddenFacet = association.getFacet(HiddenFacet.class);
266                return hiddenFacet == null || !hiddenFacet.where().inStandaloneTable();
267            }
268        };
269
270        /**
271         * Returns all fields (that is, filters out nothing).
272         *
273         * @deprecated -use {@link com.google.common.base.Predicate equivalent}
274         */
275        @Deprecated
276        public final static Filter<ObjectAssociation> ALL = new Filter<ObjectAssociation>() {
277            @Override
278            public boolean accept(final ObjectAssociation property) {
279                return true;
280            }
281        };
282
283        /**
284         * Filters only fields that are for collections (ie 1:m associations)
285         *
286         * @deprecated -use {@link com.google.common.base.Predicate equivalent}
287         */
288        @Deprecated
289        public final static Filter<ObjectAssociation> COLLECTIONS = new Filter<ObjectAssociation>() {
290            @Override
291            public boolean accept(final ObjectAssociation property) {
292                return property.isOneToManyAssociation();
293            }
294        };
295
296        /**
297         * Filters only properties that are visible statically, ie have not been
298         * unconditionally hidden at compile time.
299         *
300         * @deprecated -use {@link com.google.common.base.Predicate equivalent}
301         */
302        @Deprecated
303        public static final Filter<ObjectAssociation> VISIBLE_AT_LEAST_SOMETIMES = new Filter<ObjectAssociation>() {
304            @Override
305            public boolean accept(final ObjectAssociation property) {
306                final HiddenFacet hiddenFacet = property.getFacet(HiddenFacet.class);
307                return hiddenFacet == null || hiddenFacet.when() != When.ALWAYS || hiddenFacet.where() != Where.ANYWHERE;
308            }
309        };
310
311        /**
312        * @deprecated -use {@link com.google.common.base.Predicate equivalent}
313        */
314        @Deprecated
315        public static final Filter<ObjectAssociation> staticallyVisible(final Where context) {
316            return new Filter<ObjectAssociation>() {
317                @Override
318                public boolean accept(final ObjectAssociation association) {
319                    final HiddenFacet facet = association.getFacet(HiddenFacet.class);
320                    if(facet == null) {
321                        return true;
322                    }
323                    return !(facet.where().includes(context) && facet.when() == When.ALWAYS);
324                }
325            };
326        }
327
328        /**
329         * @deprecated -use {@link com.google.common.base.Predicate equivalent}
330         */
331        @Deprecated
332        public static Filter<ObjectAssociation> dynamicallyVisible(final AuthenticationSession session, final ObjectAdapter target, final Where where) {
333            return new Filter<ObjectAssociation>() {
334                @Override
335                public boolean accept(final ObjectAssociation objectAssociation) {
336                    final Consent visible = objectAssociation.isVisible(session, target, where);
337                    return visible.isAllowed();
338                }
339            };
340        }
341
342        /**
343         * @deprecated -use {@link com.google.common.base.Predicate equivalent}
344         */
345        @Deprecated
346        public static Filter<ObjectAssociation> enabled(final AuthenticationSession session, final ObjectAdapter adapter, final Where where) {
347            return new Filter<ObjectAssociation>() {
348                @Override
349                public boolean accept(final ObjectAssociation objectAssociation) {
350                    final Consent usable = objectAssociation.isUsable(session, adapter, where);
351                    return usable.isAllowed();
352                }
353            };
354        }
355
356    }
357
358    // //////////////////////////////////////////////////////
359    // Util
360    // //////////////////////////////////////////////////////
361
362    public static class Util {
363        private Util(){}
364        
365        public static Map<String, List<ObjectAssociation>> groupByMemberOrderName(List<ObjectAssociation> associations) {
366            Map<String, List<ObjectAssociation>> associationsByGroup = Maps.newHashMap();
367            for(ObjectAssociation association: associations) {
368                addAssociationIntoGroup(associationsByGroup, association);
369            }
370            return associationsByGroup;
371        }
372
373        private static void addAssociationIntoGroup(Map<String, List<ObjectAssociation>> associationsByGroup, ObjectAssociation association) {
374            final MemberOrderFacet memberOrderFacet = association.getFacet(MemberOrderFacet.class);
375            if(memberOrderFacet != null) {
376                final String name = memberOrderFacet.name();
377                if(!Strings.isNullOrEmpty(name)) {
378                    getFrom(associationsByGroup, name).add(association);
379                    return;
380                }
381            }
382            getFrom(associationsByGroup, "General").add(association);
383        }
384
385        private static List<ObjectAssociation> getFrom(Map<String, List<ObjectAssociation>> associationsByGroup, final String groupName) {
386            List<ObjectAssociation> list = associationsByGroup.get(groupName);
387            if(list == null) {
388                list = Lists.newArrayList();
389                associationsByGroup.put(groupName, list);
390            }
391            return list;
392        }
393    }
394}