A phone number synonym-like filter/tokenizer in Solr? - search

I'm trying to make Solr search phone numbers which are stored like this +79876543210 using a query like these:
+79876543210
79876543210
89876543210 <-- '+7' is replaced with region specific code '8'
9876543210 <-- '+7' entirely removed
This is just an example. Another one is wired line phone numbers:
+78662123456 <-- '+78662' is a specific region code
78662123456
88662123456
8662123456
123456 <-- region code entirely removed
One way I could manage this is using a separate field which is filled with these variants and used solely during search.
But this has issues with highlighting (it returns <em>123456</em> to be highlighted whereas the real value shown to user is +78662123456).
I thought that maybe it's best to make these indices using just Solr, but how?
First thought was to use managed synonyms filter and pass them along with each added record. But the docs explicitly states:
Changes made to managed resources via this REST API are not applied to the active Solr components until the Solr collection (or Solr core in single server mode) is reloaded.
So reloading a core every time after adding a record is not the way to go.
Other issues involve keeping these synonyms up to date with records.
Could there be another way to solve this?

Thanks to this comment (by MatsLindh) I've managed to assemble a simple filter based on bult-in EdgeNGramTokenFilter:
package com.step4;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ReverseCustomFilter extends TokenFilter {
private static final PatternReplacementPair[] phonePatterns = {
new PatternReplacementPair("\\+7", "7"),
new PatternReplacementPair("\\+7", "8"),
new PatternReplacementPair("\\+7", ""),
new PatternReplacementPair("\\+78662", ""),
new PatternReplacementPair("\\+78663", ""),
};
private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
private final PositionIncrementAttribute posIncrAtt = addAttribute(PositionIncrementAttribute.class);
private int curPatternIndex;
private int curPosIncr;
private State curState;
public ReverseCustomFilter(TokenStream input) {
super(input);
}
#Override
public final boolean incrementToken() throws IOException {
while (true) {
if (curPatternIndex == 0) {
if (!input.incrementToken()) {
return false;
}
curState = captureState();
curPosIncr += posIncrAtt.getPositionIncrement();
curPatternIndex = 1;
}
if (curPatternIndex <= phonePatterns.length) {
PatternReplacementPair replacementPair = phonePatterns[curPatternIndex - 1];
curPatternIndex++;
restoreState(curState);
Matcher matcher = replacementPair.getPattern().matcher(termAtt);
if (matcher.find()) {
posIncrAtt.setPositionIncrement(curPosIncr);
curPosIncr = 0;
String replaced = matcher.replaceFirst(replacementPair.getReplacement());
termAtt.setEmpty().append(replaced);
return true;
}
}
else {
restoreState(curState);
posIncrAtt.setPositionIncrement(0);
curPatternIndex = 0;
return true;
}
}
}
#Override
public void reset() throws IOException {
super.reset();
curPatternIndex = 0;
curPosIncr = 0;
}
#Override
public void end() throws IOException {
super.end();
posIncrAtt.setPositionIncrement(curPosIncr);
}
private static class PatternReplacementPair {
private final Pattern pattern;
private final String replacement;
public PatternReplacementPair(String pattern, String replacement) {
this.pattern = Pattern.compile(pattern);
this.replacement = replacement;
}
public Pattern getPattern() {
return pattern;
}
public String getReplacement() {
return replacement;
}
}
}

Related

Spring-Integration: external routing slip

