পলিমরফিক ইন্টারফেরেন্স -> 1

‘চোখ খোল মেট্রাউস’প্রায় ধমকের সুরে বলল মেগাপল৫।
‘এরকম আনন্দের সংবাদ শুনে কেউ আতংকে অজ্ঞান হয়ে যায়? কি আশ্চর্য!! আসলে আমাদের ভেতর মানবিক অনুভূতি দিয়ে যাওয়াটা উচিত হয়নি। মানুষগুলো আসলেই অদূরদর্শী ছিল।‘আপসোসের মত শোনালো মেগাপলের যান্ত্রিক কন্ঠ।
মেট্রাউসের তখনো জ্ঞান ফেরেনি। আবেগে তার কপোট্রনের বা দিকের অংশটা ফেইল করেছে। তাই রিস্টোর করে পুনরায় আবেগ লোড করতে একটু সময় লাগছে।
সিস্টেম রিস্টোর করার এই এক ঝামেলা। সুস্থ্য হুওয়ার পরেও আরো কিছুক্ষন মাথা ঝিমঝিম করবে,বমি বমি ভাব চলে আসে। অবশ্য আজ অব্দি কোন এন্ড্রয়েড সত্যিকার অর্থে বমি করেছে বলে শোনা যায়নি। তবে এই ফালতু একটা অনুভূতি কেন তাদের মধ্যে দিয়ে গেল মানুষ ভেবে মনে মনে মানুষকে কতক্ষন গালাগাল দিল মেট্রাউস। আসলে সবদিক থেকে নিজেদের মত তৈরী করতে গিয়ে খানিকটা দূর্বল করে ফেলা হয়েছে তাদেরকে। তারপরেও নিজেদের পেশীশক্তি ব্যাবহার করে মানুষকে পৃথীবী থেকে তাড়ানো গেছে এই বা কম কি? শোনা যায় এন্ড্রমিডা গ্যালাক্সির কোন এক গ্রহে আবাস গড়েছে মানুষ। ভালমত সংগঠিত হতে পারলে আবার পৃথীবীতে এসে জ্বালাতন শুরু করবে ভ…

User activity logging: Spring

Hi,

In this article I'll show you how you can log user activity in your spring application. We'll use spring's HandlerInterceptorAdapter to intercept requests and thus logging activities.

1. Create Activity entity


Create an entity that represents an activity. When user sends a request to an url we'll save this object to our database (On Condition lol).

Activity.java
@Entity
public class Activity extends BaseEntity{

private String userAgent;
private String ip;
private String expires;
@OneToOne
@JsonBackReference
private User user;
private String requestMethod;
private String url;

private Long totalVisitors;

public String getUserAgent() {
return userAgent;
}

public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}

public String getExpires() {
return expires;
}

public void setExpires(String expires) {
this.expires = expires;
}

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getIp() {
return ip;
}

public void setIp(String ip) {
this.ip = ip;
}

public String getRequestMethod() {
return requestMethod;
}

public void setRequestMethod(String requestMethod) {
this.requestMethod = requestMethod;
}

public Long getTotalVisitors() {
return totalVisitors;
}

public void setTotalVisitors(Long totalVisitors) {
this.totalVisitors = totalVisitors;
}


}

@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Temporal(TemporalType.TIMESTAMP)
private Date created;
@Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public Date getCreated() {
return created;
}

@PrePersist
public void setCreated() {
this.created = new Date();
}

public void setCreated(Date created) {
this.created = created;
}

public Date getLastUpdated() {
if (lastUpdated == null)
return created;
return lastUpdated;
}

@PreUpdate
public void setLastUpdated() {
this.lastUpdated = new Date();
}

public Date getTime() {
if (this.lastUpdated != null)
return this.lastUpdated;
return this.created;
}

public String getReadableDateTime(Date date) {
if (date == null) return "";
DateFormat dateFormat = DateUtils.getReadableDateTimeFormat();
return dateFormat.format(date);
}

