How to override metadata of an entity using XML in order to add additional gridVisibleFields? - broadleaf-commerce

I am using blc.version 5.1.5-GA.
When adding targeted products to a product group, the listgrid only displays defaultSku.name. I would like to add additional information to the listgrid.
Here's the relevant entity definition:
#OneToMany(targetEntity = ProductProductGroupXrefImpl.class, mappedBy = "productGroup",
cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region="blProducts")
#BatchSize(size = 50)
#AdminPresentationAdornedTargetCollection(friendlyName = "ProductGroup_Products",
group = GroupName.Details, order = 3000,
joinEntityClass = "com.broadleafcommerce.merchandisinggroup.domain.ProductProductGroupXrefImpl",
targetObjectProperty = "product",
parentObjectProperty = "productGroup",
gridVisibleFields = {"defaultSku.name"})
protected List<ProductProductGroupXref> productXrefs = new ArrayList<>();
Here's some things I've tried with no success, each <mo:field> block is a separate thing I tried:
<mo:overrideItem ceilingEntity="org.broadleafcommerce.core.catalog.domain.ProductGroupImpl">
<mo:field name="defaultSku.ean, defaultSku.name">
<mo:gridVisibleField value="productXrefs"/>
</mo:field>
<mo:field name="productXrefs">
<mo:gridVisibleField value="defaultSku.name, defaultSku.ean"/>
</mo:field>
<mo:field name="defaultSku.ean">
<mo:gridVisibleField value="productXrefs"/>
</mo:field>
<mo:field name="productXrefs">
<mo:gridVisibleField value="defaultSku.ean"/>
</mo:field>
</mo:overrideItem>
I was restarting my tomcat server each time to ensure the changes were actually being loaded. Is there anything I can debug into and inspect in order to confirm this?
Someone had a similar question and he was never able to get XML overriding working. This question also needs an answer: How to override the #AdminPresentation for existing attributes.

I believe the correct format would be:
<mo:overrideItem ceilingEntity="com.broadleafcommerce.merchandisinggroup.domain.ProductGroup">
<mo:field name="productXrefs">
<mo:gridVisibleField value="defaultSku.name"/>
<mo:gridVisibleField value="defaultSku.ean"/>
</mo:field>
</mo:overrideItem>
Note that the ceilingEntity is the interface for product group, not the Impl.

