Spring Integration: Extending AbstractInboundFileSynchronizer - spring

I have a working sftp inbound channel adapter defined in the config xml (apparently defining this via annotations is not fully functional in Spring as yet).
What I would like to do is override the synchronizeToLocalDirectory method so that it can be manually activated with the help of JMX.
Instantiating the class with #Component happens, however the synchronizeToLocalDirectory in my class never gets called because it is separate to the actually bean invoked by Spring though the config xml.
Is there a way to pair the two so that my synchronizeToLocalDirectory method is called every time?
My Inbound File Synchronizer :
#Component
#ManagedResource(objectName = "bean:name=InboundFileProcessor", description = "Synchronizes to the Local Directory",
log = true, logFile = "jmx.log", currencyTimeLimit = 15, persistPolicy = "OnUpdate", persistPeriod = 200,
persistLocation = "Spring", persistName = "FTP")
public class MyFtpInboundFileSynchronizer extends AbstractInboundFileSynchronizer<LsEntry> {
private static final Logger logger = LoggerFactory.getLogger(MyFtpInboundFileSynchronizer.class);
#Inject
private MultiMarkupFilter multiMarkupFilter;
#Inject
public MyFtpInboundFileSynchronizer(SessionFactory<LsEntry> ftpSessionFactory) {
super(ftpSessionFactory);
setRemoteDirectory(((FtpSessionFactory) ftpSessionFactory).getFtpSessionProperties().getRemoteDirectory());
setFilter(multiMarkupFilter);
}
public void init() {
}
#Override
protected boolean isFile(LsEntry lsEntry) {
if (lsEntry != null && !lsEntry.getAttrs().isDir()
&& !lsEntry.getFilename().equals(".")
&& !lsEntry.getFilename().equals("..")) {
logger.debug("Downloading file" + lsEntry.getFilename());
return true;
} else {
return false;
}
}
#Override
protected String getFilename(LsEntry file) {
return (file != null ? file.getFilename() : null);
}
#Override
public void synchronizeToLocalDirectory(File localDirectory) {
logger.debug("Starting synchronizeToLocalDirectory");
super.synchronizeToLocalDirectory(localDirectory);
logger.debug("Ending synchronizeToLocalDirectory");
}
#ManagedOperation(description = "synchronize To Local Directory")
#ManagedOperationParameters({ #ManagedOperationParameter(name = "localDirectory", description = "The Local Directory") })
public void synchronizeToLocalDirectory(String localDirectory) {
File localDirFile = new File(localDirectory);
if (localDirFile.exists() && localDirFile.isDirectory()) {
synchronizeToLocalDirectory(new File(localDirectory));
}
}
}
int-sftp:inbound-channel-adapter definition :
<int:annotation-config/>
<int-sftp:inbound-channel-adapter id="ftpInbound"
channel="ftpChannel" session-factory="${sessionFactory}"
auto-create-local-directory="${autoCreateLocalDirectory}"
delete-remote-files="${deleteRemoteFiles}" filter="${filter}"
remote-directory="${remoteDirectory}"
remote-file-separator="${remoteFileSeparator}"
local-directory="${localDirectory}">
<int:poller max-messages-per-poll="-1"
fixed-rate="${ftpPollInterval}" id="poller" error-channel="errorChannel" >
</int:poller>
</int-sftp:inbound-channel-adapter>
<int:channel id="ftpChannel"/>
<int:channel id="errorChannel"/>

Related

Spring Boot custom annotation design not working

