Difference between within and withincode designators in AspectJ - aop

I am new to Aspect oriented programming. In this context, I have been going through a few designators, in which I found two designators "within" and "withincode". I could not understand their difference. Can anyone please explain with a simple example ?

NĂ¡ndor's answer is correct, you should accept + upvote it. I just want to add an MCVE, a simple example illustrating what he just explained so well. I am doing this for the benefit of other users who may find this question in the future in order to help them understand better what was described here theoretically.
Driver application:
package de.scrum_master.app;
public class Application {
private int id;
private String name;
public Application(int id, String name) {
this.id = id;
this.name = name;
}
#Override
public String toString() {
return "Application[id=" + id + ", name=" + name + "]";
}
public void printName() {
System.out.println(this);
}
public static void main(String[] args) {
new Application(11, "My application").printName();
}
}
Aspect:
package de.scrum_master.aspect;
import de.scrum_master.app.Application;
public aspect WithinVsWithincodeAspect {
before() : withincode(* Application.printName()) {
System.out.println("[withincode] " + thisJoinPoint);
}
before() : within(Application) {
System.out.println("[within] " + thisJoinPoint);
}
}
Console log:
[within] staticinitialization(de.scrum_master.app.Application.<clinit>)
[within] execution(void de.scrum_master.app.Application.main(String[]))
[within] call(de.scrum_master.app.Application(int, String))
[within] preinitialization(de.scrum_master.app.Application(int, String))
[within] initialization(de.scrum_master.app.Application(int, String))
[within] execution(de.scrum_master.app.Application(int, String))
[within] set(int de.scrum_master.app.Application.id)
[within] set(String de.scrum_master.app.Application.name)
[within] call(void de.scrum_master.app.Application.printName())
[within] execution(void de.scrum_master.app.Application.printName())
[withincode] get(PrintStream java.lang.System.out)
[within] get(PrintStream java.lang.System.out)
[withincode] call(void java.io.PrintStream.println(Object))
[within] call(void java.io.PrintStream.println(Object))
[within] execution(String de.scrum_master.app.Application.toString())
[within] call(java.lang.StringBuilder(String))
[within] get(int de.scrum_master.app.Application.id)
[within] call(StringBuilder java.lang.StringBuilder.append(int))
[within] call(StringBuilder java.lang.StringBuilder.append(String))
[within] get(String de.scrum_master.app.Application.name)
[within] call(StringBuilder java.lang.StringBuilder.append(String))
[within] call(StringBuilder java.lang.StringBuilder.append(String))
[within] call(String java.lang.StringBuilder.toString())
Application[id=11, name=My application]
As you can see, within() matches a super-set of joinpoints compared to withincode(). Of course you can combine both pointcuts with others via && in order to narrow down the set of matched joinpoints even further or expand it via ||. Exclusion via ! is also possible, of course.
Please further note that withincode() matches what happens inside the target method but not the method execution itself. In this regard it is similar to cflowbelow() but it would not match anything outside of that method called from there as cflow() and cflowbelow() do.

From the AspectJ Programming Guide - Language Semantics - Pointcuts
within(TypePattern)
Picks out each join point where the executing code is defined in a type matched by TypePattern.
withincode(MethodPattern)
Picks out each join point where the executing code is defined in a method whose signature matches MethodPattern.
With within, you can only restrict to a type, so without further restrictions, it will match any join points within the matching types. On the other hand, using withincode, you can use a pattern that narrows possible matching join points further, to the level of methods.
At the end of the page I linked, there is an EBNF summary about the grammar for the patterns used in the document.

Related

Can't rewrite a URL including the query string components

