Search in this blog

Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Tuesday, July 26, 2016

Scrapping Facebook Groups almost in real time (FGIR)

Today I want to introduce a project I developed some years ago which I called FGIR (Facebook Groups Information Retriever).

FGIR is a project intended to scrape and organize data from Facebook groups, the data is retrieved using email notifications from Facebook, they are received incredibly fast (this is the reason why I said almost in real time).

Note: Facebook is very picky with automated data collection(https://www.facebook.com/apps/site_scraping_tos_terms.php), so be careful.

So, why to scrape Facebook?
In my location, people started to create groups intended to buy and sell things. It got really popular, I remember to be joined in a group having around 200k people (which I consider to be a lot for a small city). There were really good opportunities to buy cheap things but it was really difficult to find them, groups were messy and so many people were adding posts and comments every second.

I decided to store that valuable information in order to organize it and get the products I wanted to buy.

I turned on email notifications on Facebook and wrote a client to listen for new emails, there are two kind of emails handled:
1.- Added to a new group.
2.- There is a new post in a group.

Each email was parsed to detect which kind of event happened, the first one was easier than the second one, anyway, I stored the retrieved information in a database model.

Also, I wrote a web application to query the database model to be able to find what I wanted (which is lost and I can't share).

I uploaded the project code (FGIR) to github, I'm surprised that it still works.

I hope that it can be useful for you as it was for me when I wrote it.

Thanks for reading.

Monday, July 13, 2015

MyBatis-Spring Summary for Production Applications

MyBatis is my favorite ORM, when I first tried to use it I got surprised because the configuration was a really easy task.

I use it in every app with a relational database, I combine it with Spring as a Dependency Injection framework.

Most of the configuration I will write is using Annotations, read more about MyBatis-Spring to get a better understanding.

The next beans are for the Spring XML file configuration:

    <!-- DataSource -->
    <bean id="dataSource"
          class="org.apache.tomcat.jdbc.pool.DataSource">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="jdbc:postgresql://localhost:5432/dbname" />
        <property name="username" value="user" />
        <property name="password" value="password" />
        <property name="maxWait" value="15" />
        <property name="removeAbandonedTimeout" value="15" />
        <property name="defaultAutoCommit" value="false" />
    </bean>


You should define a dataSource, I use Tomcat Connection Pool, you can use the one you prefer.

Define the sqlSessionFactory:

    <!-- MyBatis config -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
    </bean>


Defina a mapper bean (the one with the methods and sql queries):

    <!-- MyBatis mappers -->
    <bean id="comercioMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="com.alex.mapper.SampleMapper" />
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>


Create the Interface for com.alex.SampleMapper:

package com.alex.mapper;

@Service
public interface SampleMapper {
 // the code later
}


Create a sample POJO:

public class Sample {
 private Integer id;
 private String name;
 private Integer age;
 // getters / setters
}


Assume you have a table like:

CREATE TABLE sample (
 id SERIAL NOT NULL PRIMARY KEY,
 name VARCHAR(50) NOT NULL,
 age INT NOT NULL
);


Declare methods to Add / Update / Delete:

    @Insert( "INSERT INTO sample (name, age) VALUES ( #{s.name), #{s.age) )" )
    @Options(useGeneratedKeys = true, keyProperty = "s.id")
    void add(@Param("s") Sample s);

    @Delete( "DELETE FROM sample WHERE id = #{s.id)" )
    void delete(@Param("s") Sample s);

    @Update( "UPDATE sample SET name = #{s.name}, age = #{s.age} WHERE id = #{s.id}" )
    void update(@Param("s") Sample s);

    @Select( "SELECT * FROM sample WHERE id = #{id}" )
    Sample find(@Param("id") Integer id);


The important part here is the "add" method which will try to insert a new row in a table with auto generated keys, if succeed, the generated key will be stored in the id field of the stored object.

This is really good, but what about is your the fields in your object have different names of the table in the database?

For example:

public class Sample {
 Integer id;
 String theName;
 Integer age;
}


You have to tell MyBatis for handling "theName" field as "name" column:

    @Select( "SELECT * FROM sample WHERE id = #{id}" )
    @Results({
        @Result(property = "theName", column = "name")
    })
    Sample find(@Param("id") Integer id);


What about reading complex objects?

public class Sample {
 Integer id;
 Data data,
}

public class Data {
 String name;
 Integer age;
}


Is the same, can you see it?

    @Select( "SELECT * FROM sample WHERE id = #{id}" )
    @Results({
        @Result(property = "data.name", column = "name"),
        @Result(property = "data.age", column = "age")
    })
    Sample find(@Param("id") Integer id);


Have you heard about TypeHandler? What about it?

The objects:

public class Sample {
 Integer id;
 Other other;
}

public class Other {
 Integer id;
}


The table:

CREATE TABLE sample (
 id INT NOT NULL PRIMARY KEY,
 other_id INT NOT NULL
)


The mapper:

    @Select( "SELECT * FROM sample WHERE id = #{id}" )
    @Results({
        @Result(
          property = "other.id", column = "other_id", typeHandler = OtherTypeHandler.class
        )
    })
    Sample find(@Param("id") Integer id);


Define the TypeHandler:

public class OtherTypeHandler extends BaseTypeHandler<Other> {
    @Override
    public Other getNullableResult(ResultSet rs, String colName) throws SQLException {
        Other other = new Other();
        other.setId(rs.getInt(colName));
        return other;
    }

    @Override
    public Other getNullableResult(ResultSet rs, int colNum) throws SQLException {
        Other other = new Other();
        other.setId(rs.getInt(colNum));
        return other;
    }

    @Override
    public Other getNullableResult(CallableStatement cs, int colNum) throws SQLException {
        Other other = new Other();
        other.setId(cs.getInt(colNum));
        return other;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Other t, JdbcType jt) throws SQLException {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
}


These are all the things I had needed using MyBatis, in the moment I needed I invest some time to find them, I put it here for using them if I forget them and hoping can be useful for you and save your time.

See you in the next post.

Sunday, July 12, 2015

Java Geocoding using Google Maps Api

Java Server-Side Geocoding

I was developing a web app including a map and some markers in it, I got a decent database having many human readable addresses like "1600 Amphitheatre Parkway, Mountain View, CA", in order to put a marker in the map for a human readable address you need coordinates, Geocoding is the process of for converting human readable address into geographic coordinates (latitude, longitude).

My app is developed in Java using Google Maps, Google has a API for geocoding (https://developers.google.com/maps/documentation/geocoding/), the problem is, the API works for JavaScript only, you can find lots of examples for using it this way.

Google has a web service which can give you geocodes in json or xml, try this link: http://maps.googleapis.com/maps/api/geocode/json?address=california&sensor=false

Here we are requesting the location of "california".

Now make in it works in Java is not a hard taks, you need to write the necessary objects for representing the json structure and some a method to do a GET request and parse the json result into the Java object.

I will use Apache Http Components for doing the request and Jackson JSON Processor for parsing the request.

First, declare the requiered objects for the geocoding API (all of them should have getters and setters, DONT FORGUET TO PUT IT).

public class GoogleGeoCode {
    private String status;
    private GoogleGeoResult [] results;
    private Boolean exclude_from_slo;
    private String error_message;
}
 
public class GoogleGeoResult   {
    private GoogleGeoAdressComponent [] address_components;
    private String formatted_address;
    private GoogleGeoGeometry geometry;
    private Boolean partial_match;
    private String place_id;
    private String [] types;
 

public class GoogleGeoAdressComponent {
    private String long_name;
    private String short_name;
    private String [] types;
}
 
public class GoogleGeoGeometry {
    private GoogleGeoBounds bounds;
    private GoogleGeoLatLng location;
    private String location_type;
    private GoogleGeoBounds viewport;
}
  
public class GoogleGeoBounds   {
    private GoogleGeoLatLng northeast;
    private GoogleGeoLatLng southwest;
}
 
public class GoogleGeoLatLng {
    private String lat;
    private String lng;
 

Before doing the next I hope you had read the documentation of Google for geocoding, if not, reading it would help you to understand some things.

Google allows you to do geocoding without having an API_KEY but in most cases having it would be better, if you will use the API_KEY you should do the request using SSL (https), if you sent the KEY over HTTP, google will reject the request, the method works for BOTH (http and https):

/**
 * Given an address asks google for geocode
 *
 * If ssl is true API_KEY should be a valid developer key (given by google)
 *
 * @param address the address to find
 * @param ssl defines if ssl should be used
 * @return the GoogleGeoCode found
 * @throws Exception in case of any error
 *
 */
public GoogleGeoCode getGeoCode(String address, boolean ssl) throws Exception {
    // build url
    StringBuilder url = new StringBuilder("http");
    if ( ssl ) {
        url.append("s");
    }
  
    url.append("://maps.googleapis.com/maps/api/geocode/json?");
  
    if ( ssl ) {
        url.append("key=");
        url.append(API_KEY);
        url.append("&");
    }
    url.append("sensor=false&address=");
    url.append( URLEncoder.encode(address) );
  
    // request url like: http://maps.googleapis.com/maps/api/geocode/json?address=" + URLEncoder.encode(address) + "&sensor=false"
    // do request
    try (CloseableHttpClient httpclient = HttpClients.createDefault();) {
        HttpGet request = new HttpGet(url.toString());

        // set common headers (may useless)
        request.setHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0 Iceweasel/31.6.0");
        request.setHeader("Host", "maps.googleapis.com");
        request.setHeader("Connection", "keep-alive");
        request.setHeader("Accept-Language", "en-US,en;q=0.5");
        request.setHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        request.setHeader("Accept-Encoding", "gzip, deflate");

        try (CloseableHttpResponse response = httpclient.execute(request)) {
            HttpEntity entity = response.getEntity();

            // recover String response (for debug purposes)
            StringBuilder result = new StringBuilder();
            try (BufferedReader in = new BufferedReader(new InputStreamReader(entity.getContent()))) {
                String inputLine;
                while ((inputLine = in.readLine()) != null) {
                    result.append(inputLine);
                    result.append("\n");
                }
            }

            // parse result
            ObjectMapper mapper = new ObjectMapper();
            GoogleGeoCode geocode = mapper.readValue(result.toString(), GoogleGeoCode.class);

            if (!"OK".equals(geocode.getStatus())) {
                if (geocode.getError_message() != null) {
                    throw new Exception(geocode.getError_message());
                }
                throw new Exception("Can not find geocode for: " + address);
            }
            return geocode;
        }
    }
}



HttpComponents and Jackson Parser made the job easier.

You may notice some request returns most than 1 result, in this case, what about for keeping the better one?

I define the better one as the result with the most similar address to the one requested (google gives you a formatted address for every result),

I used a simple approach for measuring this, the Longest Common Subsequence, the next method will help you to filter the results into the best one, I used in my app and seems to work really well.

/**
 * Given an address and google geocode find the most probable location of
 * address, the measure uses the longest common subsequence algorithm and a
 * minimum requirement for similarity
 *
 * @param address the original address
 * @param geocode the google geocode results
 * @return the most probable location (lat, lng), null if no one matches
 */
public GoogleGeoLatLng getMostProbableLocation(String address, GoogleGeoCode geocode) {
    address = address.toLowerCase();
    int expected = address.length() / 2;
    int sz = geocode.getResults().length;
    int best = expected;
    GoogleGeoLatLng latlng = null;
    for (GoogleGeoResult result : geocode.getResults()) {
        GoogleGeoLatLng cur = result.getGeometry().getLocation();
        String formattedAddress = result.getFormatted_address().toLowerCase();
        int p = lcs(address, formattedAddress);

        if (p > best) {
            latlng = cur;
            best = p;
        }
    }
    return latlng;
}




And the LCS method:

/**
 * The longest common subsequence of s and t using dynamic programming
 *
 * @param s the first string
 * @param t the second string
 * @return the length of the longest common subsequence
 */
private int lcs(String s, String t) {
    int N = s.length();
    int M = t.length();
    int[][] ans = new int[N + 1][M + 1];
    for (int k = N - 1; k >= 0; k--) {
        for (int m = M - 1; m >= 0; m--) {
            if (s.charAt(k) == t.charAt(m)) {
                ans[k][m] = 1 + ans[k + 1][m + 1];
            } else {
                ans[k][m] = Math.max(ans[k + 1][m], ans[k][m + 1]);
            }
        }
    }
    return ans[0][0];
}



I packet them into a simple class:

/**
 * Utils for Google geocoding api
 * 
 * @author Alexis Hernandez
 */
public class GoogleGeoUtils {
 public GoogleGeoCode getGeoCode(String address, boolean ssl); 
 public GoogleGeoLatLng getMostProbableLocation(String address, GoogleGeoCode geocode);
 private int lcs(String s, String t);
}


I may attach the sources later.

IMPORTANT NOTE: I used HttpClient 4.5 (the current latest version) but some previous versions have issues requesting google apis using SSL.


After I wrote the code I found a library which appears to do the work: https://code.google.com/p/geocoder-java/

If you want to, give it a try, I didn't tested.


I hope it can be useful for you, thanks for reading and see you in the next post.

Thursday, July 2, 2015

Simple Java XML Viewer

XML Files are very popular for storing and sharing information, I'll not repeat what you can find in search engines.

XML format is a tree structure for storing data and fits perfectly in a JTree.

I used dom4j for parsing the file (which is really easy to use).

This is a snapshot with a pom.xml used by maven:


















Dom4j provide a really easy way for parsing the file:

   SAXReader reader = new SAXReader();
   Document doc = reader.read(inputFile);

   Element e = doc.getRootElement();

Now you can traverse the xml tree using the root element in your favorite order.

You can see a way using recursion in the source code (attached into the jar file).

Download.

This can be helpful for parsing web pages, most of them are malformed but you can find a way to add / remove / change content with a little bit of parsing to make a well formed document and use dom4j to parse it for you (I did it several times).

I hope this can be helpful for you.

Thanks for reading and see you in the next post.