I am following a tutorial by PacktPublishing where some annotations are used in an example,
but the code is from 2018 and there have probably been some changes.
Spring does not recognize the Annotation when creating a bean.
Specifically, here is an annotation design that just does not work for me locally:
link
Some important code snippets are:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Component
public #interface ChannelHandler {
/**
* Channel patter, alias of value()
*/
String pattern() default "";
/**
* The channel pattern that the handler will be mapped to by {#link WebSocketRequestDispatcher}
* using Spring's {#link org.springframework.util.AntPathMatcher}
*/
String value() default "";
}
#ChannelHandler("/board/*")
public class BoardChannelHandler {
private static final Logger log = LoggerFactory.getLogger(BoardChannelHandler.class);
#Action("subscribe")
public void subscribe(RealTimeSession session, #ChannelValue String channel) {
log.debug("RealTimeSession[{}] Subscribe to channel `{}`", session.id(), channel);
SubscriptionHub.subscribe(session, channel);
}
#Action("unsubscribe")
public void unsubscribe(RealTimeSession session, #ChannelValue String channel) {
log.debug("RealTimeSession[{}] Unsubscribe from channel `{}`", session.id(), channel);
SubscriptionHub.unsubscribe(session, channel);
}
}
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Action {
/**
* The action pattern. It needs to be an exact match.
* <p>For example, "subscribe"
*/
String value() default "";
}
Can you see what the issue is here? Is there some other annotation missing for newer versions
of Spring?
UPDATE - adding other necessary code.
public class ChannelHandlerInvoker {
private static final Logger log = LoggerFactory.getLogger(ChannelHandlerInvoker.class);
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
private String channelPattern;
private Object handler;
// Key is the action, value is the method to handle that action
private final Map<String, Method> actionMethods = new HashMap<>();
public ChannelHandlerInvoker(Object handler) {
Assert.notNull(handler, "Parameter `handler` must not be null");
Class<?> handlerClass = handler.getClass();
ChannelHandler handlerAnnotation = handlerClass.getAnnotation(ChannelHandler.class);
Assert.notNull(handlerAnnotation, "Parameter `handler` must have annotation #ChannelHandler");
Method[] methods = handlerClass.getMethods();
for (Method method : methods) {
Action actionAnnotation = method.getAnnotation(Action.class);
if (actionAnnotation == null) {
continue;
}
String action = actionAnnotation.value();
actionMethods.put(action, method);
log.debug("Mapped action `{}` in channel handler `{}#{}`", action, handlerClass.getName(), method);
}
this.channelPattern = ChannelHandlers.getPattern(handlerAnnotation);
this.handler = handler;
}
public boolean supports(String action) {
return actionMethods.containsKey(action);
}
public void handle(IncomingMessage incomingMessage, RealTimeSession session) {
Assert.isTrue(antPathMatcher.match(channelPattern, incomingMessage.getChannel()), "Channel of the handler must match");
Method actionMethod = actionMethods.get(incomingMessage.getAction());
Assert.notNull(actionMethod, "Action method for `" + incomingMessage.getAction() + "` must exist");
// Find all required parameters
Class<?>[] parameterTypes = actionMethod.getParameterTypes();
// All the annotations for each parameter
Annotation[][] allParameterAnnotations = actionMethod.getParameterAnnotations();
// The arguments that will be passed to the action method
Object[] args = new Object[parameterTypes.length];
try {
// Populate arguments
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
Annotation[] parameterAnnotations = allParameterAnnotations[i];
// No annotation applied on this parameter
if (parameterAnnotations.length == 0) {
if (parameterType.isInstance(session)) {
args[i] = session;
} else {
args[i] = null;
}
continue;
}
// Only use the first annotation applied on the parameter
Annotation parameterAnnotation = parameterAnnotations[0];
if (parameterAnnotation instanceof Payload) {
Object arg = JsonUtils.toObject(incomingMessage.getPayload(), parameterType);
if (arg == null) {
throw new IllegalArgumentException("Unable to instantiate parameter of type `" +
parameterType.getName() + "`.");
}
args[i] = arg;
} else if (parameterAnnotation instanceof ChannelValue) {
args[i] = incomingMessage.getChannel();
}
}
actionMethod.invoke(handler, args);
} catch (Exception e) {
String error = "Failed to invoker action method `" + incomingMessage.getAction() +
"` at channel `" + incomingMessage.getChannel() + "` ";
log.error(error, e);
session.error(error);
}
}
}
#Component
public class ChannelHandlerResolver {
private static final Logger log = LoggerFactory.getLogger(ChannelHandlerResolver.class);
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
// The key is the channel ant-like path pattern, value is the corresponding invoker
private final Map<String, ChannelHandlerInvoker> invokers = new HashMap<>();
private ApplicationContext applicationContext;
public ChannelHandlerResolver(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.bootstrap();
}
public ChannelHandlerInvoker findInvoker(IncomingMessage incomingMessage) {
ChannelHandlerInvoker invoker = null;
Set<String> pathPatterns = invokers.keySet();
for (String pathPattern : pathPatterns) {
if (antPathMatcher.match(pathPattern, incomingMessage.getChannel())) {
invoker = invokers.get(pathPattern);
}
}
if (invoker == null) {
return null;
}
return invoker.supports(incomingMessage.getAction()) ? invoker : null;
}
private void bootstrap() {
log.info("Bootstrapping channel handler resolver");
Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(ChannelHandler.class);
for (String handlerName : handlers.keySet()) {
Object handler = handlers.get(handlerName);
Class<?> handlerClass = handler.getClass();
ChannelHandler handlerAnnotation = handlerClass.getAnnotation(ChannelHandler.class);
String channelPattern = ChannelHandlers.getPattern(handlerAnnotation);
if (invokers.containsKey(channelPattern)) {
throw new IllegalStateException("Duplicated handlers found for chanel pattern `" + channelPattern + "`.");
}
invokers.put(channelPattern, new ChannelHandlerInvoker(handler));
log.debug("Mapped channel `{}` to channel handler `{}`", channelPattern, handlerClass.getName());
}
}
}
<!-- begin snippet: js hide: false console: true babel: false -->
UPDATE 2
I have managed to make ChannelHandler and Action annotations work by adding #Inherited annotation and using AnnotationUtils.findAnnotation() which traverses its super methods if the annotation is not directly present on the given method itself.
However, I haven't managed to access custom annotation value of type parameter (ChannelValue)
Here, Annotation[][] allParameterAnnotations = actionMethod.getParameterAnnotations();
returns null value.
UPDATE 3 -> SOLVED
Just add #Aspect annotation to your ChannelHandler implementation (e.g.
"BoardChannelHandler").
Looks like bootstrap() method, that goes through all the #ChannelHandler annotated beans is executed too early - try to debug it to check if it detects any beans at this stage.
If not try calling bootstrap() after Spring context is ready (for example listen for ContextRefreshedEvent.

How can I use the CXF HttpConduitFeature for DOSGi?

Has anyone succesfully used the CXF HttpConduitFeature for DOSGi ?
Looking at the CXF code for HttpConduitFeature.java
public class HttpConduitFeature extends DelegatingFeature<HttpConduitFeature.Portable> {
public HttpConduitFeature() {
super(new Portable());
}
public void setConduitConfig(HttpConduitConfig conduitConfig) {
delegate.setConduitConfig(conduitConfig);
}
public static class Portable implements AbstractPortableFeature {
private HttpConduitConfig conduitConfig;
#Override
public void initialize(Client client, Bus bus) {
Conduit conduit = client.getConduit();
if (conduitConfig != null && conduit instanceof HTTPConduit) {
conduitConfig.apply((HTTPConduit)conduit);
}
}
public void setConduitConfig(HttpConduitConfig conduitConfig) {
this.conduitConfig = conduitConfig;
}
}
}
And this method from the class JAXRSClientFactoryBean.java
protected void applyFeatures(AbstractClient client) {
if (getFeatures() != null) {
getFeatures().forEach(feature -> {
feature.initialize(client.getConfiguration(), getBus());
});
}
}
Which is what happens from the RsProvider-class in CXF-DOSGi, I don't understand how the initialize() from the HttpConduitFeature.Portable class will ever get called..
I tried to create my own implementation, a copy from HttpConduitFeature, but with an override of the method initialize(final InterceptorProvider interceptorProvider, final Bus bus), but then I have nothing to add the conduitConfig to. I don't see how I can make progress here.
Anyone has a better idea to add a Basic Authentication AuthorizationPolicy to my DOSGi client ? This was my attempt :
public class BasicAuthorizationIntent implements IntentsProvider {
#Override
public List<?> getIntents() {
HttpConduitConfig conduitConfig = new HttpConduitConfig();
conduitConfig.setAuthorizationPolicy(basicAuthorization());
HttpConduitFeature conduitFeature = new HttpConduitFeature();
conduitFeature.setConduitConfig(conduitConfig);
return Arrays.asList((Object) conduitFeature);
}
private AuthorizationPolicy basicAuthorization() {
AuthorizationPolicy authorizationPolicy = new AuthorizationPolicy();
authorizationPolicy.setUserName("dosgi");
authorizationPolicy.setPassword("dosgi");
authorizationPolicy.setAuthorizationType("Basic");
return authorizationPolicy;
}
}

Dynamic Routing key on RabbitListener Annotation

I need to create a queue linked to Direct Exchange for every user who has logged in to the application. The basement routing will be 'user_' + userId.
That is, every time I receive a message through the user management queue that a user is logged on. Instantiate a bean with scope 'prototype' that contains a method annotated with RabbitListener to declare its queue. To this bean, I passed the userId to be able to configure the name of the queue and routingKey. But I can not access this instance variable in the Spel expression due to a circular reference error.
Here I put the bean with which declares the queue:
#Component("usersHandler")
#Scope(value = "prototype")
public class UsersHandler {
private static Logger logger = LoggerFactory.getLogger(UsersHandler.class);
private Long userId;
public UsersHandler(Long userId) {
this.userId = userId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
#RabbitListener(bindings
= #QueueBinding(
value = #Queue(
value = "#{'queue_'.concat(usersHandler.userId)}",
durable = "false",
autoDelete = "true",
arguments = {
#Argument(
name = "x-message-ttl",
value = "#{rabbitCustomProperties.directExchange.queueArguments['x-message-ttl']}",
type = "java.lang.Integer"
)
,
#Argument(
name = "x-expires",
value = "#{rabbitCustomProperties.directExchange.queueArguments['x-expires']}",
type = "java.lang.Integer"
)
,
#Argument(
name = "x-dead-letter-exchange",
value = "#{rabbitCustomProperties.directExchange.queueArguments['x-dead-letter-exchange']}",
type = "java.lang.String"
)
}
),
exchange = #Exchange(
value = "#{rabbitCustomProperties.directExchange.name}",
type = ExchangeTypes.DIRECT,
durable = "#{rabbitCustomProperties.directExchange.durable}",
autoDelete = "#{rabbitCustomProperties.directExchange.autoDelete}",
arguments = {
#Argument(
name = "alternate-exchange",
value = "#{rabbitCustomProperties.directExchange.arguments['alternate-exchange']}",
type = "java.lang.String"
)
}
),
key = "#{'user_'.concat(usersHandler.userId)}")
)
public void handleMessage(#Payload Notification notification) {
logger.info("Notification Received : " + notification);
}
}
This is the other bean in charge of creating as many UserHandler as users have logged in:
#Component("adminHandler")
public class AdminHandler implements UsersManadgementVisitor {
#Autowired
private ApplicationContext appCtx;
private Map<Long, UsersHandler> handlers = new HashMap<Long, UsersHandler>();
private static Logger logger = LoggerFactory.getLogger(AdminHandler.class);
#RabbitListener(queues="#{rabbitCustomProperties.adminExchange.queues['users'].name}")
public void handleMessage(#Payload UsersManadgementMessage message) {
logger.info("Message -> " + message);
message.getType().accept(this, message.getId());
}
#Override
public void visitUserConnected(Long idUser) {
logger.info("Declare new queue for user: " + idUser );
UsersHandler userHandler = appCtx.getBean(UsersHandler.class, idUser);
handlers.put(idUser, userHandler);
}
#Override
public void visitUserDisconnected(Long idUser) {
logger.info("Remove queue for user: " + idUser );
handlers.remove(idUser);
}
}
My question is this:
How can I make the variable userId available in the evaluation context of the SpEL expressions?
You could use a ThreadLocal and the T operator...
#SpringBootApplication
public class So43717710Application {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(So43717710Application.class, args);
UserHolder.setUser("someUser");
context.getBean(Listener.class);
UserHolder.clearUser();
context.getBean(RabbitTemplate.class).convertAndSend("foo", "user_someUser", "bar");
Thread.sleep(5000);
context.close();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Listener listener() {
return new Listener();
}
public static class Listener {
#RabbitListener(bindings = #QueueBinding(value = #Queue("#{'queue_' + T(com.example.UserHolder).getUser()}"),
exchange = #Exchange(value = "foo"),
key = "#{'user_' + T(com.example.UserHolder).getUser()}"))
public void listen(String in) {
System.out.println(in);
}
}
}
public class UserHolder {
private static final ThreadLocal<String> user = new ThreadLocal<String>();
public static void setUser(String userId) {
user.set(userId);
}
public static String getUser() {
return user.get();
}
public static void clearUser() {
user.remove();
}
}
If the ThreadLocal is in a #Bean you can use a bean reference...
#SpringBootApplication
public class So43717710Application {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(So43717710Application.class, args);
UserHolder.setUser("someUser");
context.getBean(Listener.class);
UserHolder.clearUser();
context.getBean(RabbitTemplate.class).convertAndSend("foo", "user_someUser", "bar");
Thread.sleep(5000);
context.close();
}
#Bean
public UserHolder holder() {
return new UserHolder();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Listener listener() {
return new Listener();
}
public static class Listener {
#RabbitListener(bindings = #QueueBinding(value = #Queue("#{'queue_' + #holder.user}"),
exchange = #Exchange(value = "foo"),
key = "#{'user_' + #holder.user}"))
public void listen(String in) {
System.out.println(in);
}
}
}

