I have a simple resource like:
#Path("/")
public class RootResource {
#Context WebConfig wc;
#PostConstruct
public void init() {
assertNotNull(wc);
}
#GET
public void String method() {
return "Hello\n";
}
}
Which I am trying to use with JerseyTest (2.x, not 1.x) and the GrizzlyTestContainerFactory.
I can't work out what I need to do in terms of config to get the WebConfig object injected.
I solved this issue by creating a subclass of GrizzlyTestContainerFactory and explicitly loading the Jersey servlet. This triggers the injection of the WebConfig object. The code looks like this:
public class ExtendedGrizzlyTestContainerFactory implements TestContainerFactory {
private static class GrizzlyTestContainer implements TestContainer {
private final URI uri;
private final ApplicationHandler appHandler;
private HttpServer server;
private static final Logger LOGGER = Logger.getLogger(GrizzlyTestContainer.class.getName());
private GrizzlyTestContainer(URI uri, ApplicationHandler appHandler) {
this.appHandler = appHandler;
this.uri = uri;
}
#Override
public ClientConfig getClientConfig() {
return null;
}
#Override
public URI getBaseUri() {
return uri;
}
#Override
public void start() {
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.log(Level.INFO, "Starting GrizzlyTestContainer...");
}
try {
this.server = GrizzlyHttpServerFactory.createHttpServer(uri, appHandler);
// Initialize and register Jersey Servlet
WebappContext context = new WebappContext("WebappContext", "");
ServletRegistration registration = context.addServlet("ServletContainer", ServletContainer.class);
registration.setInitParameter("javax.ws.rs.Application",
appHandler.getConfiguration().getApplication().getClass().getName());
// Add an init parameter - this could be loaded from a parameter in the constructor
registration.setInitParameter("myparam", "myvalue");
registration.addMapping("/*");
context.deploy(server);
} catch (ProcessingException e) {
throw new TestContainerException(e);
}
}
#Override
public void stop() {
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.log(Level.INFO, "Stopping GrizzlyTestContainer...");
}
this.server.stop();
}
}
#Override
public TestContainer create(URI baseUri, ApplicationHandler application) throws IllegalArgumentException {
return new GrizzlyTestContainer(baseUri, application);
}
Notice that the Jersey Servlet configuration is being loaded from the ApplicationHandler that is passed in as a parameter using the inner Application object's class name (ResourceConfig is a subclass of Application). Therefore, you also need to create a subclass of ResourceConfig for this approach to work. The code for this is very simple:
package com.example;
import org.glassfish.jersey.server.ResourceConfig;
public class MyResourceConfig extends ResourceConfig {
public MyResourceConfig() {
super(MyResource.class);
}
}
This assumes the resource you are testing is MyResource. You also need to override a couple of methods in your test like this:
public class MyResourceTest extends JerseyTest {
public MyResourceTest() throws TestContainerException {
}
#Override
protected Application configure() {
return new MyResourceConfig();
}
#Override
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
return new ExtendedGrizzlyTestContainerFactory();
}
#Test
public void testCreateSimpleBean() {
final String beanList = target("test").request().get(String.class);
Assert.assertNotNull(beanList);
}
}
Finally, for completeness, here is the code for MyResource:
#Path("test")
public class MyResource {
#Context WebConfig wc;
#PostConstruct
public void init() {
System.out.println("WebConfig: " + wc);
String url = wc.getInitParameter("myparam");
System.out.println("myparam = "+url);
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Collection<TestBean> createSimpleBean() {
Collection<TestBean> res = new ArrayList<TestBean>();
res.add(new TestBean("a", 1, 1L));
res.add(new TestBean("b", 2, 2L));
return res;
}
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public TestBean roundTrip(TestBean s) {
return s;
}
}
The output of running the test shows that the WebConfig is loaded and the init param is now available:
WebConfig: org.glassfish.jersey.servlet.WebServletConfig#107d0f44
myparam = myvalue
The solution from #ametke worked well but wasn't picking up my ExceptionMapper classes. To solve this I simplified the start() method to:
#Override
public void start() {
try {
initParams.put("jersey.config.server.provider.packages", "my.resources;my.config");
this.server = GrizzlyWebContainerFactory.create(uri, initParams);
} catch (ProcessingException | IOException e) {
throw new TestContainerException(e);
}
}
This was based on Problems running JerseyTest when dealing with HttpServletResponse
Related
In my spring mvc web-application i use a generic converter that converts String (id) to Company by fetch using (service and dao) components
first of all in my MVC-config i add the converter like follow :
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new GenericIdToCompanyConverter(new CompanyServiceImp()));
}
companyService
#Service
#Transactional
#Qualifier("companyService")
public class CompanyServiceImp implements ICompanyService {
#Resource
#Qualifier("companyDAO")
private ICompanyDao dao;
public void setDao(ICompanyDao dao) {
this.dao = dao;
}
#Override
public Company find(Long id) throws BusinessException {
Company current = dao.find(id);
if(current == null) {
throw new BusinessException("notFound");
}
return current;
}
....
}
Generic converter :
public class GenericIdToCompanyConverter implements GenericConverter {
private ICompanyService companyService;
public GenericIdToCompanyConverter(ICompanyService companyService) {
super();
this.companyService = companyService;
}
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
ConvertiblePair[] pairs = new ConvertiblePair[] { new ConvertiblePair(Number.class, Company.class), new ConvertiblePair(String.class, Company.class) };
return ImmutableSet.copyOf(pairs);
}
#Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
long id = 0;
if( sourceType.getType() == String.class) {
try {
id = Long.valueOf((String) source);
}catch(NumberFormatException e) {
return null;
}
}else if( sourceType.getType() == Number.class) {
id = (Long) source;
}else {
return null;
}
try {
return companyService.find(Long.valueOf(id));
} catch (BusinessException e) {
return null;
}
}
}
and here the controller that receives data form (via ajax request)
public #ResponseBody JsonResponseBean applay(#Valid VoucherForm form, BindingResult result, Locale locale) throws BusinessException {
....
}
where VoucherForm has these attributes
public class VoucherForm{
protected Long id;
protected Company company;
...
}
when i run the application and call controller method it returns type mismatch error for company attribute
and when i execute this on debug mode i see that it fails on serviceCompany - dao.find(id) statment where my dao is == null
Please help
finally i have to autowire the converter
Mvc-config
....
#Autowired
private GenericIdToCompanyConverter genericIdToCompanyConverter;
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(genericIdToCompanyConverter);
}
and update the converter like follow :
public class GenericIdToCompanyConverter implements GenericConverter {
#Resource
#Qualifier("companyService")
private ICompanyService companyService;
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
....
}
#Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
....
}
}
Using Spring MVC, I have the following setup:
An AbstractRequestLoggingFilter derived filter for logging requests.
A TaskDecorator to marshal the MDC context mapping from the web request thread to the #Async thread.
I'm attempting to collect context info using MDC (or a ThreadLocal object) for all components involved in handling the request.
I can correctly retrieve the MDC context info from the #Async thread. However, if the #Async thread were to add context info to the MDC, how can I now marshal the MDC context info to the thread that handles the response?
TaskDecorator
public class MdcTaskDecorator implements TaskDecorator {
#Override
public Runnable decorate(Runnable runnable) {
// Web thread context
// Get the logging MDC context
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
// #Async thread context
// Restore the web thread MDC context
if(contextMap != null) {
MDC.setContextMap(contextMap);
}
else {
MDC.clear();
}
// Run the new thread
runnable.run();
}
finally {
MDC.clear();
}
};
}
}
Async method
#Async
public CompletableFuture<String> doSomething_Async() {
MDC.put("doSomething", "started");
return doit();
}
Logging Filter
public class ServletLoggingFilter extends AbstractRequestLoggingFilter {
#Override
protected void beforeRequest(HttpServletRequest request, String message) {
MDC.put("webthread", Thread.currentThread().getName()); // Will be webthread-1
}
#Override
protected void afterRequest(HttpServletRequest request, String message) {
MDC.put("responsethread", Thread.currentThread().getName()); // Will be webthread-2
String s = MDC.get("doSomething"); // Will be null
// logthis();
}
}
I hope you have solved the problem, but if you did not, here comes a solution.
All you have to do can be summarized as following 2 simple steps:
Keep your class MdcTaskDecorator.
Extends AsyncConfigurerSupport for your main class and override getAsyncExecutor() to set decorator with your customized one as follows:
public class AsyncTaskDecoratorApplication extends AsyncConfigurerSupport {
#Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new MdcTaskDecorator());
executor.initialize();
return executor;
}
public static void main(String[] args) {
SpringApplication.run(AsyncTaskdecoratorApplication.class, args);
}
}
Create a bean that will pass the MDC properties from parent thread to the successor thread.
#Configuration
#Slf4j
public class AsyncMDCConfiguration {
#Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new MDCTaskDecorator());//MDCTaskDecorator i s a custom created class thet implements TaskDecorator that is reponsible for passing on the MDC properties
executor.initialize();
return executor;
}
}
#Slf4j
public class MDCTaskDecorator implements TaskDecorator {
#Override
public Runnable decorate(Runnable runnable) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
MDC.setContextMap(contextMap);
runnable.run();
} finally {
MDC.clear();
}
};
}
}
All Good now. Happy Coding
I have some solutions that roughly divided into Callable(for #Async), AsyncExecutionInterceptor(for #Async), CallableProcessingInterceptor(for controller).
1.The Callable solution for putting context infos into #Async thread:
The key is using the ContextAwarePoolExecutor to replace the default executor of #Async:
#Configuration
public class DemoExecutorConfig {
#Bean("demoExecutor")
public Executor contextAwarePoolExecutor() {
return new ContextAwarePoolExecutor();
}
}
And the ContextAwarePoolExecutor overwriting submit and submitListenable methods with ContextAwareCallable inside:
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
private static final long serialVersionUID = 667815067287186086L;
#Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new ContextAwareCallable<T>(task, newThreadContextContainer()));
}
#Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new ContextAwareCallable<T>(task, newThreadContextContainer()));
}
/**
* set infos what we need
*/
private ThreadContextContainer newThreadContextContainer() {
ThreadContextContainer container = new ThreadContextContainer();
container.setRequestAttributes(RequestContextHolder.currentRequestAttributes());
container.setContextMapOfMDC(MDC.getCopyOfContextMap());
return container;
}
}
The ThreadContextContainer is just a pojo to store infos for convenience:
public class ThreadContextContainer implements Serializable {
private static final long serialVersionUID = -6809291915300091330L;
private RequestAttributes requestAttributes;
private Map<String, String> contextMapOfMDC;
public RequestAttributes getRequestAttributes() {
return requestAttributes;
}
public Map<String, String> getContextMapOfMDC() {
return contextMapOfMDC;
}
public void setRequestAttributes(RequestAttributes requestAttributes) {
this.requestAttributes = requestAttributes;
}
public void setContextMapOfMDC(Map<String, String> contextMapOfMDC) {
this.contextMapOfMDC = contextMapOfMDC;
}
}
The ContextAwareCallable(a Callable proxy for original task) overwriting the call method to storage MDC or other context infos before the original task executing its call method:
public class ContextAwareCallable<T> implements Callable<T> {
/**
* the original task
*/
private Callable<T> task;
/**
* for storing infos what we need
*/
private ThreadContextContainer threadContextContainer;
public ContextAwareCallable(Callable<T> task, ThreadContextContainer threadContextContainer) {
this.task = task;
this.threadContextContainer = threadContextContainer;
}
#Override
public T call() throws Exception {
// set infos
if (threadContextContainer != null) {
RequestAttributes requestAttributes = threadContextContainer.getRequestAttributes();
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes);
}
Map<String, String> contextMapOfMDC = threadContextContainer.getContextMapOfMDC();
if (contextMapOfMDC != null) {
MDC.setContextMap(contextMapOfMDC);
}
}
try {
// execute the original task
return task.call();
} finally {
// clear infos after task completed
RequestContextHolder.resetRequestAttributes();
try {
MDC.clear();
} finally {
}
}
}
}
In the end, using the #Async with the configured bean "demoExecutor" like this: #Async("demoExecutor")
void yourTaskMethod();
2.In regard to your question of handling the response:
Regret to tell that I don't really have a verified solution. Maybe the org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke is possible to solve that.
And I do not think it has a solution to handle the response with your ServletLoggingFilter. Because the Async method will be returned instantly. The afterRequest method executes immediately and returns before Async method doing things. You won't get what you want unless you synchronously wait for the Async method to finish executing.
But if you just want to log something, you can add those codes into my example ContextAwareCallable after the original task executing its call method:
try {
// execute the original task
return task.call();
} finally {
String something = MDC.get("doSomething"); // will not be null
// logthis(something);
// clear infos after task completed
RequestContextHolder.resetRequestAttributes();
try {
MDC.clear();
} finally {
}
}
I am new to unit test and I am trying to use robolectric to do the test for my android app, but I encountered some problems
DemoPresenterTest
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 23)
#PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" })
#PrepareForTest(DemoPresenterImpl_.class)
public class DemoPresenterTest {
#Rule
public MockitoRule rule = MockitoJUnit.rule();
private MockDemoNetworkService mMockDemoNetworkService;
private MockLceView mLceView;
private DemoPresenterImpl_ mPresenter;
#Before
public void setup() {
mMockDemoNetworkService = Mockito.mock(MockDemoNetworkService.class);
mLceView = Mockito.mock(MockLceView.class);
PowerMockito.mockStatic(DemoPresenterImpl_.class);
mPresenter = DemoPresenterImpl_.getInstance_(RuntimeEnvironment.application);
mPresenter.service = mMockDemoNetworkService;
mPresenter.view = mLceView;
}
#Test
public void testDownloadData() {
mPresenter.downloadData();
Mockito.verify(mLceView).onError(Mockito.anyInt());
}
}
DemoPresenterImpl
#EBean
public class DemoPresenterImpl implements DemoPresenter {
#Bean(DemoNetworkService.class)
DemoService service;
protected LceView<Demo> view;
/**
* download the data from server for the first time, data will be saved into the database
* and for the next time it will query the database instead
*/
#Override
#Background(delay = 1000)
public void downloadData() {
try {
List<Demo> result = service.getDemoList();
if (result != null) {
view.setData(result);
} // add else if the result is not return empty list but null
} catch (NetworkFailException e) {
view.onError(e.getResponse().getCode());
}
}
#Override
public void attach(LceView<Demo> view) {
this.view = view;
}
}
MockDemoNetworkService
public class MockDemoNetworkService implements DemoService {
#Override
public List<Demo> getDemoList() throws NetworkFailException {
NetworkFailResponse response = new NetworkFailResponse();
response.setCode(500);
throw new NetworkFailException(response);
}
#Override
public boolean setDemoList(List<Demo> demoList) {
return false;
}
}
When I run the test it returns "Cannot subclass final class class com.*.DemoPresenterImpl_", if I change to DemoPresenterImpl, the test can run but the mLceView never get called
Wanted but not invoked: mockLceView.onError();
-> at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:245)
Actually, there were zero interactions with this mock.
am I doing something wrong?
I think you can remove #PrepareForTest, because you are not mocking the presenter, you are actually testing it. Then you should use DemoPresenterImpl_, because it is containing the needed generated code.
i'm currently have a look a springboot undertow and it's not really clear (for me) how to dispatch an incoming http request to a worker thread for blocking operation handling.
Looking at the class UndertowEmbeddedServletContainer.class, it look like there is no way to have this behaviour since the only HttpHandler is a ServletHandler, that allow #Controller configurations
private Undertow createUndertowServer() {
try {
HttpHandler servletHandler = this.manager.start();
this.builder.setHandler(getContextHandler(servletHandler));
return this.builder.build();
}
catch (ServletException ex) {
throw new EmbeddedServletContainerException(
"Unable to start embdedded Undertow", ex);
}
}
private HttpHandler getContextHandler(HttpHandler servletHandler) {
if (StringUtils.isEmpty(this.contextPath)) {
return servletHandler;
}
return Handlers.path().addPrefixPath(this.contextPath, servletHandler);
}
By default, in undertow all requests are handled by IO-Thread for non blocking operations.
Does this mean that every #Controller executions will be processed by a non blocking thread ? or is there a solution to chose from IO-THREAD or WORKER-THREAD ?
I try to write a workaround, but this code is pretty uggly, and maybe someone has a better solution:
BlockingHandler.class
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface BlockingHandler {
String contextPath() default "/";
}
UndertowInitializer.class
public class UndertowInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
configurableApplicationContext.addBeanFactoryPostProcessor(new UndertowHandlerPostProcessor());
}
}
UndertowHandlerPostProcessor.class
public class UndertowHandlerPostProcessor implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(BlockingHandler.class));
for (BeanDefinition beanDefinition : scanner.findCandidateComponents("org.me.lah")){
try{
Class clazz = Class.forName(beanDefinition.getBeanClassName());
beanDefinitionRegistry.registerBeanDefinition(clazz.getSimpleName(), beanDefinition);
} catch (ClassNotFoundException e) {
throw new BeanCreationException(format("Unable to create bean %s", beanDefinition.getBeanClassName()), e);
}
}
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
//no need to post process defined bean
}
}
override UndertowEmbeddedServletContainerFactory.class
public class UndertowEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware, ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) {
DeploymentManager manager = createDeploymentManager(initializers);
int port = getPort();
if (port == 0) {
port = SocketUtils.findAvailableTcpPort(40000);
}
Undertow.Builder builder = createBuilder(port);
Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(BlockingHandler.class);
return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(),
port, port >= 0, handlers);
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
...
override UndertowEmbeddedServletContainer.class
public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager,
String contextPath, int port, boolean autoStart, Map<String, Object> handlers) {
this.builder = builder;
this.manager = manager;
this.contextPath = contextPath;
this.port = port;
this.autoStart = autoStart;
this.handlers = handlers;
}
private Undertow createUndertowServer() {
try {
HttpHandler servletHandler = this.manager.start();
String path = this.contextPath.isEmpty() ? "/" : this.contextPath;
PathHandler pathHandler = Handlers.path().addPrefixPath(path, servletHandler);
for(Entry<String, Object> entry : handlers.entrySet()){
Annotation annotation = entry.getValue().getClass().getDeclaredAnnotation(BlockingHandler.class);
System.out.println(((BlockingHandler) annotation).contextPath());
pathHandler.addPrefixPath(((BlockingHandler) annotation).contextPath(), (HttpHandler) entry.getValue());
}
this.builder.setHandler(pathHandler);
return this.builder.build();
}
catch (ServletException ex) {
throw new EmbeddedServletContainerException(
"Unable to start embdedded Undertow", ex);
}
}
set initializer to the application context
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).initializers(new UndertowInitializer()).run(args);
}
finaly create a HttpHandler that dispatch to worker thread
#BlockingHandler(contextPath = "/blocking/test")
public class DatabaseHandler implements HttpHandler {
#Autowired
private EchoService echoService;
#Override
public void handleRequest(HttpServerExchange httpServerExchange) throws Exception {
if(httpServerExchange.isInIoThread()){
httpServerExchange.dispatch();
}
echoService.getMessage("my message");
}
}
As you can see, my "solution" is really heavy, and i would really appreciate any help to simplify it a lot.
Thank you
You don't need to do anything.
Spring Boot's default Undertow configuration uses Undertow's ServletInitialHandler in front of Spring MVC's DispatcherServlet. This handler performs the exchange.isInIoThread() check and calls dispatch() if necessary.
If you place a breakpoint in your #Controller, you'll see that it's called on a thread named XNIO-1 task-n which is a worker thread (the IO threads are named XNIO-1 I/O-n).
I have built a custom component button, but somehow the action is not invoked. When debugging the getAction-Method within the component and invoking the supplied MethodeExpression the Bean-Method is called as expected. But due to some reason, the Expression is not invoked when pressing the button in the browser.
Is there some kind of additional Interface necessary to pass the action to the embedded button-component?
Any help is very appreciated since I am stuck at this issue for some days now
MyClass:
public class MyClass extends UIPanel implements SystemEventListener
{
private UIForm form;
private HtmlCommandButton buttonOk;
public MyClass()
{
FacesContext context = getFacesContext();
UIViewRoot root = context.getViewRoot();
root.subscribeToViewEvent(PostAddToViewEvent.class, this);
}
#Override
public void processEvent(SystemEvent event)
{
this.form = new UIForm();
this.buttonOk = new HtmlCommandButton();
this.buttonOk.setId("okButtonId");
this.buttonOk.setActionExpression(getAction());
this.buttonOk.setValue("OK");
this.form.getChildren().add(this.buttonOk);
getChildren().add(this.form);
}
private enum PropertyKeys
{
action, text, titel
}
public MethodExpression getAction()
{
return (MethodExpression) getStateHelper().eval(PropertyKeys.action);
}
public void setAction(MethodExpression actionExpression)
{
getStateHelper().put(PropertyKeys.action, actionExpression);
}
public String getText()
{
return (String) getStateHelper().eval(PropertyKeys.text);
}
public void setText(String text)
{
getStateHelper().put(PropertyKeys.text, text);
}
public String getTitel()
{
return (String) getStateHelper().eval(PropertyKeys.titel);
}
public void setTitel(String titel)
{
getStateHelper().put(PropertyKeys.titel, titel);
}
#Override
public void encodeAll(FacesContext context) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement(HTML.DIV_ELEM, this);
writer.writeText(getText(), null);
this.form.encodeAll(context);
writer.endElement(HTML.DIV_ELEM);
}
#Override
public void encodeChildren(FacesContext context) throws IOException
{
}
#Override
public boolean isListenerForSource(Object source)
{
return (source instanceof MyClass);
}
}
MyClassHandler:
public class MyClassHandler extends ComponentHandler
{
public MyClassHandler(ComponentConfig config)
{
super(config);
}
#SuppressWarnings("rawtypes")
#Override
protected MetaRuleset createMetaRuleset(Class type)
{
return super.createMetaRuleset(type).addRule(new MethodRule("action", String.class, new Class[] { ActionEvent.class }));
}
}
myView Method:
...
public String myMethod()
{
System.err.println("myMethod");
return "/some/path/yadayada.xhtml";
}
...
MyView.xhtml
<myTag action="#{myView.myMethod}" id="id1" titel="bla" text="bleh" />
Exdending UICommand is enough, since you only want one action to be executed.
You have to provide two additional MethodExpressions via the tag-attributes and within the decode-method you can check which button has been pressed and redirect the particular MethodExpression to the standard-action provided by UICommand. This way, you dont have to worry about the legacy-interface ActionSource, or how Events are broadcasted.
public void decode(FacesContext contex)
{
Map<String,String> map = context.getExternalContext.getRequestParameterMap();
// your rendered buttons need a name you check for
final boolean okPressed = map.containsKey( getClientId + ":ok" );
final boolean cancelPressed = map.containsKey( getClientId + ":cancel" );
if(okPressed || cancelPressed)
{
MethodExpression exp = null;
if(okPressed)
{
exp = getActionOk();
}
else
{
exp = getActionCancel();
}
// redirect to standard action
setActionExpression(exp);
queueEvent(new ActionEvent(this));
}
}
In order to make use of of this you need two attributes (actionOk and actionCancel) which use Method Expressions (setter and getter). Those have to be configured by a ComponentHandler as you did for the action-attribute.