KeeperofDusk's answer technically answered the original question so I accepted it, but the listgrid still wasn't displaying additional gridVisibleFields. It turns out I had the wrong package name in the ceilingEntity attribute.
In my version of blc, the package for ProductGroup is in com.broadleafcommerce.merchandisinggroup.domain, not org.broadleafcommerce.core.catalog.domain.
I debugged into AbstractFieldMetadataProvider#getTargetedOverride.
protected Map<String, MetadataOverride> getTargetedOverride(DynamicEntityDao dynamicEntityDao, String configurationKey, String ceilingEntityFullyQualifiedClassname) {
if (metadataOverrides != null && (configurationKey != null || ceilingEntityFullyQualifiedClassname != null)) {
if (metadataOverrides.containsKey(configurationKey)) {
return metadataOverrides.get(configurationKey);
}
if (metadataOverrides.containsKey(ceilingEntityFullyQualifiedClassname)) {
return metadataOverrides.get(ceilingEntityFullyQualifiedClassname);
}
Class<?> test;
try {
test = Class.forName(ceilingEntityFullyQualifiedClassname);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
if (test.isInterface()) {
//if it's an interface, get the least derive polymorphic concrete implementation
Class<?>[] types = dynamicEntityDao.getAllPolymorphicEntitiesFromCeiling(test);
return metadataOverrides.get(types[types.length-1].getName());
} else {
//if it's a concrete implementation, try the interfaces
Class<?>[] types = test.getInterfaces();
for (Class<?> type : types) {
if (metadataOverrides.containsKey(type.getName())) {
return metadataOverrides.get(type.getName());
}
}
}
}
return null;
}
On this exact line: return metadataOverrides.get(types[types.length-1].getName()); I was always getting null. Proper behavior is that this line should return a LinkedHashMap of the field and its FieldMetadataOverrides.
The types[types.length-1].getName() should resolve to the fully qualified classname for the target ceiling entity. I tried manually evaluating that line in my IDE but kept getting null. At this point I still haven't realized the wrong fully qualified classname was getting passed in.
Then I tried debugging into the controller endpoint in AdminBasicEntityController.
#RequestMapping(value = "/{id}/{collectionField:.*}/add", method = RequestMethod.GET)
public String showAddCollectionItem(HttpServletRequest request, HttpServletResponse response, Model model,
#PathVariable Map<String, String> pathVars,
#PathVariable(value = "id") String id,
#PathVariable(value = "collectionField") String collectionField,
#RequestParam MultiValueMap<String, String> requestParams) throws Exception {
String sectionKey = getSectionKey(pathVars);
String mainClassName = getClassNameForSection(sectionKey);
List<SectionCrumb> sectionCrumbs = getSectionCrumbs(request, sectionKey, id);
ClassMetadata mainMetadata = service.getClassMetadata(getSectionPersistencePackageRequest(mainClassName,
sectionCrumbs, pathVars)).getDynamicResultSet().getClassMetaData();
Property collectionProperty = mainMetadata.getPMap().get(collectionField);
FieldMetadata md = collectionProperty.getMetadata();
It turns out BLC_ADMIN_SECTION associates a section key with a fully qualified classname which is used to resolve the metadata for the entity. I thought the problem was that the wrong fully qualified classname was entered into the ceiling_entity column, so I changed it to org.broadleafcommerce.core.catalog.domain.ProductGroup, which didn't solve the problem. That also makes no sense, because I don't think any information about ProductGroup would render in that admin page if that were the case.
Finally I went to check if that fully qualified classname even exists, and that's when I realized I went on a wild goose chase for 10 hours.
The lesson learned for future googlers is to use auto-complete.

Related

How to read uid of the CMSParagraphComponent on the View

Here is what I exactly want to achieve but didn't have an answer. What's best practice to get the uid of CMSParagraphComponent on the storefront?
DefaultCMSComponentService
protected Collection<String> getEditorProperties(AbstractCMSComponentModel component, boolean readableOnly) {
String code = component.getItemtype();
if (!this.cEditorProperties.containsKey(code)) {
LOG.debug("caching editor properties for CMSComponent [" + component.getItemtype() + "]");
List<String> props = new ArrayList();
Collection<String> systemProps = this.getSystemProperties(component);
Set<AttributeDescriptorModel> attributeDescriptors = this.getTypeService()
.getAttributeDescriptorsForType(this.getTypeService().getComposedTypeForCode(code));
Iterator var8 = attributeDescriptors.iterator();
while (true) {
AttributeDescriptorModel ad;
String qualifier;
do {
do {
if (!var8.hasNext()) {
this.cEditorProperties.put(code, props);
return (Collection) this.cEditorProperties.get(code);
}
ad = (AttributeDescriptorModel) var8.next();
qualifier = ad.getQualifier();
} while (systemProps.contains(qualifier));
} while (readableOnly && !ad.getReadable());
props.add(qualifier);
}
} else {
return (Collection) this.cEditorProperties.get(code);
}
}
public Collection<String> getSystemProperties(AbstractCMSComponentModel component) {
String code = component.getTypeCode();
if (!this.cSystemProperties.containsKey(code)) {
LOG.debug("caching system properties for CMSComponent [" + component.getTypeCode() + "]");
List props = null;
try {
props = (List) Registry.getApplicationContext().getBean(code + "SystemProperties");
} catch (NoSuchBeanDefinitionException var5) {
LOG.debug("No bean found for : " + code + "SystemProperties", var5);
props = this.getSystemProperties();
}
this.cSystemProperties.put(code, props);
}
return (Collection) this.cSystemProperties.get(code);
}
It's not being populated because it's considered as the system property. Hence, as per the above logic system property will not be consider as redable property.
Now question is, How hybris get the list of system property for the given type? In another words, where this Registry.getApplicationContext().getBean(code + "SystemProperties") bean declare?
EDIT: The fact I know is, if Property attribute of AttributeDescriptor is set to false then it consider as system property. But when I checked for uid AttributeDescriptor, it (Property attribute) already set true.
There are some functionalities oob in hybris that uses the uid inside a view. For example the SearchPageController. To be more specific, let's take a look at this method :
private static final String COMPONENT_UID_PATH_VARIABLE_PATTERN = "{componentUid:.*}";
...
#ResponseBody
#RequestMapping(value = "/autocomplete/" + COMPONENT_UID_PATH_VARIABLE_PATTERN, method = RequestMethod.GET)
public AutocompleteResultData getAutocompleteSuggestions(...){
final SearchBoxComponentModel component = (SearchBoxComponentModel) cmsComponentService.getSimpleCMSComponent(componentUid);
}
The actual COMPONENT_UID_PATH_VARIABLE_PATTERN value is in the searchboxcomponent.jsp :
<spring:url value="/search/autocomplete/{/componentuid}" var="autocompleteUrl" htmlEscape="false">
<spring:param name="componentuid" value="${component.uid}"/>
</spring:url>
How does this work? Every time you type something, a call to this endpoint is made, with the component uid extracted using ${component.uid}.
Why does this work? Let's take a look at the productLayout1Page.jsp and take a simple tag from there:
<cms:pageSlot position="CrossSelling" var="comp" element="div" class="productDetailsPageSectionCrossSelling">
<cms:component component="${comp}" element="div" class="productDetailsPageSectionCrossSelling-component"/>
</cms:pageSlot>
Now we see that there is a <cms:component component=${..}.../> tag which reference a component instance and you can access it using ${component.attributeName} inside the component's jsp.
It seems not easy to get the uid on the storefront. But I have managed it like this
Create a dynamic attribute and return uid value from the getter method
Now you can populate this dynamic attribute on the frontend

Dapper Extensions custom ClassMapper isn't called on Insert()

I'm using Dapper Extensions and have defined my own custom mapper to deal with entities with composite keys.
public class MyClassMapper<T> : ClassMapper<T> where T : class
{
public MyClassMapper()
{
// Manage unmappable attributes
IList<PropertyInfo> toIgnore = typeof(T).GetProperties().Where(x => !x.CanWrite).ToList();
foreach (PropertyInfo propertyInfo in toIgnore.ToList())
{
Map(propertyInfo).Ignore();
}
// Manage keys
IList<PropertyInfo> propsWithId = typeof(T).GetProperties().Where(x => x.Name.EndsWith("Id") || x.Name.EndsWith("ID")).ToList();
PropertyInfo primaryKey = propsWithId.FirstOrDefault(x => string.Equals(x.Name, $"{nameof(T)}Id", StringComparison.CurrentCultureIgnoreCase));
if (primaryKey != null && primaryKey.PropertyType == typeof(int))
{
Map(primaryKey).Key(KeyType.Identity);
}
else if (propsWithId.Any())
{
foreach (PropertyInfo prop in propsWithId)
{
Map(prop).Key(KeyType.Assigned);
}
}
AutoMap();
}
}
I also have this test case to test my mapper:
[Test]
public void TestMyAutoMapper()
{
DapperExtensions.DapperExtensions.DefaultMapper = typeof(MyClassMapper<>);
MySubscribtionEntityWithCompositeKey entity = new MySubscribtionEntityWithCompositeKey
{
SubscriptionID = 145,
CustomerPackageID = 32
};
using (var connection = new SqlConnection(CONNECTION_STRING))
{
connection.Open();
var result = connection.Insert(entity);
var key1 = result.SubscriptionID;
var key2 = result.CustomerPackageID;
}
}
Note that I set the default mapper in the test case.
The insert fails and I notive that my customer mapper is never called. I have no documentation on the github page on the topic, so I'm not sure if there's anything else I need to do to make dapper extensions use my mapper.
Thanks in advance!
Looking at your question, you are attempting to write your own defalut class mapper derived from the existing one. I never used this approach; so I do not know why it is not working or whether it should work.
I explicitly map the classes as below:
public class Customer
{
public int CustomerID { get; set; }
public string Name { get; set; }
}
public sealed class CustomerMapper : ClassMapper<Customer>
{
public CustomerMapper()
{
Schema("dbo");
Table("Customer");
Map(x => x.CustomerID).Key(KeyType.Identity);
AutoMap();
}
}
The AutoMap() will map rest of the properties based on conventions. Please refer to these two resources for more information about mapping.
Then I call SetMappingAssemblies at the startup of the project as below:
DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { Assembly.GetExecutingAssembly() });
The GetExecutingAssembly() is used in above code because mapping classes (CustomerMapper and other) are in same assembly which is executing. If those classes are placed in other assembly, provide that assembly instead.
And that's it, it works.
To set the dialect, I call following line just below the SetMappingAssemblies:
DapperExtensions.DapperExtensions.SqlDialect = new DapperExtensions.Sql.SqlServerDialect();
Use your preferred dialect instead of SqlServerDialect.
Apparently, the solution mentioned here may help you achieve what you are actually trying to. But, I cannot be sure, as I said above, I never used it.

ADF RichSelectOneChoice get text (label)

I'm dealing with something that seems to be a trivial task but haven't found a solution: How can I access the text on a RichSelectOneChoice? I've only found the values with richSelectOneChoice.getValue() and valueChangeEvent.getNewValue()
But, how is it possible to access the actual text?
My last attempt was this:
private RichSelectOneChoice selClaim;
public void claimTypeVCL(ValueChangeEvent ve){
Map s = selClaim.getAttributes();
Object ss = s.get(ve.getNewValue());
System.out.println(ss);
}
At the moment the console output is null for the corresponding value, no matter what the choice is.
The ADF component bound to the RichSelectOneChoice object is created as a component with inner elements.
I've also tried the solution proposed by Frank Nimphius here https://community.oracle.com/thread/1050821 with the proper object type (RichSelectOneChoice) but the if clause doesn't execute because the children are not instanceof RichSelectOneChoice as suggested but rather javax.faces.component.UISelectItem and this class doesn't include the getLabel() method and running the code actually throws a wide range of errors related either to casting an object to the target type or null pointers when trying to access the label.
Solved it using the UISelectionItem object and its getItemValue() and getItemLabel() methods instead of getLabel() or getValue(), the latter of which was available but didn't render the expected result.
The working code looks like this:
public String selectedOptionStr;
public void socClaimTypeVCL(ValueChangeEvent ve){
selectedOptionStr = "";
RichSelectOneChoice sct = (RichSelectOneChoice)ve.getSource();
List childList = sct.getChildren();
for (int i = 0; i < childList.size(); i++) {
if (childList.get(i) instanceof javax.faces.component.UISelectItem) {
javax.faces.component.UISelectItem csi = (javax.faces.component.UISelectItem) childList.get(i);
if (csi.getItemValue().toString() == ve.getNewValue().toString()) {
selectedOptionStr = csi.getItemLabel();
}
}
}
}

How do I call a method of an attribute derived from a generic interface, where the specific type is not known?

Core Question:
I have a generic interface IValidatingAttribute<T>, which creates the contract bool IsValid(T value); The interface is implemented by a variety of Attributes, which all serve the purpose of determining if the current value of said Field or Property they decorate is valid per the interface spec that I'm dealing with. What I want to do is create a single validation method that will scan every field and property of the given model, and if that field or property has any attributes that implement IValidatingAttribute<T>, it should validate the value against each of those attributes. So, using reflection I have the sets of fields and properties, and within those sets I can get the list of attributes. How can I determine which attributes implement IValidatingAttribute and then call IsValid(T value)?
background:
I am working on a library project that will be used to develop a range of later projects against the interface for a common third party system. (BL Server, for those interested)
BL Server has a wide range of fairly arcane command structures that have varying validation requirements per command and parameter, and then it costs per transaction to call these commands, so one of the library requirements is to easily define the valdiation requirements at the model level to catch invalid commands before they are sent. It is also intended to aid in the development of later projects by allowing developers to catch invalid models without needing to set up the BL server connections.
Current Attempt:
Here's where I've gotten so far (IsValid is an extension method):
public interface IValidatingAttribute<T>
{
bool IsValid(T value);
}
public static bool IsValid<TObject>(this TObject sourceObject) where TObject : class, new()
{
var properties = typeof(TObject).GetProperties();
foreach (var prop in properties)
{
var attributeData = prop.GetCustomAttributesData();
foreach (var attribute in attributeData)
{
var attrType = attribute.AttributeType;
var interfaces = attrType.GetInterfaces().Where(inf => inf.IsGenericType).ToList();
if (interfaces.Any(infc => infc.Equals(typeof(IValidatingAttribute<>))))
{
var value = prop.GetValue(sourceObject);
//At this point, I know that the current attribute implements 'IValidatingAttribute<>', but I don't know what T is in that implementation.
//Also, I don't know what data type 'value' is, as it's currently boxed as an object.
//The underlying type to value will match the expected T in IValidatingAttribute.
//What I need is something like the line below:
if (!(attribute as IValidatingAttribute<T>).IsValid(value as T)) //I know this condition doesn't work, but it's what I'm trying to do.
{
return false;
}
}
}
return true;
}
}
Example usage:
Just to better explain what I am trying to achieve:
public class SomeBLRequestObject
{
/// <summary>
/// Required, only allows exactly 2 alpha characters.
/// </summary>
[MinCharacterCount(2), MaxCharacterCount(2), IsRequired, AllowedCharacterSet(CharSets.Alpha))]
public string StateCode {get; set;}
}
And then, later on in code:
...
var someBLObj = SomeBLRequestObjectFactory.Create();
if(!someBLObj.IsValid())
{
throw new InvalidObjectException("someBLObj is invalid!");
}
Thank you, I'm really looking for a solution to the problem as it stands, but I'm more than willing to listen if somebody has a viable alternative approach.
I'm trying to go generic extension method with this because there are literally hundreds of the BL Server objects, and I'm going with attributes because each of these objects can have upper double digit numbers of properties, and it's going to make things much, much easier if the requirements for each object are backed in and nice and readable for the next developer to have to use this thing.
Edit
Forgot to mention : This Question is the closest I've found, but what I really need are the contents of \\Do Something in TcKs's answer.
Well, after about 6 hours and a goods nights sleep, I realized that I was over-complicating this thing. Solved it with the following (ExtValidationInfo is the class that the below two extensions are in.):
Jon Skeet's answer over here pointed me at a better approach, although it still smells a bit, this one at least works.
public static bool IsValid<TObject>(this TObject sourceObject) where TObject : class, new()
{
var baseValidationMethod = typeof(ExtValidationInfo).GetMethod("ValidateProperty", BindingFlags.Static | BindingFlags.Public);
var properties = TypeDataHandler<TObject>.Properties;
foreach (var prop in properties)
{
var attributes = prop.GetCustomAttributes(typeof(IValidatingAttribute<>)).ToList();
if (!attributes.Any())
{
continue; // No validators, skip.
}
var propType = prop.PropertyType;
var validationMethod = baseValidationMethod.MakeGenericMethod(propType);
var propIsValid = validationMethod.Invoke(null, prop.GetValue(sourceObject), attributes);
if(!propIsValid)
{
return false;
}
}
return true;
}
public static bool ValidateProperty<TPropType>(TPropType value, List<IValidatingAttribute<TPropType>> validators)
{
foreach (var validator in validators)
{
if (!validator.IsValid(value))
{
return false;
}
}
return true;
}

