Wicket: Label with default text if model empty + CompoundPropertyModel - label

I have:
public FooPage( ... ) {
this.setDefaultModel( new CompoundPropertyModel(new GenericIdLDM( Foo.class, 1)) );
add(new Label("title"));
I'd like to have $subj.
I've found this solution from 2007 (point below) : http://www.mail-archive.com/wicket-user#lists.sourceforge.net/msg29603.html
However, it wouldn't work for CPM as it needs the constructor with model.
How could I make it work with CPM?
public class DefaultTextModel extends AbstractReadOnlyModel<String> {
private final IModel<String> delegate;
private final String def;
public DefaultTextModel(String def, IModel delegate) {
this.def = def;
this.delegate = delegate;
}
public String getObject() {
String s = delegate.getObject();
return (Strings.isEmpty(s)) ? def : s;
}
public void detach() {
delegate.detach();
}
}

You could have a custom Converter for your label. I think the better reflects your intentions as well. See for example https://cwiki.apache.org/WICKET/using-custom-converters.html#Usingcustomconverters-InWicket1.4
Other option could be JavaScript, check if the span is empty and then provide the default value.

I can override Label#initModel():
protected IModel<?> initModel() {
return new DefaultTextModel(defaultModel, super.initModel());
}
A simpler solution is to override Label#onComponentTagBody() and just apply the default text there.
(Sven Meier replied on the mailing list)

Related

Pass method argument in Aspect of custom annotation

I'm trying to use something similar to org.springframework.cache.annotation.Cacheable :
Custom annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CheckEntity {
String message() default "Check entity msg";
String key() default "";
}
Aspect:
#Component
#Aspect
public class CheckEntityAspect {
#Before("execution(* *.*(..)) && #annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntitty checkEntity) {
System.out.println("running entity check: " + joinPoint.getSignature().getName());
}
}
Service:
#Service
#Transactional
public class EntityServiceImpl implements EntityService {
#CheckEntity(key = "#id")
public Entity getEntity(Long id) {
return new Entity(id);
}
}
My IDE (IntelliJ) doesn't see anything special with the key = "#id" usage in contrast to similar usages for Cacheable where it's shown with different color than plain text. I'm mentioning the IDE part just as a hint in case it helps, it looks like the IDE is aware in advance about these annotations or it just realizes some connection which doesn't exist in my example.
The value in the checkEntity.key is '#id' instead of an expected number.
I tried using ExpressionParser but possibly not in the right way.
The only way to get parameter value inside the checkEntity annotation is by accessing the arguments array which is not what I want because this annotation could be used also in methods with more than one argument.
Any idea?
Adding another simpler way of doing it using Spring Expression. Refer below:
Your Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CheckEntity {
String message() default "Check entity msg";
String keyPath() default "";
}
Your Service:
#Service
#Transactional
public class EntityServiceImpl implements EntityService {
#CheckEntity(keyPath = "[0]")
public Entity getEntity(Long id) {
return new Entity(id);
}
#CheckEntity(keyPath = "[1].otherId")
public Entity methodWithMoreThanOneArguments(String message, CustomClassForExample object) {
return new Entity(object.otherId);
}
}
class CustomClassForExample {
Long otherId;
}
Your Aspect:
#Component
#Aspect
public class CheckEntityAspect {
#Before("execution(* *.*(..)) && #annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntitty checkEntity) {
Object[] args = joinPoint.getArgs();
ExpressionParser elParser = new SpelExpressionParser();
Expression expression = elParser.parseExpression(checkEntity.keyPath());
Long id = (Long) expression.getValue(args);
// Do whatever you want to do with this id
// This works for both the service methods provided above and can be re-used for any number of similar methods
}
}
PS: I am adding this solution because I feel this is a simpler/clearner approach as compared to other answers and this might be helpful for someone.
Thanks to #StéphaneNicoll I managed to create a first version of a working solution:
The Aspect
#Component
#Aspect
public class CheckEntityAspect {
protected final Log logger = LogFactory.getLog(getClass());
private ExpressionEvaluator<Long> evaluator = new ExpressionEvaluator<>();
#Before("execution(* *.*(..)) && #annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntity checkEntity) {
Long result = getValue(joinPoint, checkEntity.key());
logger.info("result: " + result);
System.out.println("running entity check: " + joinPoint.getSignature().getName());
}
private Long getValue(JoinPoint joinPoint, String condition) {
return getValue(joinPoint.getTarget(), joinPoint.getArgs(),
joinPoint.getTarget().getClass(),
((MethodSignature) joinPoint.getSignature()).getMethod(), condition);
}
private Long getValue(Object object, Object[] args, Class clazz, Method method, String condition) {
if (args == null) {
return null;
}
EvaluationContext evaluationContext = evaluator.createEvaluationContext(object, clazz, method, args);
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, clazz);
return evaluator.condition(condition, methodKey, evaluationContext, Long.class);
}
}
The Expression Evaluator
public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
// shared param discoverer since it caches data internally
private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
/**
* Create the suitable {#link EvaluationContext} for the specified event handling
* on the specified method.
*/
public EvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {
Method targetMethod = getTargetMethod(targetClass, method);
ExpressionRootObject root = new ExpressionRootObject(object, args);
return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
}
/**
* Specify if the condition defined by the specified expression matches.
*/
public T condition(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class<T> clazz) {
return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext, clazz);
}
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if (targetMethod == null) {
targetMethod = method;
}
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
}
The Root Object
public class ExpressionRootObject {
private final Object object;
private final Object[] args;
public ExpressionRootObject(Object object, Object[] args) {
this.object = object;
this.args = args;
}
public Object getObject() {
return object;
}
public Object[] getArgs() {
return args;
}
}
I think you probably misunderstand what the framework is supposed to do for you vs. what you have to do.
SpEL support has no way to be triggered automagically so that you can access the actual (resolved) value instead of the expression itself. Why? Because there is a context and as a developer you have to provide this context.
The support in Intellij is the same thing. Currently Jetbrains devs track the places where SpEL is used and mark them for SpEL support. We don't have any way to conduct the fact that the value is an actual SpEL expression (this is a raw java.lang.String on the annotation type after all).
As of 4.2, we have extracted some of the utilities that the cache abstraction uses internally. You may want to benefit from that stuff (typically CachedExpressionEvaluator and MethodBasedEvaluationContext).
The new #EventListener is using that stuff so you have more code you can look at as examples for the thing you're trying to do: EventExpressionEvaluator.
In summary, your custom interceptor needs to do something based on the #id value. This code snippet is an example of such processing and it does not depend on the cache abstraction at all.
Spring uses internally an ExpressionEvaluator to evaluate the Spring Expression Language in the key parameter (see CacheAspectSupport)
If you want to emulate the same behaviour, have a look at how CacheAspectSupport is doing it. Here is an snippet of the code:
private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
/**
* Compute the key for the given caching operation.
* #return the generated key, or {#code null} if none can be generated
*/
protected Object generateKey(Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey, evaluationContext);
}
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
private EvaluationContext createEvaluationContext(Object result) {
return evaluator.createEvaluationContext(
this.caches, this.metadata.method, this.args, this.target, this.metadata.targetClass, result);
}
I don't know which IDE you are using, but it must deal with the #Cacheable annotation in a different way than with the others in order to highlight the params.
Your annotation can be used with methods with more than 1 parameter, but that doesn't mean you can't use the arguments array. Here's a sollution:
First we have to find the index of the "id" parameter. This you can do like so:
private Integer getParameterIdx(ProceedingJoinPoint joinPoint, String paramName) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = methodSignature.getParameterNames();
for (int i = 0; i < parameterNames.length; i++) {
String parameterName = parameterNames[i];
if (paramName.equals(parameterName)) {
return i;
}
}
return -1;
}
where "paramName" = your "id" param
Next you can get the actual id value from the arguments like so:
Integer parameterIdx = getParameterIdx(joinPoint, "id");
Long id = joinPoint.getArgs()[parameterIdx];
Of course this assumes that you always name that parameter "id". One fix there could be to allow to specify the parameter name on the annotation, something like
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CheckEntity {
String message() default "Check entity msg";
String key() default "";
String paramName() default "id";
}