public String getReadableDayMonth(Date date) {
if (date == null) return "";
return new SimpleDateFormat("dd MMMM").format(date);
}

public String getReadableDateWithoutTime(Date date) {
if (date == null) return "";
SimpleDateFormat sdf = new SimpleDateFormat("MMMM, dd yyyy");
return sdf.format(date);
}

@Override
public String toString() {
return "BaseEntity{" +
"id=" + id +
", created=" + created +
", lastUpdated=" + lastUpdated +
'}';
}
}


2. Repository for that Entity


@Repository
public interface ActivityRepository extends JpaRepository<Activity,Long> {
Activity findFirstBy();
Activity findFirstByUserOrderByIdDesc(User user);
Page<Activity> findByUser(User user, Pageable pageable);
}

3. Activity Service


@Service
public class ActivityServiceImpl implements ActivityService {
private final ActivityRepository activityRepo;

@Autowired
public ActivityServiceImpl(ActivityRepository activityRepo) {
this.activityRepo = activityRepo;
}

public Activity save(Activity activity) {
if (activity.getId() == null) { // new activity (user logged in)
Activity firstActivity = this.findFirst();
if (firstActivity != null) {
long total = firstActivity.getTotalVisitors();
activity.setTotalVisitors(++total);
firstActivity.setTotalVisitors(total);
this.activityRepo.save(firstActivity);
}
}
return this.activityRepo.save(activity);
}

@Override
public Activity findFirst() {
return this.activityRepo.findFirstBy();
}

@Override
public Activity findLast(User user) {
return activityRepo.findFirstByUserOrderByIdDesc(user);
}

@Override
public Page<Activity> findByUser(User user, int page, int size) {
return this.activityRepo.findByUser(user, new PageRequest(page, size, Sort.Direction.DESC, SortAttributes.FIELD_ID));
}

@Override
public Activity findOne(long id) {
return this.activityRepo.findOne(id);
}

@Override
public List<Activity> findAll() {
return this.activityRepo.findAll();
}

@Override
public void delete(Long id) {
this.activityRepo.delete(id);
}

}

Okay, now that we've prepared our object. We can now execute some basic queries to read/write to database. Let's jump into the main work. Create an interceptor to intercept request and do things.

4. Create Interceptor


@Component
public class ActivityInterceptor extends HandlerInterceptorAdapter {

private final ActivityService activityService;

@Autowired
public ActivityInterceptor(ActivityService activityService) {
this.activityService = activityService;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userAgent = request.getHeader("User-Agent");
if (userAgent == null) userAgent = request.getHeader("user-agent");
String expires = response.getHeader("Expires");
Activity activity = new Activity();
activity.setIp(this.getClientIpAddress(request));
activity.setExpires(expires);
activity.setRequestMethod(request.getMethod());
activity.setUrl(request.getRequestURI());

Matcher m = Pattern.compile("\\(([^)]+)\\)").matcher(userAgent);
if (m.find()) {
activity.setUserAgent(m.group(1));
}

if (SecurityContextHolder.getContext().getAuthentication() != null &&
SecurityContextHolder.getContext().getAuthentication().isAuthenticated() &&
//when Anonymous Authentication is enabled
!(SecurityContextHolder.getContext().getAuthentication()
instanceof AnonymousAuthenticationToken)) {
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
activity.setUser(user);
if (!activity.getUrl().contains("image") && !activity.getUrl().equals("/"))
activity = activityService.save(activity);
return super.preHandle(request, response, handler);
} else if (activity.getUrl().equals("/")) {
Activity existingActivity = this.activityService.findFirst();
if (existingActivity != null) {
activity.setId(existingActivity.getId());
activity.setCreated(existingActivity.getCreated());
long totalVisitors = existingActivity.getTotalVisitors();
activity.setTotalVisitors(++totalVisitors);
} else
activity.setTotalVisitors(1L);
activity = this.activityService.save(activity);
}
return super.preHandle(request, response, handler);
}

private String getClientIpAddress(HttpServletRequest request) {
String xForwardedForHeader = request.getHeader("X-Forwarded-For");
if (xForwardedForHeader == null) {
return request.getRemoteAddr();
} else {
// As of https://en.wikipedia.org/wiki/X-Forwarded-For
// The general format of the field is: X-Forwarded-For: client, proxy1, proxy2 ...
// we only want the client
return new StringTokenizer(xForwardedForHeader, ",").nextToken().trim();
}
}

}

