Wednesday, August 29, 2012

Fast way to kill many MySQL processes

I think everyone who works for some time with MySQL has been in trouble with suspended processes or reaching the limit of connections. The fastest way to fix it is to restart MySQL service
service mysql restart

But if it's the production server and you cannot afford restarting the service then here is the tip for you.

At first we need to find out what processes are causing troubles. Let's login to MySQL:
mysql -uroot -ptoor

And watch the list of all processes
select * from information_schema.processlist;

or
show processlist;

Let's assume we need to delete processes that are active more then 1000 seconds. To watch them we need this query:
select * from information_schema.processlist where TIME > 1000;

Let's format the output and save it to the file (if you have problems saving to the file then check this post)
select concat('kill ',id,';') 
into outfile '/home/anton/mysql/kill.sql' 
from information_schema.processlist 
where TIME > 1000;

After this let's load the created file
source /home/anton/mysql/kill.sql;

Thus we have deleted multiple MySQL processes with our own criteria.

Sunday, August 26, 2012

Reading XML files using XPath

Nowadays declarative programming has took a well-deserved place in imperative languages. In Java we can declare what we want in annotations or in XML. The main plus of XML is that we can reconfigure our application in one place and update it without rebuilding.

There a lot of ways to read data from XML. The most common ones are: XPath, XSLT and XQuery.
  • XPath consists of path expressions, conditions and some XPath functions
  • XSLT(Extensible Stylesheet Language (with) Transformation) consists of XPath and some transformations
  • XQuery consists of XPath and a Query Language. This query language is pretty powerful but it does not have strong underlying mathematics like SQL.

In this tutorial we will develop a simple XPath XML file parser. But as you probably know, there are different kind of parsers:
  • DOM(Document Object Model) parsers. DOM API creates a DOM tree in memory for a XML document
  • SAX(Simple API For XML) parsers. They are event driven(as they parse documents they invoke the callback methods)

We will develop DOM parser. For this purpose we will use only standard Java classes without any additional dependencies.
As an example XML file we will use JBoss 5.1 datasource file. Here it is:
<?xml version="1.0" encoding="UTF-8"?>

<datasources>
    <xa-datasource>
        <jndi-name>DevDbDS</jndi-name>
 <!--
        <use-java-context>false</use-java-context>
 -->
 <xa-datasource-class>
  com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
 </xa-datasource-class>
 <xa-datasource-property name="URL">
  URL:jdbc:mysql://localhost:3306/dev_db
 </xa-datasource-property>

        <user-name>root</user-name>
        <password>root</password>
 <!--
        <security-domain>VocdmsDSEncryptedLogon</security-domain>
 -->
    <xa-datasource-property name="characterEncoding">UTF-8</xa-datasource-property>

 <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
        <min-pool-size>5</min-pool-size>
        <max-pool-size>20</max-pool-size>
        <blocking-timeout-millis>30000</blocking-timeout-millis>
        <idle-timeout-minutes>15</idle-timeout-minutes>
        <prefill>true</prefill>
        <transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
        <connection-property name="characterEncoding">UTF-8</connection-property>
        <connection-property name="autoReconnect">true</connection-property>
        <connection-property name="maxReconnects">4</connection-property>
        <connection-property name="initialTimeout">3</connection-property>
        <metadata>
            <type-mapping>mySQL</type-mapping>
        </metadata>
    </xa-datasource>
</datasources>

Next we need to remind ourselves how to write XPath expressions. You can do it here

Now finally we are ready to write the code to get some information from the datasource. In this example let's assume we need user name, password and URL(you may need this data to establish java.sql.Connection). So here is our code:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import java.io.*;

public class XmlParser {
 //Immutable object for data storage
 private static class ConnectionInfo {
  private final String url;
  private final String userName;
  private final String password;
  
  public ConnectionInfo(String url, String userName, String password) {
   this.url = url;
   this.userName = userName;
   this.password = password;
  }
 
  public String getUrl() {
   return url;
  }
  public String getUserName() {
   return userName;
  }
  public String getPassword() {
   return password;
  } 
  @Override
  public String toString() {
   return "URL: " + url +
    "\nuserName: " + userName + 
    "\npassword: " + password;
  }
 }