Is there something like #PostPostRequest?

I often want to refine posted data before use it, for example
public class Song() {
public String[] tags;
public String csvTags;
public void setTagsWithCsv() {
// this one should be more complicated for handling real data
this.tags = csvTags.split(",");
}
}
In this case, I have to call setTagsWithCsv method inside the method of the controller class.
#RequestMapping(value = "/song/create", method = POST)
public String createSong(Song song) {
song.setTagsWithCsv();
songService.create(song); // some code like this will come here
...
}
Is there any way to call the method with an annotation like '#PostConstruct'? The method should be called after a post request.
Maybe you just provided a bad example, but If your Song is in a form of POJO, you do it on a call to setCsvTags
public class Song {
private String[] tags;
private String csvTags;
public void setCsvTags(String csvTags) {
this.csvTags = csvTags;
this.tags = csvTags.split(",");
}
public void setTags(String[] tags) {
this.tags == tags;
String newCsvTags = Arrays.toString(tags);
this.csvTags = newCsvTags.substring(1, newCsvTags.length() - 1); // get rid of []
}
}
or make a method, without keeping explicit tags array
public class Song {
private String csvTags;
public void getTags() {
return csvTags.split(",");
}
}
Otherwise, there is no standard way of doing this, you can play with request interception before reaching your Controller, but I think it would be just a waste of time.

