How to use AspectJ to log different data, conditionally? - java

I am pretty new to AspectJ and AOP in general. I do know that AspectJ has many Annotations (After, AfterReturning, etc) to execute code before a method is called, after it is called, after it returns, when an exception is thrown, etc.
I would like to use it for logging, a pretty typical use case. I've been looking at this article and I think it's most of what I need. It uses AspectJ as well as "jcambi aspects" to perform logging.
But I would like to do something like the following:
public void login(User user) {
String userType = user.getType();
if (!user.isActive()) {
// point cut 1 -- log inactive user
} else if (!user.isPasswordValid()) {
// point cut 2 -- log wrong password
} else {
// point cut 3 -- log successful login
}
}
We have an established log format. Something like:
<actor>|<action_code>|<error_code>|<extra_info>
All of the Actor types, Actions and Error Codes are contained in enums.
Is there any way to tell AspectJ to :
log within 'ifs', and
log different info, depending on what happened? for example, in point cut 1 log one of the following:
admin|login|001|Admin user inactive
user|login|001|Normal user inactive
... and in point cut 2 log one of the following:
admin|login|002|Invalid Admin password
user|login|002|Invalid normal user password
... and in point cut 3 log one of the following:
admin|login|000|Successful Admin login
user|login|000|Successful Normal user login
Something tells me it is not possible. Or at least not easy. But I'm not sure if it's even worth attempting. So I'm torn. On the one hand I'd like to "sanitize" my code of all logging. On the other hand, I'm not sure if it's going to be too much work to implement this.
Any ideas?
*************************************** EDIT ***************************************
Thank you both for your answers! I realize now two things: 1. I've got a lot of work ahead of me. And 2. I think I put too much emphasis on the "login" example.
Login is just one tiny use case. My task is to add logging everywhere ... in a bunch of methods in many, many classes. Basically everywhere I see a LOG.debug() or LOG.info(), anywhere in the application, to replace it with Aspect logging. This also means that as much as I'd like to, I can't just refactor all of the code to make my life easier. I'd love to make login use Exceptions but it's beyond the scope of my task: add logging.
And of course, in each method the business logic will be different, and as such, so will the logging. So my question becomes: what's the best practice to do this? I mean, each method will have its own logic, its ifs ... and will log different things conditionally. So do I go ahead and create an aspect class for each one of these use cases and basically have the same "ifs" there as well?
An example (that's not login!): A method that imports data.
public void import(String type) {
if (type.equals("people")) {
try {
int result = importPeople();
if (result > 0) {
// do some stuff
LOG.info("ok");
} else {
// do some stuff
LOG.info("problem");
}
} catch (Exception e) {
// do some stuff
LOG.debug("exception ...");
}
} else if (type.equals("places")) {
try {
int result = importPlaces();
if (result > 0) {
// do some stuff
LOG.info("ok");
} else {
// do some stuff
LOG.info("problem");
}
} catch (Exception e) {
// do some stuff
LOG.debug("exception ...");
}
}
}
Mind you it's a crap example, with repeated code, etc. But you get the idea. Should I also create an "import" aspect, for logging this method ... with all the accompanying "ifs" to log "ok", "problem", "exception" ? And do this for every use case?
I'm all for getting rid of intrusive logging code but ... it seems like something of a code smell to have to have the logic, with its "ifs", etc., both in the original method (because the method is "doing more stuff" than logging) as well as in the corresponding Aspect ...
Anyway, you both answered my original question ... but I can only have 1 be the answer, so I'm going to accept kriegaex's because he seems to have put a lot of work into it!

Yes, it is possible. But if I were you, I would model the whole story a bit differently. First of all, I would throw exceptions for failed logins due to unknown or inactive users or wrong passwords. Alternatively, the login method could return a boolean value (true for successful login, false otherwise). But in my opinion this would rather be old-fashioned C style than modern OOP.
Here is a self-consistent example. Sorry for the ugly UserDB class with lots of static members and methods. And in reality, you would not store clear-text passwords but randomised salts and salted hashes. But after all it is just a proof of concept for aspect-based, conditional logging.
User bean used for logins:
package de.scrum_master.app;
public class User {
private String id;
private String password;
public User(String id, String password) {
this.id = id;
this.password = password;
}
public String getId() {
return id;
}
public String getPassword() {
return password;
}
}
User database:
There are hard-coded DB entries, static enums, members and methods as well as static inner classes for simplicity's sake. Sorry! You can easily imagine how to do the same with better design, I hope.
package de.scrum_master.app;
import java.util.HashMap;
import java.util.Map;
public class UserDB {
public static enum Role { admin, user, guest }
public static enum Action { login, logout, read, write }
public static enum Error { successful_login, user_inactive, invalid_password, unknown_user }
private static class UserInfo {
String password;
Role role;
boolean active;
public UserInfo(String password, Role role, boolean active) {
this.password = password;
this.role = role;
this.active = active;
}
}
private static Map<String, UserInfo> knownUsers = new HashMap<>();
static {
knownUsers.put("bruce", new UserInfo("alm1GHTy", Role.admin, true));
knownUsers.put("john", new UserInfo("LetMe_in", Role.user, true));
knownUsers.put("jane", new UserInfo("heLL0123", Role.guest, true));
knownUsers.put("richard", new UserInfo("dicky", Role.user, false));
knownUsers.put("martha", new UserInfo("paZZword", Role.admin, false));
}
public static class UserDBException extends Exception {
private static final long serialVersionUID = 7662809670014934460L;
public final String userId;
public final Role role;
public final Action action;
public final Error error;
public UserDBException(String userId, Role role, Action action, Error error, String message) {
super(message);
this.userId = userId;
this.role = role;
this.action = action;
this.error = error;
}
}
public static boolean isKnown(User user) {
return knownUsers.get(user.getId()) != null;
}
public static boolean isActive(User user) {
return isKnown(user) && knownUsers.get(user.getId()).active;
}
public static boolean isPasswordValid(User user) {
return isKnown(user) && knownUsers.get(user.getId()).password.equals(user.getPassword());
}
public static Role getRole(User user) {
return isKnown(user) ? knownUsers.get(user.getId()).role : null;
}
public static void login(User user) throws UserDBException {
String userId = user.getId();
if (!isKnown(user))
throw new UserDBException(
userId, getRole(user), Action.login,
Error.unknown_user, "Unknown user"
);
if (!isActive(user))
throw new UserDBException(
userId, getRole(user), Action.login,
Error.user_inactive, "Inactive " + getRole(user)
);
if (!isPasswordValid(user))
throw new UserDBException(
userId, getRole(user), Action.login,
Error.invalid_password, "Invalid " + getRole(user) + " password"
);
}
}
Please note how the login(User) method throws exceptions with details helpful for logging.
Driver application simulating logins for several user/password combinations:
package de.scrum_master.app;
import java.util.Arrays;
import java.util.List;
public class Application {
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("mr_x", "foobar"),
new User("bruce", "foobar"),
new User("bruce", "alm1GHTy"),
new User("john", "foobar"),
new User("john", "LetMe_in"),
new User("jane", "foobar"),
new User("jane", "heLL0123"),
new User("richard", "foobar"),
new User("richard", "dicky"),
new User("martha", "foobar"),
new User("martha", "paZZword")
);
for (User user : users) {
try {
UserDB.login(user);
System.out.printf("%-8s -> %s%n", user.getId(), "Successful " + UserDB.getRole(user) + " login");
} catch (Exception e) {
System.out.printf("%-8s -> %s%n", user.getId(), e.getMessage());
}
}
}
}
Please note that we just catch and log all exceptions so as to avoid the application from exiting after failed login attempts.
Console log:
mr_x -> Unknown user
bruce -> Invalid admin password
bruce -> Successful admin login
john -> Invalid user password
john -> Successful user login
jane -> Invalid guest password
jane -> Successful guest login
richard -> Inactive user
richard -> Inactive user
martha -> Inactive admin
martha -> Inactive admin
Login logger aspect:
I suggest you first comment out the two System.out.printf(..) calls in Application.main(..) so as not to mix them up with aspect logging.
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import de.scrum_master.app.User;
import de.scrum_master.app.UserDB;
import de.scrum_master.app.UserDB.Action;
import de.scrum_master.app.UserDB.Error;
import de.scrum_master.app.UserDB.UserDBException;
#Aspect
public class UserActionLogger {
#Around("execution(void de.scrum_master.app.UserDB.login(*)) && args(user)")
public void captureLogin(ProceedingJoinPoint thisJoinPoint, User user) throws Throwable {
try {
thisJoinPoint.proceed();
System.out.printf("%s|%s|%d03|%s%n",
user.getId(), Action.login, Error.successful_login.ordinal(),
"Successful " + UserDB.getRole(user) + " login"
);
} catch (UserDBException e) {
System.out.printf("%s|%s|%03d|%s%n",
e.userId, e.action, e.error.ordinal(),
e.getMessage()
);
throw e;
}
}
}
Console log for aspect:
mr_x|login|003|Unknown user
bruce|login|002|Invalid admin password
bruce|login|003|Successful admin login
john|login|002|Invalid user password
john|login|003|Successful user login
jane|login|002|Invalid guest password
jane|login|003|Successful guest login
richard|login|001|Inactive user
richard|login|001|Inactive user
martha|login|001|Inactive admin
martha|login|001|Inactive admin
Et voilà! I hope that this is roughly what you want.