Using FieldInfo.SetValue with a DynamicObject as argument 2

I ran into a problem today when trying to set a field using FieldInfo.SetValue() passing a DynamicObject as the second argument. In my case, the field is a Guid and the DynamicObject should be able to convert itself to a one (using TryConvert) but it fails with an ArgumentException.
Some code that shows the problem:
// Simple impl of a DynamicObject to prove point
public class MyDynamicObj : DynamicObject
{
public override bool TryConvert(ConvertBinder binder, out object result)
{
result = null;
// Support converting this to a Guid
if (binder.Type == typeof(Guid))
{
result = Guid.NewGuid();
return true;
}
return false;
}
}
public class Test
{
public Guid MyField;
}
class Program
{
static void Main(string[] args)
{
dynamic myObj = new MyDynamicObj();
// This conversion works just fine
Guid guid = myObj;
var test = new Test();
var testField = typeof(Test).GetField("MyField");
// This, however, fails with:
// System.ArgumentException
// Object of type 'ConsoleApplication1.MyDynamicObj' cannot be converted to type 'System.Guid'.
testField.SetValue(test, myObj);
}
}
I'm not very familiar with the whole dynamicness of C# 4, but this felt to me like something that should work.. What am I doing wrong? Is there another way of doing this?
No, this shouldn't work - because the dynamic portion ends where your code ends. The compiler is calling a method with a signature of
void SetValue(Object obj, Object value)
That method call is dynamic, but it's just going to end up passing in a reference to the instance of MyDynamicObj. The call is resolved at execution time, but nothing in SetValue knows anything about the dynamic nature of the object whose reference you're passing in.
Basically you need to perform the dynamic part (the conversion in this case) in your code - the bit that involves the C# 4 compiler doing all its tricks. You've got to perform that conversion, and then you can call SetField.
To put it another way - it's a bit like calling SetField with a field of type XName, but passing in a string. Yes, there's a conversion from string to XName, but it's not SetField's job to work that out. That's the compiler's job.
Now, you can get this to work by making the compiler do some of the work, but you still need to do some with reflection:
static void Main(string[] args)
{
dynamic myObj = new MyDynamicObj();
var test = new Test();
var testField = typeof(Test).GetField("MyField");
var method = typeof(Program)
.GetMethod("Convert", BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(testField.FieldType);
object converted = method.Invoke(null, new object[] {myObj});
testField.SetValue(test, converted);
}
static T Convert<T>(dynamic input)
{
return input;
}
You need an explicit cast to invoke the TryConvert:
testField.SetValue(test, (Guid)myObj);
Not sure if this is what you need though. Maybe there's some way to reflectively say ((DynamicObject)myObj).TryConvert(/*reflected destination type here*/, result)
Other attempts that failed, some of them require things like a certain interface be implemented, so they basically don't make use of TryConvert but maybe an alternative way to accomplish what you want:
Type secondType = testField.FieldType;
TypeConverter tc = TypeDescriptor.GetConverter(typeof(MyDynamicObj));
object secondObject = tc.ConvertTo(myObj,typeof( Guid));
//var secondObject = Convert.ChangeType(myObj, secondType);//Activator.CreateInstance(secondType);
//secondObject = myObj;
testField.SetValue(test, secondObject);

Resources