unable to set radio button in struts2

I am not able to get the value of radio button selected in a page
I have a JSP page as
<body>
<s:form action="/YYY" id="frmPersonalPage" name="frmPersonalPage" >
<s:radio name ="radio" list="skillMasterData"></s:radio>
</s:form>
</body>
This renders properly . In my struts.xml I have
<action name="YYY" class="com.tdi.atom.actions.CCC" method="showEditSkillMasterPage">
<result name ="success">/jsp/modules/skillmap/createskillmaster.jsp</result>
</action>
In my action class I have this
public class CCC extends BaseActionSupport {
private ArrayList skillMasterData;
public String radio;
private ArrayList l1;
private ArrayList l2;
private ArrayList l3;
public ArrayList getSkillMasterData() {
return skillMasterData;
}
public void setSkillMasterData(ArrayList skillMasterData) {
this.skillMasterData = skillMasterData;
}
public String showEditSkillMasterPage()
{ log.info("at showEditSkillMasterPage");
System.out.println("radio buttoneee : "+getRadio());//this is null
setEditType("EDIT");
return SUCCESS;
}
public String showListSkillMasterPage()
{
SkillMasterDB pddb =new SkillMasterDB();
JdbcHelper helper;
l1 =new ArrayList();
l2=new ArrayList();
l3=new ArrayList();
l1.add("asda");
l1.add("rqwrq");
l2.add("!####");
l2.add("9087907");
l3.add("./,/");
l3.add("[][][]");
skdto.add(l1);
skdto.add(l2);
skdto.add(l3);
setSkillMasterData(skdto);
return SUCCESS;
}
public String getRadio() {
return radio;
}
public void setRadio(String radio) {
this.radio = radio;
}
}
In BaseActionSupport class I have
public class BaseActionSupport extends ActionSupport implements SessionAware {
private Map userSession;
public UserDTO user;
public UserDTO getUser() {
return mgr.getUser();
}
public boolean isAdmin() {
return mgr.isUserADMIN();
}
public void setSession(Map session) {
userSession = session; mgr = new SecurityManager(userSession);
}
}
I just can't figure out why such simple code is not working. Else where very similar code works fine.
There are some strange things in your code.
Assuming CCC and YYY are obfuscated names created only for posting them here (otherwise, you should use CamelCase with first letter capitalized for class names, like MyAction),
you should respect JavaBeans conventions, then userSession should become session, because the accessors methods (getters and setters) should always respect the name of the private variable);
skdto variable is not initialized nor defined in your showListSkillMasterPage method;
I'm not sure if using ArrayList<ArrayList<String>> for radio button is good. You could try with a simple ArrayList<String>, like:
public String showListSkillMasterPage() {
SkillMasterDB pddb =new SkillMasterDB();
JdbcHelper helper;
List<String> skdto = new ArrayList<String>();
skdto.add("asda");
skdto.add("rqwrq");
skdto.add("!####");
skdto.add("9087907");
skdto.add("./,/");
skdto.add("[][][]");
setSkillMasterData(skdto);
return SUCCESS;
}
Finally, you can test with Firebug's Net module what is going out of your page, check the radio parameter value now and after the edit to see what is going wrong now and how it will be going (hopefully) right later...

Spring -Mongodb storing/retrieving enums as int not string

