Wednesday, November 26, 2008

Maven + hibernate + jpa + ehcache + spring + terracotta + composite keys

Some user were having some problem using composite keys with hibernate and ehcache as second level cache with Terracotta.

I tried beefing up a sample app with composite keys so that I can run the app with Terracotta... and it worked out smoothly in a quite small amount of time.

I reused much of what we did for Examinator, and came up with the app without much pain in very small amount of time. Really, Examinator (source) contains quite a lot of things that can be re-used to come up with with these kind of apps.

I'll try to put in the main parts of the sample app that I came up here:

Used maven-quickstart archetype to generate a quick project skeleton.

First the domain classes -- Its a Product class, which is uniquely identified by a combination of its productId and groupId. It also has a description property.

package sample.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Entity
@IdClass(ProductCompositeKey.class)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Product {

@Id
private Long productId;

@Id
private Long groupId;

@Column(name = "DESCRIPTION")
private String description;

//...getters and setters...

}



We are using JPA annotations to define the entities. We annotate the Product class with the @Entity annotation, and as normally, annotate the productId and groupId properties with @Id.
We are using hibernate annotation @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) to enable second-level caching on this entity.
The @IdClass annotation refers to a class which will be the composite-key class.
The ProductCompositeKey is this class and is as follows:

package sample.model;

import java.io.Serializable;

import javax.persistence.Embeddable;

@Embeddable
public class ProductCompositeKey implements Serializable {

private Long productId;
private Long groupId;

//...getters and setters...

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((groupId == null) ? 0 : groupId.hashCode());
result = prime * result + ((productId == null) ? 0 : productId.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ProductCompositeKey other = (ProductCompositeKey) obj;
if (groupId == null) {
if (other.groupId != null)
return false;
} else if (!groupId.equals(other.groupId))
return false;
if (productId == null) {
if (other.productId != null)
return false;
} else if (!productId.equals(other.productId))
return false;
return true;
}

}




The ProductCompositeKey class is annotated with the @Embeddable annotation. Note that it is not annotated with @Entity
We need to override equals() and hashcode() and also implement the Serializable interface to make hibernate happy.


Next we define DAO classes for the Product. Here's the ProductDao interface:

package sample.dao;

import java.util.List;

import sample.model.Product;

public interface ProductDao {

public boolean delete(final Product product);

public boolean deleteById(final Long id);

public Product findById(final Long id);

public Product findByName(final String productName);

public List getAllProducts();

public long getNumberOfProducts();

public Product saveOrUpdate(final Product product);

}


And the ProductDaoImpl class:

package sample.dao;

import java.util.List;

import org.apache.log4j.Logger;

import sample.model.Product;

public class ProductDaoImpl implements ProductDao {
private static final Logger logger = Logger.getLogger(ProductDaoImpl.class);

final DaoHelper daoHelper;

public ProductDaoImpl(final DaoHelper daoHelper) {
if (null == daoHelper) throw new IllegalArgumentException("daoHelper can't be null");
this.daoHelper = daoHelper;
}

public boolean delete(final Product product) {
if (logger.isDebugEnabled()) logger.debug("delete: " + product.getId());

return daoHelper.deleteById(Product.class, product.getId());
}

public boolean deleteById(final Long id) {
if (logger.isDebugEnabled()) logger.debug("deleteById: " + id);

return daoHelper.deleteById(Product.class, id);
}

public Product findById(final Long id) {
if (logger.isDebugEnabled()) logger.debug("findById: " + id);

return daoHelper.findById(Product.class, id);
}

public Product findByName(final String productName) {
if (logger.isDebugEnabled()) logger.debug("findByName: " + productName);

final List<Product> list = daoHelper.findByAttribute(Product.class, "name", productName);
if (null == list || 0 == list.size()) return null;
assert list.size() == 1;

return list.get(0);
}

public List<Product> getAllProducts() {
if (logger.isDebugEnabled()) logger.debug("getAllProducts");

return daoHelper.getAllEntities(Product.class);
}

public long getNumberOfProducts() {
if (logger.isDebugEnabled()) logger.debug("getNumberOfProducts");

return daoHelper.countEntities(Product.class);
}

public Product saveOrUpdate(final Product product) {
if (logger.isDebugEnabled()) logger.debug("saveOrUpdate: " + product);

if (null == product) throw new IllegalArgumentException("product can't be null");
if (product.getId() == null) {
return daoHelper.save(Product.class, product);
} else {
return daoHelper.update(Product.class, product);
}
}

// public PageData<Product> getProductsByPage(final PageRequest pageRequest) {
// if (logger.isDebugEnabled()) logger.debug("getProductsByPage: pageRequest=" + pageRequest);
// return daoHelper.getEntitiesByPage(Product.class, pageRequest, "isDeleted", Boolean.FALSE);
// }
}



