Hibernate Search programmatic API HTMLStripCharFilterFactory - hibernate-search

I want to setup Hibernate Search (5.5.1.Final) using Programmatic API.
With annotations i write
#AnalyzerDefs({
#AnalyzerDef(name = "el",
charFilters = {#CharFilterDef(factory = HTMLStripCharFilterFactory.class)},
tokenizer = #TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
#TokenFilterDef(factory = StandardFilterFactory.class),
#TokenFilterDef(factory = GreekLowerCaseFilterFactory.class),
#TokenFilterDef(factory = StopFilterFactory.class,
params = {#Parameter(name="words", value="stopwords-gr.txt")}),
#TokenFilterDef(factory = EdgeNGramFilterFactory.class,
params = {#Parameter(name="minGramSize", value = "3"),#Parameter(name="maxGramSize", value = "15"),#Parameter(name="side", value = "front")})
}
)
})
With Programmatic API i write
SearchMapping mapping = new SearchMapping();
mapping.analyzerDef("el", StandardTokenizerFactory.class)
.filter(StandardFilterFactory.class)
.filter(GreekLowerCaseFilterFactory.class)
.filter(StopFilterFactory.class)
.filter(EdgeNGramFilterFactory.class)
.param("minGramSize", "3")
.param("maxGramSize", "15")
.param("side", "front");
But i cannot figure out how i will use the HTMLStripCharFilterFactory.

The short answer is, that you cannot. When the charFilters option got introduced as part of HSEARCH-477, it was missed to also add it to the programmatic API. So the functionality just does not exist yet. I created HSEARCH-2199 as a feature request to add this functionality.

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.

PSI update resource custom field with lookup table (Project Server)

Can someone show me a code to update a enterprise resource custom field with lookup table ? Already ran the internet looking for some sample code but did not succeed.
You can create and update a custom field with a lookup table using the below code . But we can not update or delete builtin custom fields
var projContext = new ProjectContext(projectServerUrl);
CustomFieldCollection CustomField = projContext.CustomFields;
EntityTypes Entitytype = projContext.EntityTypes;
LookupTableCollection lookupTables = projContext.LookupTables;
projContext.Load(CustomField);
projContext.Load(Entitytype);
projContext.Load(lookupTables);
projContext.ExecuteQuery();
CustomFieldCreationInformation NewfieldInfo = new CustomFieldCreationInformation();
NewfieldInfo.Id = new Guid();
NewfieldInfo.Name = "The Name";
NewfieldInfo.Description = "The Description";
NewfieldInfo.IsWorkflowControlled = true;
NewfieldInfo.IsRequired = true;
NewfieldInfo.IsEditableInVisibility = false;
NewfieldInfo.IsMultilineText = false;
LookupTable lookuptable = lookupTables.ToList().Find(x => x.Name == "LookupTableName");
projContext.Load(lookuptable);
projContext.ExecuteQuery();
NewfieldInfo.LookupTable = lookuptable;
NewfieldInfo.EntityType = Entitytype.ProjectEntity;
NewfieldInfo.FieldType = CustomFieldType.TEXT;
projContext.CustomFields.Add(NewfieldInfo);
projContext.CustomFields.Update();
projContext.ExecuteQuery();

How do I programmatically set configuration for the ImageResizer SQLReader plugin v4?

I was previously using v3 of ImageResizer but am now trying to use v4.
See: http://imageresizing.net/docs/v4/plugins/sqlreader
I need to programmatically set the several config options for the SQLReader plugin. I had this previous code, but it no longer works, stating that the type SqlReaderSettings could not be found:
// SqlReader Plugin
var fileSettings = new SqlReaderSettings
{
ConnectionString = ApplicationConfigurationContext.Current.DefaultSiteSqlConnectionString,
PathPrefix = "~/Images",
StripFileExtension = true,
ImageIdType = SqlDbType.UniqueIdentifier,
ImageBlobQuery = ???,
ModifiedDateQuery = ???,
ImageExistsQuery = ???,
CacheUnmodifiedFiles = true,
RequireImageExtension = true
};
I cannot use the web.config file to store some of these settings. Specifically the connection string may change at run-time, and cannot be stored un-encrypted in the web.config file due to company policy.
Thanks for the help.
Update: This is the code I now use. I did not add in this plugin from the Web.config as it would be redundant.
new SqlReaderPlugin
{
ConnectionString = ApplicationConfigurationContext.Current.DefaultSiteSqlConnectionString,
ImageIdType = SqlDbType.VarChar,
QueriesAreStoredProcedures = true,
ModifiedDateQuery = "procFileImageResizerSelectTimestamps",
ImageBlobQuery = "procFileImageResizerSelectData",
ExposeAsVpp = true,
VirtualFilesystemPrefix = filesUri,
RequireImageExtension = true,
StripFileExtension = true,
CacheUnmodifiedFiles = true
}.Install(Config.Current);
You can replace SqlReaderSettings with SqlReaderPlugin directly; it no longer uses a separate settings class. Nearly all the class members should be the same, so just change the name of the class you are initializing.

Can't add TaxService to QBO with .NET SDK