 public static void main(String[] args) {
  System.out.println(parseDataSource("mysql-ds.xml"));
 }

 public static ConnectionInfo parseDataSource(String fileName) {
  try {   
      DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
      domFactory.setNamespaceAware(true); // never forget this!
      DocumentBuilder builder = domFactory.newDocumentBuilder();
      Document doc = builder.parse(new FileInputStream(fileName));

      XPathFactory factory = XPathFactory.newInstance();
      XPath xpath = factory.newXPath();
      String url = xpath.evaluate("//datasources/xa-datasource/xa-datasource-property[@name='URL']/text()", doc).trim();     
      String userName = xpath.evaluate("//datasources/xa-datasource/user-name/text()", doc).trim();
      String password = xpath.evaluate("//datasources/xa-datasource/password/text()", doc).trim();
      return new ConnectionInfo(url, userName, password);
  } catch (Exception ex) {
      ex.printStackTrace(); 
      return null;
  }
     } 
}

Friday, August 24, 2012

Google Analytics API usage example

It's very useful in SEO business to keep track of different user's action on your site. One of the best tools for such kind of analysis is Google Analytics. Furthermore it's free and provides the API. In this post I will show you the simple example of usage of this API.

As usual let's start with the Maven dependencies. But unfortunately Google do not provide jars that we need in any repository. Thus we need to download and install them to Maven by ourselves. Google for "gdata core jar" and "gdata analytics jar". When you will have those files execute in command line following code to install these jars:
mvn install:install-file -Dfile=gdata-core-1.0.jar -DgroupId=com.google.gdata -DartifactId=gdata-core -Dversion=1.0 -Dpackaging=jar -DgeneratePom=true
mvn install:install-file -Dfile=gdata-analytics-2.1.jar -DgroupId=com.google.gdata -DartifactId=gdata-analytics -Dversion=2.1 -Dpackaging=jar -DgeneratePom=true

Add to pom.xml following Maven dependencies:
<dependency>
    <groupId>com.google.gdata</groupId>
    <artifactId>gdata-core</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>com.google.gdata</groupId>
    <artifactId>gdata-analytics</artifactId>
    <version>2.1</version>
</dependency>