I would like to allow callers to pass an external routing slip, e.g. by posting:
POST http://localhost:8080/transform?routing-slip=capitalize&routing-slip=lowercase
Content-Type: text/plain
camelCase
It should be possible to use the given routing-slip array as external routing slip from a pojo:
#Bean
public IntegrationFlow transformerChain(RoutingSlipRouteStrategy routeStrategy) {
return IntegrationFlows.from(
Http.inboundGateway("/transform")
.headerExpression("routingSlipParam",
"#requestParams['routing-slip']")
.requestPayloadType(String.class))
.enrichHeaders(spec -> spec.header(
IntegrationMessageHeaderAccessor.ROUTING_SLIP,
new RoutingSlipHeaderValueMessageProcessor(
"#routePojo.get(request, reply)")
)
)
.logAndReply();
}
The pojo can access the routingSlipParam header and you would think it can then hold the slip as internal state, or at least that is what TestRoutingSlipRoutePojo lead me to believe, so I built this (with a slight doubt, given that there is only one instance of the pojo):
public class ExternalRoutingSlipRoutePojo {
private List<String> routingSlip;
private int i = 0;
public String get(Message<?> requestMessage, Object reply) {
if (routingSlip == null) {
routingSlip = (LinkedList)requestMessage.getHeaders()
.get("routingSlipParam");
}
try {
return this.routingSlip.get(i++);
} catch (Exception e) {
return null;
}
}
}
It turns out that this only works exactly once, which is not surprising after all - the index is incremented for every incoming message and the routing slip is never updated.
So I thought, sure, I have to hold the internal status for all incoming messages and came up with this RouteStrategy:
public class ExternalRoutingSlipRouteStrategy implements RoutingSlipRouteStrategy {
private Map<UUID, LinkedList<String>> routingSlips = new WeakHashMap<>();
private static final LinkedList EMPTY_ROUTINGSLIP = new LinkedList<>();
#Override
public Object getNextPath(Message<?> requestMessage,Object reply) {
MessageHeaders headers = requestMessage.getHeaders();
UUID id = headers.getId();
if (!routingSlips.containsKey(id)) {
#SuppressWarnings("unchecked")
List<String> routingSlipParam =
headers.get("routingSlipParam", List.class);
if (routingSlipParam != null) {
routingSlips.put(id,
new LinkedList<>(routingSlipParam));
}
}
LinkedList<String> routingSlip = routingSlips.getOrDefault(id,
EMPTY_ROUTINGSLIP);
String nextPath = routingSlip.poll();
if (nextPath == null) {
routingSlips.remove(id);
}
return nextPath;
}
}
That does not work either because the strategy is not only called for the incoming message but also for all the new messages which are created by the dynamic routing, which of course have different IDs.
But it is only called twice for the original message, so the routing slip never gets exhausted and the application runs in an endless loop.
How can I make spring-integration use an external routing slip?
UPDATE:
As suggested by Gary Russel, neither the external routing slip index nor the external routing slip itself should be stored in the Spring bean, rather one can use message headers to maintain them separately for each request:
Http.inboundGateway("/transform")
.headerExpression("routingSlipParam",
"#requestParams['routing-slip']")
.requestPayloadType(String.class))
.enrichHeaders(spec -> spec
.headerFunction("counter",h -> new AtomicInteger())
.header(IntegrationMessageHeaderAccessor.ROUTING_SLIP,
new RoutingSlipHeaderValueMessageProcessor(externalRouteStrategy)
)
)
The externalRouteStrategy is an instance of the following class:
public class ExternalRoutingSlipRouteStrategy implements
RoutingSlipRouteStrategy {
#Override
public Object getNextPath(Message<?> requestMessage, Object reply) {
List<String> routingSlip = (List<String>)
requestMessage.getHeaders().get("routingSlipParam");
int routingSlipIndex = requestMessage.getHeaders()
.get("counter", AtomicInteger.class)
.getAndIncrement();
String routingSlipEntry;
if (routingSlip != null
&& routingSlipIndex < routingSlip.size()) {
routingSlipEntry = routingSlip.get(routingSlipIndex);
} else {
routingSlipEntry = null;
}
return routingSlipEntry;
}
}
For reference, I have published the example in Github.
Go back to your first version and store i in a message header (AtomicInteger) in the header enricher.
.headerExpression("counter", "new java.util.concurrent.atomic.AtomicInteger()")
then
int i = requestMessage.getHeaders().get("counter", AtomicInteger.class).getAndIncrement();

AdviceAdapter onMethodExit never called