How do I create a tax rate in QBO using API v3 and the TaxService resource? When I try to add it the same way as I would any other object, Visual Studio gives me this error: "The type 'Intuit.Ipp.Data.TaxService' cannot be used as type parameter 'T' in the generic type or method Intuit.Ipp.DataService.DataService.Add(T)'. There is no implicit reference conversion from 'Intuit.Ipp.Data.TaxService' to 'Intuit.Ipp.Data.IEntity'."
Here's the code:
Intuit.Ipp.Data.TaxService ts = new Intuit.Ipp.Data.TaxService();
// Populate fields here...
DataService ds = new DataService(ServiceContext);
Intuit.Ipp.Data.TaxService newTs = ds.Add<Intuit.Ipp.Data.TaxService>(ts);
Use GlobalTaxService endpoint and JSON format only. Try this code:
GlobalTaxService taxSvc = new GlobalTaxService(context);
Intuit.Ipp.Data.TaxService taxCodetobeAdded = new Data.TaxService();
taxCodetobeAdded.TaxCode = "taxC_" + Guid.NewGuid().ToString("N");
QueryService<TaxAgency> taxagency = new QueryService<TaxAgency>(context);
TaxAgency taxagencyResult = taxagency.ExecuteIdsQuery("select * from TaxAgency").FirstOrDefault<TaxAgency>();
List<TaxRateDetails> lstTaxRate = new List<TaxRateDetails>();
TaxRateDetails taxdetail1 = new TaxRateDetails();
taxdetail1.TaxRateName = "taxR1_" + Guid.NewGuid().ToString("N");
taxdetail1.RateValue = 3m;
taxdetail1.RateValueSpecified = true;
taxdetail1.TaxAgencyId = taxagencyResult.Id.ToString();
taxdetail1.TaxApplicableOn = TaxRateApplicableOnEnum.Sales;
taxdetail1.TaxApplicableOnSpecified = true;
lstTaxRate.Add(taxdetail1);
TaxRateDetails taxdetail2 = new TaxRateDetails();
taxdetail2.TaxRateName = "taxR2_" + Guid.NewGuid().ToString("N");
taxdetail2.RateValue = 2m;
taxdetail2.RateValueSpecified = true;
taxdetail2.TaxAgencyId = taxagencyResult.Id.ToString();
taxdetail2.TaxApplicableOn = TaxRateApplicableOnEnum.Sales;
taxdetail2.TaxApplicableOnSpecified = true;
lstTaxRate.Add(taxdetail2);
//TaxRateDetails taxdetail3 = new TaxRateDetails();
//taxdetail3.TaxRateName = "rate298";
//taxdetail3.TaxRateId = "2";
//lstTaxRate.Add(taxdetail3);
taxCodetobeAdded.TaxRateDetails = lstTaxRate.ToArray();
Intuit.Ipp.Data.TaxService taxCodeAdded = taxSvc.AddTaxCode(taxCodetobeAdded);

How to set TotalAmount for SalesReciept

I'm using QBO Rest API V3 SDK and trying to create a deposit onto an account. It seems there isn't a deposit transaction anymore, so am trying to use a SalesReciept to do so.
The call is succeeding and the transaction is created however the SalesReciept is returned with a TotalAmount of zero. When I look at the QBO application it shows a 0 Deposit amount as well.
I noticed there was a UnitPrice on the API, but was missing from the SDK, so I hand crafted a web request and it still came back with a 0.
If there is another approach I should take let me know.
var deposit = new SalesReceipt()
{
DepositToAccountRef = new ReferenceType()
{
Value = "1",
name = "MyAccount"
},
TxnDate = transaction.TransactionDate,
TxnDateSpecified = true,
TotalAmt = transaction.Amount,
TotalAmtSpecified = true,
Line = new[]
{
new Line()
{
Amount = transaction.Amount,
AmountSpecified = true,
Description = transaction.DisplayBody,
DetailType = LineDetailTypeEnum.SalesItemLineDetail,
DetailTypeSpecified = true,
AnyIntuitObject = new SalesItemLineDetail()
{
ItemRef = new ReferenceType(){
Value = qboIntegration.IncomeAccountId,
name = GetIncomeAccountName(),
},
Qty = 1,
QtySpecified = true,
TaxInclusiveAmt = transaction.Amount,
TaxInclusiveAmtSpecified = true,
ServiceDate = transaction.TransactionDate,
ServiceDateSpecified = true,
},
}
},
};
I've not tried this using .net sdk. You can try the following ( Ref - SO Thread).
AnyIntuitObject = new SalesItemLineDetail()
{
ItemElementName = ItemChoiceType.UnitPrice,
AnyIntuitObject = amount,
...
},
DetailType = LineDetailTypeEnum.SalesItemLineDetail
To get the correct object structure you can create a salesReceipt from QBO UI(with desired attribute value) and retrieve the same using getById endpoint.
To do the above, you can use the ApiExplorer tool as well.
https://developer.intuit.com/apiexplorer?apiname=V3QBO
Thanks
After contacting quickbooks. It is not possible with the current iteration (V3) of the api. They are considering adding this in the next version.