MapStruct protobuf List to Pojo Mapping - mapstruct

As part of GRPC api I am trying to map proto autogenerated classes to pojo. This is the .proto file
message AccountModelProto
{
repeated VerificationModelProto verification = 1;
}
message VerificationModelProto
{
string status = 1;
string comment = 2;
string verificationType = 3;
repeated VerificationAttributeModelProto verificationAttributes = 4;
}
message VerificationAttributeModelProto
{
string type = 1;
string label = 2;
bool attributeStatus = 3;
}
The mapper for the above code is. I referred to the examples provided by mapstruct according to it I don't need to provide explicit mapping of VerificationModelProto to List but I am getting compiler error
error: Can't map property "Collection verification" to "VerificationModelProto verificationList". Consider to declare/implement a mapping method: "VerificationModelProto map(Collection value)".
#Mapping(source = "verification", target = "verificationList", qualifiedByName = "verificationModelToVerificationProtoMapping")
AccountModelProto map(AccountModel accountModel);
#Named("verificationModelToVerificationProtoMapping")
default VerificationModelProto map (VerificationModel verificationModel)
{
VerificationModelProto.Builder builder = VerificationModelProto.newBuilder()
.setComment(verificationModel.getComment())
.setStatus(verificationModel.getStatus().toString())
.setVerificationType(verificationModel.getVerificationType());
for(VerificationAttributeModel verificationAttributeModel: verificationModel.getVerificationAttributes())
{
builder.addVerificationAttributes(getVerificationAttributeBuilder(verificationAttributeModel));
}
return builder.build();
}
default VerificationAttributeModelProto getVerificationAttributeBuilder(VerificationAttributeModel verificationAttributeModel)
{
VerificationAttributeModelProto.Builder builder = VerificationAttributeModelProto.newBuilder()
.setAttributeStatus(verificationAttributeModel.getAttributeStatus())
.setType(verificationAttributeModel.getType())
.setLabel(verificationAttributeModel.getLabel());
return builder.build();
}
How to get through this. I added CollectionMappingStrategy as CollectionMappingStrategy.ADDER_PREFERRED.

Related

How to support tokenized and untokenized search at the same time