As you have seen, the ProductDaoImpl class delegates all its work to the DaoHelper class. The DaoHelper class is copied from the Examinator project (the api's related to paging are commented, as we don't need the paging api's here... hope to come up with a post for the paging soon).
The DaoHelper class is given here for reference:

package sample.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
* The GenericDao is a helper class providing common data access functionality for use (via delegation) by Dao
* implementations.
*/
public class DaoHelper {

@PersistenceContext
private EntityManager entityManager;

/**
* Defines ASC and DESC sort orders for queries.
*/
public enum SortOrder {
ASC, DESC
}

public DaoHelper() {
// entityManager will be set via JSR250 injection
}

/**
* Custom entity manager that will not automatically be injected.
*/
public DaoHelper(final EntityManager entityManager) {
this.entityManager = entityManager;
}

public <T> T findById(final Class<T> entityClass, final Object id) {
if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");
if (null == id) throw new IllegalArgumentException("id can't be null");

return entityManager.find(entityClass, id);
}

public boolean delete(final Object entity) {
if (null == entity) throw new IllegalArgumentException("entity can't be null");

entityManager.remove(entity);
return true;
}

public <T> boolean deleteById(final Class<T> entityClass, final Object id) {
if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");
if (null == id) throw new IllegalArgumentException("id can't be null");

return delete(findById(entityClass, id));
}

public int deleteByAttribute(final Class entityClass, final String attributeName, final Object attributeValue) {
if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");
if (null == attributeName) throw new IllegalArgumentException("attributeName can't be null");
if (null == attributeValue) throw new IllegalArgumentException("attributeValue can't be null");

return entityManager.createQuery(
"delete from " + entityClass.getSimpleName() + " e where e." + attributeName
+ " = ?1").setParameter(1, attributeValue).executeUpdate();
}

public <T> List<T> findByAttribute(final Class<T> entityClass, final String attributeName, final Object attributeValue) {
if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");
if (null == attributeName) throw new IllegalArgumentException("attributeName can't be null");
if (null == attributeValue) throw new IllegalArgumentException("attributeValue can't be null");

return entityManager.createQuery(
"select e from " + entityClass.getSimpleName() + " e where e." + attributeName
+ " = ?1").setParameter(1, attributeValue).getResultList();
}

public <T> List<T> findByAttribute(final Class<T> entityClass, final String attributeName,
final Object attributeValue, final String orderByAttributeName,
final SortOrder sortOrder) {
if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");
if (null == attributeName) throw new IllegalArgumentException("attributeName can't be null");
if (null == attributeValue) throw new IllegalArgumentException("attributeValue can't be null");
if (null == orderByAttributeName) throw new IllegalArgumentException("orderByAttributeName can't be null");

return entityManager.createQuery(
"select e from " + entityClass.getSimpleName() + " e where e." + attributeName
+ " = ?1 ORDER BY e." + orderByAttributeName + " " + sortOrder.name())
.setParameter(1, attributeValue).getResultList();
}

public <T> List<T> getAllEntities(final Class<T> entityClass) {
if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");

return entityManager.createQuery("select e from " + entityClass.getSimpleName() + " e").getResultList();
}

public <T> List<T> getAllEntities(final Class<T> entityClass, final String orderByAttributeName,
final SortOrder sortOrder) {
if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");
if (null == orderByAttributeName) throw new IllegalArgumentException("orderByAttributeName can't be null");

return entityManager.createQuery(
"select e from " + entityClass.getSimpleName() + " e order by e."
+ orderByAttributeName + " " + sortOrder.name()).getResultList();
}

public <T> T save(final Class<T> entityClass, final T entity) {
if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");
if (null == entity) throw new IllegalArgumentException("entity can't be null");

entityManager.persist(entity);
return entity;
}

public <T> T update(final Class<T> entityClass, final T entity) {
if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");
if (null == entity) throw new IllegalArgumentException("entity can't be null");

return entityManager.merge(entity);
}

public long countEntities(final Class entityClass) {
if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");

return (Long) entityManager.createQuery("select count(entity) from " + entityClass.getSimpleName() + " entity")
.getSingleResult();
}

public long countEntitiesByAttribute(final Class entityClass, final String attributeName, final Object attributeValue) {
if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");
if (null == attributeName) throw new IllegalArgumentException("attributeName can't be null");
if (null == attributeValue) throw new IllegalArgumentException("attributeValue can't be null");

return (Long) entityManager.createQuery(
"select count(e) from " + entityClass.getSimpleName() + " e where e."
+ attributeName + " = ?1").setParameter(1, attributeValue)
.getSingleResult();
}

// public <T> PageData<T> getEntitiesByPage(final Class<T> entityClass, final
// PageRequest pageRequest) {
// return getEntitiesByPage(entityClass, pageRequest, null, null, "id",
// SortOrder.ASC);
// }
//
// public <T> PageData<T> getEntitiesByPage(final Class<T> entityClass, final
// PageRequest pageRequest, final String attributeName,
// final Object attributeValue) {
// return getEntitiesByPage(entityClass, pageRequest, attributeName,
// attributeValue, "id", SortOrder.ASC);
// }
//
// public <T> PageData<T> getEntitiesByPage(final Class<T> entityClass, final
// PageRequest pageRequest, final String attributeName,
// final Object attributeValue, final String orderByAttributeName,
// final SortOrder sortOrder) {
// if (null == entityClass) throw new
// IllegalArgumentException("entityClass can't be null");
// if (null == pageRequest) throw new
// IllegalArgumentException("pageRequest can't be null");
// if (null == orderByAttributeName) throw new
// IllegalArgumentException("orderByAttributeName can't be null");
//
// String queryStr = "";
// if (attributeName != null) {
// queryStr = "select e from " + entityClass.getSimpleName() + " e where e." +
// attributeName + " = ?1 ORDER BY e."
// + orderByAttributeName + " " + sortOrder.name();
// } else {
// queryStr = "select e from " + entityClass.getSimpleName() +
// " e ORDER BY e." + orderByAttributeName + " "
// + sortOrder.name();
// }
// long total;
// if (attributeName != null) total = countEntitiesByAttribute(entityClass,
// attributeName, attributeValue);
// else total = countEntities(entityClass);
//
// final PageRequest newPageRequest =
// PageRequest.adjustPageRequest(pageRequest, total);
//
// Query query;
// if (attributeName != null) query =
// entityManager.createQuery(queryStr).setParameter(1, attributeValue);
// else query = entityManager.createQuery(queryStr);
// final List<T> data = query.setFirstResult(newPageRequest.getStart() -
// 1).setMaxResults(newPageRequest.getPageSize()).getResultList();
// return new PageData<T>(newPageRequest, total, data);
// }
}


This is the class that does the main work for talking with your DB through JPA. You can note that the class does not have any compile time dependency on Spring or any other external library except for JPA. So if you are going to work with JPA, this class will make you happy :).
If you are thinking about writing your own dao's, you can consider reusing this class, its cool.