I'm trying to do some URL rewriting in asp.net core 2.2 but it doesn't seem to work with the query string part. I want to change any path like "finditem?txn=3" into something like "find/item?transactionid=3". As a simpler example, without a smart replacement of the transactionID, look at this code:
private static RewriteOptions GetRewriteOptions() => new RewriteOptions()
.AddRewrite(#"^bananatxn=\d$", "Download", true) // Works with bananatxn=1
.AddRewrite(#"^banana\?txn=\d$", "Download", true); // Does NOT work with banana?txn=1
Why can't the rewriter match on the question mark character? I've tested my patterns in http://regexstorm.net/tester and although the pattern seems to be correct it doesn't work. Can the rewriter in asp.net core rewrite the entire URL, including the query string, or only the part before the question mark?
I've investigate and think (but am not sure) this functionality is not available in the built-in rules provided by asp.net core. This worked for me. Definitely NOT tested thoroughly, probably gives too much importance to upper and lower case, and I'm not super familiar with all the URL components and formats.
public class RewritePathAndQuery : IRule
{
private Regex _regex;
private readonly string _replacement;
private readonly RuleResult _resultIfRewrite;
/// <param name="regex">Pattern for the path and query, excluding the initial forward slash.</param>
public RewritePathAndQuery(string regex, string replacement, bool skipRemainingRules)
{
_regex = new Regex(regex);
_replacement = replacement;
_resultIfRewrite = skipRemainingRules ? RuleResult.SkipRemainingRules : RuleResult.ContinueRules;
}
public void ApplyRule(RewriteContext context)
{
HttpRequest request = context.HttpContext.Request;
string pathExcludingInitialForwardSlash = request.Path.Value.Substring(1);
string queryStringWithLeadingQuestionCharacter = request.QueryString.Value;
string original = $"{pathExcludingInitialForwardSlash}{queryStringWithLeadingQuestionCharacter}";
string replaced = _regex.Replace(original, _replacement);
if (replaced.StartsWith('/')) { // Replacement pattern may include this character
replaced = replaced.Substring(1);
}
if (original != replaced) { // Case comparison?
string[] parts = replaced.Split('?');
request.Path = $"/{parts[0]}";
request.QueryString = new QueryString(parts.Length == 2 ? $"?{parts[1]}" : "");
context.Result = _resultIfRewrite;
}
else {
context.Result = RuleResult.ContinueRules;
}
}
}

Should we have one action class in selenium keyword driven framework even though we have a lot of methods?

As per my understanding, keyword driven framework is, we create a keyword for each action we do and write the test cases in excel using those keywords.
For example, opening browser, entering username, password, clicking on login button etc we create a keyword for each action and create a method for each of these keywords and store all these methods in a class like actionmethods() etc.
We use java reflection class to call these methods.
If we have less no. of methods that should be ok. I am working on a small project where I got like 200 keywords. So I have to write 200 methods here. Should I store all these methods in one class?
What if I have 1000 keywords (for a big project)?
If I create separate files grouping keyword methods based on the pages, it is becoming very complicated. Can someone please explain if we use only one class to hold all the methods?
Thank you.
Maintain the keyword methods as separate class for each page like we do in page object pattern.
While calling the keyword, we can specify the class name as well along with method name. For e.g., LoginPage.login
For e.g., if you are maintaining the page class under package com.myproject.test.pages You can change the reflection code for invoking as,
public Object invokeKeywordMethod(String keywordName)
throws InvocationTargetException, IllegalAccessException, InstantiationException {
String[] keywords = keywordName.split("\\.");
if (keywords.length == 1)
throw new Error("Invalid keyword: " + keywordName + ". The keyword must be as ClassName.methodName");
String className = keywords[0];
String methodName = keywords[1];
Class<?> pageClass = getPageClass(className);
Method method;
try {
method = getPageClass("").getDeclaredMethod(methodName);
} catch (NoSuchMethodException e) {
throw new Error("The keyword method '" + methodName + "' is not found in the class");
}
return method.invoke(pageClass.newInstance());
}
private Class<?> getPageClass(String className) {
Class<?> pageClass = null;
try {
pageClass = Class.forName("com.myproject.test.pages." + className);
} catch (ClassNotFoundException e) {
throw new Error(className + " not found in package 'com.myproject.test.pages' ");
}
return pageClass;
}

Bean Validation with JAX-RS (rest-easy): parameter name not recognized

I'm using JAX-RS resources with Bean Validation and integration between these two works as expected.
However, the default error messages generated in case of a validation error report parameter names as arg0, like so
[PARAMETER]
[login.arg0.password]
[password is required]
[]
Corresponding method definition:
#POST //and other JAX-RS annotations
public Response login(
#NotNull
#Valid
LoginBody loginBody) {
[...]
protected static class LoginBody {
#NotNull(message = EMAIL_REQUIRED)
public String email;
#NotNull(message = PASSWORD_REQUIRED)
public String password;
}
While I'm generally fine with this message pattern, what actually is annyoing, is the fact that the original parameter name is not recognized, i. e. I'd rather like to see
login.loginBody.password instead of arg0.
Is there an easy way to fix this, e. g. somehow provide an explicit name for that parameter?
I'm using WildFly Swarm 2017.6.0. From what I found out this means I have resteasy + resteasy-validator + hibernate-validator
Thanks.
You could try to compile your app with -parameters or instruct your IDE to do so, e.g. in case of
eclipse: preferences -> java -> compiler -> "store information about method parameters (usable via reflection)"
With that in place you then need to instruct the Bean Validation infrastructure (e.g. ) hibernate-validator to
use the ReflectiveParameterNamer via META-INF/validation.xml.
<parameter-name-provider>org.hibernate.validator.parameternameprovider.ReflectionParameterNameProvider</parameter-name-provider>
See also Hibernate Validator Configuration
I got something reliably working with the Paranamer library
META-INF/validation.xml:
<?xml version="1.0" encoding="UTF-8"?>
<validation-config
xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.org/xml/ns/javax/validation/configuration
validation-configuration-1.1.xsd"
version="1.1">
<default-provider>org.hibernate.validator.HibernateValidator
</default-provider>
<message-interpolator>org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator
</message-interpolator>
<traversable-resolver>org.hibernate.validator.internal.engine.resolver.DefaultTraversableResolver
</traversable-resolver>
<constraint-validator-factory>org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl
</constraint-validator-factory>
<parameter-name-provider>org.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider</parameter-name-provider>
</validation-config>
To get paranamer working with wildfly I needed to create a parameter-namer jboss-module
and reference that module from the module.xml of the hibernate-validator module.
With that in place I could simply write:
#POST
public Response login(#NotNull #Valid #Named("authRequest") AuthRequest authRequest) {
return Response.ok().build();
}
...
public class AuthRequest {
#NotNull(message = AuthMessages.EMAIL_REQUIRED)
public String email;
#NotNull(message = AuthMessages.PASSWORD_REQUIRED)
public String password;
}
which yields the following response for a request sent via curl:
curl -H "Content-Type: application/json" -H "Accept: application/json" -d '{"email":"foo#bar.com"}' -v http://localhost:8080/javaweb-training/resources/auth
Response:
{"exception":null,"fieldViolations":[],"propertyViolations":[],"classViolations":[],"parameterViolations":[{"constraintType":"PARAMETER","path":"login.authRequest.password","message":"password.required","value":""}],"returnValueViolations":[]}%
... note login.authRequest.password instead of just login.arg0.password
There is a very simple solution: you can set your own error message in the constraint definition as follows
#NotNull(message = "password is required")
If you want a more generic solution based on the JAX-RS parameter annotations you can implement your own simple ParameterNamProvider and register it in validation.xml as follows. This has the advantage of not having to change the jboss module structure. I also didn't have to change any compiler flags...
public class AnnotatedParameterNameProvider implements ParameterNameProvider {
#Override
public List<String> getParameterNames(Constructor<?> constructor) {
return lookupParameterNames(constructor.getParameterAnnotations());
}
#Override
public List<String> getParameterNames(Method method) {
return lookupParameterNames(method.getParameterAnnotations());
}
private List<String> lookupParameterNames(Annotation[][] annotations) {
final List<String> names = new ArrayList<>();
if (annotations != null) {
for (Annotation[] annotation : annotations) {
String annotationValue = null;
for (Annotation ann : annotation) {
annotationValue = getAnnotationValue(ann);
if (annotationValue != null) {
break;
}
}
// if no matching annotation, must be the request body
if (annotationValue == null) {
annotationValue = "requestBody";
}
names.add(annotationValue);
}
}
return names;
}
private static String getAnnotationValue(Annotation annotation) {
if (annotation instanceof HeaderParam) {
return ((HeaderParam) annotation).value();
} else if (annotation instanceof PathParam) {
return ((PathParam) annotation).value();
} else if (annotation instanceof QueryParam) {
return ((QueryParam) annotation).value();
}
return null;
}
}
In validation.xml:
<validation-config xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration validation-configuration-1.1.xsd"
version="1.1">
<parameter-name-provider>com.yourcompany.providers.AnnotatedParameterNameProvider</parameter-name-provider>
</validation-config>
Note that you can also customize how the error message is formatted by implementing your own MessageInterpolator and registering it in the validation.xml
Can you try to implement an exception mapper for ConstraintViolationExceptions and see if the information you have there (the list of constraint violations) can help you to obtain the parameter name?
Updated version of #thomas-darimont for Hibernate Validator 6.X.
Variant#1 - with build in Java 8 (using -parameters compile parameter)
Specify dependencies (gradle example):
// Define explicit hibernate validator 6.x
implementation('org.hibernate.validator:hibernate-validator:6.0.13.Final')
implementation('org.jboss.resteasy:resteasy-validator-provider-11:3.6.2.Final') {
// Exclude transitive hibernate validator 5.x
exclude group: 'org.hibernate', module: 'hibernate-validator'
}
Specify validator(s):
#GET
#Path("user/{userId}")
public Response getUser(#Size(min = 2) #PathParam("userId") String userId) {
return null;
}
Note: org.hibernate.validator.internal.engine.DefaultParameterNameProvider will return parameter names obtained from the Java reflection API.
Variant #2 - use ParaNamer library. (xml configuration)
In case you don't want to be dependant on compilation flag.
Specify dependencies (gradle example):
// Define explicit hibernate validator 6.x
implementation('org.hibernate.validator:hibernate-validator:6.0.13.Final')
implementation('org.jboss.resteasy:resteasy-validator-provider-11:3.6.2.Final') {
// Exclude transitive hibernate validator 5.x
exclude group: 'org.hibernate', module: 'hibernate-validator'
}
// ParaNamer library
implementation('com.thoughtworks.paranamer:paranamer:2.8')
Specify validator(s):
#GET
#Path("user/{userId}")
public Response getUser(#Size(min = 2) #PathParam("userId") String userId) {
return null;
}
Put <project_dir>/src/main/resources/META-INF/validation.xml
<?xml version="1.0" encoding="UTF-8"?>
<validation-config
xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration
http://xmlns.jcp.org/xml/ns/validation/configuration/validation-configuration-2.0.xsd"
version="2.0">
<parameter-name-provider>org.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider</parameter-name-provider>
</validation-config>
Note: Since Hibernate Validator 6.x org.hibernate.validator.parameternameprovider.ReflectionParameterNameProvider is deprecated, use org.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider instead.
Question: Can I configure this with Java-code style only?
Unfortunately, no. (See details here).

Only show effective SQL string P6Spy

I'm using p6spy to log the sql statements generated by my program. The format for the outputted spy.log file looks like this:
current time|execution time|category|statement SQL String|effective SQL string
I'm just wondering if anyone knows if there's a way to alter the spy.properties file and have only the last column, the effective SQL string, output to the spy.log file? I've looked through the properties file but haven't found anything that seems to support this.
Thanks!
In spy.properties there is a property called logMessageFormat that you can set to a custom implementation of MessageFormattingStrategy. This works for any type of logger (i.e. file, slf4j etc.).
E.g.
logMessageFormat=my.custom.PrettySqlFormat
An example using Hibernate's pretty-printing SQL formatter:
package my.custom;
import org.hibernate.jdbc.util.BasicFormatterImpl;
import org.hibernate.jdbc.util.Formatter;
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
public class PrettySqlFormat implements MessageFormattingStrategy {
private final Formatter formatter = new BasicFormatterImpl();
#Override
public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql) {
return formatter.format(sql);
}
}
There is no such option provided to achieve it via configuration only yet. I think you have 2 options here:
fill a new bug/feature request report (which could bring benefit to others using p6spy as well) on: https://github.com/p6spy/p6spy/issues?state=open or
provide custom implementation.
For the later option, I believe you could achieve it via your own class (depending on the logger you use, let's assume you use Log4jLogger).
Well, if you check relevant part of the Log4jLogger github as well as sourceforge version, your implementation should be rather straightforward:
spy.properties:
appender=com.EffectiveSQLLog4jLogger
Implementation itself could look like this:
package com;
import com.p6spy.engine.logging.appender.Log4jLogger;
public class EffectiveSQLLog4jLogger extends Log4jLogger {
public void logText(String text) {
super.logText(getEffectiveSQL(text));
}
private String getEffectiveSQL(String text) {
if (null == text) {
return null;
}
final int idx = text.lastIndexOf("|");
// non-perfect detection of the exception logged case
if (-1 == idx) {
return text;
}
return text.substring(idx + 1); // not sure about + 1, but check and see :)
}
}
Please note the implementation should cover github (new project home, no version released yet) as well as sourceforge (original project home, released 1.3 version).
Please note: I didn't test the proposal myself, but it could be a good starting point and from the code review itself I'd say it could work.
I agree with #boberj, we are used to having logs with Hibernate formatter, but don't forget about batching, that's why I suggest to use:
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
import org.hibernate.engine.jdbc.internal.Formatter;
/**
* Created by Igor Dmitriev on 1/3/16
*/
public class HibernateSqlFormatter implements MessageFormattingStrategy {
private final Formatter formatter = new BasicFormatterImpl();
#Override
public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql) {
if (sql.isEmpty()) {
return "";
}
String template = "Hibernate: %s %s {elapsed: %sms}";
String batch = "batch".equals(category) ? ((elapsed == 0) ? "add batch" : "execute batch") : "";
return String.format(template, batch, formatter.format(sql), elapsed);
}
}
In p6Spy 3.9 this can be achieved quite simply. In spy.properties set
customLogMessageFormat=%(effectiveSql)
You can patch com.p6spy.engine.spy.appender.SingleLineFormat.java
removing the prepared element and any reference to P6Util like so:
package com.p6spy.engine.spy.appender;
public class SingleLineFormat implements MessageFormattingStrategy {
#Override
public String formatMessage(final int connectionId, final String now, final long elapsed, final String category, final String prepared, final String sql) {
return now + "|" + elapsed + "|" + category + "|connection " + connectionId + "|" + sql;
}
}
Then compile just the file
javac com.p6spy.engine.spy.appender.SingleLineFormat.java
And replace the existing class file in p6spy.jar with the new one.

Best way to find the type of the object locator by passing the object locator alone in Selenium Webdriver

Is their any way to find the object locator type, by passing the object locator alone.
for e.g. i need to click on a login button, where its id=login, classname=loginbutton or xpath=//input[#name='login']. I need to build method where i will be just passing the objectlocator (either id or name) as the input and its type(either id or name) should be decided in the method like if it contains // then type should be of xpath etc.
I need to pass the objectLocator() which returns type to the findElement()
WebElement element = driver.findElement(objectLocator());
I do not think it is available off the shelf, you would have to implement your own logic.
The only thing is, let's say you want to search by linktext. As per your usecase, you would, in your object repo specify, "this is my linktext".
Now how do you know it is an id or a name or a linktext?
For xpath you can check if it starts with /, then its an xpath. If its only id or name then you can use ByIdorName, but i think it would become tricky with css and linktext.
The one thing I can think is you can establish some sort of conventions like if it is linktext precede your lcoator definition with linktext=blah blah and then you split and consume it.
I find it very useful to store all my locators as By objects and either use the By directly or pass the By into methods as I need them. For example:
By passwordField= By.id("login");
By userNameField = By.name("username");
By submitButton = By.xpath("\\myxpath\div[2]");
public void clickLogin() {
driver.findElement(submitButton).click();
}
I also use static Bys from other classes as well:
public void clickLogin() {
driver.findElement(LoginPage.SUBMIT_BUTTON).click();
}
The modern way to do this is using PageFactory and PageObjects
The following is a quick and dirty which will adapt selenium locators strings to WebDriver locators.
public enum LocatorType {
CLASSNAME, CSS, ID, LINK, NAME, TAGNAME, XPATH ;
}
public WebElement objectLocator(LocatorType type, String ref) {
switch(type) {
case ID:
return this.webDriver.findElement(By.id(ref));
case CLASSNAME:
return this.webDriver.findElement(By.className(ref));
case XPATH:
return this.webDriver.findElement(By.xpath(ref));
case CSS:
return this.webDriver.findElement(By.cssSelector(ref));
case LINK:
return this.webDriver.findElement(By.linkText(ref));
case NAME:
return this.webDriver.findElement(By.name(ref));
case TAGNAME:
return this.webDriver.findElement(By.tagName(ref));
}
return null;
}
public WebElement objectLocator(String identifier) {
String typeString = identifier.substring(0, identifier.indexOf('='));
String ref = identifier.substring(identifier.indexOf('=')+1, identifier.length());
if (typeString.toLowerCase().contains("classname")) {
return objectLocator(LocatorType.CLASSNAME, ref);
} else if (typeString.toLowerCase().contains("css")) {
return objectLocator(LocatorType.CSS, ref);
} else if (typeString.toLowerCase().contains("id")) {
return objectLocator(LocatorType.ID, ref);
} else if (typeString.toLowerCase().contains("link")) {
return objectLocator(LocatorType.LINK, ref);
} else if (typeString.toLowerCase().contains("name")) {
return objectLocator(LocatorType.NAME, ref);
} else if (typeString.toLowerCase().contains("tagname")) {
return objectLocator(LocatorType.TAGNAME, ref);
} else if (typeString.toLowerCase().contains("xpath")) {
return objectLocator(LocatorType.XPATH, ref);
} else {
return null;
}
}
It looks like you are looking for this solution because you have an object repository maintained somewhere outside of your code in some kind of properties file or xml.
Using gui maps has lot of disadvantages like,
- maintain an external file with a list of locators
- parse locator files to read keys (you can abstract this but still an overhead)
- when writing PageObjects you need to switch back and forth from Page to gui map
- possibility of multiple duplicate locators in gui maps
- object repo grows over time and becomes impossible to maintain
- debugging is far more difficult
What you are looking for is adding one more layer of complexity which is not required in my opinion. Automating browsers is a challenge in itself and writing maintainable test automation code is utmost important.
Use PageFactory in your page objects.
- Natural place for your locators are Page Objects themselves.
- Locators easily accessible in page objects for review or correction
- No need for explicit driver.findElement, with #FindBy you get that for free
- modern Java and awesome annotations make page objects look beautiful & readable
I have used gui maps before and struggled a lot. Switching to page factory made me realize that using object repository was such a bad idea!
This should do for locating element. I have given example till 3 level deep.
public WebElement findElement(String locator){
WebElement w = null;
try{
return (driver.findElement(By.id(locator)));
}catch(Exception e1){
try{
return ( driver.findElement(By.name(locator)));
}catch(Exception e2){
try{
return (driver.findElement(By.xpath(locator)));
}catch(Exception e3){
System.out.println("Cound not find a locator");
e3.printStackTrace();
}
}
}
return(w);
}
public void type(String locator, String value){
try{
WebElement w= findElement(locator);
w.sendKeys(""+value);
Thread.sleep(sleepTime);
}catch(Exception e){
e.printStackTrace();
}
}
-Vinay