Asdfasf

Sunday, July 15, 2012

Logging & LogBack & Slf4j

Loglamanin, bir yazilim projesi icin ne kadar onemli oldugunu biliyoruz. Yapilan arastirmalar, bir projesinin, %4'luk kisminin loglama'ya ayrildigini gosteriyor ki hacim olarak hic de azimsanmayacak bir deger.

Bir loglama framework'u olarak LogBack urununun kullanimini paylasacagim, Ceki Gulcu tarafindan gelistirilmis ve slf4j'nin kiz kardesi. Slf4j, loglama framework'unden bagimsiz olarak kodumuzu gelistirmemizi saglayan bir onyuz (facade) saglarken, logback, bizatihi loglama framework'unun kendisi.

Slf4j 'yi daha iyi ifade eden yine kendi sitesinden : The Simple Logging Facade for Java or (SLF4J) serves as a simple facade or abstraction for various logging frameworks, e.g. java.util.logging, log4j and logback, allowing the end user to plug in the desired logging framework at deployment time:

Hemen uygulamaya gecelim. Eclipse'de maven projemizi olusturuyoruz. Pom.xml'e girecegimiz dependency'ler su sekilde: 
<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.0.6</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.0.6</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-access</artifactId>
        <version>1.0.6</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.6.6</version>
    </dependency>            
  </dependencies>

logback.xml'imizi hazirliyoruz. LogBack'in configurasyon dosyasini bulabilmesi icin, logback.xml'in classpath'de olmasi gerekiyor.

Maven der ki, configurasyon dosyalarinizi src->main->config altina koyun ki biz de oyle yapiyoruz.

<configuration debug="true" scan="true" scanPeriod="10 seconds" >

    
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>testFile.log</file>
    <append>true</append>
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
        
  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
  
  
  
</configuration>


Peki eclipse logback.xml'i nasil classpath'e ekleyecek? Java Build Path -> Libraries -> Add External Class Folder:

En basit bir HelloWorld uygulamasi su sekilde:

package com.ferhat.logback_work;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld1 {

    static Logger logger = LoggerFactory.getLogger(HelloWorld1.class);

    public static void main(String[] args) {
        logger.debug("Hello world. {}", getString());
    }

    public static String getString() {
        return "ferhat";
    }
}

Dikkat ederseniz, kod icinde direk logBack dependency'si yok, slf4j hangi logging framework'u kullanacagini, classpath'imizde bulacagi logging framework'une gore karar veriyor ki biz logback jar'larini classpath'imizde bulunduruyoruz.

Logback kullanimi ile alakali detaylari burdan bulabilirsiniz.

logback.xml'deki bir-iki ufak detay dan bahsedeyim
<configuration debug="true" scan="true" scanPeriod="10 seconds" >

1.)debug="true" oldugu durumda, uygulamamiz baslarken logback, sistemde buldugu configurasyon dosyalari ve varsa hatalar hakkinda log basiyor ki faydali olacaktir.


2.)scan="true" oldugu durumda, logback belli araliklarla (scanPeriod="10 seconds"), logback.xml dosyasina goz atip var ise degisiklikleri uyguluyor.

3.)Log metodunun cagrildigi satir numarasini yazdirmak icin [%file:%line] ekliyoruz ancak runtime'da satir numarasi bilgisine ulasmanin maliyetli bir is oldugunu eklemekte fayda var:
<pattern>%-4relative [%thread] %-5level %logger{35} [%file:%line] - %msg%n</pattern>


Eclipse'de Maven Projesi Olusturma

Maven, standard bir maven projesinde, dosya yapisinin su sekilde olusturulmasini oneriyor:

src/main/javaApplication/Library sources
src/main/resourcesApplication/Library resources
src/main/filtersResource filter files
src/main/assemblyAssembly descriptors
src/main/configConfiguration files
src/main/scriptsApplication/Library scripts
src/main/webappWeb application sources
src/test/javaTest sources
src/test/resourcesTest resources
src/test/filtersTest resource filter files
src/siteSite
LICENSE.txtProject's license
NOTICE.txtNotices and attributions required by libraries that the project depends on
README.txtProject's readme