My enums are stored as int in mongodb (from C# app). Now in Java, when I try to retrieve them, it throws an exception (it seems enum can be converted from string value only). Is there any way I can do it?
Also when I save some collections into mongodb (from Java), it converts enum values to string (not their value/cardinal). Is there any override available?
This can be achieved by writing mongodb-converter on class level but I don't want to write mondodb-converter for each class as these enums are in many different classes.
So do we have something on the field level?
After a long digging in the spring-mongodb converter code,
Ok i finished and now it's working :) here it is (if there is simpler solution i will be happy see as well, this is what i've done ) :
first define :
public interface IntEnumConvertable {
public int getValue();
}
and a simple enum that implements it :
public enum tester implements IntEnumConvertable{
vali(0),secondvali(1),thirdvali(5);
private final int val;
private tester(int num)
{
val = num;
}
public int getValue(){
return val;
}
}
Ok, now you will now need 2 converters , one is simple ,
the other is more complex. the simple one (this simple baby is also handling the simple convert and returns a string when cast is not possible, that is great if you want to have enum stored as strings and for enum that are numbers to be stored as integers) :
public class IntegerEnumConverters {
#WritingConverter
public static class EnumToIntegerConverter implements Converter<Enum<?>, Object> {
#Override
public Object convert(Enum<?> source) {
if(source instanceof IntEnumConvertable)
{
return ((IntEnumConvertable)(source)).getValue();
}
else
{
return source.name();
}
}
}
}
the more complex one , is actually a converter factory :
public class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
#Override
public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
if (enumType == null) {
throw new IllegalArgumentException(
"The target type " + targetType.getName() + " does not refer to an enum");
}
return new IntegerToEnum(enumType);
}
#ReadingConverter
public static class IntegerToEnum<T extends Enum> implements Converter<Integer, Enum> {
private final Class<T> enumType;
public IntegerToEnum(Class<T> enumType) {
this.enumType = enumType;
}
#Override
public Enum convert(Integer source) {
for(T t : enumType.getEnumConstants()) {
if(t instanceof IntEnumConvertable)
{
if(((IntEnumConvertable)t).getValue() == source.intValue()) {
return t;
}
}
}
return null;
}
}
}
and now for the hack part , i personnaly didnt find any "programmitacly" way to register a converter factory within a mongoConverter , so i digged in the code and with a little casting , here it is (put this 2 babies functions in your #Configuration class)
#Bean
public CustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
converters.add(new IntegerEnumConverters.EnumToIntegerConverter());
// this is a dummy registration , actually it's a work-around because
// spring-mongodb doesnt has the option to reg converter factory.
// so we reg the converter that our factory uses.
converters.add(new IntegerToEnumConverterFactory.IntegerToEnum(null));
return new CustomConversions(converters);
}
#Bean
public MappingMongoConverter mappingMongoConverter() throws Exception {
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setApplicationContext(appContext);
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mappingContext);
mongoConverter.setCustomConversions(customConversions());
ConversionService convService = mongoConverter.getConversionService();
((GenericConversionService)convService).addConverterFactory(new IntegerToEnumConverterFactory());
mongoConverter.afterPropertiesSet();
return mongoConverter;
}
You will need to implement your custom converters and register it with spring.
http://static.springsource.org/spring-data/data-mongo/docs/current/reference/html/#mongo.custom-converters
Isn't it easier to use plain constants rather than an enum...
int SOMETHING = 33;
int OTHER_THING = 55;
or
public class Role {
public static final Stirng ROLE_USER = "ROLE_USER",
ROLE_LOOSER = "ROLE_LOOSER";
}
String yourRole = Role.ROLE_LOOSER

Spring: escaping input when binding to command

How do you handle the case where you want user input from a form to be htmlEscape'd when
you are binding to a command object?
I want this to sanitize input data automatically in order to avoid running through all fields in command object.
thanks.
If you are using a FormController you can register a new property editor by overriding the initBinder(HttpServletReques, ServletRequestDataBinder) method. This property editor can escape the html, javascript and sql injection.
If you are using a property editor the values from the request object will be processed by the editor before assigning to the command object.
When we register a editor we have to specify the type of the item whose values has to be processed by the editor.
Sorry, now I don't the syntax of the method. But I'm sure this is how we have achieved this.
EDITED
I think the following syntax can work
In your controller override the following method as shown
#Override
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception {
super.initBinder(request, binder);
binder.registerCustomEditor(String.class,
new StringEscapeEditor(true, true, false));
}
Then create the following property editor
public class StringEscapeEditor extends PropertyEditorSupport {
private boolean escapeHTML;
private boolean escapeJavaScript;
private boolean escapeSQL;
public StringEscapeEditor() {
super();
}
public StringEscapeEditor(boolean escapeHTML, boolean escapeJavaScript,
boolean escapeSQL) {
super();
this.escapeHTML = escapeHTML;
this.escapeJavaScript = escapeJavaScript;
this.escapeSQL = escapeSQL;
}
public void setAsText(String text) {
if (text == null) {
setValue(null);
} else {
String value = text;
if (escapeHTML) {
value = StringEscapeUtils.escapeHtml(value);
}
if (escapeJavaScript) {
value = StringEscapeUtils.escapeJavaScript(value);
}
if (escapeSQL) {
value = StringEscapeUtils.escapeSql(value);
}
setValue(value);
}
}
public String getAsText() {
Object value = getValue();
return (value != null ? value.toString() : "");
}
}
Hopes this helps you
You can use #Valid and #SafeHtml from hibernate validator. See details at https://stackoverflow.com/a/40644276/548473

Resources