What we've done here is that, if user is logged in, we're recording all of the requests from loggedin user except "/" request and image requests.

If user isn't logged in we're recording pageviews for "/" request in the first row. We don't need to record every request for users who aren't logged in, right?

Okay now register this interceptor to spring in a configuration class that extends WebMvcConfigurerAdapter.

5. Register Interceptor


@Configuration
public class AppConfig extends WebMvcConfigurerAdapter{

private final ActivityInterceptor activityInterceptor;

@Autowired
public AppConfig(ActivityInterceptor activityInterceptor) {
this.activityInterceptor = activityInterceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(activityInterceptor);
}

}

Bamn! boy! you're app will now track users activities.

Okay, you may not like the idea to record every requests forever. It may flood your database with, I don't know, garbage!!?

So we'll schedule a task to clean activities that are older than a month. Let's make it quick.

 

6. Enable Scheduling


Add @EnableScheduling annotation to your main class. This will enable cron jobs across your application.
@SpringBootApplication
@EnableWebSecurity
@EnableScheduling
public class HrsApplication {

public static void main(String[] args) {
SpringApplication.run(HrsApplication.class, args);
}

}

7. Create scheduler to execute task in a specific time (Here everyday at 12 am)


@Component
public class ScheduledTasks {

private final ActivityService activityService;

@Autowired
public ScheduledTasks(ActivityService activityService) {
this.activityService = activityService;
}

@Scheduled(cron = "0 1 15 * * ?")
void deleteActivitiesOlderThanAMonth() {
Activity firstActivity = this.activityService.findFirst();
List<Activity> activityList = this.activityService.findAll();
for (Activity activity : activityList) {
if (!activity.getId().equals(firstActivity.getId())) { // exclude first activity
if (DateUtils.getDateDiff(activity.getCreated(), new Date(), TimeUnit.DAYS) >= 31) {
this.activityService.delete(activity.getId());
System.out.println("Deleted activity! " + activity.getId());
}
}
}
System.out.println("Deleted old user activities!");
}

}

 

You may wonder what is those"0 1 15 * * ?" expressions inside @Scheduled(cron = "0 1 15 * * ?"). They are called regular expressions.

Here's a useful quick explanation I found at stackoverflow:
* "0 0 * * * *" = the top of every hour of every day.
* "*/10 * * * * *" = every ten seconds.
* "0 0 8-10 * * *" = 8, 9 and 10 o'clock of every day.
* "0 0/30 8-10 * * *" = 8:00, 8:30, 9:00, 9:30 and 10 o'clock every day.
* "0 0 9-17 * * MON-FRI" = on the hour nine-to-five weekdays
* "0 0 0 25 12 ?" = every Christmas Day at midnight

Cron expression is represented by six fields:
second, minute, hour, day of month, month, day(s) of week

(*) means match any
*/X means "every X"
? ("no specific value") - useful when you need to specify something in one of the two fields in which the character is allowed, but not the other. For example, if I want my trigger to fire on a particular day of the month (say, the 10th), but don't care what day of the week that happens to be, I would put "10" in the day-of-month field, and "?" in the day-of-week field.

 

 

Comments

  1. hi, I used this tutorial but when i get all the activities from preHandler method,it is not saving ..it was returning null,please can you explain why

    ReplyDelete
    Replies
    1. Hi, Thank you for your comment. But you need to be a little more specific. Can you please explain a bit with the code snippet so that I can understand in which part you are facing the problem?

      Delete

Post a Comment

Popular posts from this blog

Deploy Spring Boot app in digitalocean cloud (or any cloud as long asyou have ssh access)

Upload large files : Spring Boot