Commit 1f070783 authored by Ate Douma's avatar Ate Douma

CMS7-7024: splitting up ContentTypeField into separate ContentTypeProperty and ContentTypeChild

- now allows same named child/property for derived ContentTypes, but not for non-derived/explicit defined ContentTypes
- also add support for (but only when derived) multi-typed property/child items, e.g. properties or children with the same name but different type
- fixed and renamed several methods with more appropriate naming
parent 8302915f
/*
* Copyright 2013 Hippo B.V. (http://www.onehippo.com)
*
* Licensed 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.onehippo.cms7.services.contenttype;
public class ContentTypeChildImpl extends ContentTypeItemImpl implements ContentTypeChild {
public ContentTypeChildImpl(String definingType, String name, String itemType) {
super(definingType, name, itemType);
}
public ContentTypeChildImpl(EffectiveNodeTypeChild child) {
super(child);
}
public ContentTypeChildImpl(ContentTypeChildImpl other) {
super(other);
}
@Override
public EffectiveNodeTypeChild getEffectiveNodeTypeItem() {
return (EffectiveNodeTypeChild)super.getEffectiveNodeTypeItem();
}
}
......@@ -22,58 +22,64 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ContentTypeFieldImpl extends Sealable implements ContentTypeField {
public class ContentTypeItemImpl extends Sealable implements ContentTypeItem {
private final String definingType;
private final String name;
private EffectiveNodeTypeItem nti;
private final boolean propertyField;
private boolean derivedField;
private final String fieldType;
private final boolean property;
private boolean derivedItem;
private final String itemType;
private boolean primaryField;
private final String effectiveType;
private boolean primaryItem;
private boolean multiple;
private boolean mandatory;
private boolean autoCreated;
private boolean protect;
private boolean ordered;
private boolean multiTyped;
private List<EffectiveNodeTypeItem> multiTypes = Collections.emptyList();
private List<String> validators = new ArrayList<String>();
private Map<String, List<String>> fieldProperties = new HashMap<String, List<String>>();
private Map<String, List<String>> itemProperties = new HashMap<String, List<String>>();
@Override
protected void doSeal() {
validators = Collections.unmodifiableList(validators);
for (Map.Entry<String,List<String>> entry : fieldProperties.entrySet()) {
for (Map.Entry<String,List<String>> entry : itemProperties.entrySet()) {
entry.setValue(Collections.unmodifiableList(entry.getValue()));
}
fieldProperties = Collections.unmodifiableMap(fieldProperties);
for (EffectiveNodeTypeItem item : multiTypes) {
((Sealable)item).seal();
}
itemProperties = Collections.unmodifiableMap(itemProperties);
}
public ContentTypeFieldImpl(String definingType, String name, String fieldType, String itemType) {
protected ContentTypeItemImpl(String definingType, String name, String itemType, String effectiveType) {
this.definingType = definingType;
this.name = name;
this.fieldType = fieldType;
this.itemType = itemType;
this.propertyField = true;
this.effectiveType = effectiveType;
this.property = true;
}
public ContentTypeFieldImpl(String definingType, String name, String fieldType) {
protected ContentTypeItemImpl(String definingType, String name, String itemType) {
this.definingType = definingType;
this.name = name;
this.fieldType = fieldType;
this.itemType = fieldType;
this.propertyField = false;
this.itemType = itemType;
this.effectiveType = itemType;
this.property = false;
}
public ContentTypeFieldImpl(EffectiveNodeTypeProperty property) {
protected ContentTypeItemImpl(EffectiveNodeTypeProperty property) {
this.definingType = property.getDefiningType();
this.nti = property;
this.primaryField = false;
this.propertyField = true;
this.derivedField = true;
this.primaryItem = false;
this.property = true;
this.derivedItem = true;
this.name = property.getName();
this.itemType = property.getType();
this.fieldType = this.itemType;
this.effectiveType = property.getType();
this.itemType = this.effectiveType;
this.multiple = property.isMultiple();
this.mandatory = property.isMandatory();
this.autoCreated = property.isAutoCreated();
......@@ -81,15 +87,15 @@ public class ContentTypeFieldImpl extends Sealable implements ContentTypeField {
this.ordered = false;
}
public ContentTypeFieldImpl(EffectiveNodeTypeChild child) {
protected ContentTypeItemImpl(EffectiveNodeTypeChild child) {
this.definingType = child.getDefiningType();
this.nti = child;
this.primaryField = false;
this.propertyField = false;
this.derivedField = true;
this.primaryItem = false;
this.property = false;
this.derivedItem = true;
this.name = child.getName();
this.itemType = child.getType();
this.fieldType = this.itemType;
this.effectiveType = child.getType();
this.itemType = this.effectiveType;
this.multiple = child.isMultiple();
this.mandatory = child.isMandatory();
this.autoCreated = child.isAutoCreated();
......@@ -97,22 +103,22 @@ public class ContentTypeFieldImpl extends Sealable implements ContentTypeField {
this.ordered = false;
}
public ContentTypeFieldImpl(ContentTypeFieldImpl other) {
protected ContentTypeItemImpl(ContentTypeItemImpl other) {
this.definingType = other.definingType;
this.nti = other.nti;
this.primaryField = other.primaryField;
this.propertyField = other.propertyField;
this.derivedField = other.derivedField;
this.primaryItem = other.primaryItem;
this.property = other.property;
this.derivedItem = other.derivedItem;
this.name = other.name;
this.effectiveType = other.effectiveType;
this.itemType = other.itemType;
this.fieldType = other.fieldType;
this.multiple = other.multiple;
this.mandatory = other.mandatory;
this.autoCreated = other.autoCreated;
this.protect = other.protect;
this.ordered = other.ordered;
this.validators.addAll(other.validators);
this.fieldProperties.putAll(other.fieldProperties);
this.itemProperties.putAll(other.itemProperties);
}
@Override
......@@ -136,33 +142,33 @@ public class ContentTypeFieldImpl extends Sealable implements ContentTypeField {
}
@Override
public boolean isPropertyField() {
return propertyField;
public boolean isProperty() {
return property;
}
@Override
public boolean isDerivedField() {
return derivedField;
public boolean isDerivedItem() {
return derivedItem;
}
@Override
public String getFieldType() {
return fieldType;
public String getItemType() {
return itemType;
}
@Override
public String getItemType() {
return itemType;
public String getEffectiveType() {
return effectiveType;
}
@Override
public boolean isPrimaryField() {
return primaryField;
public boolean isPrimaryItem() {
return primaryItem;
}
public void setPrimaryField(boolean primaryField) {
public void setPrimaryItem(boolean primaryItem) {
checkSealed();
this.primaryField = primaryField;
this.primaryItem = primaryItem;
}
@Override
......@@ -221,7 +227,38 @@ public class ContentTypeFieldImpl extends Sealable implements ContentTypeField {
}
@Override
public Map<String, List<String>> getFieldProperties() {
return fieldProperties;
public Map<String, List<String>> getItemProperties() {
return itemProperties;
}
@Override
public boolean isMultiTyped() {
return !multiTypes.isEmpty();
}
public List<EffectiveNodeTypeItem> getMultiTypes() {
return multiTypes;
}
public void setMultiPropertyTypes(List<EffectiveNodeTypeProperty> types) {
checkSealed();
if (types != null) {
multiTypes = Collections.unmodifiableList(new ArrayList<EffectiveNodeTypeItem>(types));
}
else {
multiTypes = Collections.emptyList();
}
multiTyped = !multiTypes.isEmpty();
}
public void setMultiChildTypes(List<EffectiveNodeTypeChild> types) {
checkSealed();
if (types != null) {
multiTypes = Collections.unmodifiableList(new ArrayList<EffectiveNodeTypeItem>(types));
}
else {
multiTypes = Collections.emptyList();
}
multiTyped = !multiTypes.isEmpty();
}
}
/*
* Copyright 2013 Hippo B.V. (http://www.onehippo.com)
*
* Licensed 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.onehippo.cms7.services.contenttype;
public class ContentTypePropertyImpl extends ContentTypeItemImpl implements ContentTypeProperty {
public ContentTypePropertyImpl(String definingType, String name, String itemType, String effectiveType) {
super(definingType, name, itemType, effectiveType);
}
public ContentTypePropertyImpl(EffectiveNodeTypeProperty property) {
super(property);
}
public ContentTypePropertyImpl(ContentTypePropertyImpl other) {
super(other);
}
@Override
public EffectiveNodeTypeProperty getEffectiveNodeTypeItem() {
return (EffectiveNodeTypeProperty)super.getEffectiveNodeTypeItem();
}
}
......@@ -142,7 +142,7 @@ class ContentTypesCache extends Sealable implements ContentTypes {
these extended field properties needs to have all fields resolved first which complicates the proper moment for sealing
for (String typeName : typeNodes.keySet()) {
loadContentTypeFieldProperties(types.get(typeName), typeNodes.get(typeName));
loadContentTypeItemProperties(types.get(typeName), typeNodes.get(typeName));
}
*/
......@@ -164,9 +164,9 @@ class ContentTypesCache extends Sealable implements ContentTypes {
resolveContentType(name);
}
// 5th pass: resolve all ContentTypeFields and seal all types
// 5th pass: resolve all ContentTypeItems and seal all types
for (AggregatedContentTypesCache.Key key : actCache.getKeys()) {
resolveContentTypeFieldsAndSeal(actCache.get(key));
resolveContentTypeItemsAndSeal(actCache.get(key));
}
//lock down the cache itself
......@@ -206,8 +206,8 @@ class ContentTypesCache extends Sealable implements ContentTypes {
if (field.isNodeType(HippoNodeType.NT_FIELD)) {
ContentTypeFieldImpl ctf;
String fieldType;
ContentTypeItemImpl cti;
String itemType;
String fieldName = field.getProperty(HippoNodeType.HIPPO_PATH).getString();
......@@ -216,38 +216,43 @@ class ContentTypesCache extends Sealable implements ContentTypes {
continue;
}
fieldType = JcrUtils.getStringProperty(field, HippoNodeType.HIPPOSYSEDIT_TYPE, PropertyType.TYPENAME_STRING);
itemType = JcrUtils.getStringProperty(field, HippoNodeType.HIPPOSYSEDIT_TYPE, PropertyType.TYPENAME_STRING);
if (propertyTypeMappings.containsKey(fieldType)) {
ctf = new ContentTypeFieldImpl(ct.getName(), fieldName, fieldType, propertyTypeMappings.get(fieldType));
if (propertyTypeMappings.containsKey(itemType)) {
cti = new ContentTypePropertyImpl(ct.getName(), fieldName, itemType, propertyTypeMappings.get(itemType));
}
else if (types.containsKey(fieldType)) {
ctf = new ContentTypeFieldImpl(ct.getName(), fieldName, fieldType);
else if (types.containsKey(itemType)) {
cti = new ContentTypeChildImpl(ct.getName(), fieldName, itemType);
}
else if (entCache.getTypes().containsKey(fieldType)) {
ctf = new ContentTypeFieldImpl(ct.getName(), fieldName, fieldType);
else if (entCache.getTypes().containsKey(itemType)) {
cti = new ContentTypeChildImpl(ct.getName(), fieldName, itemType);
}
else {
// TODO: log warn unknown fieldType value
// TODO: log warn unknown itemType value
continue;
}
ctf.setMandatory(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_MANDATORY, false));
ctf.setAutoCreated(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_AUTOCREATED, false));
ctf.setMultiple(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_MULTIPLE, false));
ctf.setOrdered(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_ORDERED, false));
ctf.setProtected(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_PROTECTED, false));
ctf.setPrimaryField(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_PRIMARY, false));
cti.setMandatory(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_MANDATORY, false));
cti.setAutoCreated(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_AUTOCREATED, false));
cti.setMultiple(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_MULTIPLE, false));
cti.setOrdered(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_ORDERED, false));
cti.setProtected(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_PROTECTED, false));
cti.setPrimaryItem(JcrUtils.getBooleanProperty(field, HippoNodeType.HIPPO_PRIMARY, false));
if (field.hasProperty(HippoNodeType.HIPPO_VALIDATORS)) {
Value[] values = field.getProperty(HippoNodeType.HIPPO_VALIDATORS).getValues();
for (Value value : values) {
String validator = value.getString();
if (validator.length() > 0) {
ctf.getValidators().add(validator);
cti.getValidators().add(validator);
}
}
}
ct.getFields().put(ctf.getName(), ctf);
if (cti.isProperty()) {
ct.getProperties().put(cti.getName(), (ContentTypeProperty)cti);
}
else {
ct.getChildren().put(cti.getName(), (ContentTypeChild)cti);
}
}
}
}
......@@ -365,12 +370,12 @@ class ContentTypesCache extends Sealable implements ContentTypes {
return result;
}
private void resolveContentTypeFieldsAndSeal(ContentTypeImpl ct) {
private void resolveContentTypeItemsAndSeal(ContentTypeImpl ct) {
if (!ct.isSealed()) {
for (String s : ct.getSuperTypes()) {
resolveContentTypeFieldsAndSeal(actCache.get(s));
resolveContentTypeItemsAndSeal(actCache.get(s));
}
ct.resolveFields(this);
ct.resolveItems(this);
ct.seal();
}
}
......
......@@ -173,16 +173,20 @@ public class HippoContentTypeServiceTest extends PluginTest {
fail("UnsupportedOperationException expected for ContentTypes.getType(test:test).getSuperTypes().clear()");
} catch (UnsupportedOperationException uoe) {}
try {
ctCache.getType("test:test").getFields().clear();
fail("UnsupportedOperationException expected for ContentTypes.getType(test:test).getFields().clear()");
ctCache.getType("test:test").getChildren().clear();
fail("UnsupportedOperationException expected for ContentTypes.getType(test:test).getChildren().clear()");
} catch (UnsupportedOperationException uoe) {}
try {
ctCache.getType("test:test").getFields().get("test:title").getFieldProperties().clear();
fail("UnsupportedOperationException expected for ContentTypes.getType(test:test).getFields().get(test:title).getFieldProperties().clear()");
ctCache.getType("test:test").getProperties().clear();
fail("UnsupportedOperationException expected for ContentTypes.getType(test:test).getProperties().clear()");
} catch (UnsupportedOperationException uoe) {}
try {
ctCache.getType("test:test").getFields().get("test:title").getValidators().clear();
fail("UnsupportedOperationException expected for ContentTypes.getType(test:test).getFields().get(test:title).getValidators().clear()");
ctCache.getType("test:test").getProperties().get("test:title").getItemProperties().clear();
fail("UnsupportedOperationException expected for ContentTypes.getType(test:test).getProperties().get(test:title).getItemProperties().clear()");
} catch (UnsupportedOperationException uoe) {}
try {
ctCache.getType("test:test").getItem("test:title").getValidators().clear();
fail("UnsupportedOperationException expected for ContentTypes.getType(test:test).getItem(test:title).getValidators().clear()");
} catch (UnsupportedOperationException uoe) {}
// repeat sealed check for EffectiveNodeType underlying the ContentType
......@@ -229,7 +233,8 @@ public class HippoContentTypeServiceTest extends PluginTest {
ContentType ct = ctCache.getType("test:test");
assertNotNull(ct);
assertEquals(4, ct.getFields().size());
assertEquals(3, ct.getProperties().size());
assertEquals(1, ct.getChildren().size());
assertEquals(1, ct.getAggregatedTypes().size());
assertTrue(!ct.getSuperTypes().contains("hippostd:container"));
......@@ -246,8 +251,8 @@ public class HippoContentTypeServiceTest extends PluginTest {
ct = ctCache.getType("test:test");
// added test:extraField shouldn't be merged as there is no matching property in the EffectiveNodeType
assertEquals(4, ct.getFields().size());
assertTrue(!ct.getFields().containsKey("test:extraField"));
assertEquals(3, ct.getProperties().size());
assertTrue(!ct.getProperties().containsKey("test:extraField"));
// adding relaxed mixin should expose and 'enable' the extraField
......@@ -259,10 +264,10 @@ public class HippoContentTypeServiceTest extends PluginTest {
ctCache = service.getContentTypes();
ct = ctCache.getType("test:test");
assertEquals(5, ct.getFields().size());
assertTrue(ct.getFields().containsKey("test:extraField"));
assertTrue(ct.getFields().get("test:extraField").getEffectiveNodeTypeItem().getName().equals("*"));
assertTrue(ct.getFields().get("test:extraField").getEffectiveNodeTypeItem().getDefiningType().equals("hippostd:relaxed"));
assertEquals(4, ct.getProperties().size());
assertTrue(ct.getProperties().containsKey("test:extraField"));
assertTrue(ct.getItem("test:extraField").getEffectiveNodeTypeItem().getName().equals("*"));
assertTrue(ct.getItem("test:extraField").getEffectiveNodeTypeItem().getDefiningType().equals("hippostd:relaxed"));
assertTrue(ct.getAggregatedTypes().contains("hippostd:relaxed"));
assertTrue(ct.getSuperTypes().contains("hippostd:container"));
......@@ -271,15 +276,16 @@ public class HippoContentTypeServiceTest extends PluginTest {
ct = service.getContentTypes().getContentTypeForNodeByPath(session, "/testNode");
assertEquals(4, ct.getFields().size());
assertEquals(3, ct.getProperties().size());
assertEquals(1, ct.getChildren().size());
assertEquals(1, ct.getAggregatedTypes().size());
session.getNode("/testNode").addMixin("hippostd:relaxed");
session.save();
ct = service.getContentTypes().getContentTypeForNodeByPath(session, "/testNode");
assertEquals(5, ct.getFields().size());
assertTrue(ct.getFields().containsKey("test:extraField"));
assertEquals(4, ct.getProperties().size());
assertTrue(ct.getProperties().containsKey("test:extraField"));
assertTrue(ct.getAggregatedTypes().contains("hippostd:relaxed"));
assertTrue(ct.getSuperTypes().contains("hippostd:container"));
......
......@@ -10,23 +10,24 @@
"mixin" : false,
"templateType" : false,
"cascadeValidate" : false,
"fields" : {
"properties" : {
"test:title" : {
"definingType" : "test:test",
"name" : "test:title",
"propertyField" : true,
"derivedField" : false,
"fieldType" : "String",
"property" : true,
"derivedItem" : false,
"itemType" : "String",
"primaryField" : false,
"effectiveType" : "String",
"primaryItem" : false,
"multiple" : false,
"mandatory" : false,
"autoCreated" : false,
"ordered" : false,
"multiTyped" : false,
"multiTypes" : [ ],
"validators" : [ ],
"fieldProperties" : {
"itemProperties" : {
},
"protected" : false,
"effectiveNodeTypeItem" : {
"name" : "test:title",
"definingType" : "test:test",
......@@ -40,54 +41,26 @@
"defaultValues" : [ ],
"type" : "String",
"protected" : false
}
},
"test:child" : {
"definingType" : "test:test",
"name" : "test:child",
"propertyField" : false,
"derivedField" : false,
"fieldType" : "nt:unstructured",
"itemType" : "nt:unstructured",
"primaryField" : false,
"multiple" : false,
"mandatory" : false,
"autoCreated" : false,
"ordered" : false,
"validators" : [ ],
"fieldProperties" : {
},
"protected" : false,
"effectiveNodeTypeItem" : {
"name" : "test:child",
"definingType" : "test:test",
"nodeType" : true,
"residual" : false,
"multiple" : false,
"mandatory" : false,
"autoCreated" : false,
"defaultPrimaryType" : null,
"type" : "nt:unstructured",
"requiredPrimaryTypes" : [ "nt:unstructured" ],
"protected" : false
}
"protected" : false
},
"test:extraField" : {
"definingType" : "test:test",
"name" : "test:extraField",
"propertyField" : true,
"derivedField" : false,
"fieldType" : "String",
"property" : true,
"derivedItem" : false,
"itemType" : "String",
"primaryField" : false,
"effectiveType" : "String",
"primaryItem" : false,
"multiple" : false,
"mandatory" : false,
"autoCreated" : false,
"ordered" : false,
"multiTyped" : false,
"multiTypes" : [ ],
"validators" : [ ],
"fieldProperties" : {
"itemProperties" : {
},
"protected" : false,
"effectiveNodeTypeItem" : {
"name" : "*",
"definingType" : "hippostd:relaxed",
......@@ -101,24 +74,26 @@
"defaultValues" : [ ],
"type" : "String",
"protected" : false
}
},
"protected" : false
},
"jcr:mixinTypes" : {
"definingType" : "nt:base",
"name" : "jcr:mixinTypes",
"propertyField" : true,
"derivedField" : true,
"fieldType" : "Name",
"property" : true,
"derivedItem" : true,
"itemType" : "Name",
"primaryField" : false,
"effectiveType" : "Name",
"primaryItem" : false,
"multiple" : true,
"mandatory" : false,
"autoCreated" : false,
"ordered" : false,
"multiTyped" : false,
"multiTypes" : [ ],
"validators" : [ ],
"fieldProperties" : {
"itemProperties" : {
},
"protected" : true,
"effectiveNodeTypeItem" : {
"name" : "jcr:mixinTypes",
"definingType" : "nt:base",
......@@ -132,24 +107,26 @@
"defaultValues" : [ ],
"type" : "Name",
"protected" : true
}
},
"protected" : true
},
"jcr:primaryType" : {
"definingType" : "nt:base",
"name" : "jcr:primaryType",
"propertyField" : true,
"derivedField" : true,
"fieldType" : "Name",
"property" : true,
"derivedItem" : true,
"itemType" : "Name",
"primaryField" : false,
"effectiveType" : "Name",
"primaryItem" : false,
"multiple" : false,
"mandatory" : true,
"autoCreated" : true,
"ordered" : false,
"multiTyped" : false,
"multiTypes" : [ ],
"validators" : [ ],
"fieldProperties" : {
"itemProperties" : {
},
"protected" : true,
"effectiveNodeTypeItem" : {
"name" : "jcr:primaryType",
"definingType" : "nt:base",
......@@ -163,7 +140,42 @@
"defaultValues" : [ ],
"type" : "Name",
"protected" : true
}
},
"protected" : true
}
},
"children" : {
"test:child" : {