Bu yapiyi, Eclipse'de manuel olarak olusturabilecegimiz gibi, daha bircok avantaji ile beraber, m2e plugin'i sayesinde olusturabiliriz.

Eclipse'de Help->Eclipse Marketplace adimindan  Maven Integration for Eclipse kurulumunu yapalim.






Kurulum sonrasi, mavenized bir projeyi, New -> Maven Project adimi ile olusturabiliriz:

m2e, bizim icin standart bir maven projesi olusturuyor ve proje uzerinde kosabilecegimiz maven komutlarini, proje uzerine sag tiklayip Run menusunden sagliyor:

eclipse:eclipse

Proje dependency'lerimizi pom.xml'e girdikten sonra, bu dependency'lerin local repository'mize indirilip, projenin buildpath'ine eklenmesi icin eclipse:eclipse goal'unu kullanabiliriz. Peki local repository'miz neresi ve eclipse build path'e dependency'leri nasil ekliyor?
Eclipse'de, Project -> Preferences->Maven->User Settings' de maven kurulum ve settings.xml bilgisini gorebiliriz. Local repository bilgisi, settings.xml icinde mevcuttur.


Local repository bilgisini, Eclipse'e M2_REPO degiskeni olarak tanimliyoruz:
Project->Preferences -> Java->Build Path-> Class Path Variables -> New :
Proje uzerinde eclipse:eclipse goal'unu kostuktan sonra, projemizi refresh edince, dependency'lerin build path'imize eklendigini gorecegiz.

Friday, July 13, 2012

Simple JAX-WS WebService

Tomcat, WAR, AXIS ile ugrasmadan, JAX-WS ile standalone bir webservice ayaga kaldirmak bu kadar kolay:
Orjinali http://www.mkyong.com/webservices/jax-ws/jax-ws-hello-world-example/ da olan ornegi baz alip, sadece input parametrelerine Enum ve ArrayList ekleyip, annotation lar ile ornegi biraz daha zenginlestirmeye calistim.
JAX-WS annotation'larinin kullanimi icin :http://jax-ws.java.net/jax-ws-ea3/docs/annotations.html

Ilk adimimiz, acacagimiz servis'in interface'i

package com.mkyong.ws;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;

//Service Endpoint Interface
@WebService
@SOAPBinding(style = Style.RPC)
public interface HelloWorld {

    @WebMethod
    String getHelloWorldAsString(@WebParam(name = "request") Request request);

}


Metodumuz Request objesi aliyor ki icerigi su sekilde:

package com.mkyong.ws;

import java.util.ArrayList;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;

public class Request {
    private String name;
    private CallType callType;
    private ArrayList<String> clients;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public CallType getCallType() {
        return callType;
    }

    public void setCallType(CallType callType) {
        this.callType = callType;
    }

    @XmlElementWrapper(name = "clients")
    @XmlElement(name = "client")
    public ArrayList<String> getClients() {
        return clients;
    }

    public void setClients(ArrayList<String> clients) {
        this.clients = clients;
    }

}


package com.mkyong.ws;

public enum CallType {
    TEST, PROD;
}


WebService interface'imizi implemente ediyoruz:

package com.mkyong.ws;

import javax.jws.WebService;

//Service Implementation
@WebService(endpointInterface = "com.mkyong.ws.HelloWorld")
public class HelloWorldImpl implements HelloWorld {

    @Override
    public String getHelloWorldAsString(Request request) {
        System.out.println(request.getClients().size());
        return "Hello World JAX-WS " + request.getName() + ", "
                + request.getCallType() + ", " + request.getClients().get(0);
    }
}

Ve en can alici nokta: Kodladigimiz buservisi tek satirda publish ediyoruz:

package com.mkyong.endpoint;

import javax.xml.ws.Endpoint;