Its possible.
Create a point-cut/within around login method and get the user object also in your aspect class and once you get User object you can do your logging conditionally.
To get the user object,please check the below answered question by me and how it got the value of surveyId.Same way you can get the User object.
#Around("updateDate()"
public Object myAspect(final ProceedingJoinPoint pjp) {
//retrieve the runtime method arguments (dynamic)
Object returnVal = null;
for (final Object argument : pjp.getArgs())
{
if (argument instanceof SurveyHelper)
{
SurveyHelper surveyHelper = (SurveyHelper) argument;
surveyId = surveyHelper.getSurveyId();
}
}
try
{
returnVal = pjp.proceed();
}
catch (Throwable e)
{
gtLogger.debug("Unable to use JointPoint :(");
}
return returnVal;
}
Here is the complete link for your reference:
Spring AOP for database operation

Related

NPE in WebService vert.x when returning OperationResponse()

I have taken the web-api-service-example and is trying to adapt it to a Mongo based backend. The code looks almost identical except I have added (duplicate/rename) the Transaction.class to a User.class as well as the Service implementation and web handler.
I am trying to get the user json from the URL /users/username. Mongo correctly retrieves it.
My question is this: I have two pieces of code, one working (returning the user json) and one not working throwing a NPE.
Not working code (it compiles and I cannot understand why is not working):
#Override
public void getUser(
String username,
OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) {
resultHandler.handle(
persistence.getUser(username)
.compose(user -> {
if (user.isPresent()) {
System.err.println("D: " + user.get());
return Future.succeededFuture(OperationResponse.completedWithJson(user.get()));
} else {
return Future.succeededFuture(new OperationResponse().setStatusCode(404).setStatusMessage("Not Found"));
}
})
);
}
It thows the following exception:
SEVERE: Failed to handleMessage. address: __vertx.reply.1
java.lang.NullPointerException
at io.vertx.ext.web.api.OperationResponseConverter.fromJson(OperationResponseConverter.java:15)
at io.vertx.ext.web.api.OperationResponse.<init>(OperationResponse.java:30)
at io.vertx.ext.web.api.contract.impl.RouteToEBServiceHandler.lambda$handle$1(RouteToEBServiceHandler.java:35)
at io.vertx.core.eventbus.impl.EventBusImpl.lambda$convertHandler$2(EventBusImpl.java:342)
at io.vertx.core.eventbus.impl.HandlerRegistration.deliver(HandlerRegistration.java:278)
...
...
D: {"_id":"5fbebeb97d3c4c0e48f9c7e0","email":"some#user.dk","firstName":"firstname","id":0,"lastName":"lastname","password":"password","phone":"+4512345678","username":"username"}
The NPE is thrown first, then the System.err.println("D: " + user.get()); gets executed and prints the user. The browser then waits forever for a reply.
When this piece of code working fine:
#Override
public void getUser(
String username,
OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) {
persistence.getUser(username).compose(user -> {
if (user.isPresent()) {
resultHandler.handle(Future.succeededFuture(OperationResponse.completedWithJson(user.get())));
return Future.succeededFuture();
} else {
resultHandler.handle(Future.succeededFuture(new OperationResponse().setStatusCode(404).setStatusMessage("Not Found")));
return Future.failedFuture("Error");
}
});
}
One would presume that returing the Future.succeededFuture() to the resultHandler.handle() would be the correct way to do it? But it throws the NPE!
I got it working using this getUser() persistance method:
#Override
public Future<JsonObject> getUser(String username) {
Promise<JsonObject> promise = Promise.promise();
MongoClient mongoClient = MongoClient.createShared(vertx, mongoconfig);
JsonObject query = new JsonObject().put("username", username);
mongoClient.findOne(collectionName, query, null, promise::handle);
return promise.future();
}
and this service handler:
#Override
public void getUser(
String username,
OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) {
persistence.getUser(username)
.onSuccess(jsonObject -> {
resultHandler.handle(Future.succeededFuture(OperationResponse.completedWithJson(jsonObject)));
});
}
Great code comes with great responsability... One has to love Futures (once understood) :)