Activtii/OSGI, TaskListener not added as Activiti service to delegate cache

Hello I have a problem with osgi on servicemix.
It cannot bind or unbind service to delegate cache, when using TaskListener interface. JavaDelegate works fine in osgi with delegate expression.
Could this solution solve the problem or is there something else needed with BlueprintContextELResolver? Because the blueprint with the
service ref="something" interface="org.activiti.engine.delegate.TaskListener"/>
package org.activiti.osgi.blueprint;
/**
* #see org.activiti.spring.ApplicationContextElResolver
*/
public class BlueprintELResolver extends ELResolver {
private Map<String, JavaDelegate> delegateMap = new HashMap<String, JavaDelegate>();
private Map<String, TaskListener> taskListenerMap = new HashMap<String, TaskListener>();
private Map<String, ActivityBehavior> activityBehaviourMap = new HashMap<String, ActivityBehavior>();
public Object getValue(ELContext context, Object base, Object property) {
if (base == null) {
// according to javadoc, can only be a String
String key = (String) property;
LOGGER.info("Show string key: {}", key);
LOGGER.info("Show property: {}", property);
for (String name : delegateMap.keySet()) {
if (name.equalsIgnoreCase(key)) {
LOGGER.info("Show property JavaDelegate: {}", name);
context.setPropertyResolved(true);
return delegateMap.get(name);
}
}
for (String name : taskListenerMap.keySet()) {
if (name.equalsIgnoreCase(key)) {
LOGGER.info("Show property TaskListener: {}", name);
context.setPropertyResolved(true);
return taskListenerMap.get(name);
}
}
for (String name : activityBehaviourMap.keySet()) {
if (name.equalsIgnoreCase(key)) {
context.setPropertyResolved(true);
return activityBehaviourMap.get(name);
}
}
}
return null;
}
#SuppressWarnings("rawtypes")
public void bindService(JavaDelegate delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
delegateMap.put(name, delegate);
LOGGER.info("added Activiti service to delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void unbindService(JavaDelegate delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
if(delegateMap.containsKey(name)) {
delegateMap.remove(name);
}
LOGGER.info("removed Activiti service from delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void bindTaskListenerService(TaskListener delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
taskListenerMap.put(name, delegate);
LOGGER.info("added Activiti service to delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void unbindTaskListenerService(TaskListener delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
if(taskListenerMap.containsKey(name)) {
taskListenerMap.remove(name);
}
LOGGER.info("removed Activiti service from delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void bindActivityBehaviourService(ActivityBehavior delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
activityBehaviourMap.put(name, delegate);
LOGGER.info("added Activiti service to activity behaviour cache {}", name);
}
#SuppressWarnings("rawtypes")
public void unbindActivityBehaviourService(ActivityBehavior delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
if(activityBehaviourMap.containsKey(name)) {
activityBehaviourMap.remove(name);
}
LOGGER.info("removed Activiti service from activity behaviour cache {}", name);
}
public boolean isReadOnly(ELContext context, Object base, Object property) {
return true;
}
public void setValue(ELContext context, Object base, Object property,
Object value) {
}
public Class<?> getCommonPropertyType(ELContext context, Object arg) {
return Object.class;
}
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context,
Object arg) {
return null;
}
public Class<?> getType(ELContext context, Object arg1, Object arg2) {
return Object.class;
}
}
After this I still get:
Exception while invoking TaskListener: Unknown property used in
expression: ${expression}
I have experienced a similar problem where the El resolver was not being fully initiated.
I resolved the problem by overloading the BlueprintExpressionManager when loading the configuration:
public class BlueprintExpressionManagerReordered extends ExpressionManager
{
#Override
protected ELResolver createElResolver(VariableScope variableScope){
CompositeELResolver elResolver = new CompositeELResolver();
elResolver.add(new VariableScopeElResolver(variableScope));
elResolver.add(new ArrayELResolver());
elResolver.add(new ListELResolver());
elResolver.add(new MapELResolver());
if (_blueprintContextELResolver != null) {
elResolver.add(_blueprintContextELResolver);
}
elResolver.add(_blueprintELResolver);
elResolver.add(new BeanELResolver());
return elResolver;
}
}
It is called during init of the process engine configuration:
public class ProcessEngineFactoryWithELResolverReordered extends ProcessEngineFactoryWithELResolver
{
private BlueprintELResolver _blueprintELResolver;
private BlueprintContextELResolver _blueprintContextELResolver;
public void init() throws Exception
{
super.init();
ProcessEngineConfigurationImpl configImpl = (ProcessEngineConfigurationImpl)getProcessEngineConfiguration();
final BlueprintExpressionManagerReordered mgr = new BlueprintExpressionManagerReordered();
configImpl.setExpressionManager(mgr);
if (configImpl.getActivityBehaviorFactory() instanceof DefaultActivityBehaviorFactory) {
((DefaultActivityBehaviorFactory)configImpl.getActivityBehaviorFactory()).setExpressionManager(mgr);
}
if (configImpl.getListenerFactory() instanceof DefaultListenerFactory) {
((DefaultListenerFactory)configImpl.getListenerFactory()).setExpressionManager(mgr);
}
}
And referenced in my context.xml
<bean id="processEngineFactory"
class="com.bp3.oss.custom.ProcessEngineFactoryWithELResolverReordered"
init-method="init"
destroy-method="destroy">
<property name="processEngineConfiguration"
ref="configuration"/>
<property name="bundle" ref="blueprintBundle"/>
<property name="blueprintELResolver"
ref="blueprintELResolver"/>
<property name="blueprintContextELResolver" ref="blueprintContextELResolver"/>
</bean>
Not sure if this will help, but may give you something to go after.

Is there a PropertyPlaceholderConfigurer for <portlet-preferences>?

I understand that there is the ServletContextPropertyPlaceholderConfigurer which:
resolves placeholders as ServletContext init parameters (that is, web.xml context-param entries).
Does anyone know of a PropertyPlaceholderConfigurer that would similarly resolve placeholders as portlet-preferences (that is, portlet.xml portlet-preference entries)?
Here's how I solved the problem, I ended up writing a class similar to ServletContextPropertyPlaceholderConfigurer.. :-)
public class PortletConfigPropertyPlaceholderConfigurer extends
PropertyPlaceholderConfigurer implements PortletConfigAware {
private PortletConfig portletConfig;
private boolean configOverride = false;
public void setPortletConfig(PortletConfig portletConfig) {
this.portletConfig = portletConfig;
}
public void setConfigOverride(boolean configOverride) {
this.configOverride = configOverride;
}
#Override
protected String resolvePlaceholder(String placeholder, Properties props) {
String value = null;
if (this.configOverride && this.portletConfig != null) {
value = resolvePlaceholder(placeholder, this.portletConfig);
}
if (value == null) {
value = super.resolvePlaceholder(placeholder, props);
}
return value;
}
protected String resolvePlaceholder(String placeholder,
PortletConfig portletConfig) {
return portletConfig.getInitParameter(placeholder);
}
}
Cheers,
Gerson

Resources