I'm attempting to instrument a cassandra driver and in particular need to modify a ResultSet class to hang on to some information. In order to do this I need to modify the code where the instance is being allocated, which is a static method in another class. The code has this snippet in it:
return r.metadata.pagingState == null
? new SinglePage(columnDefs, tokenFactory, protocolVersion, columnDefs.codecRegistry, r.data, info)
: new MultiPage(columnDefs, tokenFactory, protocolVersion, columnDefs.codecRegistry, r.data, info, r.metadata.pagingState, session);
It also has other returns within the method. So my thought was to use an AdviceAdapter on this method and use the onMethodExit(). However, my method was never called. That seems absurd since.. the method has to be returning! After a little debugging, I find the visitInsn() in the AdviceAdapter class is being called just once, with an opcode of IALOAD (load an int from an array?).
I guess my question is.. what the hell is going on? Heh.. sorry, bonked my head on my desk a few too many times today.
EDIT: I changed my class to be a simple MethodVisitor just to see if could see more opcodes, and indeed I do! I see it all! I just no longer have access to dup(). :(
I have used an EmailAdviceAdaptor (for javax/mail/Transport) in one of my project, Code is given below. Hope this code will help to resolve your issue.
package com.mail.agent.adapter;
import com.mail.jtm.BTMConstants;
import com.mail.org.objectweb.asm.Label;
import com.mail.org.objectweb.asm.MethodVisitor;
import com.mail.org.objectweb.asm.Opcodes;
import com.mail.org.objectweb.asm.Type;
import com.mail.org.objectweb.asm.commons.AdviceAdapter;
public class MyEmailAdviceAdapter extends AdviceAdapter {
private String methodName;
private String className;
private String description;
private static final String MAIL_SEND_METHOD1_DESC="(Ljavax/mail/Message;)V";
private static final String MAIL_SEND_METHOD2_DESC="(Ljavax/mail/Message;[Ljavax/mail/Address;)V";
private static final String MAIL_SENDMESSAGE_METHOD_DESC="(Ljavax/mail/Message;[Ljavax/mail/Address;)V";
private boolean isSendMethod;
private int okFlag = newLocal(Type.BOOLEAN_TYPE);
Label startFinally = new Label();
public MyEmailAdviceAdapter(int access , MethodVisitor mv , String methodName, String description, String className, int classFileVersion){
super(Opcodes.ASM5 , mv, access, methodName, description);
this.className = className;
this.methodName = methodName;
this.description = description;
this.isSendMethod = false;
if(methodName.equals("send")){
if( description.equals(MAIL_SEND_METHOD1_DESC) || description.equals(MAIL_SEND_METHOD2_DESC)){
isSendMethod = true;
}
}
else if(methodName.equals("sendMessage") && description.equals(MAIL_SENDMESSAGE_METHOD_DESC)){
isSendMethod = true;
}
}
public void visitCode() {
super.visitCode();
mv.visitLabel(startFinally);
}
protected void onMethodEnter(){
if(isSendMethod) {
mv.visitInsn(Opcodes.ICONST_0);
mv.visitVarInsn(ISTORE, okFlag);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitLdcInsn(className);
mv.visitLdcInsn(methodName);
mv.visitLdcInsn(description);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/mail/agent/trace/MailTracer", "mailMethodBegin", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z"; , false);
mv.visitVarInsn(ISTORE, okFlag);
}
}
protected void onMethodExit(int opcode){
if(opcode!=ATHROW) {
onFinally(opcode);
}
}
public void visitMaxs(int maxStack, int maxLocals){
Label endFinally = new Label();
mv.visitTryCatchBlock(startFinally, endFinally, endFinally, null);
mv.visitLabel(endFinally);
onFinally(ATHROW);
mv.visitInsn(ATHROW);
mv.visitMaxs(maxStack, maxLocals);
}
private void onFinally(int opcode){
if(isSendMethod){
// If the method throws any exception
if(opcode == ATHROW){
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(className);
mv.visitLdcInsn(methodName);
mv.visitLdcInsn(description);
mv.visitVarInsn(ILOAD, okFlag);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/mail/agent/trace/MailTracer", "recordException", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V", false);
}
mv.visitLdcInsn(className);
mv.visitLdcInsn(methodName);
mv.visitLdcInsn(description);
mv.visitVarInsn(ILOAD, okFlag);
mv.visitLdcInsn(opcode);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/mail/agent/trace/MailTracer", "mailMethodEnd", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZI)V", false);
}
}
}

Adding gauge in location finder class in j2me