import com.mkyong.ws.HelloWorldImpl;

//Endpoint publisher
public class HelloWorldPublisher {

    public static void main(String[] args) {
        Endpoint.publish("http://localhost:9999/ws/hello", new HelloWorldImpl());
    }

}

WSDL'den wsimport ile client code uretip kullanmanin detaylari mkyong da mevcut, sadece HelloWorld interface'i elimizde mevcut ise client kod'u su sekilde olusturup kullanmamiz mumkun:


package com.mkyong.client;

import java.net.URL;
import java.util.ArrayList;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

import com.mkyong.ws.CallType;
import com.mkyong.ws.HelloWorld;
import com.mkyong.ws.Request;

public class HelloWorldClient {

    public static void main(String[] args) throws Exception {

        URL url = new URL("http://localhost:9999/ws/hello?wsdl");

        // 1st argument service URI, refer to wsdl document above
        // 2nd argument is service name, refer to wsdl document above
        QName qname = new QName("http://ws.mkyong.com/",
                "HelloWorldImplService");

        Service service = Service.create(url, qname);

        HelloWorld hello = service.getPort(HelloWorld.class);

        ArrayList<String> clients = new ArrayList<String>();
        clients.add("c1");

        Request request = new Request();
        request.setName("ferhat");
        request.setCallType(CallType.TEST);
        request.setClients(clients);

        System.out.println(hello.getHelloWorldAsString(request));

    }
}

Throttling


Cozmemiz gereken problem su, uygulamamiz belli bir interval'da belli sayida istegi kabul etmeli, bunun disindakileri reject etmeli. Yani sistemin TPS degerini garanti ettigimiz bir seviyede tutmak.

Bu problemin cozumu icin internette arastirirken, sagolsun Microsoft'dan Sergey abimizin cozumune rastladim. thanks Sergey, I owe a lot to you (http://1-800-magic.blogspot.com/)

Asagida Sergey'in Throttling mekanizmasini, thread pool ile bereaber kullanmaya calistim,
Talepleri, 10 TPS ile sinirlandirip, bu talepleri de 20'lik bir BlockingQueue icinden thread pool kullanarak eritelim.
  •  Max 10 TPS calisiyor  
RequestThrottler throttler = new RequestThrottler(10, 1000);
  • Talepleri 20'lik bir queue icinden eritiyoruz
new ArrayBlockingQueue(20);
  • Her talebin islenmesi 2 sn suruyor.

System.out.println("Executing index:" + index + " by "
                    + Thread.currentThread().getName());
            Thread.sleep(2000);

Test class'imiz asagidaki gibi:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test implements Runnable {
    int index;

    @Override
    public void run() {
        try {
            System.out.println("Executing index:" + index + " by "
                    + Thread.currentThread().getName());
            Thread.sleep(2000);
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 10 trx per second
        RequestThrottler throttler = new RequestThrottler(10, 1000);

        TestRejectedExecutionHandler execHandler = new TestRejectedExecutionHandler();

        BlockingQueue q = new ArrayBlockingQueue(20);
        ThreadPoolExecutor ex = new ThreadPoolExecutor(4, 10, 20,
                TimeUnit.SECONDS, q);
        ex.setRejectedExecutionHandler(execHandler);

        for (int i = 0; i < 10000; i++) {

            Thread.sleep(20);

            if (throttler.tryStartRequest() == 0) {
                System.out.println("Request Accepted " + i + " "
                        + System.currentTimeMillis());
                Test test = new Test();
                test.index = i;
                ex.execute(test);
            } else {
                System.err.println("Request Rejected by Throttler " + i);
            }
        }
    }
}

class TestRejectedExecutionHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable arg0, ThreadPoolExecutor arg1) {
        Test test = (Test) arg0;
        System.out.println(test.index + " is rejected");
    }
} 

Asagida RequestThrottler'in code'u mevcut. Orjinali icin: http://1-800-magic.blogspot.com/2008/02/throttling-requests-java.html

