Thymeleaf has a number of useful utilities like #strings.capitalize(...) or #lists.isEmpty(...). I'm trying to add a custom one but have no idea how to register this.
Have made a custom Util class:
public class LabelUtil {
public String[] splitDoubleWord(String str) {
return str.split("[A-Z]", 1);
}
}
Now I'm going to use it like this:
<span th:each="item : ${#labels.splitDoubleWord(name)}" th:text="${item}"></span>
Of course, it won't work because I haven't registered the Util and defined #labels var.
So, the question is how and where to do it?
This answer is for thymeleaf 2.x.
If you use thymeleaf 3.x or later, please see other answers.
public class MyDialect extends AbstractDialect implements IExpressionEnhancingDialect {
public MyDialect() {
super();
}
#Override
public String getPrefix() {
// #see org.thymeleaf.dialect.IDialect#getPrefix
return "xxx";
}
#Override
public boolean isLenient() {
return false;
}
#Override
public Map<String, Object> getAdditionalExpressionObjects(IProcessingContext ctx) {
Map<String, Object> expressions = new HashMap<>();
expressions.put("labels", new LabelUtil());
return expressions;
}
}
and register your dialect.
#Configuration
public class ThymeleafConfig {
#Bean
public MyDialect myDialect() {
return new MyDialect();
}
}
thymeleaf-extras-java8time source code is good reference for creating custom thymeleaf expressions.
The API for registering a custom expression object has changed in Thymeleaf 3, for example:
public class MyDialect extends AbstractDialect implements IExpressionObjectDialect {
MyDialect() {
super("My Dialect");
}
#Override
public IExpressionObjectFactory getExpressionObjectFactory() {
return new IExpressionObjectFactory() {
#Override
public Set<String> getAllExpressionObjectNames() {
return Collections.singleton("myutil");
}
#Override
public Object buildObject(IExpressionContext context,
String expressionObjectName) {
return new MyUtil();
}
#Override
public boolean isCacheable(String expressionObjectName) {
return true;
}
};
}
}
Related
I have a Spring Boot application where an interface has a constraint:
#Constraint(
validatedBy = {
MyValidator.class
})
public #interface MyInterface {
...
}
I'm using Togglz to enable/disable some features and one class where I want to implement some Togglz code is in MyValidator.
public class MyValidator
implements MyInterface<
MyInterface, TDLDetails> {
private FeatureManager featureManager;
public static final Feature FEATURE_ONE =
new NamedFeature("FEATURE_ONE ");
public MyValidator(FeatureManager featureManager) {
this.featureManager = featureManager;
}
#Override
public void initialize(MyInterface arg0) {}
#Override
public boolean isValid(TDLDetails tdlDetails, ConstraintValidatorContext ctx)
{
if (!featureManager.isActive(FEATURE_ONE)) {
if (tdlDetails.getType().equals(TDLType.ANA)) {
return (tdlDetails.getPlaceOfIssue() != null);
}
}
return true;
}
}
Am I wrong to have the parameterized constructor? It seems I need it for Togglz but I'm not sure how it should be used by #Constraint if it takes a parameter. What's the correct way to do this?
I have a Bpmn file with an extension element like this.
<bpmn:extensionElements>
<zeebe:taskDefinition type="customer-interaction-service" retries="0" />
<zeebe:taskHeaders>
<zeebe:header key="operation" value="save" />
<zeebe:header key="#type" value="Organization[]" />
<zeebe:header key="updateReliesOnReferredTypes" value="#Customer.engagedParty" />
</zeebe:taskHeaders>
</bpmn:extensionElements>
I want to access the header elements and get keys and values.
I followed this tutorial for cmn but Bpmn api did not register my custom classes.
You can clone full code from here
I copppy some important parts here.
Main class:
public class Main {
public static void main(String[] args) {
InputStream is = Main.class.getResourceAsStream("/dclm-addservice.bpmn");
BpmnModelInstance model =CustomBpmn.readModelFromStream(is );
ServiceTask serviceTask=model.getModelElementById("ServiceTask_1jn441o");
int count=serviceTask.getExtensionElements().getElementsQuery().filterByType(TaskHeaders.class).count();
System.out.println(count);
count=serviceTask.getExtensionElements().getElementsQuery().count();
System.out.println(count);
}
}
result:
0
2
When filtering by TaskHeaders.class, it has 0 result.
customBpmn
public class CustomBpmn extends Bpmn {
public static CustomBpmn INSTANCE = new CustomBpmn();
public CustomBpmn() {
super();
System.out.println("constructor");
}
#Override
protected void doRegisterTypes(ModelBuilder modelBuilder) {
super.doRegisterTypes(modelBuilder);
System.out.println("registering");
HeaderImpl.registerType(modelBuilder);
TaskHeadersImpl.registerType(modelBuilder);
}
}
header interface
public interface Header extends BpmnModelElementInstance {
public String getKey();
public void setKey(String key);
public String getValue();
public void setValue(String value);
}
TaskHeaders interface
public interface TaskHeaders extends BpmnModelElementInstance {
Collection<Header> getHeaders();
void addHeader(Header header);
boolean addHeaders(List<Header> headers);
List<Header> findByKey(String key);
}
HeaderImpl
public class HeaderImpl extends BpmnModelElementInstanceImpl implements Header {
protected static Attribute<String> keyAttribute;
protected static Attribute<String> valueAttribute;
public static void registerType(ModelBuilder modelBuilder) {
ModelElementTypeBuilder typeBuilder = modelBuilder.defineType(Header.class, HEADER)
.namespaceUri(ZEEBE_SCHEMA)
.instanceProvider(new ModelTypeInstanceProvider<Header>() {
public Header newInstance(ModelTypeInstanceContext instanceContext) {
return new HeaderImpl(instanceContext);
}
});
keyAttribute =typeBuilder.stringAttribute(KEY_NAME)
.namespace(ZEEBE_SCHEMA)
.build();
valueAttribute =typeBuilder.stringAttribute(VALUE_NAME)
.namespace(ZEEBE_SCHEMA)
.build();
typeBuilder.build();
}
public HeaderImpl(ModelTypeInstanceContext instanceContext) {
super(instanceContext);
}
//some getter and setters.
TaskHeadersImple
public class TaskHeadersImpl extends BpmnModelElementInstanceImpl implements TaskHeaders {
protected static ChildElementCollection<Header> headerCollection;
public static void registerType(ModelBuilder modelBuilder) {
ModelElementTypeBuilder typeBuilder = modelBuilder.defineType(TaskHeaders.class, TASK_HEADERS)
.namespaceUri(ZEEBE_SCHEMA)
.instanceProvider(new ModelTypeInstanceProvider<TaskHeaders>() {
public TaskHeaders newInstance(ModelTypeInstanceContext instanceContext) {
return new TaskHeadersImpl(instanceContext);
}
});
SequenceBuilder sequenceBuilder = typeBuilder.sequence();
headerCollection =sequenceBuilder.elementCollection(Header.class)
.build();
typeBuilder.build();
}
public TaskHeadersImpl(ModelTypeInstanceContext instanceContext) {
super(instanceContext);
}
//some getters and setters.
Thanks.
Edit:
I could access TaskHeaders by using Zeebe java api instead of Camunda java api but the above question is still valid because TaskHeaders are not custom bpmn elements for zeebe modeler java api but are custom extension for Camunda.
I have set qualifier name from properties file as isomessage.qualifier=isoMessageMember1:
public class BankBancsConnectImpl implements BankBancsConnect{
#Autowired
#Resource(name="${isomessage.qualifier}")
private Iso8583Message iso8583Message;
public BancsConnectTransferComp getFundTransfer(IpsDcBatchDetail ipsDcBatchDetail) {
bancsxfr = iso8583Message.getFundTransfer(bancsxfr);
}
}
The value of ${isomessage.qualifier} is static as it is defined in the properties file. However i want it to be dynamic and get it's value from database based on certain condition. For instance i have multiple implementation of Iso8583Message (member wise) and has to call respective class of member id that is currently logged in. Please guide me to achieve this in the best and java spring way.
And my implementation class will look like this:
#Service("isoMessageMember1")
public class Iso8583MessageEBLImpl implements Iso8583Message{
public BancsConnectTransferComp getFundTransfer(BancsConnectTransferComp bancsxfr) throws Exception {
...
}
You can use Condition instead Qualifier if you are using Spring4+.
First, you need a ConfigDAO which read the qualifier name which you
need from database.
public class ConfigDAO {
public static String readFromDataSource() {
return " ";
}
}
Suppose there are two implemetions of Iso8583Message, you can
create two Condition objects.
IsoMessageMember1_Condition
public class IsoMessageMember1_Condition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String qualifier = ConfigDAO.readFromDataSource();
if (qualifier.equals("IsoMessageMember1_Condition")) {
return true;
} else {
return false;
}
}
}
IsoMessageMember2_Condition
public class IsoMessageMember2_Condition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String qualifier = ConfigDAO.readFromDataSource();
if (qualifier.equals("IsoMessageMember2_Condition")) {
return true;
} else {
return false;
}
}
}
Return different implemetion according to condition in config class.
#Configuration
public class MessageConfiguration {
#Bean(name = "iso8583Message")
#Conditional(IsoMessageMember1_Condition.class)
public Iso8583Message isoMessageMember1() {
return new Iso8583MessageEBLImpl();
}
#Bean(name = "iso8583Message")
#Conditional(IsoMessageMember2_Condition.class)
public Iso8583Message isoMessageMember2() {
return new OtherMessageEBLImpl();
}
}
Remove the #Qulifier and #Autowire annotations which you do not need anymore, you can retrieve the message from context every time you use it.
public class BankBancsConnectImpl implements BankBancsConnect{
private Iso8583Message iso8583Message;
public BancsConnectTransferComp getFundTransfer(IpsDcBatchDetail ipsDcBatchDetail) {
iso8583Message = (Iso8583Message)context.getBean("iso8583Message");
bancsxfr = iso8583Message.getFundTransfer(bancsxfr);
}
}
In spring it is possible to autowire the application context, and retrieve any bean based on its name.
For example, your interface signature similar to the below syntax
public interface Iso8583Message {
public String getFundDetails(String uniqueId);
}
and 2 different implementations follow below format
#Service("iso8583-message1")
public class Iso8583MessageImpl1 implements Iso8583Message {
#Override
public String getFundDetails(String uniqueId) {
return "Iso8583MessageImpl1 details ";
}
}
and
#Service("iso8583-message2")
public class Iso8583MessageImpl2 implements Iso8583Message {
#Override
public String getFundDetails(String uniqueId) {
return "Iso8583MessageImpl2 details ";
}
}
We can retrieve the beans as follows
public class BankBancsConnectImpl implements BankBancsConnect{
#Autowired
private ApplicationContext applicationContext;
public BancsConnectTransferComp getFundTransfer(IpsDcBatchDetail
ipsDcBatchDetail) {
//for retrieving 1st implementation
Iso8583Message iso8583Message=applicationContext.getBean("iso8583-message1", Iso8583Message.class);
//For retrieving 2nd implementation
Iso8583Message iso8583Message=applicationContext.getBean("iso8583-message2", Iso8583Message.class);
String result = iso8583Message.getFundTransfer(bancsxfr);
}
}
In this case, we can configure the bean names coming from the database instead of hard coded values("iso8583-message1","iso8583-message2").
Wicket use of models can be cumbersome. For a stateful page to properly render an object, you need to use lots of boiler-plate code, overriding classes to properly get the visibility status, etc... A simple example:
private IModel<FooBar> fooBarModel;
public MyPage() {
Label lbl1 = new Label("field1",
new PropertyModel<>(fooBarModel, "field1")) {
#Override public boolean isVisible() {
return fooBarModel.getObject().someCondition();
} }
add(lbl1);
/// Etc... same for dozen of other fields
}
I'm often using a trick using a ListView to help. Same example:
public MyPage() {
add(new ListView<FooBar>("content",
new SingleListModel<FooBar>(fooBarModel)) {
#Override protected void populateItem(...) {
FooBar fooBar = item.getModelObject();
// Code here gets simpler:
Label lbl1 = new Label("field1", fooBar.getField1());
lbl1.setVisible(fooBar.someCondition());
item.add(lbl1);
// Etc...
}
});
}
With a simple utility class SingleListModel, that transform a IModel<T> to a ListModel<T>, having 1 or 0 elements, depending whether T is null or not:
public class SingleListModel<T>
extends LoadableDetachableModel<List<T>> {
private IModel<T> tModel;
public SingleListModel(IModel<T> tModel) {
this.tModel = tModel;
}
#Override
protected List<T> load() {
List<T> ret = new ArrayList<>(1);
T t = tModel.getObject();
if (t != null)
ret.add(tModel.getObject());
return ret;
}
}
The nice side-effect of this is that the whole "content" element in the markup is hidden if fooBarModel returns null; no special treatment needed.
But all this smells like a hack to me, as I use ListView in a somehow "unnatural" fashion.
Is there a cleaner way to get the same result? A standard wicket framework?
You should use Behavior instead to avoid such duplications.
public class MyBehavior extends Behavior {
private final MyModel model;
public MyBehavior(MyModel model) {this.model = model;}
#Override public void onConfigure(Component c) {
if (model.someCondition()) {
component.setVisible(false);
}
}
}
Usage:
MyBehavior b = new MyBehavior(modelInstance);
component1.add(b);
component2.add(b);
// dozen more
Label lbl1 = new Label("field1",
new PropertyModel<>(fooBarModel, "field1")) {
#Override public boolean isVisible() {
return fooBarModel.getObject().someCondition();
} }
add(lbl1);
with little refactoring it can be converted into
add(new FLabel("id","text")
.setVisibilityFunction(()->model.getObject().isVisible()))
);
the FLabel class:
public class FLabel extends Label implements IComponentWithVisibilityFunction<FLabel> {
private SerializableBooleanSupplier visibilityFunction;
public FLabel(String id) {
super(id);
}
public FLabel(String id, Serializable label) {
super(id, label);
}
public FLabel(String id, IModel<?> model) {
super(id, model);
}
#Override
public FLabel setVisibilityFunction(SerializableBooleanSupplier visibilityFunction) {
this.visibilityFunction = visibilityFunction;
return this;
}
#Override
protected void onConfigure() {
if (visibilityFunction != null) {
setVisible(visibilityFunction.getAsBoolean());
}
}
}
public interface IComponentWithVisibilityFunction<T> {
T setVisibilityFunction(SerializableBooleanSupplier visibilityFunction);
}
Moreover you can put supplier into constructor:
add(new FLabel("id","text", ()->model.getObject().isVisible()));
again a small problem by understanding "how tapestry works".
I've got a Tapestry component (in this case a value encoder):
public class EditionEncoder implements ValueEncoder<Edition>, ValueEncoderFactory<Edition> {
#Inject
private IEditionManager editionDao;
public EditionEncoder(IEditionManager editionDao) {
this.editionManager = editionDao;
}
#Override
public String toClient(Edition value) {
if(value == null) {
return "";
}
return value.getName();
}
#Override
public Edition toValue(String clientValue) {
if(clientValue.equals("")) {
return null;
}
return editionManager.getEditionByName(clientValue);
}
#Override
public ValueEncoder<Edition> create(Class<Edition> type) {
return this;
}
}
Injecting the the Manager is not working, because the Encoder is created within a page like that:
public void create() {
editionEncoder = new EditionEncoder();
}
casued by this, i'm forced to use this ugly solution:
#Inject
private IEditionManager editionmanager;
editionEncoder = new EditionEncoder(editionManager);
Is there a better way to inject components during runtime or is there a better solution in general for it?
Thanks for your help in advance,
As soon as you use "new" then tapestry-ioc is not involved in object creation and can't inject. You should inject everything and never use "new" for singleton services. This is true for all ioc containers, not just tapestry-ioc.
Also if you put #Inject on a field then you don't also need a constructor to set it. Do one or the other, never both.
You should do something like this:
public class MyAppModule {
public void bind(ServiceBinder binder) {
binder.bind(EditionEncoder.class);
}
}
Then in your page/component/service
#Inject EditionEncoder editionEncoder;
If you wanted to put your own instantiated objects in there you can do
public class MyServiceModule {
public void bind(ServiceBinder binder) {
binder.bind(Service1.class, Service1Impl.class);
binder.bind(Service2.class, Service2Impl.class);
}
public SomeService buildSomeService(Service1 service1, Service2 service2, #AutoBuild Service3Impl service3) {
Date someDate = new Date();
return new SomeServiceImpl(service1, service2, service3, someDate);
}
}