Details
-
Bug
-
Status: Closed
-
Top
-
Resolution: Fixed
-
None
-
None
-
None
-
Pulsar
-
Pulsar 244 - Site Dev APIs 4
Description
Problem description
The BaseHstDynamicComponent sets any picked documents as model on the HstRequest such that referenced documents are available in FTLs or in the PageModelAPI, both v0.9 and v1.0. The code that does this is:
for (DynamicParameter param: componentParametersInfo.getDynamicComponentParameters()) { try { DynamicParameterConfig componentParameterConfig = param.getComponentParameterConfig(); if (componentParameterConfig instanceof JcrPathParameterConfig) { HippoBean bean = getContentBeanForPath(getComponentParameter(param.getName()), request, ((JcrPathParameterConfig)componentParameterConfig).isRelative()); request.setModel(param.getName(), bean); } } catch (ObjectBeanManagerException obme) { log.error("Problem fetching or converting bean", obme); } }
The above makes sure that if there is a dynamic JcrPathParameterConfig component parameter, the HstRequest gets the referenced bean if present set on the model for the name of the parameter. As a result, assume the parameter is called 'document' in the dynamic component parameters, for example the PMA v1.0 should contain something like this:
"page" : { "uid0" : { "id" : "r27", "name" : "homepage", "type" : "component", "componentClass" : "org.hippoecm.hst.component.support.bean.dynamic.BaseHstDynamicComponent", "document" : { "$ref" : "/page/uid1" } } "uid1" : { "type" : "document", "data" : { "name" : "homepage", "displayName" : "Home Page", "summary" : "Summary of the homepage", "date" : 1249891507580, "title" : "This is the homepage", } } }
In general, the above code works and results in the desired output, except in three cases:
- use case 1: When changing the picked document in the CM, it does not update in the rendered page directly: only after saving and navigating to another page and back results in the picked document to become visible
- use case 2: For targeting in case the targeted variants points to, say, a different banner, the targeted banner won't be put as model but just the default banner
- use case 3: in case the picked document shouldn't be on the request or serialized at all
The reason is very delicate: In the code above, there is:
getComponentParameter(param.getName())
This however does not take into account
- that via a query param you can override a specific parameter value in case
HstRequestUtils#isComponentRenderingPreviewRequest'
- that targeting can indicate that not param.getName() should used but a prefixed name for the specific variant
The reason why this normally all 'just' works (worked) is that properties which are modifiable in the CM were always backed by an HstComponent ParametersInfo interface, and fetching the values was always done via the BaseHstComponent
protected <T> T getComponentParametersInfo(final HstRequest request) { return (T) ParameterUtils.getParametersInfo(this, getComponentConfiguration(), request); }
The above usage for example looks something like:
ListViewInfo info = getComponentParametersInfo(request);
String scope = info.getScope();
Now the crux is that the 'ParameterUtils.getParametersInfo' not just creates a proxy instance for the ParametersInfo interface and delegates it to )getComponentParameter(param.getName())_ . The proxy invocation handler is way more sophisticated and handles also use case 1 and 2 above. You can see this in org.hippoecm.hst.core.component.HstParameterInfoProxyFactoryImpl.ParameterInfoInvocationHandler#getParameterValue for the 'query param overriding value logic' and you can see in org.hippoecm.hst.core.component.HstParameterInfoProxyFactoryImpl.ParameterInfoInvocationHandler#getPrefixedParameterName the targeting support.
What about use case 3? Take a look at
DocumentQueryDynamicComponentInfo
The scope part in there:
@Parameter(name = "scope") @JcrPath( isRelative = true, pickerSelectableNodeTypes = {"hippostd:folder"} ) String getScope();
means that 'scope' is obviously not a residual property, but why should it ever be set as a model on the HstRequest? It is input for a search. A subclass should decide what the relevant Model objects are for non-residual properties.
Solution details
Use case 1: BaseHstDynamicComponent is not subclassed
If the BaseHstDynamicComponent is not subclassed, the parameters info interface is DynamicComponentInfo resulting in that there are only residual properties. The
Map<String, Object> getResidualParameterValues();
does return the correct values for the use case 1 and 2 described above, this is handled in org.hippoecm.hst.core.component.HstParameterInfoProxyFactoryImpl.ParameterInfoInvocationHandler#invoke . Therefore, the logic
getComponentParameter(param.getName())
should be replaced by fetching the value via 'getResidualParameterValues' (these are for example also the values which are used in the PageModelAPI, not the 'getComponentParameter(param.getName())'
Use case 2: BaseHstDynamicComponent is subclassed
In this case, there is a subclass MyComponent of BaseHstDynamicComponent . If that subclass still has
@ParametersInfo(type = DynamicComponentInfo.class)
then it works the same as use case 1: all parameters are residual. If however the subclass also has its own parameter info interface, for example:
@ParametersInfo(type = MyComponentInfo.class)
and MyComponentInfo looks something like:
public interface MyComponentInfo extends DynamicComponentInfo { @Parameter(name = "newsdoc") @JcrPath( isRelative = true, pickerSelectableNodeTypes = {"hippostd:folder"} ) String getNewsDoc(); }
then the HstComponent class should decide whether or not the 'getNewsDoc()' is set as model. It is up to the java code and should not be done automatically. This thus also means that the DocumentQueryDynamicComponentInfo#getScope example from above should not be set as model automatically
Implementation details
The implementation is easy after all the input from above. Note
getComponentParameter(param.getName())
should be used to fetch a value, but
componentParametersInfo.getResidualParameterValues().get(param.getName());