CyberMega

7269767679

  • Experience search better than Google:
  • Simple logging with Spring

    There are many ways how to do logging, and this is yet another one. I haven’t fully developed it yet but it is already usable as it is.

    Let’s say I have a method like this:

    public void doSomething() {
       logger.debug("Entering method doSomething()");
       // stuff
    }

    If we want to get rid of that logger line, but retain the functionality, we have several choices. We can use AspectJ to insert the code. We can also create a proxy, which would invoke the logger before (and after) the method of the class is invoked.

    My solution is based on a proxy and annotations, with a little bit of help from Spring. In the end, we’ll only have to write the following to log the invocation of a method:

    @Log
    public void doSomething() {
      // stuff
    }

    The code for the interface will look like this:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Documented
    public @interface Log {
    }

    In Spring we have a BeanPostProcessor interface, which can be used to process beans managed by Spring. With this class, we’ll check each bean whether it uses the @Log interface. If we see that it does, then we’ll create a proxy for the bean which will log the invocation of the method.

    import com.joo.money.annotations.Log;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.util.ReflectionUtils;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.HashSet;
    import java.util.Set;
    
    import static com.google.common.base.Preconditions.checkArgument;
    import static com.google.common.base.Preconditions.checkNotNull;
    
    /** Processor class */
    public class LoggerProcessor implements BeanPostProcessor {
    
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
            final Class<?> clazz = bean.getClass();
            final Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            final Set<String> loggingMethodSet = new HashSet<String>();
            for (Method m : methods) {
                if (m.getAnnotation(Log.class) != null) {
                    loggingMethodSet.add(methodToString(m));
                }
            }
    
            if (loggingMethodSet.isEmpty()) {
                return bean;
            } else {
                return Proxy.newProxyInstance(clazz.getClassLoader(),
                        clazz.getInterfaces(),
                        new LoggerProxy(bean, loggingMethodSet));
            }
        }
    
        /** Uniquely identify a method */
        private static String methodToString(Method m) {
            try {
                StringBuffer sb = new StringBuffer();
                sb.append(m.getReturnType().getName() + " ");
                sb.append(m.getName() + "(");
                Class[] params = m.getParameterTypes();
                for (int j = 0; j < params.length; j++) {
                    sb.append(params&#91;j&#93;.getName());
                    if (j < (params.length - 1))
                        sb.append(",");
                }
                sb.append(")");
                Class&#91;&#93; exceptions = m.getExceptionTypes(); // avoid clone
                if (exceptions.length > 0) {
                    sb.append(" throws ");
                    for (int k = 0; k < exceptions.length; k++) {
                        sb.append(exceptions&#91;k&#93;.getName());
                        if (k < (exceptions.length - 1))
                            sb.append(",");
                    }
                }
                return sb.toString();
            } catch (Exception e) {
                return "<" + e + ">";
            }
        }
    
        /** Proxy class */
        protected class LoggerProxy implements InvocationHandler {
    
            private final Object original;
            private final Set<String> loggingMethodSet;
            private final Logger logger;
    
            public LoggerProxy(Object proxy, Set<String> loggingMethodSet) {
                this.original = proxy;
                this.loggingMethodSet = loggingMethodSet;
    
                checkNotNull(loggingMethodSet);
                checkArgument(!loggingMethodSet.isEmpty());
    
                logger = LoggerFactory.getLogger(original.getClass());
            }
    
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (loggingMethodSet.contains(methodToString(method))) {
                    logger.debug("Invoking "+original.getClass().getCanonicalName()+" :: "+method.getName());
                    return method.invoke(original, args);
                } else {
                    return method.invoke(original, args);
                }
            }
    
        }
    
    }

    The methodToString method is very important here, because with it we identify a method. The Method class has a toString() method which isn’t good enough, because it ties the method to the interface, or implementation class, and differentiates between the two even though they’re both really the same method. This is why I wrote a separate method which doesn’t check the parent object of the method.

    Every method that has the @Log annotation is processed by methodToString(), its id is placed in a set, and later we just check whether that method should be logged or not.

    The logging functionality can be extended by allowing the developer to specify his own custom string. Currently it’s always the same message that’s printed out.

    Posted in programming and tagged , , . Use this permalink for a bookmark.
    * * * * *

    Building Eclipse products from the command line

    This is a quick overview of how to build products using Eclipse 3.6 (Helios) PDE builder.

    First of all, you need to download Eclipse. Then go to the Software installation window and install “RCP Development resources” (or just anything with the word RCP in it). Installing the RCP delta pack is necessary for having an exe file. Without it, you can build the product, but your final ZIP file will only contain all the files, but not the EXE.

    Create a directory called “builder”. In it, create a subdirectory called “buildDirectory”. Then create two new subdirectories: features and products. In the end, you should have the following directory structure:

    +-- builder
    +--+-- buildDirectory
       +---- features
       +---- plugins

    Copy all your features into the features, and all your plugins and products into the plugins directory.

    Finally, you need to write a build configuration script that tells the PDE builder what to do. Name this file “build.properties” and put it in the “builder” directory.

    You can find a template for that file in your C:\eclipse\plugins\org.eclipse.pde.build_3.6.1.R36x_v20100823\templates\headless-build directory. I’ve taken this template, removed the unnecessary garbage, and this is what I got:

    product=path/to/the/product/file
    runPackager=true
    archivePrefix=eclipse
    collectingFolder=${archivePrefix}
    configs=win32, win32, x86 
    allowBinaryCycles = true
    flattenDependencies = true
    zipargs=
    tarargs=
    buildDirectory=path/to/the/builder/buildDirectory
    buildType=I
    buildId=$buildId
    buildLabel=${buildType}.${buildId}
    timestamp=007
    base=C:
    baseLocation=${base}/eclipse
    baseos=win32
    basews=win32
    basearch=x86
    filteredDependencyCheck=false
    resolution.devMode=false
    skipBase=true
    eclipseURL=
    eclipseBuildId=
    eclipseBaseURL=${eclipseURL}/eclipse-platform-${eclipseBuildId}-win32.zip
    skipMaps=true
    mapsRepo=:pserver:anonymous@example.com/path/to/repo
    mapsRoot=path/to/maps
    mapsCheckoutTag=HEAD
    mapsTagTag=v${buildId}
    skipFetch=true
    bootclasspath=${java.home}/lib/rt.jar;\${java.home}/lib/jsse.jar;\${java.home}/lib/jce.jar
    logExtension=.log
    javacDebugInfo=false
    javacFailOnError=true
    javacVerbose=true
    javacSource=1.6
    javacTarget=1.6

    You should modify the stuff that’s in red to suit your local system. In the bootclasspath property, tt’s important to include not only rt.jar but jsse.jar and jce.jar, just in case you’re doing some security-related things. For some reason Eclipse doesn’t pick these up automatically.

    Once the configuration file is written, you can run the following command:

    java -jar %ECLIPSE_HOME%/plugins/org.eclipse.equinox.launcher_1.1.1.R36x_v20101122_1400.jar -application org.eclipse.ant.core.antRunner -buildfile %ECLIPSE_HOME%/plugins/org.eclipse.pde.build_3.6.2.R36x_20110203/scripts/productBuild/productBuild.xml -Dbuilder=C:/absolute/path/to/builder
    Posted in programming and tagged , , , , . Use this permalink for a bookmark.
    * * * * *

    Unescape UTF8

    Because of various reasons, we cannot commit code with non-ascii characters. This is often a problem with internationalization, and is usually solved with escape codes. For example, č is \u010E. This escape code is usually converted nicely back to č by Java, but sometimes, it just won’t work. In this case, we need to do it manually. This method, obtained from Velocityreviews, does the trick:

    public static String unescapeUTF8(String s) {
    int i=0,len=s.length(); char c; StringBuffer sb = new StringBuffer(len);
    while (i<len) {
    c = s.charAt(i++);
    if (c=='\\') {
    if (i<len) {
    c = s.charAt(i++);
    if (c=='u') {
    c = (char) Integer.parseInt(s.substring(i,i+4),16);
    i += 4;
    } // add other cases here as desired...
    }} // fall through: \ escapes itself, quotes any character but u
    sb.append(c);
    }
    return sb.toString();
    }
    Posted in programming and tagged , , , . Use this permalink for a bookmark.
    * * * * *

    Perl, MySQL and UTF8

    Perl has a pretty clunky support for utf8, which is weird since it’s mainly a text-processing language and we’re in 2010 now… Anyway, there are ways to make Perl work with utf8. First of all, you need to add the following to get the basic things moving:

    use URI::Escape;
    use Encode;
    
    setlocale(LC_ALL, 'en_US.UTF-8');
    binmode(STDOUT, ":utf8");

    Now if you print “őúéá”, it will probably turn out fine (unless your console doesn’t support such characters). You may try printing that to a file, then it’ll probably show up fine.

    What to do if you want to print some more exotic characters to a file, like the Hungarian ű (not mentioning Chinese letters)? In that case, you need to set the output stream to utf8 encoding. You do that like this:

    require PerlIO;
    
    open(FILE, ">:utf8", "$filename") or die "OMG!\n";

    Finally, let’s say we want to insert utf8 into a MySQL database. Let’s assume that the mysql tables are already set to use the utf8 charset. In that case, you need to make some additional settings in Perl, because for whatever reason utf8 is not set automatically.

    our $dbh = DBI->connect($dsn, $user, $password)
    or die "Cant connect to the DB: $DBI::errstr\n";
    $dbh->{'mysql_enable_utf8'} = 1;
    $dbh->do("SET NAMES 'utf8'");

    Pretty complicated, and for no good reason…

    Posted in programming and tagged , , . Use this permalink for a bookmark.
    * * * * *

    Setting up VNC server on CentOS/RedHat

    It was unnecessarily difficult to setup the vnc server on a CentOS machine, so I’ll write down the steps here for future reference.

    First I needed to check if the vncserver is installed (it was):

    yum install vnc-server

    When I tried to run the server with /etc/init.d/vncserver start, I got an error saying there are no displays set up. However, when I run the vncserver command manually, it asked me for a password. I think this command should be run first, so you would type in the vnc password. After this, you should kill the process.

    Then go to /etc/sysconfig and edit the vncservers file. You need to configure a display, and tie a user to it. This means only one user will be able to login to that display.

    VNCSERVERS="0:johndoe"
    VNCSERVERARGS[0]="-geometry 800x600"

    Finally, you need to open port 5900 for the vnc server to be accessible from the outside, with the following rule:

    -A RH-Firewall-1-INPUT -p tcp -m state --state NEW -m tcp --dport 5900 -j ACCEPT

    Be careful, because the iptables may contain a line like this:

    -A RH-Firewall-1-INPUT -j REJECT --reject-with icmp-host-prohibited

    If your rule is added after this one, then it will be ignored. To change the ordering of the rules, export the rules to a textfile with

    /sbin/iptables-save > iptables.txt

    then open and edit the file, and then

    /sbin/iptables-restore < iptables.txt

    You should note that the vnc server, once started, binds to port 5900 + $displayNum. This means that, if you’ve configured johndoe on display 2, then you must open port 5902 and you must connect to this port. In a normal situation, you would only configure display 0.

    You also need to setup vncserver so it runs on system startup. You do this with:

    /sbin/chkconfig --level 1 vncserver on
    /sbin/chkconfig --level 2 vncserver on
    /sbin/chkconfig --level 3 vncserver on
    /sbin/chkconfig --level 4 vncserver on
    /sbin/chkconfig --level 5 vncserver on

    Posted in programming and tagged , , . Use this permalink for a bookmark.
    * * * * *

    Lexmark driver for Ubuntu

    I had a Lexmark x3650 multifunctional driver for some time now, and it’s main problem was that it wouldn’t work under Ubuntu. Apparently Lexmark is not a very Linux-friendly company… however, very recently they released a driver package for Debian-based systems, which of course includes Ubuntu. It has a very nice installation wizard interface, and the printer works more-or-less perfectly. Sometimes the driver fails for no reason and I have to unplug the printer to get it going again, but other than that, I can’t complain.

    The driver can be downloaded from Lexmark’s website: http://support.lexmark.com/index?page=content&productCode=LEXMARK_X3650&actp=PRODUCT&id=DR20523&segment=DOWNLOAD&userlocale=EN_US&locale=en

    Posted in hardware and tagged , , , , . Use this permalink for a bookmark.
    * * * * *

    How to find all files containing some text?

    The usual and fastest way would be

    grep -nre “some text” *

    However, this prints out too much information. If we only want to print out the name of the file, then the following can be used:

    find . -exec grep -q “some text” {} \; -exec echo {} \;

    A more hacky, but faster solution is the following:

    grep -rne “some text” * | cut -d: -f1 | uniq

    Also, if you only want to search in .txt files, then you can use the following:

    find -type f -iregex .*txt -exec grep textToFind {} \; -print

    Posted in programming and tagged . Use this permalink for a bookmark.
    * * * * *

    How to use Comet under Tomcat?

    I’ve tried to run the demo application from Atmosphere‘s examples page, and for some reason I kept getting the error message

     

    Tomcat failed to detect this is a Comet application

     

    It was telling me to put context.xml with some content into the META-INF directory in my war file. I’ve done this, but it didn’t help. Then, later, I’ve found on some forum that the file actually needs to be placed in the WEB-INF directory. Apparently this fixed the issue for JBoss… anyway, it didn’t fix anything for Tomcat. I’m using Tomcat6 as installed by apt-get on Ubuntu 9.10

     

    Then finally, I found a thread where they were talking about something completely different, but they did mention what the <Connector> tag looks like in Tomcat. I’ve looked at my Tomcat’s server.xml, and found out it didn’t look the same! It looked like this:

    <Connector port="8080" protocol="HTTP/1.1"
    connectionTimeout="20000"
    redirectPort="8443" />

    I’ve changed it to this:

    <Connector port="8080" address="0.0.0.0" maxThreads="250" maxHttpHeaderSize="8192" emptySessionPath="true" protocol="org.apache.coyote.http11.Http11NioProtocol" enableLookups="false" redirectPort="8443" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true" />

    I don’t exactly know what this does… I can see that the protocol has been changed to NIO, so I guess maybe that was the trick. In any case, after restarting Tomcat, the Atmosphere demo worked like a charm.

    Posted in programming and tagged , , , , . Use this permalink for a bookmark.
    * * * * *

    How to download rpm files with yum?

    There is a way to just download and not install rpm files with yum. This can be useful when you want yum to resolve dependencies, and then transfer the rpm files to another computer where yum is blocked by firewall or other obstacles.

    To be able to download files, you need to install the downloadonly plugin for yum.

    sudo yum install yum-downloadonly

    To download files to directory “out”, run the following command:

    sudo yum install xxx -y --downloadonly --downloaddir=/home/csaba/out

    All the rpms necessary for installing xxx will be downloaded to the out directory.

    Posted in programming and tagged , , . Use this permalink for a bookmark.
    * * * * *