Manipulate with cache as with collection in Spring

I looked a lot of stuff on on internet but I don't found any solution for my needs.
Here is a sample code which doesn't work but show my requirements for better understanding.
#Service
public class FooCachedService {
#Autowired
private MyDataRepository dataRepository;
private static ConcurrentHashMap<Long, Object> cache = new ConcurrentHashMap<>();
public void save(Data data) {
Data savedData = dataRepository.save(data);
if (savedData.getId() != null) {
cache.put(data.getRecipient(), null);
}
}
public Data load(Long recipient) {
Data result = null;
if (!cache.containsKey(recipient)) {
result = dataRepository.findDataByRecipient(recipient);
if (result != null) {
cache.remove(recipient);
return result;
}
}
while (true) {
try {
if (cache.containsKey(recipient)) {
result = dataRepository.findDataByRecipient(recipient);
break;
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return result;
}
}
and data object:
public class Data {
private Long id;
private Long recipient;
private String payload;
// getters and setters
}
As you can see in code above I need implement service which will be stored new data into database and into cache as well.
Whole algorithm should looks something like that:
Some userA create POST request to my controller to store data and it fire save method of my service.
Another userB logged in system send request GET to my controller which fire method load of my service. In this method is compared logged user's id which sent request with recipients' ids in map. If map contains data for this user they are fetched with repository else algorithm check every second if there are some new data for that user (this checking will be some timeout, for example 30s, and after 30s request return empty data, and user create new GET request and so on...)
Can you tell me if it possible do it with some elegant way and how? How to use cache for that or what is the best practice for that? I am new in this area so I will be grateful for any advice.

Cucumber Java - How to use returned String from a step in next step?

I need to automate some webservices, i create some methods for that and i want to use Cucumber for that but i can't figure how to use returned value in next step.
So, i have this Feature:
Feature: Create Client and place order
Scenario: Syntax
Given I create client type: "66"
And I create for client: "OUTPUTVALUEfromGiven" an account type "123"
And I create for client: "OUTPUTVALUEfromGiven" an account type "321"
And I want to place order for: "outputvalueFromAnd1"
and i have this Steps:
public class CreateClientSteps {
#Given("^I create client type: \"([^\"]*)\"$")
public static String iCreateClient(String clientType) {
String clientID = "";
System.out.println(clientType);
try {
clientID = util.createClient(clientType);
} catch (IOException e) {
e.printStackTrace();
}
return clientID;
}
#And("^I create for client: \"([^\"]*)\" an account type \"([^\"]*)\"$")
public static String createAccount(String clientID, String accountType) {
String orderID = "";
try {
orderID = util.createAccount(clientID,accountType);
} catch (IOException e) {
e.printStackTrace();
}
return orderID;
}
}
It's any way to use the returned values from step to step?
Thank you!
Sharing state between steps, which is how I interpret your question, is not done by examining a returned value. It is done by setting the value in an instance variable and later read that instance variable in another step.
I would change your steps to this in order to achieve that:
public class CreateClientSteps {
private String clientID;
private String orderID;
#Given("^I create client type: \"([^\"]*)\"$")
public void iCreateClient(String clientType) {
System.out.println(clientType);
try {
clientID = util.createClient(clientType);
} catch (IOException e) {
e.printStackTrace();
}
}
#And("^I create for client: \"([^\"]*)\" an account type \"([^\"]*)\"$")
public void createAccount(String clientID, String accountType) {
try {
orderID = util.createAccount(clientID, accountType);
} catch (IOException e) {
e.printStackTrace();
}
}
}
The things I changed are
Two fields for sharing state - other steps can read the values later
Non static methods - Cucumber recreates the step class for every scenario, therefore I don't want the fields to be static since that would mean that their values would leak between scenarios
This is how you share state between steps in the same class. Sharing state between steps in different classes can also be done. It is a bit more complicated. Ask if you are interested.
I solved in other way, i know the question it's from last year but maybe someone will find this usefull in the future.
So, i created a 'ClientsMap.java' where i store the outputs from last sentences.
EX:
public class ClientsMap {
private static ArrayListMultimap<Integer, String> multimapCardNumber = ArrayListMultimap.create();
...
public static void addCardNumber(String cardNumberValue) {
multimapCardNumber.put(multimapCardNumber.size(), cardNumberValue);
}
public static String returnSpecificCardNumber(int specificCardNumberPosition) {
String returnedCardNumber = multimapCardNumber.get(specificCardNumberPosition - 1).get(0);
return returnedCardNumber;
}
}
Then i created some specific keywords to be used in sentences like:
And I want to make a card transaction with this parameters:
| XXX | 9999 |
| accountNumber | account1-client1 |
| cardNumber | cardNumber1 |
Then i have a method behind who check for keywords like 'cardNumber' and retrieve position of card like this:
if (paramsList[i].startsWith("cardNumber")) {
String cardNumberPosition = paramsList[i].replaceAll("[^0-9]", "");
paramsList[i] = returnSpecificCardNumber(Integer.valueOf(cardNumberPosition));
}
And after each Scenario don't forget to delete map.

JSF with RMI tutorial

I have to implement RMI with a JSF application, but I haven't found any tutorials on how to integrate these two technologies. I have very little experience with Java, only one simple app developed with JSF so I'm still a beginner.
If you have knowledge of any links that might be useful please share, or tips if you can explain the main points I have to go over in order to structure/configure my project correctly.
Thank you in advance.
Update:
This is the project's requirements:
"Consider a distributed web application called “job-searching service”. The application should keep record of all job offers in the past year mentioning whether the job is already taken or not. A user should be able to post a job offer and search through the job offers either by a date or by categories.
When implementing this system using Distributed Objects technologies consider the following constraints:
A Remote Object should expose the “post jobs” system functionality, letting the user to add a job to the system.
A Remote Object should expose the “search jobs” functionality, which allows the user to search for jobs either by a date or by a category.
All job offers must have a job title, company name, deadline, contact details and job specification.
Your task:
Design, implement and test the distributed application using Java or .NET specific technologies. (RMI or .NET Remoting technologies are compulsory)"
There's no mystery on doing this. Remember that JSF managed beans are just Java classes, so the only thing here will be your application design. For doing this, you can have a (very) basic skeleton like this:
In the JSF Project:
#ManagedBean
#RequestScoped
public class LoginBean {
private String user;
private String password;
public LoginBean() {
}
//getters and setters...
public String validateUser() {
UserService userService = new UserService();
if (userService.validateUser(user, password)) {
return "success";
}
return "problems";
}
}
public class UserService extends {
public UserService() {
}
public boolean validateUser(String user, String password) {
//do the Java RMI client job here...
boolean result = false;
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
try {
String name = "UserServiceRMI";
Registry registry = LocateRegistry.getRegistry(args[0]);
UserServiceRMI userServiceRMI = (UserServiceRMI) registry.lookup(name);
result = userServiceRMI.validateUser(user, password);
} catch (Exception e) {
//you can (and must) do a better error handling
System.out.println("UserService exception:");
e.printStackTrace();
}
return result;
}
}
In the RMI Server project:
public interface UserRMIService extends java.rmi.Remote {
boolean validateUser(String user, String password) throws java.rmi.RemoteException;
}
public class UserService implements UserRMIService {
public UserService() {
super();
}
public boolean validateUser(String user, String password) {
//do the user validation here...
//check against the database or another way you want/need
}
public static void main(String[] args) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
try {
String name = "UserRMIService";
UserRMIService engine = new UserService();
UserRMIService stub =
(UserRMIService) UnicastRemoteObject.exportObject(engine, 0);
Registry registry = LocateRegistry.getRegistry();
registry.rebind(name, stub);
System.out.println("UserService bound");
} catch (Exception e) {
//you can (and must) do a better error handling
System.out.println("UserService exception:");
e.printStackTrace();
}
}
}

Catching the IllegalArgumentException thrown by PropertyEditors in Spring

I have a PropertyEditor in order to translate ids into Persons, with it's setAsText (String text) as follows:
public void setAsText(String text) throws IllegalArgumentException {
try {
int id = Integer.parseInt(text);
Person person = peopleService.get(id);
this.setValue(person);
}
catch (NumberFormatException ex) {
// ...
throw new IllegalArgumentException("Not a number!: " + text);
}
catch (PersonNotFoundExcetion ex) {
// ...
throw new IllegalArgumentException("Impossible to get Person: " + text);
}
}
And my PeopleController has a method as follows:
#RequestMapping("/getPerson")
public void ver (#RequestParam Person person, Model model) {
model.addAttribute (person);
// ...
}
I want to catch the IllegalArgumentException in order to show a friendly message to the user, such as "Sorry, the Person you are looking for isn't here", but I don't know where to do that...
Thanks!
General exception handling can be done in this way:
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleAllExceptions(Exception e) {
return "redirect:/error.html"; /* use the correct view name */
}
More specfic you could use BindingResult
#RequestMapping(value = "/datedata", method = RequestMethod.POST)
public String create(
#ModelAttribute("datedata") final DateData datedata,
final BindingResult result) {
if (result.hasErrors()) {
return "datedata/create";
} else {
...
return "myView";
}
}
But I guess this works only for "Forms" (ModelAttribute)
In my humble opinion it is not a good idea to let Spring handle validaten of user input by property editors. I would strongly recommend to use the Form way: Build a command object with a STRING field an use a validator on it.
The exception ought to be caught in the Controller. It should never leak out to the view and end user.
If this is a web app, I'd recommend using the validation and binding API rather than PropertyEditor. That will allow you to return Errors that you can use to tell the UI what needs to be corrected.
Your exception handling needs work. I would not recommend catching an exception and doing nothing other than wrapping it and re-throwing. That's not handling anything or adding new information. It's actually less information as coded.

Categories

Resources