SoftwareProduct
and HardwareProduct
itemtypes extending the standard Product
itemtype.<itemtype code="SoftwareProduct" extends="Product" autocreate="true" generate="true">
<attributes>
<attribute qualifier="license" type="java.lang.String">
<description>License applicable to the software product</description>
<persistence type="property"/>
</attribute>
</attributes>
</itemtype>
<itemtype code="HardwareProduct" extends="Product" autocreate="true" generate="true">
<attributes>
<attribute qualifier="power" type="java.lang.Double">
<description>Watts required to power the product</description>
<persistence type="property"/>
</attribute>
</attributes>
</itemtype>
SoftwareProductData
and HardwareProductData
objects extending the standard ProductData
object.<bean class="mypackage.SoftwareProductData"
extends="de.hybris.platform.commercefacades.product.data.ProductData">
<property name="license" type="java.lang.String"/>
</bean>
<bean class="mypackage.HardwareProductData"
extends="de.hybris.platform.commercefacades.product.data.ProductData">
<property name="power" type="java.lang.Double"/>
</bean>
<bean class="mypackage.SoftwareProductWsDTO"
extends="de.hybris.platform.commercewebservicescommons.dto.product.ProductWsDTO">
<property name="license" type="java.lang.String"/>
</bean>
<bean class="mypackage.HardwareProductWsDTO"
extends="de.hybris.platform.commercewebservicescommons.dto.product.ProductWsDTO">
<property name="power" type="java.lang.Double"/>
</bean>
<!-- Software --!>
<bean id="softwareProductFieldMapper" parent="fieldMapper">
<property name="sourceClass" value="mypackage.SoftwareProductData"/>
<property name="destClass" value="mypackage.SoftwareProductWsDTO"/>
</bean>
<bean parent="fieldSetLevelMapping">
<property name="dtoClass" value="mypackage.SoftwareProductWsDTO"/>
<property name="levelMapping">
<map>
<entry key="BASIC" value="license"/>
<entry key="DEFAULT" value="BASIC"/>
<entry key="FULL" value="DEFAULT"/>
</map>
</property>
</bean>
<!-- Hardware --!>
<bean id="hardwareProductFieldMapper" parent="fieldMapper">
<property name="sourceClass" value="mypackage.HardwareProductData"/>
<property name="destClass" value="mypackage.HardwareProductWsDTO"/>
</bean>
<bean parent="fieldSetLevelMapping">
<property name="dtoClass" value="mypackage.HardwareProductWsDTO"/>
<property name="levelMapping">
<map>
<entry key="BASIC" value="power"/>
<entry key="DEFAULT" value="BASIC"/>
<entry key="FULL" value="DEFAULT"/>
</map>
</property>
</bean>
/products/{productCode}
with a software or hardware product code, it does not return the license
or power
fields. It only returns the fields from the ProductWsDTO
object. Why? While the reason is detailed in the next section, the short answer is: SoftwareProductWsDTO
and HardwareProductWsDTO
are both dynamic types and require additional setup.DataMapper.map()
method, you pass the target type and the field mapping filter, also known as field level mapping. For example:dataMapper.map(productData, ProductWsDTO.class,
"code,name,catalogVersion(catalog(id),version),supercategories(code)");
ProductWsDTO
) and the field mapping filter. If the target object of the mapping is called destination
, the list of allowed mappings in the previous example is:destination.code
destination.name
destination.supercategories.code
destination.catalogVersion.version
destination.catalogVersion.catalog.id
supercategories
is of type CategoryWsDTO
) and then looks for the definition of field mapping level associated to these data types to build the list of allowed mappings.dataMapper.map(productData, ProductWsDTO.class,
"code,name,catalogVersion(DEFAULT),supercategories(DEFAULT)")
DefaultFieldSetBuilder
class present in the webservicecommons
extension.productData
is of type SoftwareProductData
, it will use the configured mapper to transform to a SoftwareProductWsDTO
but will not map the license
field, because it used the field mappings defined for ProductWsDTO
to determine the list of allowed mappings.OrderEntryWsDTO
. The data mapper will recognize the type ProductWsDTO
for the product
attribute and we can't tell the mapper to consider other types like SoftwareProductWsDTO
. Therefore, the list of allowed mappings will never include fields defined in these other types and license
as well as power
will never be mapped.fieldSetBuilder
bean, responsible for generating the list of allowed mapping, so that it can take dynamic types into account. Its default implementation located in the class DefaultFieldSetBuilder
already accounts for that using the subclassRegistry
to lookup potential subclasses. However, the defaultFieldSetBuilder
bean has not its property subclassRegistry
set and consequently, by default, it never checks for subclasses. The first configuration step is to reconfigure the fieldSetBuilder
to inject the subclassRegistry
bean:<alias alias="fieldSetBuilder" name="myFieldSetBuilder"/>
<bean id="myFieldSetBuilder" parent="defaultFieldSetBuilder">
<property name="subclassRegistry" ref="subclassRegistry"/>
</bean>
*-web-spring.xml
file of your OCC extension as the `fieldSetBuilder` bean is declared in the Spring web application context.subclassRegistry
, which can be done by creating a bean extending subclassMapping
. The subclassRegistry
bean fetches all Spring beans extending subclassMapping
during its initialization.<bean parent="subclassMapping">
<property name="parentClass"
value="de.hybris.platform.commercewebservicescommons.dto.product.ProductWsDTO"/>
<property name="subclassesSet">
<set>
<value>mypackage.SoftwareProductWsDTO</value>
<value>mypackage.HardwareProductWsDTO</value>
</set>
</property>
</bean>
*-spring.xml
file of your OCC extension since the subclassRegistry
bean is defined in the Spring core application context.ProductData
) are dynamic types and that the target type shall be resolved dynamically.<bean id="customProductDataObjectFactory"
class="de.hybris.platform.webservicescommons.mapping.config.DynamicTypeFactory"
init-method="init">
<property name="baseType" value="de.hybris.platform.commercefacades.product.data.ProductData"/>
</bean>
*-web-spring.xml
file of your OCC extension. I could not really figure out why the dynamic type factory is needed, as everything was working fine without it.DefaultFieldSetBuilder
does consider dynamic types when building the list of allowed mappings but only when the field filter is not based on field set levels. For example, calling /products/{productCode}?fields=code,name,license
with a software product code will include the license
field. However, /products/{productCode}?fields=DEFAULT
will not. Why? The reason is that the default implementation resolves the field set levels only for the target types and does not include the field set levels of the their sub classes. It's annoying but fortunately very easy to fix with the following customization:public class MyFieldSetBuilder extends DefaultFieldSetBuilder {
@Override
protected Set<String> createFieldSetForLevel(final Class fieldClass, final String prefix, final String levelName, final FieldSetBuilderContext context) {
final Set<String> fieldSet = super.createFieldSetForLevel(fieldClass, prefix, levelName, context);
if (getSubclassRegistry() != null) {
getSubclassRegistry().getSubclasses(fieldClass).forEach(fieldSubclass -> {
if (!context.isRecurencyLevelExceeded(fieldSubclass)) {
context.addToRecurrencyMap(fieldSubclass);
fieldSet.addAll(super.createFieldSetForLevel(fieldSubclass, prefix, levelName, context));
context.removeFromRecurrencyMap(fieldSubclass);
}
});
}
return fieldSet;
}
}
fieldSetBuilder
bean customization presented at the very beginning like this:<alias alias="fieldSetBuilder" name="myFieldSetBuilder"/>
<bean id="myFieldSetBuilder" parent="defaultFieldSetBuilder"
class="mypackage.MyFieldSetBuilder">
<property name="subclassRegistry" ref="subclassRegistry"/>
</bean>
type
in the objects based on a dynamic type, set with the name of the dynamic type (e.g. softwareProductWsDTO
or hardwareProductWsDTO
). It is added automatically when the web service response entity is serialized. Here is an example of response returned by OCC web service after adding a software product to the cart:{
"entry": {
"entryNumber": 0,
"product": {
"type": "softwareProductWsDTO",
"code": "80023414",
"name": "SAP Commerce 2105",
"url": "/c/SAP-Commerce-2105/p/80023414",
"license": "SAP"
},
"quantity": 2,
[...]
},
[...]
}
type
or you want to rename the field to be more meaningful, you have to re-configure the jsonHttpMessageConverter
bean as following:<alias name="myJsonHttpMessageConverter" alias="jsonHttpMessageConverter"/>
<bean id="myJsonHttpMessageConverter" parent="defaultJsonHttpMessageConverter">
<property name="marshallerProperties">
<map merge="true">
<entry key="eclipselink.json.type-attribute-name" value="__class"/>
</map>
</property>
</bean>
@XmlType
annotation.<bean class="mypackage.SoftwareProductWsDTO"
extends="de.hybris.platform.commercewebservicescommons.dto.product.ProductWsDTO">
<import type="javax.xml.bind.annotation.XmlType"/>
<annotations>@XmlType(name = "software")</annotations>
[...]
</bean>
<bean class="mypackage.HardwareProductWsDTO"
extends="de.hybris.platform.commercewebservicescommons.dto.product.ProductWsDTO">
<import type="javax.xml.bind.annotation.XmlType"/>
<annotations>@XmlType(name = "hardware")</annotations>
[...]
</bean>
/products/{productCode}
can return one of ProductWsDTO
, SoftwareProductWsDTO
and HardwareProductWsDTO
. Secondly, Swagger does not know about dynamic types and does not list them under the Models section. The first problem seems to be caused by a limitation in Swagger v2. It accepts only one response type for an endpoint and does not support subtypes. This limitation is lifted from Swagger v3 but SAP Commerce 2205 still runs Swagger v2. The second problem can be solved by adding manually the annotation @JsonSubTypes
to the base type like in the following example:<bean class="de.hybris.platform.commercewebservicescommons.dto.product.ProductWsDTO">
<import type="com.fasterxml.jackson.annotation.JsonSubTypes"/>
<import type="com.fasterxml.jackson.annotation.JsonSubTypes.Type"/>
<annotations>
@JsonSubTypes({
@Type(mypackage.SoftwareProductWsDTO.class),
@Type(mypackage.HardwareProductWsDTO.class),
})
</annotations>
</bean>
package de.hybris.platform.webservicescommons.swagger;
[...]
@Component
@Order(-2147482650)
public class DynamicTypeOperationModelsProvider implements OperationModelsProviderPlugin {
protected final TypeResolver typeResolver;
protected final SubclassRegistry subclassRegistry;
@Autowired
public DynamicTypeOperationModelsProvider(final TypeResolver typeResolver, final SubclassRegistry subclassRegistry) {
this.typeResolver = typeResolver;
this.subclassRegistry = subclassRegistry;
}
@Override
public void apply(final RequestMappingContext requestMappingContext) {
final Class<?> returnTypeClass = forClass(requestMappingContext.getReturnType());
final Set<Class> returnTypeSubclasses = subclassRegistry.getAllSubclasses(returnTypeClass);
if (isNotEmpty(returnTypeSubclasses)) {
returnTypeSubclasses.forEach(returnTypeSubclass -> {
requestMappingContext.operationModelsBuilder().addReturn(typeResolver.resolve(
returnTypeSubclass, new java.lang.reflect.Type[0]));
});
}
}
protected Class<?> forClass(final ResolvedType resolvedType) {
return this.typeResolver.resolve(resolvedType, new java.lang.reflect.Type[0]).getErasedType();
}
@Override
public boolean supports(final DocumentationType documentationType) {
return SwaggerPluginSupport.pluginDoesApply(documentationType);
}
}
de.hybris.platform.webservicescommons.swagger
package, in one of your OCC extension. Otherwise, your plugin won't load. Swagger is configured via the WebConfig
class set as configuration for the Spring MVC servlet. There is unfortunately no mechanism available to extend this configuration as of now and the only trick available to us is to hijack the standard SAP Commerce packages, so that the component scan finds our custom plugin.type
field for routing.<bean class="mypackage.SoftwareProductWsDTO"
extends="de.hybris.platform.commercewebservicescommons.dto.product.ProductWsDTO">
<import type="javax.xml.bind.annotation.XmlType"/>
<annotations>@XmlType(name = "software")</annotations>
[...]
</bean>
<bean class="mypackage.HardwareProductWsDTO"
extends="de.hybris.platform.commercewebservicescommons.dto.product.ProductWsDTO">
<import type="javax.xml.bind.annotation.XmlType"/>
<annotations>@XmlType(name = "hardware")</annotations>
[...]
</bean>
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
8 | |
2 | |
2 | |
2 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 |