I am working on a j2me application which contain a class to find the location of mobile using GPS.I need to include gauge while the location provider API is called and it finds the location.I am new to j2me so still not clear with all the concepts.I am pasting my code below.Please help me through this.Thanks in advance..
package org.ets.utils;
import javax.microedition.lcdui.*;
import javax.microedition.location.*;
import javax.microedition.io.*;
import java.io.*;
import org.ets.midlet.ETS_infozech;
import javax.microedition.midlet.*;
public class Locfinder {
public Locfinder(ETS_infozech midlet)
{
this.midlet = midlet;
}
public static String ex()
{
try {
checkLocation();
} catch (Exception ex)
{
ex.printStackTrace();
}
//System.out.println(string);
return string;
}
public static void checkLocation() throws Exception
{
Location l;
LocationProvider lp;
Coordinates c;
// Set criteria for selecting a location provider:
// accurate to 500 meters horizontally
Criteria cr= new Criteria();
cr.setHorizontalAccuracy(500);
// Get an instance of the provider
lp= LocationProvider.getInstance(cr);
//Request the location, setting a one-minute timeout
l = lp.getLocation(60);
c = l.getQualifiedCoordinates();
if(c != null ) {
// Use coordinate information
double lat = c.getLatitude();
double lon = c.getLongitude();
string = " LAT-" + lat + " LONG-" + lon;
}
}
}
There's no way you can link a Gauge to some task.
You have to set values to the Gauge manually. So you'd create a Gauge and add it to your Form. Then start your code to perform the look-up.
In between your lines of code, you'd add myGauge.setValue(some_value); to increase the indicator.
Of course, this becomes difficult when most of the task is contained in a single line of code, like e.g. lp.getLocation(60);.
I think, in that case, I would create a Thread that automatically increases the value on the Gauge in the 60 seconds, but can be stopped/overridden by a manual setting.
class Autoincrementer implements Runnable {
private boolean running;
private Gauge gauge;
private int seconds;
private int secondsElapsed;
public Autoincrementer(Gauge gauge) {
this.gauge = gauge;
this.seconds = gauge.getMaxValue();
this.running = true;
this.secondsElapsed = 0;
}
public void run() {
if (running) {
secondsElapsed++;
gauge.setValue(secondsElapsed);
if (secondsElapsed>=gauge.getMaxValue()) running = false; // Stop the auto incrementing
try {
Thread.sleep(1000); // Sleep for 1 second
} catch (Exception e) {}
}
}
public void stop() {
running = false;
}
}
You would then create a Gauge and add it to your Form
myGauge = new Gauge("Process", false, 60, 0);
myForm.append(myGauge);
Then start the auto-increment.
myIncrementer = new Autoincrementer(myGauge);
new Thread(myIncrementer).start();
And then call your look-up code.
checkLocation();
Inside your look-up code, add code to stop the auto-incrementing and set the Gauge object to 100%, if the look-up was successful (meaning before the timeout).
myIncrementer.stop();
myGauge.setValue(60);
LWUIT 1.5 can help you in this. Am not sure for Location API which you are using.
But you will get Gauge using LWUIT 1.5. Use Lwuit instead of LCDUI.
http://www.oracle.com/technetwork/java/javame/javamobile/download/lwuit/index.html

How to get current used color theme of Visual Studio