Genel hatlari ile yaptigi, her bir request'i, talep zamanlari ile indexleyip
long[] _ticks
, ilk ve son talebin _interval araliginde ve _maxCalls adedini gecmemesini kontrol etmek, bu sureci her interval'da resetleyip tekrar etmek:

/**
 * Class that throttles requests. Ensures that the StartRequest cannot be called
 * more than a given amount of time in any given interval.
 * 
 * StartRequest blocks until this requirement is satisfied.
 * 
 * TryStartRequest returns 0 if the request was cleared, or a non-0 number of
 * millisecond to sleep before the next attempt.
 * 
 * Simple usage, 10 requests per second:
 * 
 * Throttler t = new Throttler(10, 1000); ... ServiceRequest(Throtter t, ...) {
 * t.StartRequest(); .. do work ..
 * 
 * @author sergey@solyanik.com (Sergey Solyanik)
 *         http://1-800-magic.blogspot.com/2008/02/throttling-requests-java.html
 * 
 */
public class RequestThrottler {
    /**
     * The interval within we're ensuring max number of calls.
     */
    private final long _interval;

    /**
     * The maximum number of calls that can be made within the interval.
     */
    private final int _maxCalls;

    /**
     * Previous calls within the interval.
     */
    private final long[] _ticks;

    /**
     * Available element at the insertion point (back of the queue).
     */
    private int _tickNext;

    /**
     * Element at the removal point (front of the queue).
     */
    private int _tickLast;

    /**
     * the time when the last expired request occured. might be used to auto
     * disable some services all together
     */
    private long _lastExpiredMaxWait = 0;

    /**
     * Constructor.
     * 
     * @param maxCalls
     *            Max number of calls that can be made within the interval.
     * @param interval
     *            The interval.
     */
    public RequestThrottler(final int maxCalls, final long interval) {
        if (maxCalls < 1) {
            throw new IllegalArgumentException("maxCalls must be >=1, was "
                    + maxCalls);
        }
        if (interval < 1) {
            throw new IllegalArgumentException("interval must be >=1, was "
                    + interval);
        }
        _interval = interval;
        _maxCalls = maxCalls + 1;
        _ticks = new long[_maxCalls];
        _tickLast = _tickNext = 0;
    }

    /**
     * Returns the next element in the queue.
     * 
     * @param index
     *            The element for which to compute the next.
     * @return
     */
    private int next(int index) {
        index += 1;
        return index < _maxCalls ? index : 0;
    }

    /**
     * Attempts to clear the request.
     * 
     * @return Returns 0 if successful, or a time hint (ms) in which we should
     *         attempt to clear it again.
     */
    public synchronized long tryStartRequest() {
        long result = 0;
        final long now = System.currentTimeMillis();
        while (_tickLast != _tickNext) {
            if (now - _ticks[_tickLast] < _interval) {
                break;
            }
            _tickLast = next(_tickLast);
        }

        final int next = next(_tickNext);
        if (next != _tickLast) {
            _ticks[_tickNext] = now;
            _tickNext = next;
        } else {
            result = _interval - (now - _ticks[_tickLast]);
        }
        return result;
    }

    /**
     * Clears the request. Blocks until the request can execute.
     */
    public void startRequest() {
        startRequest(Integer.MAX_VALUE);
    }

    /**
     * Clears the request. Blocks until the request can execute or until waxWait
     * would be exceeded.
     * 
     * @return true if successful or false if request should not execute
     */
    public boolean startRequest(final int maxWait) {
        long sleep;
        long total = 0;
        while ((sleep = tryStartRequest()) > 0) {
            if (maxWait > 0 && (total += sleep) > maxWait) {
                _lastExpiredMaxWait = System.currentTimeMillis();
                return false;
            } else {
                try {
                    Thread.sleep(sleep);
                } catch (final InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        }
        return true;
    }

    public long getLastExpiredMaxWait() {
        return _lastExpiredMaxWait;
    }

}