I try to make hibernate search to support both tokenized and untokenized search(pardon me if I use the wrong term here). An example is as following.
I have a list of entities of the following type.
#Entity
#Indexed
#NormalizerDef(name = "lowercase",
filters = {
#TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
#TokenFilterDef(factory = LowerCaseFilterFactory.class)
}
)
public class Deal {
//other fields omitted for brevity purposes
#Field(store = Store.YES)
#Field(name = "name_Sort", store = Store.YES, normalizer= #Normalizer(definition="lowercase"))
#SortableField(forField = "name_Sort")
#Column(name = "NAME")
private String name = "New Deal";
//Getters/Setters omitted here
}
I also used the keyword method to build the query builder shown as follows. The getSearchableFields method returns a list of searchable fields. In the this example, "name" will be in this returned list as the field name in Deal is searchable.
protected Query inputFilterBuilder() {
return queryBuilder.keyword()
.wildcard().onFields(getSearchableFields())
.matching("*" + searchRequest.getQuery().toLowerCase() + "*").createQuery();
}
This setup works fine when I only use an entire words to search. For example, if I have two Deal entity, one's name is "Practical Concrete Hat" and the other one's name is "Practical Cotton Cheese". When searching by "Practical", I get these two entities back. But when searching by "Practical Co", I get 0 entity back. The reason is because the field name is tokenized and "Practical Co" is not a key word.
My question is how to support both search at the same time so these 2 entities are returned if searching by "Practical" or "Practical Co".
I read through the official hibernate search documentation and my hunch is that I should add one more field that is for untokenized search. Perhaps the way I construct the query builder needs to be updated as well?
Update
Not working solution using SimpleQueryString.
Based on the provided answer, I've written the following query builder logic. However, it doesn't work.
protected Query inputFilterBuilder() {
String[] searchableFields = getSearchableFields();
if(searchableFields.length == 0) {
return queryBuilder.simpleQueryString().onField("").matching("").createQuery();
}
SimpleQueryStringMatchingContext simpleQueryStringMatchingContext = queryBuilder.simpleQueryString().onField(searchableFields[0]);
for(int i = 1; i < searchableFields.length; i++) {
simpleQueryStringMatchingContext = simpleQueryStringMatchingContext.andField(searchableFields[i]);
}
return simpleQueryStringMatchingContext
.matching("\"" + searchRequest.getQuery() + "\"").createQuery();
}
Working solution using separate analyzer for query and phrase queries.
I found from the official documentation that we can use phrase queries to search for more than one word. So I wrote the following query builder method.
protected Query inputFilterBuilder() {
String[] searchableFields = getSearchableFields();
if(searchableFields.length == 0) {
return queryBuilder.phrase().onField("").sentence("").createQuery();
}
PhraseMatchingContext phraseMatchingContext = queryBuilder.phrase().onField(searchableFields[0]);
for(int i = 1; i < searchableFields.length; i++) {
phraseMatchingContext = phraseMatchingContext.andField(searchableFields[i]);
}
return phraseMatchingContext.sentence(searchRequest.getQuery()).createQuery();
}
This does not work for search using more than one word with a space in between. Then I added separate analyzers for indexing and querying as suggested, all of a sudden, it works.
Analyzers definitons:
#AnalyzerDef(name = "edgeNgram", tokenizer = #TokenizerDef(factory = WhitespaceTokenizerFactory.class),
filters = {
#TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
#TokenFilterDef(factory = LowerCaseFilterFactory.class),
#TokenFilterDef(factory = EdgeNGramFilterFactory.class,
params = {
#Parameter(name = "minGramSize", value = "1"),
#Parameter(name = "maxGramSize", value = "10")
})
})
#AnalyzerDef(name = "edgeNGram_query", tokenizer = #TokenizerDef(factory = WhitespaceTokenizerFactory.class),
filters = {
#TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
#TokenFilterDef(factory = LowerCaseFilterFactory.class)
})
Annotation for Deal name field:
#Field(store = Store.YES, analyzer = #Analyzer(definition = "edgeNgram"))
#Field(name = "edgeNGram_query", store = Store.YES, analyzer = #Analyzer(definition = "edgeNGram_query"))
#Field(name = "name_Sort", store = Store.YES, normalizer= #Normalizer(definition="lowercase"))
#SortableField(forField = "name_Sort")
#Column(name = "NAME")
private String name = "New Deal";
Code that override name field's analyzer to use the query analyzer
String[] searchableFields = getSearchableFields();
if(searchableFields.length > 0) {
EntityContext entityContext = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder().forEntity(this.getClass().getAnnotation(SearchType.class).clazz()).overridesForField(searchableFields[0], "edgeNGram_query");
for(int i = 1; i < searchableFields.length; i++) {
entityContext.overridesForField(searchableFields[i], "edgeNGram_query");
}
queryBuilder = entityContext.get();
}
Follow up question
Why does the above tweak actually works?
Your problem here is the wildcard query. Wildcard queries do not support tokenization: they only work on single tokens. In fact, they don't even support normalization, which is why you had to lowercase the user input yourself...
The solution would not be to mix tokenized and untokenized search (that's possible, but wouldn't really solve your problem). The solution would be to forget about wildcard queries altogether and use an edgengram filter in your analyzer.
See this answer for an extended explanation.
If you use the ELasticsearch integration, you will have to rely on a hack to make the "query-only" analyzer work properly. See here.

Build dynamic LINQ queries from a string - Use Reflection?

