/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.isis.core.metamodel.layoutmetadata.json;
import java.io.IOException;
import java.util.*;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.isis.applib.annotation.MemberGroupLayout.ColumnSpans;
import org.apache.isis.applib.annotation.Render;
import org.apache.isis.applib.annotation.Render.Type;
import org.apache.isis.applib.annotation.When;
import org.apache.isis.applib.annotation.Where;
import org.apache.isis.applib.filter.Filter;
import org.apache.isis.core.commons.lang.ClassExtensions;
import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacet;
import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacetImpl;
import org.apache.isis.core.metamodel.facets.members.order.MemberOrderFacet;
import org.apache.isis.core.metamodel.facets.members.render.RenderFacet;
import org.apache.isis.core.metamodel.facets.object.membergroups.MemberGroupLayoutFacet;
import org.apache.isis.core.metamodel.facets.object.paged.PagedFacet;
import org.apache.isis.core.metamodel.facets.objpropparam.typicallen.TypicalLengthFacet;
import org.apache.isis.core.metamodel.facets.propparam.multiline.MultiLineFacet;
import org.apache.isis.core.metamodel.layout.memberorderfacet.MemberOrderFacetComparator;
import org.apache.isis.core.metamodel.layoutmetadata.*;
import org.apache.isis.core.metamodel.spec.ActionType;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.ObjectSpecifications;
import org.apache.isis.core.metamodel.spec.ObjectSpecifications.MemberGroupLayoutHint;
import org.apache.isis.core.metamodel.spec.feature.Contributed;
import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
public class LayoutMetadataReaderFromJson implements LayoutMetadataReader {
public Properties asProperties(Class<?> domainClass) {
LayoutMetadata metadata;
try {
metadata = readMetadata(domainClass);
} catch (Exception e) {
throw new ReaderException("Failed to locate/parse " + domainClass.getName() + ".layout.json file (" + e.getMessage() + ")", e);
}
if(metadata.getColumns() == null || metadata.getColumns().size() != 4) {
throw new ReaderException("JSON metadata must have precisely 4 columns (prop/prop/prop/coll)");
}
final Properties props = new Properties();
setMemberGroupLayoutColumnSpans(metadata, props);
setMemberGroupLayoutColumnLists(metadata, 0, "left", props);
setMemberGroupLayoutColumnLists(metadata, 1, "middle", props);
setMemberGroupLayoutColumnLists(metadata, 2, "right", props);
int[] memberSeq = {0};
setProperties(metadata, props, memberSeq);
setCollections(metadata, props, memberSeq);
setFreestandingActions(metadata, props);
return props;
}
private static void setMemberGroupLayoutColumnSpans(LayoutMetadata metadata, final Properties props) {
final List<ColumnRepr> columns = metadata.getColumns();
final String columnSpansStr = Joiner.on(",").join(Iterables.transform(columns, new Function<ColumnRepr,Integer>(){
@Override
public Integer apply(ColumnRepr input) {
return input.span;
}}));
props.setProperty("class.memberGroupLayout.columnSpans", columnSpansStr);
}
private static void setMemberGroupLayoutColumnLists(LayoutMetadata metadata, int colIdx, String propkey, Properties props) {
final ColumnRepr column = metadata.getColumns().get(colIdx);
final Map<String, MemberGroupRepr> memberGroups = column.memberGroups;
final String val = memberGroups != null ? Joiner.on(",").join(memberGroups.keySet()) : "";
props.setProperty("class.memberGroupLayout." + propkey, val);
}
private static void setProperties(LayoutMetadata metadata, Properties props, int[] memberSeq) {
final List<ColumnRepr> columns = metadata.getColumns();
for (final ColumnRepr columnRepr : columns) {
final Map<String, MemberGroupRepr> memberGroups = columnRepr.memberGroups;
if(memberGroups == null) {
continue;
}
for (final String memberGroupName : memberGroups.keySet()) {
final MemberGroupRepr memberGroup = memberGroups.get(memberGroupName);
final Map<String, MemberRepr> members = memberGroup.members;
if(members == null) {
continue;
}
setMembersAndAssociatedActions(props, memberGroupName, members, memberSeq);
}
}
}
private static void setCollections(LayoutMetadata metadata, Properties props, int[] memberSeq) {
final ColumnRepr columnRepr = metadata.getColumns().get(3);
final Map<String, MemberRepr> collections = columnRepr.collections;
setMembersAndAssociatedActions(props, null, collections, memberSeq);
}
private static void setMembersAndAssociatedActions(Properties props, final String memberGroupName, final Map<String, MemberRepr> members, int[] memberSeq) {
for(final String memberName: members.keySet()) {
props.setProperty("member." + memberName + ".memberOrder.sequence", ""+ ++memberSeq[0]);
if(memberGroupName != null) {
props.setProperty("member." + memberName + ".memberOrder.name", memberGroupName);
}
final MemberRepr memberRepr = members.get(memberName);
final NamedFacetRepr named = memberRepr.named;
if(named != null) {
props.setProperty("member." + memberName + ".named.value", named.value);
}
final DescribedAsFacetRepr describedAs = memberRepr.describedAs;
if(describedAs!= null) {
props.setProperty("member." + memberName + ".describedAs.value", describedAs.value);
}
final CssClassFacetRepr cssClass = memberRepr.cssClass;
if(cssClass!= null) {
props.setProperty("member." + memberName + ".cssClass.value", cssClass.value);
}
final TypicalLengthFacetRepr typicalLength = memberRepr.typicalLength;
if(typicalLength!= null) {
props.setProperty("member." + memberName + ".typicalLength.value", ""+typicalLength.value);
}
final MultiLineFacetRepr multiLine = memberRepr.multiLine;
if(multiLine!= null) {
props.setProperty("member." + memberName + ".multiLine.numberOfLines", ""+multiLine.numberOfLines);
}
final LabelAtFacetRepr labelAt = memberRepr.labelAt;
if(labelAt!= null) {
props.setProperty("member." + memberName + ".labelAt.value", ""+labelAt.value);
}
final PagedFacetRepr paged = memberRepr.paged;
if(paged != null) {
props.setProperty("member." + memberName + ".paged.value", ""+paged.value);
}
final DisabledFacetRepr disabled = memberRepr.disabled;
if(disabled != null) {
// same default as in Disabled.when()
final When disabledWhen = disabled.when!=null?disabled.when: When.ALWAYS;
props.setProperty("member." + memberName + ".disabled.when", disabledWhen.toString());
// same default as in Disabled.where()
final Where disabledWhere = disabled.where!=null?disabled.where: Where.ANYWHERE;
props.setProperty("member." + memberName + ".disabled.where", disabledWhere.toString());
// same default as in Disabled.reason()
final String disabledReason = disabled.reason!=null?disabled.reason: "";
props.setProperty("member." + memberName + ".disabled.reason", disabledReason);
}
final HiddenFacetRepr hidden = memberRepr.hidden;
if(hidden != null) {
// same default as in Hidden.when()
final When hiddenWhen = hidden.when!=null?hidden.when: When.ALWAYS;
props.setProperty("member." + memberName + ".hidden.when", hiddenWhen.toString());
// same default as in Hidden.where()
final Where hiddenWhere = hidden.where!=null?hidden.where: Where.ANYWHERE;
props.setProperty("member." + memberName + ".hidden.where", hiddenWhere.toString());
}
final RenderFacetRepr render = memberRepr.render;
if(render != null) {
// same default as in Render.Type.value()
final Type renderType = render.value!=null?render.value: Render.Type.EAGERLY;
props.setProperty("member." + memberName + ".render.value", renderType.toString());
}
final Map<String, ActionRepr> actions = memberRepr.actions;
if(actions != null) {
int actSeq = 0;
for(final String actionName: actions.keySet()) {
final ActionRepr actionRepr = actions.get(actionName);
String nameKey = "action." + actionName + ".memberOrder.name";
props.setProperty(nameKey, memberName);
setRemainingActionProperties(props, "action", actionName, actionRepr, ++actSeq);
}
}
}
}
private static void setFreestandingActions(LayoutMetadata metadata, Properties props) {
if(metadata.getActions() == null) {
return;
}
int xeq=0;
final Map<String, ActionRepr> actions = metadata.getActions();
for (final String actionName : actions.keySet()) {
final ActionRepr actionRepr = actions.get(actionName);
setRemainingActionProperties(props, "member", actionName, actionRepr, ++xeq);
}
}
private static void setRemainingActionProperties(Properties props, String prefix, final String actionNameOrig, final ActionRepr actionRepr, final int seq) {
final String actionName = actionNameOrig + ("action".equals(prefix)?"":"()");
props.setProperty(prefix + "." + actionName + ".memberOrder.sequence", ""+ seq);
final NamedFacetRepr actionNamed = actionRepr.named;
if(actionNamed != null) {
props.setProperty(prefix +"." + actionName + ".named.value", actionNamed.value);
}
final DescribedAsFacetRepr describedAs = actionRepr.describedAs;
if(describedAs!= null) {
props.setProperty(prefix +"." + actionName + ".describedAs.value", describedAs.value);
}
final CssClassFacetRepr cssClass = actionRepr.cssClass;
if(cssClass!= null) {
props.setProperty(prefix +"." + actionName + ".cssClass.value", cssClass.value);
}
final CssClassFaFacetRepr cssClassFa = actionRepr.cssClassFa;
if(cssClassFa != null) {
props.setProperty(prefix +"." + actionName + ".cssClassFa.value", cssClassFa.value);
}
}
public LayoutMetadata asLayoutMetadata(Class<?> domainClass) throws ReaderException {
try {
return readMetadata(domainClass);
} catch (IOException e) {
throw new ReaderException(e);
} catch (RuntimeException e) {
throw new ReaderException(e);
}
}
// //////////////////////////////////////
private LayoutMetadata readMetadata(Class<?> domainClass) throws IOException {
final String content = ClassExtensions.resourceContent(domainClass, ".layout.json");
return readMetadata(content);
}
LayoutMetadata readMetadata(final String content) {
final Gson gson = new GsonBuilder().create();
return gson.fromJson(content, LayoutMetadata.class);
}
// //////////////////////////////////////
private final static MemberOrderFacetComparator memberOrderFacetComparator = new MemberOrderFacetComparator(false);
/**
* not API
*/
public String asJson(ObjectSpecification objectSpec) {
final LayoutMetadata metadata = new LayoutMetadata();
metadata.setColumns(Lists.<ColumnRepr>newArrayList());
final MemberGroupLayoutFacet mglf = objectSpec.getFacet(MemberGroupLayoutFacet.class);
final ColumnSpans columnSpans = mglf.getColumnSpans();
Set<String> actionIdsForAssociations = Sets.newTreeSet();
ColumnRepr columnRepr;
columnRepr = addColumnWithSpan(metadata, columnSpans.getLeft());
updateColumnMemberGroups(objectSpec, MemberGroupLayoutHint.LEFT, columnRepr, actionIdsForAssociations);
columnRepr = addColumnWithSpan(metadata, columnSpans.getMiddle());
updateColumnMemberGroups(objectSpec, MemberGroupLayoutHint.MIDDLE, columnRepr, actionIdsForAssociations);
columnRepr = addColumnWithSpan(metadata, columnSpans.getRight());
updateColumnMemberGroups(objectSpec, MemberGroupLayoutHint.RIGHT, columnRepr, actionIdsForAssociations);
columnRepr = addColumnWithSpan(metadata, columnSpans.getCollections());
updateCollectionColumnRepr(objectSpec, columnRepr, actionIdsForAssociations);
addActions(objectSpec, metadata, actionIdsForAssociations);
final Gson gson = new GsonBuilder().setPrettyPrinting().create();
return gson.toJson(metadata);
}
private static void updateColumnMemberGroups(ObjectSpecification objectSpec, final MemberGroupLayoutHint hint, ColumnRepr columnRepr, Set<String> actionIdsForAssociations) {
final List<ObjectAssociation> objectAssociations = propertiesOf(objectSpec);
final Map<String, List<ObjectAssociation>> associationsByGroup = ObjectAssociation.Util.groupByMemberOrderName(objectAssociations);
final List<String> groupNames = ObjectSpecifications.orderByMemberGroups(objectSpec, associationsByGroup.keySet(), hint);
columnRepr.memberGroups = Maps.newLinkedHashMap();
for (String groupName : groupNames) {
final MemberGroupRepr memberGroupRepr = new MemberGroupRepr();
columnRepr.memberGroups.put(groupName, memberGroupRepr);
final List<ObjectAssociation> associationsInGroup = associationsByGroup.get(groupName);
memberGroupRepr.members = Maps.newLinkedHashMap();
if(associationsInGroup == null) {
continue;
}
for (ObjectAssociation assoc : associationsInGroup) {
final MemberRepr memberRepr = newMemberRepr(objectSpec, assoc, actionIdsForAssociations);
memberGroupRepr.members.put(assoc.getId(), memberRepr);
}
}
}
private static void addActions(ObjectSpecification objectSpec, final LayoutMetadata metadata, Set<String> actionIdsForAssociations) {
Map<String, ActionRepr> actions = Maps.newLinkedHashMap();
final List<ObjectAction> actionsOf = actionsOf(objectSpec, actionIdsForAssociations);
for(ObjectAction action: actionsOf) {
actions.put(action.getId(), newActionRepr(objectSpec, action));
}
metadata.setActions(actions);
}
private static ActionRepr newActionRepr(ObjectSpecification objectSpec, ObjectAction action) {
ActionRepr actionRepr = new ActionRepr();
CssClassFacet cssClassFacet = action.getFacet(CssClassFacet.class);
if(cssClassFacet != null && !cssClassFacet.isNoop()) {
CssClassFacetRepr cssClassFacetRepr = new CssClassFacetRepr();
cssClassFacetRepr.value = cssClassFacet.value();
actionRepr.cssClass = cssClassFacetRepr;
}
DescribedAsFacet describedAsFacet = action.getFacet(DescribedAsFacet.class);
if(describedAsFacet != null && !describedAsFacet.isNoop() && !Strings.isNullOrEmpty(describedAsFacet.value())) {
DescribedAsFacetRepr describedAsFacetRepr = new DescribedAsFacetRepr();
describedAsFacetRepr.value = describedAsFacet.value();
actionRepr.describedAs = describedAsFacetRepr;
}
NamedFacet namedFacet = action.getFacet(NamedFacet.class);
if(namedFacet != null && !namedFacet.isNoop()) {
NamedFacetRepr namedFacetRepr = new NamedFacetRepr();
namedFacetRepr.value = namedFacet.value();
actionRepr.named = namedFacetRepr;
}
return actionRepr;
}
private static void updateCollectionColumnRepr(ObjectSpecification objectSpec, ColumnRepr columnRepr, Set<String> actionIdsOfAssociations) {
final List<ObjectAssociation> objectAssociations = collectionsOf(objectSpec);
columnRepr.collections = Maps.newLinkedHashMap();
for(ObjectAssociation assoc: objectAssociations) {
final MemberRepr memberRepr = newMemberRepr(objectSpec, assoc, actionIdsOfAssociations);
columnRepr.collections.put(assoc.getId(), memberRepr);
}
}
private static MemberRepr newMemberRepr(ObjectSpecification objectSpec, ObjectAssociation assoc, Set<String> actionIdsForAssociations) {
final MemberRepr memberRepr = new MemberRepr();
CssClassFacet cssClassFacet = assoc.getFacet(CssClassFacet.class);
if(cssClassFacet != null && !cssClassFacet.isNoop()) {
CssClassFacetRepr cssClassFacetRepr = new CssClassFacetRepr();
cssClassFacetRepr.value = cssClassFacet.value();
memberRepr.cssClass = cssClassFacetRepr;
}
DescribedAsFacet describedAsFacet = assoc.getFacet(DescribedAsFacet.class);
if(describedAsFacet != null && !describedAsFacet.isNoop() && !Strings.isNullOrEmpty(describedAsFacet.value())) {
DescribedAsFacetRepr describedAsFacetRepr = new DescribedAsFacetRepr();
describedAsFacetRepr.value = describedAsFacet.value();
memberRepr.describedAs = describedAsFacetRepr;
}
NamedFacet namedFacet = assoc.getFacet(NamedFacet.class);
if(namedFacet != null && !namedFacet.isNoop()) {
NamedFacetRepr namedFacetRepr = new NamedFacetRepr();
namedFacetRepr.value = namedFacet.value();
memberRepr.named = namedFacetRepr;
}
DisabledFacet disabledFacet = assoc.getFacet(DisabledFacet.class);
if(disabledFacet != null && !disabledFacet.isNoop()) {
DisabledFacetRepr disabledFacetRepr = new DisabledFacetRepr();
if(disabledFacet instanceof DisabledFacetImpl) {
DisabledFacetImpl disabledFacetImpl = (DisabledFacetImpl) disabledFacet;
disabledFacetRepr.reason = Strings.emptyToNull(disabledFacetImpl.getReason());
}
disabledFacetRepr.when = whenAlwaysToNull(disabledFacet.when());
disabledFacetRepr.where = whereAnywhereToNull(disabledFacet.where());
memberRepr.disabled = disabledFacetRepr;
}
HiddenFacet hiddenFacet = assoc.getFacet(HiddenFacet.class);
if(hiddenFacet != null && !hiddenFacet.isNoop()) {
HiddenFacetRepr hiddenFacetRepr = new HiddenFacetRepr();
hiddenFacetRepr.when = whenAlwaysToNull(hiddenFacet.when());
hiddenFacetRepr.where = whereAnywhereToNull(hiddenFacet.where());
memberRepr.hidden = hiddenFacetRepr;
}
MultiLineFacet multiLineFacet = assoc.getFacet(MultiLineFacet.class);
if(multiLineFacet != null && !multiLineFacet.isNoop()) {
MultiLineFacetRepr multiLineFacetRepr = new MultiLineFacetRepr();
multiLineFacetRepr.numberOfLines = multiLineFacet.numberOfLines();
memberRepr.multiLine = multiLineFacetRepr;
}
PagedFacet pagedFacet = assoc.getFacet(PagedFacet.class);
if(pagedFacet != null && !pagedFacet.isNoop()) {
PagedFacetRepr pagedFacetRepr = new PagedFacetRepr();
pagedFacetRepr.value = pagedFacet.value();
memberRepr.paged = pagedFacetRepr;
}
RenderFacet renderFacet = assoc.getFacet(RenderFacet.class);
if(renderFacet != null && !renderFacet.isNoop()) {
RenderFacetRepr renderFacetRepr = new RenderFacetRepr();
renderFacetRepr.value = renderFacet.value();
memberRepr.render = renderFacetRepr;
}
TypicalLengthFacet typicalLengthFacet = assoc.getFacet(TypicalLengthFacet.class);
if(typicalLengthFacet != null && !typicalLengthFacet.isNoop()) {
TypicalLengthFacetRepr typicalLengthFacetRepr = new TypicalLengthFacetRepr();
typicalLengthFacetRepr.value = typicalLengthFacet.value();
memberRepr.typicalLength = typicalLengthFacetRepr;
}
final List<ObjectAction> actions = objectSpec.getObjectActions(
ActionType.USER, Contributed.INCLUDED, ObjectAction.Filters.memberOrderOf(assoc));
if(!actions.isEmpty()) {
memberRepr.actions = Maps.newLinkedHashMap();
sortByMemberOrderFacet(actions);
for (final ObjectAction action : actions) {
final String actionId = action.getId();
memberRepr.actions.put(actionId, new ActionRepr());
actionIdsForAssociations.add(actionId);
}
}
return memberRepr;
}
private static Where whereAnywhereToNull(Where where) {
return where != Where.ANYWHERE? where: null;
}
private static When whenAlwaysToNull(When when) {
return when != When.ALWAYS? when: null;
}
private static void sortByMemberOrderFacet(final List<ObjectAction> actions) {
Collections.sort(actions, new Comparator<ObjectAction>() {
@Override
public int compare(ObjectAction o1, ObjectAction o2) {
final MemberOrderFacet m1 = o1.getFacet(MemberOrderFacet.class);
final MemberOrderFacet m2 = o2.getFacet(MemberOrderFacet.class);
return memberOrderFacetComparator.compare(m1, m2);
}});
}
private static ColumnRepr addColumnWithSpan(final LayoutMetadata metadata, final int span) {
final ColumnRepr col = new ColumnRepr();
metadata.getColumns().add(col);
col.span = span;
return col;
}
private static List<ObjectAssociation> propertiesOf(final ObjectSpecification objSpec) {
return objSpec.getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.PROPERTIES);
}
private static List<ObjectAssociation> collectionsOf(final ObjectSpecification objSpec) {
return objSpec.getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.COLLECTIONS);
}
private static List<ObjectAction> actionsOf(final ObjectSpecification objSpec, final Set<String> excludedActionIds) {
return objSpec.getObjectActions(ActionType.ALL, Contributed.INCLUDED, excluding(excludedActionIds));
}
@SuppressWarnings({ "deprecation" })
private static Filter<ObjectAction> excluding(final Set<String> excludedActionIds) {
return new Filter<ObjectAction>(){
@Override
public boolean accept(ObjectAction t) {
return !excludedActionIds.contains(t.getId());
}
};
}
@Override
public String toString() {
return getClass().getName();
}
}