I'm creating my own IntelliSense Presenter, since Visual Studio2012 support change theme, so I want my background color of the presenter can be auto-changed when the theme been changed. Is there a way to track the theme changes event, or get the current color theme of the Visual Studio?
Yes, this is possible. I had to solve a similiar issue with one of my extensions...
The current theme is stored in the Windows Registry; so I implemented the following utility class.
public enum VsTheme
{
Unknown = 0,
Light,
Dark,
Blue
}
public class ThemeUtil
{
private static readonly IDictionary<string, VsTheme> Themes = new Dictionary<string, VsTheme>()
{
{ "de3dbbcd-f642-433c-8353-8f1df4370aba", VsTheme.Light },
{ "1ded0138-47ce-435e-84ef-9ec1f439b749", VsTheme.Dark },
{ "a4d6a176-b948-4b29-8c66-53c97a1ed7d0", VsTheme.Blue }
};
public static VsTheme GetCurrentTheme()
{
string themeId = GetThemeId();
if (string.IsNullOrWhiteSpace(themeId) == false)
{
VsTheme theme;
if (Themes.TryGetValue(themeId, out theme))
{
return theme;
}
}
return VsTheme.Unknown;
}
public static string GetThemeId()
{
const string CategoryName = "General";
const string ThemePropertyName = "CurrentTheme";
string keyName = string.Format(#"Software\Microsoft\VisualStudio\11.0\{0}", CategoryName);
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName))
{
if (key != null)
{
return (string)key.GetValue(ThemePropertyName, string.Empty);
}
}
return null;
}
}
Okay; this just helps to figur out the current settings... listening for the theme changed notification is a bit trickier. After your package is loaded, you must obtain an IVsShell instance via the DTE; once you have this object you can utilize the AdviceBroadcastMessages method to subscribe for event notifications. You have to provide an object whose type implements the IVsBroadcastMessageEvents interface...
I don´t want to post the whole implementation, but the following lines might illustrate the key scenario...
class VsBroadcastMessageEvents : IVsBroadcastMessageEvent
{
int IVsBroadcastMessageEvent.OnBroadcastMessage(uint msg, IntPtr wParam, IntPtr lParam)
{
const uint WM_SYSCOLORCHANGE = 0x15;
if (msg == WM_SYSCOLORCHANGE)
{
// obtain current theme from the Registry and update any UI...
}
}
}
Consider implementing IDisposable on that type as well, in order to be able to unsubscribe from the event source, when the package gets unloaded.
This is how I subscribe for event notifications...
class ShellService
{
private readonly IVsShell shell;
private bool advised;
public ShellService(IVsShell shellInstance)
{
this.shell = shellInstance;
}
public void AdviseBroadcastMessages(IVsBroadcastMessageEvents broadcastMessageEvents, out uint cookie)
{
cookie = 0;
try
{
int r = this.shell.AdviseBroadcastMessages(broadcastMessageEvents, out cookie);
this.advised = (r == VSConstants.S_OK);
}
catch (COMException) { }
catch (InvalidComObjectException) { }
}
public void UnadviseBroadcastMessages(uint cookie)
{
...
}
}
Keep the value of the cookie parameter; you´ll need it to successfully unsubscribe.
Hope that helps (-:
Just wanted to put an update just in case anyone else comes along.. #Matze and #Frank are totally right.. However in VS 2015.. they added a easy way to detect the theme change. So you need to include PlatformUI an dyou get a super easy event
using Microsoft.VisualStudio.PlatformUI;
....
//Then you get an event
VSColorTheme.ThemeChanged += VSColorTheme_ThemeChanged;
You should make sure your control is disposable so you can unsubscribe from the event...
BONUS!
It also give you easy access to the colors.. even if the user has changed them from the default .. so you can do stuff like this in when set your colors
var defaultBackground = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);
var defaultForeground = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowTextColorKey);
For VS 2015 this has changed, the solution #Matze has still works but you need to update the GetThemeId() function to check for the version and if it's 14.0 (VS2015) look in a different place in the registry. The way the value is stored has changed also, it's still a string but now contains other values seperated by a '*'. The theme guid is the last value in the list.
if (version == "14.0")
{
string keyName = string.Format(#"Software\Microsoft\VisualStudio\{0}\ApplicationPrivateSettings\Microsoft\VisualStudio", version);
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName))
{
if (key != null)
{
var keyText = (string)key.GetValue("ColorTheme", string.Empty);
if (!string.IsNullOrEmpty(keyText))
{
var keyTextValues = keyText.Split('*');
if (keyTextValues.Length > 2)
{
return keyTextValues[2];
}
}
}
}
return null;
}

Loading an object from a db4o database

I am developing an e-commerce website that utilises db4o as the backend. All was well until last week when I came across a problem that I have been unable to solve. The code below is quite straight forward. I open a database file, save an object and then try to retrieve it. However I get nothing back. The "users" variable has a count of zero.
public class Program
{
private static string _connectionString = string.Format(#"c:\aaarrrr.db4o");
static void Main(string[] args)
{
TestUser container = new TestUser() { id = 1, Name = "Mohammad", Surname = "Rafiq" };
Db4oFactory.Configure().Diagnostic().AddListener(new DiagnosticToConsole());
using (var dbc = Db4oFactory.OpenFile(_connectionString))
{
dbc.Store(container);
}
IList<TestUser> users = null;
using (var dbc = Db4oFactory.OpenFile(_connectionString))
{
users = dbc.Query<TestUser>(x => x.id == 1).ToList();
}
if (users.Count > 0)
{
Console.WriteLine("{0} {1} with id of {2}", users.First().Name, users.First().Surname, users.First().id);
}
else
{
Console.WriteLine("\nNo data returned.");
}
Console.ReadLine();
}
}
public class TestUser
{
[Indexed]
private int _id = 0;
private string _name = string.Empty;
private string _surname = string.Empty;
public int id { get { return _id; } set { _id = value; } }
public string Name { get { return _name; } set { _name = value; } }
public string Surname { get { return _surname; } set { _surname = value; } }
}
I have attached db4o diagnostic listener and I see nothing in the console output. Everything seems fine. I know I am writing to the file because I can see the file size increase and the timestamp is also updated. I have checked all the project settings and they are all set to default. I am using .net 4, visual studio 2010 beta and windows 7. I have done some reading regarding reflection permission but I cant see how this applies here. Any help or ideas would be knidly appreciated.
After calling store(), you need to commit() before leaving the using{} statement. You closed your database before committing your changes.

Resources