I have some word templates(maybe thousands). Each template has merge fields which will be filled from database. I don`t like writing separate code for every template and then build the application and deploy it whenever a template is changed or a field on the template is added!
Instead, I'm trying to define all merge fields in a separate xml file and for each field I want to write the "query" which will be called when needed. EX:
mergefield1 will call query "Case.Parties.FirstOrDefault.NameEn"
mergefield2 will call query "Case.CaseNumber"
mergefield3 will call query "Case.Documents.FirstOrDefault.DocumentContent.DocumentType"
Etc,
So, for a particular template I scan its merge fields, and for each merge field I take it`s "query definition" and make that request to database using EntityFramework and LINQ. Ex. it works for these queries: "TimeSlots.FirstOrDefault.StartDateTime" or
"Case.CaseNumber"
This will be an engine which will generate word documents and fill it with merge fields from xml. In addition, it will work for any new template or new merge field.
Now, I have worked a version using reflection.
public string GetColumnValueByObjectByName(Expression<Func<TEntity, bool>> filter = null, string objectName = "", string dllName = "", string objectID = "", string propertyName = "")
{
string objectDllName = objectName + ", " + dllName;
Type type = Type.GetType(objectDllName);
Guid oID = new Guid(objectID);
dynamic Entity = context.Set(type).Find(oID); // get Object by Type and ObjectID
string value = ""; //the value which will be filled with data from database
IEnumerable<string> linqMethods = typeof(System.Linq.Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Select(s => s.Name).ToList(); //get all linq methods and save them as list of strings
if (propertyName.Contains('.'))
{
string[] properies = propertyName.Split('.');
dynamic object1 = Entity;
IEnumerable<dynamic> Child = new List<dynamic>();
for (int i = 0; i < properies.Length; i++)
{
if (i < properies.Length - 1 && linqMethods.Contains(properies[i + 1]))
{
Child = type.GetProperty(properies[i]).GetValue(object1, null);
}
else if (linqMethods.Contains(properies[i]))
{
object1 = Child.Cast<object>().FirstOrDefault(); //for now works only with FirstOrDefault - Later it will be changed to work with ToList or other linq methods
type = object1.GetType();
}
else
{
if (linqMethods.Contains(properies[i]))
{
object1 = type.GetProperty(properies[i + 1]).GetValue(object1, null);
}
else
{
object1 = type.GetProperty(properies[i]).GetValue(object1, null);
}
type = object1.GetType();
}
}
value = object1.ToString(); //.StartDateTime.ToString();
}
return value;
}
I`m not sure if this is the best approach. Does anyone have a better suggestion, or maybe someone has already done something like this?
To shorten it: The idea is to make generic linq queries to database from a string like: "Case.Parties.FirstOrDefault.NameEn".
Your approach is very good. I have no doubt that it already works.
Another approach is using Expression Tree like #Egorikas have suggested.
Disclaimer: I'm the owner of the project Eval-Expression.NET
In short, this library allows you to evaluate almost any C# code at runtime (What you exactly want to do).
I would suggest you use my library instead. To keep the code:
More readable
Easier to support
Add some flexibility
Example
public string GetColumnValueByObjectByName(Expression<Func<TEntity, bool>> filter = null, string objectName = "", string dllName = "", string objectID = "", string propertyName = "")
{
string objectDllName = objectName + ", " + dllName;
Type type = Type.GetType(objectDllName);
Guid oID = new Guid(objectID);
object Entity = context.Set(type).Find(oID); // get Object by Type and ObjectID
var value = Eval.Execute("x." + propertyName, new { x = entity });
return value.ToString();
}
The library also allow you to use dynamic string with IQueryable
Wiki: LINQ-Dynamic

CRM 2011 : Plugin to create duplicate records

i created a simple plugin to create a duplicate record that refers to the parent record.
Here is my code
var pluginExecutionContext = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
abc= pluginExecutionContext.InputParameters["Target"] as Entity;
if (pluginExecutionContext.Depth == 1)
{
Guid abcId = abc.Id;
Entity abcCopy = new Entity("mcg_abc");
if (abc.Attributes.Contains("mcg_abccategoryoptioncode"))
{
abcCopy.Attributes["mcg_abccategoryoptioncode"] = abc.GetAttributeValue<OptionSetValue>("mcg_abccategoryoptioncode");
}
if (abc.Attributes.Contains("mcg_effectivedate"))
{
abcCopy.Attributes["mcg_effectivedate"] = isp.GetAttributeValue<DateTime>("mcg_effectivedate");
}
if (abc.Attributes.Contains("mcg_startdate"))
{
abcCopy.Attributes["mcg_startdate"] = isp.GetAttributeValue<DateTime>("mcg_startdate");
}
if (abc.Attributes.Contains("mcg_enddate"))
{
abcCopy.Attributes["mcg_enddate"] = isp.GetAttributeValue<DateTime>("mcg_enddate");
}
if (abc.Attributes.Contains("mcg_amendeddate"))
{
abcCopy.Attributes["mcg_amendeddate"] = isp.GetAttributeValue<DateTime>("mcg_amendeddate");
}
if ((abc.GetAttributeValue<OptionSetValue>("mcg_abccategoryoptioncode").Value) == 803870001)
{
//Some more fields;
}
else
{
//Some more fields;
}
// SOme more fields;
abcCopy.Attributes["mcg_parentabc"] = new EntityReference("mcg_abc", abc.Id);
service.Create(abcCopy);
}
Now the problem is all the fields before the below check are getting copied
if ((abc.GetAttributeValue<OptionSetValue>("mcg_abccategoryoptioncode").Value) == 803870001)
However fields after this check are not getting copied.
Please if anybody could suggest what mistake i have made.
In case you take field from Target - this field was updated on a client side. In case field was not updated - it would not be in Target. You should use Images to get values of unchanged fields.
The field must be empty so a exception may arise. Try to use the plugin image or change your code to this way:
if (abc.Attributes.Contains("mcg_abccategoryoptioncode")){
if ((abc.GetAttributeValue<OptionSetValue>("mcg_abccategoryoptioncode").Value) == 803870001)
....

Implementing an OPC DA client from scratch

I would like to implement my own OPC DA client (versions 2.02, 2.05a, 3.00) from scratch but without using any third-party. Also I would like to make use of OPCEnum.exe service to get a list of installed OPC servers. Is there any kind of document that explains detailed and step by step the process to implement an OPC client?
I have a c# implementation but actually it's hard to fit it in here. I'll try to summarize the steps required.
Mostly you need to have OpcRcw.Comn.dll and OpcRcw.Da.dll from the OPC Core Components Redistributable package downloable for free from Opcfoundation.org. Once installed, the files are located in C:\Windows\assembly\GAC_MSIL. Create a reference in your project.
About coding, this is what you should do (there are three objects you want to implement, Server, Group and Item):
Let's start with server:
Type typeofOPCserver = Type.GetTypeFromProgID(serverName, computerName, true);
m_opcServer = (IOPCServer)Activator.CreateInstance(typeofOPCserver);
m_opcCommon = (IOPCCommon)m_opcServer;
IConnectionPointContainer icpc = (IConnectionPointContainer)m_opcServer;
Guid sinkGUID = typeof(IOPCShutdown).GUID;
icpc.FindConnectionPoint(ref sinkGUID, out m_OPCCP);
m_OPCCP.Advise(this, out m_cookie_CP);
I've striped a LOT of checking to fit it in here, take it as a sample...
Then you need a method on server to add groups:
// Parameter as following:
// [in] active, so do OnDataChange callback
// [in] Request this Update Rate from Server
// [in] Client Handle, not necessary in this sample
// [in] No time interval to system UTC time
// [in] No Deadband, so all data changes are reported
// [in] Server uses english language to for text values
// [out] Server handle to identify this group in later calls
// [out] The answer from Server to the requested Update Rate
// [in] requested interface type of the group object
// [out] pointer to the requested interface
m_opcServer.AddGroup(m_groupName, Convert.ToInt32(m_isActive), m_reqUpdateRate, m_clientHandle, pTimeBias, pDeadband, m_LocaleID, out m_serverHandle, out m_revUpdateRate, ref iid, out objGroup);
// Get our reference from the created group
m_OPCGroupStateMgt = (IOPCGroupStateMgt)objGroup;
Finally you need to create items:
m_OPCItem = (IOPCItemMgt)m_OPCGroupStateMgt;
m_OPCItem.AddItems(itemList.Length, GetAllItemDefs(itemList), out ppResults, out ppErrors);
Where itemlist is an array of OPCITEMDEF[]. I build the above using GetAllItemDefs from a structure of mine.
private static OPCITEMDEF[] GetAllItemDefs(params OpcItem[] opcItemList)
{
OPCITEMDEF[] opcItemDefs = new OPCITEMDEF[opcItemList.Length];
for (int i = 0; i < opcItemList.Length; i++)
{
OpcItem opcItem = opcItemList[i];
opcItemDefs[i].szAccessPath = "";
opcItemDefs[i].bActive = Convert.ToInt32(opcItem.IsActive);
opcItemDefs[i].vtRequestedDataType = Convert.ToInt16(opcItem.ItemType, CultureInfo.InvariantCulture);
opcItemDefs[i].dwBlobSize = 0;
opcItemDefs[i].pBlob = IntPtr.Zero;
opcItemDefs[i].hClient = opcItem.ClientHandle;
opcItemDefs[i].szItemID = opcItem.Id;
}
return opcItemDefs;
}
Finally, about enumerating Servers, I use this two functions:
/// <summary>
/// Enumerates hosts that may be accessed for server discovery.
/// </summary>
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public string[] EnumerateHosts()
{
IntPtr pInfo;
int entriesRead = 0;
int totalEntries = 0;
int result = NetServerEnum(
IntPtr.Zero,
LEVEL_SERVER_INFO_100,
out pInfo,
MAX_PREFERRED_LENGTH,
out entriesRead,
out totalEntries,
SV_TYPE_WORKSTATION | SV_TYPE_SERVER,
IntPtr.Zero,
IntPtr.Zero);
if (result != 0)
throw new ApplicationException("NetApi Error = " + String.Format("0x{0,0:X}", result));
string[] computers = new string[entriesRead];
IntPtr pos = pInfo;
for (int ii = 0; ii < entriesRead; ii++)
{
SERVER_INFO_100 info = (SERVER_INFO_100)Marshal.PtrToStructure(pos, typeof(SERVER_INFO_100));
computers[ii] = info.sv100_name;
pos = (IntPtr)(pos.ToInt32() + Marshal.SizeOf(typeof(SERVER_INFO_100)));
}
NetApiBufferFree(pInfo);
return computers;
}
/// <summary>
/// Returns a list of servers that support the specified specification on the specified host.
/// </summary>
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public string[] GetAvailableServers(Specification specification)
{
lock (this)
{
// connect to the server.
ArrayList servers = new ArrayList();
MULTI_QI[] results = new MULTI_QI[1];
GCHandle hIID = GCHandle.Alloc(IID_IUnknown, GCHandleType.Pinned);
results[0].iid = hIID.AddrOfPinnedObject();
results[0].pItf = null;
results[0].hr = 0;
try
{
// create an instance.
Guid srvid = CLSID;
CoCreateInstanceEx(srvid, null, CLSCTX.CLSCTX_LOCAL_SERVER, IntPtr.Zero, 1, results);
m_server = (IOPCServerList2)results[0].pItf;
// convert the interface version to a guid.
Guid catid = new Guid(specification.ID);
// get list of servers in the specified specification.
IOPCEnumGUID enumerator = null;
m_server.EnumClassesOfCategories(1, new Guid[] { catid }, 0, null, out enumerator);
// read clsids.
Guid[] clsids = ReadClasses(enumerator);
// release enumerator
if (enumerator != null && enumerator.GetType().IsCOMObject)
Marshal.ReleaseComObject(enumerator);
// fetch class descriptions.
foreach (Guid clsid in clsids)
{
try
{
string url = CreateUrl(specification, clsid);
servers.Add(url);
}
catch (Exception) { }
}
}
catch
{
}
finally
{
if (hIID.IsAllocated) hIID.Free();
if (m_server != null && m_server.GetType().IsCOMObject)
Marshal.ReleaseComObject(m_server);
}
return (string[])servers.ToArray(typeof(string));
}
}
I know I've striped a lot but maybe it can still help you ;)
Please mark the answer as correct if you think I've been clear ;)
Kind Regards,
D.

Programmatically creating an Assign in a Flowchart Workflow

I need to programmatically define a serializable flowchart Windows Workflow that accepts input arguments and returns a result. I understand how to create these workflows using a designer, but I need to do it in code and have the flowchart workflow be serializable (so no lambda expressions).
I'm having trouble getting the "To" field of the Assign. The code below creates a flowchart workflow of a WriteLine followed by an Assign.
var ab = new ActivityBuilder<string> {
Name = "ActivityBuilt",
Implementation = new Flowchart {
StartNode = new FlowStep {
Action = new WriteLine { Text = new VisualBasicValue<string>("greeting") },
Next = new FlowStep {
Action = new Assign {
DisplayName = "Set result",
To = new OutArgument<string>(new VisualBasicReference<string> {
ExpressionText = "results"}),
Value = new VisualBasicValue<string>("bye")
}
}
}
}
};
ab.Properties.Add(new DynamicActivityProperty {
Name = "greeting",
Type = typeof (InArgument<string>),
Value = "hello"});
ab.Properties.Add(new DynamicActivityProperty {
Name = "results",
Type = typeof (OutArgument<string>),
Value = "bye"});
// Convert the ActivityBuilder to a callable activity
using (var sw = new StringWriter()) {
using (var xw = ActivityXamlServices.CreateBuilderWriter(new XamlXmlWriter(sw, new XamlSchemaContext()))) {
XamlServices.Save(xw, LastCreated);
}
using (var sr = new StringReader(sw.ToString())) {
var wf = ActivityXamlServices.Load(sr);
return wf;
}
}
The above code fails when I try to convert from ActivityBuilder to Activity saying "Failed to create a 'OutArgument' from the text 'bye'." This works fine if I don't use the OutArgument and just pass things in.
My question is what to put in the To property? How do I reference the OutArgument I add to the ActivityBuilder.Properties? A VisualBasicReference isn't an OutArgument. Am I making this more difficult than it needs to be?
Thanks for any hints!
Edit: I want to create a workflow programmatically. The workflow needs to have input arguments and return results (output arguments).
I understand how to create the workflow and how to declare and use input arguments. I'm using an ActivityBuilder to create the workflow and to set the InArgument via the ActivityBuilder's properties. I create the workflow from the ActivityBuilder by serializing to XAML and then deserializing using ActivityXamlServices.Load.
What I don't understand is how to get a result from the workflow. I assume it involves an OutArgument. How/where do I add an OutArgument to the workflow? I thought the code snippet I gave would assign a value to an OutArgument, but the call to ActivityXamlServices.Load fails with a complaint that it is unable to create the OutArgument.
Is the approach of using ActivityBuilder correct?
Is the "To" field of the Assign action properly referencing an OutArgument?
How do I make the ActivityBuilder aware of the OutArgument and still be able to convert to an Activity / workflow?
Hope this clarifies my problem.
There are atleast 3 problems with the code:
The Value of the Assign needs to be an InArgument().
The value you are trying to read from is named "greeting" not "bye".
The OutArgument named "results" can't have a default value.
Try the following code:
var ab = new ActivityBuilder<string>
{
Name = "ActivityBuilt",
Implementation = new Flowchart
{
StartNode = new FlowStep
{
Action = new WriteLine { Text = new VisualBasicValue<string>("greeting") },
Next = new FlowStep
{
Action = new Assign
{
DisplayName = "Set result",
To = new OutArgument<string>(new VisualBasicReference<string>
{
ExpressionText = "results"
}),
Value = new InArgument<string>(new VisualBasicValue<string>("greeting"))
}
}
}
}
};
ab.Properties.Add(new DynamicActivityProperty
{
Name = "greeting",
Type = typeof(InArgument<string>),
Value = "hello"
});
ab.Properties.Add(new DynamicActivityProperty
{
Name = "results",
Type = typeof(OutArgument<string>)
});
// Convert the ActivityBuilder to a callable activity
using (var sw = new StringWriter())
{
using (var xw = ActivityXamlServices.CreateBuilderWriter(new XamlXmlWriter(sw, new XamlSchemaContext())))
{
XamlServices.Save(xw, ab);
}
using (var sr = new StringReader(sw.ToString()))
{
var wf = ActivityXamlServices.Load(sr);
WorkflowInvoker.Invoke(wf);
}
}