Also you may need Guava(which is Google's library with different basic and universal functions.
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>10.0.1</version>
</dependency>

After maven fetches us our new dependencies we may continue and develop Java source code for Google Analytics API data fetching.
public static DataFeed getDataFeed(String dimensions, String metrics, String userName, String password, 
        String tableId, String startDate, String endDate, int startIndex) throws IOException, ServiceException  {
    System.out.println("Building data feed");
    // Configure GA API.
    AnalyticsService as = new AnalyticsService("My application name");

    // Client Login Authorization.
    as.setUserCredentials(userName, password);
    DataQuery query = new DataQuery(new URL("https://www.google.com/analytics/feeds/data"));
    query.setIds(tableId);
    query.setDimensions(dimensions);
    query.setMetrics(metrics);
    query.setSort("-" + metrics);
    query.setMaxResults(10000);        
    query.setStartDate(startDate);
    query.setEndDate(endDate);
    query.setStartIndex(startIndex);
    URL url = query.getUrl();
    
    System.out.println("URL: " + url.toString());

    // Send our request to the Analytics API and wait for the results to
    // come back.
    DataFeed feed = as.getFeed(url, DataFeed.class);
    return feed;
}

As a parameters you should provide
  • dimensions and metrics (you can get both from Google Analytics API Reference)
  • Google Analytics credentials (user name and password)
  • Google Analytics Profile ID(also called table ID)
  • date range for quering
  • start index(We need to use start index because Google Analytics service won't return result set with more then 10000 records)

Let's save data that we got to CSV file using opencsv:
public static void saveAnalyticsFile(DataFeed feed, String fileName) {
 System.out.println("Saving to file");
    try {
        CSVWriter writer = new CSVWriter(new FileWriter(fileName), ',' ,CSVWriter.DEFAULT_QUOTE_CHARACTER, '\\');
        
        DataEntry singleEntry = feed.getEntries().get(0);
        List<String> feedDataNames = new ArrayList<String>();
        List<String> dataRowValues = new ArrayList<String>(LoaderConstants.GA_FILE_HEADER.length);

        // Put all the dimension and metric names into an array.
        for (Dimension dimension : singleEntry.getDimensions()) {
            feedDataNames.add(dimension.getName());
        }
        for (Metric metric : singleEntry.getMetrics()) {
            feedDataNames.add(metric.getName());
        }

        //write header
        writer.writeNext(LoaderConstants.GA_FILE_HEADER);

        for (DataEntry entry : feed.getEntries()) {         
         //assuming that the first entry is a keyword and others are numeric values
            if (!entry.stringValueOf(feedDataNames.get(0)).equals("(not provided)") && 
                    !entry.stringValueOf(feedDataNames.get(0)).equals("(not set)")) {
             dataRowValues.add(entry.stringValueOf(feedDataNames.get(0))); //keyword
             for (int i = 1; i < feedDataNames.size(); i++) {
                 Double d = Double.parseDouble(entry.stringValueOf(feedDataNames.get(i)));
                 String googleVal = new Long(d.longValue()).toString();
                 dataRowValues.add(googleVal); 
             }
                writer.writeNext(dataRowValues.toArray(new String[0]));
             
                dataRowValues.clear();
            }
        }
        writer.close();        
        System.out.println("Saving is done");
        
    } catch (Exception e) {
        System.out.println("Cannot save file ");
        throw new RuntimeException(e);
    }
}

And finally here is the main method:
public static void main(String[] args) {
 int startIndex = 1;
 boolean fetch = true;
 while (fetch) {
     DataFeed dataFeed = getDataFeed("ga:keyword", "ga:visits", "userName", "password", 
                "profileId", "2012-08-13", "2012-08-19", startIndex);
        CsvWriteUtils.saveAnalyticsFile(dataFeed, "test");
        startIndex += 10000; //Google Analytics Max result value
        //continue fetching until we receive all data
        if (startIndex > dataFeed.getTotalResults()) {
         fetch = false;
        }
 }
}

Wednesday, July 4, 2012

LDAP recovery

If you have suffered from LDAP wreckage then probably you already know how difficult it is to make it work again. In my case all problems started with such message in system log:
slapd[11705]: bdb(dc=xxx,dc=com): PANIC: fatal region error detected; run recovery

Note: my bdb version is 4.7, yours may differ;
slapd stands for Stand-alone LDAP Daemon, your service name may be ldap


At first I tried to recover LDAP using
db4.7_verify -h /var/lib/ldap
db4.7_recover -v -h /var/lib/ldap 

But LDAP got broken again and again.
Thus I decided to recreate whole LDAP folder. Here is the scenario:

  1. Stop the LDAP server
    service slapd stop
  2. Dump the directory structure to a text file
    slapcat -l /etc/ldap/backup/ldap_old.ldif
  3. Recover LDAP
    db4.7_recover -v -h /var/lib/ldap
  4. Dump the directory structure to a text file
    slapcat -l /etc/ldap/backup/ldap.ldif
  5. Verify that ldap.ldif contains your entries. If it does not, or if slapcat returned errors in step 3, try running db4.7_recover in catastrophic mode:
    db4.7_recover -v -h /var/lib/ldap -c
    and repeat step 3
  6. Delete the LDAP directory
    rm -fr /var/lib/ldap/*
  7. Load LDAP directory from the file you have create in step 3
    slapadd -l /etc/ldap/backup/slap.ldif
  8. Make openldap the owner of the LDAP directory
    chown -R openldap:openldap /var/lib/ldap/*
  9. Start the LDAP server
    service slapd start

Saturday, June 9, 2012

AuthorityLabs Partner API

If you need to get SERP(search engine results page) rank information on some keywords, there is a very good solution - AuthorityLabs Partner API. I have successfully used it on one of my projects and I want to share with you some tips on developing functionality to work with it.

AuthorityLabs Partner API is as a REST service. The main API methods are for POSTing keywords that you want to get info on and GETting the information from POSTed keywords. There are 2 different ways of work with POST requests:
  • Instant POST request(expensive)
  • Delayed POST request(cheaper)
We'll work with Delayed requests.
At first we should think how are we going to work with the Web service. I have chosen to use Apache HttpComponents because it's straightforward and easy to use. To download the library I used the following Maven dependencies:
<dependency>
 <groupId>org.apache.httpcomponents</groupId>
 <artifactId>httpcore</artifactId>
 <version>4.2</version>
</dependency>
 <dependency>
 <groupId>org.apache.httpcomponents</groupId>
 <artifactId>httpclient</artifactId>
 <version>4.1.3</version>
</dependency>

Now let's develop method to POST keywords to the AuthorityLabs Partner API.
public class AuthorityLabsTest {
 private static final String AUTHORITYLABS_AUTH_TOKEN = "XXX";
 private static final String AUTHORITYLABS_URL_POST_KEYWORD = "http://api.authoritylabs.com/keywords";

 
  /**
     * Post keyword to the delayed queue of the AuthorityLabs API
     * @param keyword
     * @param engine
     * @param locale
     * @return
     */
 static boolean postKeyword(String keyword, String engine, String locale) {
  HttpClient client = new DefaultHttpClient();
  
  HttpPost post = new HttpPost(AUTHORITYLABS_URL_POST_KEYWORD);
  try {
   List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
   nameValuePairs.add(new BasicNameValuePair("auth_token", AUTHORITYLABS_AUTH_TOKEN));
   nameValuePairs.add(new BasicNameValuePair("keyword",keyword));
   nameValuePairs.add(new BasicNameValuePair("engine", engine));
   nameValuePairs.add(new BasicNameValuePair("locale", locale));  
   post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
   System.out.println("Post keyword; parameters - " + nameValuePairs);
 
   HttpResponse response = client.execute(post);
   BufferedReader rd = new BufferedReader(new InputStreamReader(
     response.getEntity().getContent()));
   String line = "";
   while ((line = rd.readLine()) != null) {
    System.out.println(line);
   }

  } catch (IOException e) {
   e.printStackTrace();
   return false;
  }
  return true;
 }
}

Next we need method to GET keyword info.
public class AuthorityLabsTest {
 private static final String AUTHORITYLABS_AUTH_TOKEN = "XXX";
 private static final String AUTHORITYLABS_URL_POST_KEYWORD = "http://api.authoritylabs.com/keywords";
 private static final String AUTHORITYLABS_URL_GET_RESULTS = "http://api.authoritylabs.com/keywords/get.json?keyword={0}&auth_token={1}&engine={2}&locale={3}&rank_date={4}";

 static boolean postKeyword(String keyword, String engine, String locale) {
    ...
 }
 
 /**
  * Gets JSON results from the AuthorityLabs API
  * @param keyword
  * @param engine
  * @param locale
  * @param stringPostKeywordDate
  * @return
  */
 static InputStream getKeywordResults(String keyword, String engine, String locale, String stringPostKeywordDate) {
  String urlPattern = AUTHORITYLABS_URL_GET_RESULTS;
  String url = MessageFormat.format(urlPattern, keyword, AUTHORITYLABS_AUTH_TOKEN, 
    engine, locale, stringPostKeywordDate);
  //Prepare URL (e.g. replace spaces with %20 etc)
  try {
   url = URIUtil.encodeQuery(url);
  } catch (URIException e1) {
   e1.printStackTrace();
   return null;
  }
  System.out.println("Get keyword results; url - " + url);
  
  HttpClient client = new DefaultHttpClient();
  HttpGet get = new HttpGet(url); 
  HttpResponse response;
  try {
   response = client.execute(get);
   System.out.println(response.getStatusLine());   
   return response.getEntity().getContent();
  } catch (Exception e) {
   e.printStackTrace();
  }   
  return null;
 }
}

Note that we need to encode URL in order to be able to get results. To choose correct engine and locale for that engine please check the documentation on AuthorityLabs API.

That's the core of the work with AuthorityLabs API.