Next, coming to the service classes, I'll add a ProductService class which can add/remove/list products (from the DB of course). Here's the interface:

package sample.service;

import java.util.List;

import sample.model.Product;

public interface ProductService {

public void addProduct(Product product);

public void deleteProduct(Product product);

public List<product> getAllProducts();
}


The ProductServiceImpl implements the above interface:

package sample.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import sample.dao.ProductDao;
import sample.model.Product;

public class ProductServiceImpl implements ProductService {

@Autowired
private ProductDao productDao;

@Transactional(readOnly = false)
public void addProduct(Product product) {
try {
productDao.saveOrUpdate(product);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

@Transactional(readOnly = false)
public void deleteProduct(Product product) {
try {
productDao.delete(product);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

@Transactional(readOnly = true)
public List<product> getAllProducts() {
try {
return productDao.getAllProducts();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

}



You can see that I am using Spring's @Transactional annotation to demarcate my transactions. I will show you shortly how it is configured.
Am also using the @Autowired annotation, doing so I just need to declare a bean of type ProductDao and Spring will inject the bean into this ProductService bean. Saves me some xml in my application-context file from instead of explicitly setting the dao bean ;-)

And here's my application-context file:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">


<context:annotation-config />

<bean id="productService" class="sample.service.ProductServiceImpl" />

<bean id="daoHelper" class="sample.dao.DaoHelper" />
<bean id="productDao" class="sample.dao.ProductDaoImpl">
<constructor-arg ref="daoHelper" />
</bean>
<import resource="data-access.xml"/>
</beans>


This just declares my beans and imports the data-access.xml which configures my settings for talking with the DB.

Here's the data-access.xml:


The tag tells Spring to provide transactions to my annotated classes (ProductServiceImpl class).

I am configuring my properties from a properties file called "jdbc.properties" from the classpath.
I am using commons-dbcp connection pooling library and hence the org.apache.commons.dbcp.BasicDataSource datasource property for the LocalContainerEntityManagerFactoryBean

We are using ehcache as the hibernate second-level cache provider.
We are setting hibernate.cache.use_second_level_cache to true to enable hibernate second level caching and using net.sf.ehcache.hibernate.SingletonEhCacheProvider as the cache provider.

The name of the Persistence unit is samplewebapp as defined in the META-INF/persistence.xml (which basically contains nothing other than the PU name)


Here's my jdbc.properties:

## Properties file for JDBC settings

##-----------------
# MySQL DB Settings
##-----------------
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/samplewebapp?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=


##--------------------
# Hibernate properties
##--------------------
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.hbm2ddl.auto=validate

# Debugging
hibernate.show_sql=false
hibernate.format_sql=true
hibernate.use_sql_comments=true
hibernate.generate_statistics=false




We need to add dependency on all these libraries that we are using - spring, persistence-api (JPA), hibernate, ehcache, dbcp, mysql connector classes etc. This is declared in pom.xml



Now the last thing that we need is the main class that will demonstrate all these glued together.
Here's the main class that I am using to drive the App,


package sample;

import java.util.List;
import java.util.Random;
import java.util.UUID;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import sample.model.Product;
import sample.service.ProductService;

public class App {

private static Random random = new Random();

public static final ApplicationContext ctxt;
static {
ctxt = new ClassPathXmlApplicationContext("application-context.xml");
}

private ProductService productService = (ProductService) ctxt.getBean("productService");

public static void main(String[] args) {
new App().test();
}

private void test() {
listProducts();
addRandomProducts(random.nextInt(10) + 1);
listProducts();
}

private void addRandomProducts(int numProducts) {
System.out.println("======= Adding " + numProducts + " random products...");
for (int i = 0; i <> allProducts = productService.getAllProducts();
System.out.println("Number of products: " + allProducts.size());
for (Product prod : allProducts) {
System.out.println("Product: " + prod);
}
}

private Product getRandomProduct() {
Product product = new Product();
product.setProductId(Long.valueOf(UUID.randomUUID().getLeastSignificantBits()));
product.setGroupId(Long.valueOf(UUID.randomUUID().getLeastSignificantBits()));
product.setDescription("A description : " + System.currentTimeMillis());
return product;
}
}



You can try out running this class using


$ mvn exec:java -Dexec.mainClass=sample.App





Now the interesting part: clustering the app with Terracotta.
For this we create a tc-config.xml and just tell Terracotta that we are using hibernate and ehcache and it will automatically cluster the app.


That's all that's needed to cluster with Terracotta.
Here's a script that will launch the app with Terracotta, just replace the TC_INSTALL_DIR with the location where you have downloaded and installed Terracotta:


#!/bin/bash

TC_INSTALL_DIR=/Users/asingh/terracottaKit/terracotta-2.7.1

mvn compile

CP_FILE=cp.txt
mvn dependency:build-classpath -Dmdep.outputFile=$CP_FILE
echo ":./target/classes" >> $CP_FILE

$TC_INSTALL_DIR/bin/dso-java.sh -cp `cat $CP_FILE` sample.App

rm $CP_FILE




I am sure I must have missed some parts of explaining the glue-points here and there.
You can download the attached tarball from here and play around with this simple app.

Looks like this was quite a long (hopefully not boring) post. Hope you are still with me and reading this :)... and do leave a comment if you are not reading this ;-)

25 comments:

abhi.sanoujam said...

Update: Sorry about the crappy formats of the source code earlier. Updated to display them nicely.

Why do I have to pay so much attention while posting source codes for making them display nicely.... is no one in Blogger free enough to write just some cleanup code so that (techie) users can post source code without thinking too much... like changing < to &lt

Anonymous said...

How did you generate the annotations?...by hand? or using Dali or some such tool?
Is there a way to generate annotated POJOs using maven?

abhi.sanoujam said...

I generated the annotations by hand (am assuming you are asking about the jpa annotations).

I am not sure if there is a way to generate the annotations (from hibernate.cfg.xml) or some other file using maven.
I would personally not do that. Writing down the annotations by hand didn't take much time, and I would of course think about which/how to annotate the members of the domain classes.

Krishna said...

Spring and JMS Integration

Matthieu said...

Hello Abhi,

Thanks for this cool blog ! I came here first due to a JPOA configuration issue (solved thanks to you !)... but the more I read your article the more I was interesting in Terracotta (I have to admit I quite did not know anything about it).

So I tried your sample and (I had to update some files (pom+config) to work with tc 2.7.2) and at the end it worked well...

but but but... I'm not sure I saw the added value of using Terracotta, what could be a usage to demonstrate how cool it is ? run a second client elsewhere ?

So if you can provide me a little scenario I can test based on this sample, it would be nice !

(btw in your pom, the exec-maven-plugin is missing)

abhi.sanoujam said...

Hi Matthieu,

Nice to hear that you liked the blog. And thanks for pointing out that missing exec-maven plugin. I've been a little (did i say little:) ) out of touch from making new posts.

There are quite a lot of added value for using Terracotta.... the blog was a small app that was just demonstrating usage with maven, hibernate, jpa and other stuffs.

The best way to see Terracotta in action would be to start up another node (on same or different machines). You would love seeing your data getting clustered without much hassle using Terracotta.
There are many use-cases for Terracotta. There is one nicely documented example in terracotta.org for a session-clustering use-case, in which Terracotta provides clustering of HTTP sessions. You can read more about it here

I'll try to write up simple app where you can see terracotta in action.... as I told you before, the wow-factor comes in when you have two or more nodes (jvm's).

Hope you have a great time clustering your app with Terracotta :)

Term Papers said...

I have been visiting various blogs for my term papers writing research. I have found your blog to be quite useful. Keep updating your blog with valuable information... Regards

Anonymous said...

Malaysia & Singapore & brunei best on the internet blogshop for wholesale
& supply korean add-ons, earrings, earstuds, pendant, rings, hair, bracelet &
bracelet accessories. Deal 35 % wholesale discount. Ship Worldwide
Here is my webpage : curso unas de gel

Anonymous said...

Malaysia & Singapore & brunei finest on-line blogshop for wholesale & supply korean accessories, earrings, earstuds, necklace, rings, hair, bracelet & bangle add-ons.

Offer 35 % wholesale price cut. Ship Worldwide
Also visit my web blog iTunes music advertising

Anonymous said...

You can definitely see your enthusiasm in the work
you write. The sector hopes for more passionate writers like you who are not afraid to say how they believe.

All the time go after your heart.
Here is my web-site Trick Photography and Special Effects

Anonymous said...

I loved as much as you will receive carried out right here.

The sketch is tasteful, your authored subject matter stylish.

nonetheless, you command get bought an shakiness over that you wish be delivering the following.
unwell unquestionably come more formerly again as exactly the same nearly a
lot often inside case you shield this increase.
Here is my web site ; neucopia compensation plan

Anonymous said...

That is a great tip particularly to those fresh to the blogosphere.

Simple but very accurate information… Thanks for sharing this one.
A must read article!
Here is my web page network transfer simultaneously

Anonymous said...

Amazing blog! Do you have any recommendations for aspiring writers?
I'm hoping to start my own website soon but I'm a little lost on everything.
Would you recommend starting with a free platform like Wordpress or go for a paid
option? There are so many options out there that I'm completely confused .. Any recommendations? Thank you!
Here is my web-site ... Viagra

Anonymous said...

Hello There. I found your blog using msn. This
is a really well written article. I'll be sure to bookmark it and return to read more of your useful information. Thanks for the post. I will definitely return.

My web page; Summitt Energy

Anonymous said...

I like the helpful info you provide in your articles.
I will bookmark your blog and check again
here frequently. I'm quite sure I'll learn many new stuff right here!
Best of luck for the next!

Review my web site :: amsterdam escorts

Anonymous said...

I'm not sure why but this web site is loading extremely slow for me. Is anyone else having this issue or is it a problem on my end? I'll check
back later on and see if the problem still exists.



Look into my homepage :: Graduate Quarterly

Anonymous said...

Wonderful beat ! I wish to apprentice even as you amend your site, how can i subscribe
for a blog web site? The account helped me a applicable deal.
I have been tiny bit acquainted of this your broadcast provided shiny clear concept

Feel free to visit my web blog: Kenny Nordlund

Anonymous said...

Heya i'm for the first time here. I found this board and I find It truly helpful & it helped me out much. I'm hoping to present one thing back and aid others
such as you aided me.

my webpage :: zipline tours thailand

Anonymous said...

My developer is trying to convince me to move to .
net from PHP. I have always disliked the idea because of the costs.

But he's tryiong none the less. I've been using Movable-type on several websites
for about a year and am concerned about switching to another platform.
I have heard fantastic things about blogengine.net. Is there
a way I can import all my wordpress content into it? Any help
would be really appreciated!

Visit my weblog: zipline tours thailand

Anonymous said...

Why users still use to read news papers when in this technological world the whole
thing is accessible on net?

My site ... noticias primero

Anonymous said...

I blog frequently and I genuinely appreciate your content.
This great article has really peaked my interest. I am going to bookmark your website
and keep checking for new information about once a week.
I opted in for your Feed as well.

Feel free to visit my homepage :: noticias primero

Anonymous said...

Fantastic goods from you, man. I've understand your stuff prior to and you're
just too magnificent. I actually like what you've got here, certainly like what you're stating
and the best way during which you say it. You make it
enjoyable and you still care for to stay it smart. I can't wait to read far more from you. This is really a terrific web site.

my site - Free Home Based Business Opportunity

Anonymous said...

hey there and thank you for your info – I have
definitely picked up anything new from right here. I did however expertise a few
technical issues using this website, as I experienced to
reload the web site a lot of times previous to I could get it
to load properly. I had been wondering if your web hosting is OK?
Not that I am complaining, but slow loading instances times will very frequently affect your placement in google
and could damage your quality score if advertising and marketing with
Adwords. Anyway I'm adding this RSS to my email and can look out for a lot more of your respective interesting content. Ensure that you update this again soon.

my webpage; xxx tube

Anonymous said...

Hey I know this is off topic but I was wondering if you knew of any
widgets I could add to my blog that automatically
tweet my newest twitter updates. I've been looking for a plug-in like this for quite some time and was hoping maybe you would have some experience with something like this. Please let me know if you run into anything. I truly enjoy reading your blog and I look forward to your new updates.

Also visit my page; www.babesflick.com

Anonymous said...

I couldn't resist commenting. Very well written!

Also visit my website: free porn clips

Post a Comment