Websphere Application Server SAML Token processing - java

I'm able to successfully setup websphere to authenticate with an IdP and access the web resource as expected. But now my application needs the claims/assertions/attributes available in the SAML token/response to proceed further. What is best option available to access the SAML response/attributes inside my java application?

I want to add to the previous answer.For Websphere Application Server, if you are using already available WebsphereSamlSP application as SP then you can use following code inside handleRedirect() method of IBMWebpshereSamlACSListenerServlet to get saml attributes. Or use this in your custom SP code.
SAMLToken samlToken = (SAMLToken) AccessController
.doPrivileged(new java.security.PrivilegedExceptionAction() {
public Object run() throws java.lang.Exception {
final java.util.Iterator authIterator = subject
.getPrivateCredentials(SAMLToken.class)
.iterator();
if (authIterator.hasNext()) {
final SAMLToken token = (SAMLToken) authIterator
.next();
return token;
}
return null;
}
});
// Log attribute name and values
List<SAMLAttribute> attributes = samlToken.getSAMLAttributes();
if (attributes != null && !attributes.isEmpty()) {
for (SAMLAttribute attr : attributes) {
logger.debug(attr.getName());
if (attr.getStringAttributeValue() != null) {
for (int i = 0; i < attr.getStringAttributeValue().length; i++) {
logger.debug(attr.getStringAttributeValue()[i]);
}
}
}
}

For WebSphere Liberty profile:
get com.ibm.websphere.security.saml2.Saml20Token from RunAsSubject:
Saml20Token samlToken = null;
Subject subject = WSSubject.getRunAsSubject();
Iterator authIterator = subject.getPrivateCredentials(Saml20Token.class).iterator();
if (authIterator.hasNext()) {
samlToken = (Saml20Token) authIterator.next();
}
You can get List of com.ibm.websphere.security.saml2.Saml20Attribute from Saml20Token
samlToken.getSAMLAttributes();
You can also get most SAML assertions from Saml20Token. For example
samlToken.getSAMLIssuerName();
For classic WebSphere:
Subject subject = WSSubject.getRunAsSubject();
SAMLToken samlToken = (SAMLToken) AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction() {
public Object run() throws java.lang.Exception
{
final java.util.Iterator authIterator = subject.getPrivateCredentials(SAMLToken.class).iterator();
if ( authIterator.hasNext() ) {
final SAMLToken token = (SAMLToken) authIterator.next();
return token;
}
return null;
}
});
SAMLNameID = samlToken.getSAMLNameID();
List<SAMLAttribute> attributes = samlToken.getSAMLAttributes();

Related

is it possible to return "if condition satisfies return a list else return an error message" using a java method

I know that in Java a method can return only one return type... But if there is any possiblity to this, kindly let me know. From the below method I am trying to return a list if condition satisfies else i am trying to return an error message.
Here is my code:
#RequestMapping(value = "/getcompanies", method = RequestMethod.POST)
public List<CompanyMaster> getCompanies(#RequestBody UserDetails user) {
String OrgLoginId = user.getOrgLoginId();
String password = user.getuPassword();
String checkLoginId = null;
String uPassword = null;
String encPassword = null;
String loginId = null;
String checkAuthorized = null;
// String loginId=userService.getLoginId(OrgLoginId);
List<Object[]> CheckIdPassword = userService.checkLoginId(OrgLoginId);
List<Object[]> results = CheckIdPassword;
for (Object[] obj : results) {
checkLoginId = obj[0].toString();
if (null == obj[1]) {
uPassword = "";
} else {
uPassword = obj[1].toString();
}
loginId = obj[2].toString();
}
checkAuthorized = loginId.substring(0, 3);
if (null != password) {
MD5 md5 = new MD5();
encPassword = md5.getPassword(password);
}
if (checkLoginId == null) {
return "Incorrect loginId..Please enter valid loginId";
} else if (encPassword.equals(uPassword)) {
if (checkAuthorized.equals("STE")) {
List<CompanyMaster> companyList = userService.getCompanyList(OrgLoginId);
return companyList;
} else {
return "You are not Authorized";
}
} else {
return "Incorrect Password";
}
Yes its possible, create a custom Exception say 'MyAppException' and throw that exception with the error message you want.
Write your logic in a try{}catch block and throw the exception in catch so that the response has the error message
public List<CompanyMaster> getCompanies(#RequestBody UserDetails user) throws MyAppppException
{
try
{
//your logic which throws error
return companyList;
}
catch( final MyAppException we )
{
throw new MyAppException("User not found", HttpStatus.NOT_FOUND);
}
}
Refer this link
https://www.codejava.net/java-core/exception/how-to-create-custom-exceptions-in-java
You can achieve this by creating a new presenter Class which contains List and status of type String and change the return type of getCompanies method to presenter class like
public CompaniesPresenter getCompanies()
And your CompaniesPresenter class should look like
public class CompaniesPresenter {
private List<CompanyMaster> companyMaster;
private string status;
//default constructor
public CompaniesPresenter(){
}
//parameterized constructor to return only string in exception case
public CompaniesPresenter(Stirng status){
this.status = status;
}
//parametirized constructor to return success case
public CompaniesPresenter(List<CompanyMaster> companyMaster, Stirng status){
this.companyMaster = companyMaster;
this.status = status;
}
//getters and setters
}
This is how your updated method lokks like
#RequestMapping(value = "/getcompanies", method = RequestMethod.POST)
public CompaniesPresenter getCompanies(#RequestBody UserDetails user) {
String OrgLoginId = user.getOrgLoginId();
String password = user.getuPassword();
String checkLoginId = null;
String uPassword = null;
String encPassword = null;
String loginId = null;
String checkAuthorized = null;
// String loginId=userService.getLoginId(OrgLoginId);
List<Object[]> CheckIdPassword = userService.checkLoginId(OrgLoginId);
List<Object[]> results = CheckIdPassword;
for (Object[] obj : results) {
checkLoginId = obj[0].toString();
if (null == obj[1]) {
uPassword = "";
} else {
uPassword = obj[1].toString();
}
loginId = obj[2].toString();
}
checkAuthorized = loginId.substring(0, 3);
if (null != password) {
MD5 md5 = new MD5();
encPassword = md5.getPassword(password);
}
if (checkLoginId == null) {
return new CompaniesPresenter("Incorrect loginId..Please enter valid loginId");
} else if (encPassword.equals(uPassword)) {
if (checkAuthorized.equals("STE")) {
List<CompanyMaster> companyList = userService.getCompanyList(OrgLoginId);
return new CompaniesPresenter(companyList,"success");
} else {
return new CompaniesPresenter("You are not Authorized");
}
} else {
return new CompaniesPresenter("Incorrect Password");
}
This is not tested please make sure for any compilation errors
vavr's Either class would be a good choice.
The usage of custom exception is most reasonable solution. However, creating custom exception for just one case is not ideal always.
Another solution is to return empty List from your method, check if the List is empty in your servlet (or wherever you are invoking this method from), and show error message there.
It seems like you want to return multiple error messages for different cases. In this case, custom exception is recommended solution. If you don't like custom exceptions, you can return List<Object> and populate error message as the first element in the list. In the place where this List is obtained, check if the first element is instanceOf String or CompanyMaster. Based on what it is, you can perform your operations. This is a weird but possible solution (only if you don't like custom exceptions).
You need to understand the problem first. You are mixing two things here, first authorization, does the user has correct privileges to get company details, second giving the company details itself. Let's understand the first problem when a user tries to access "/getcompanies" endpoint will you let him in if does not have access, in REST world your security model should take care of it. I would use spring security to achieve this. My recommendation would be to explore on "interceptor" and solve the problem of invalid user. This will make your other problem easy as your "/getcompanies" endpoint can focus only on getting the details and return it (SRP).

How can I update custom properties in alfresco workflow task using only Java?

First, I want to say thanks to everyone that took their time to help me figure this out because I was searching for more than a week for a solution to my problem. Here it is:
My goal is to start a custom workflow in Alfresco Community 5.2 and to set some custom properties in the first task trough a web script using only the Public Java API. My class is extending AbstractWebScript. Currently I have success with starting the workflow and setting properties like bpm:workflowDescription, but I'm not able to set my custom properties in the tasks.
Here is the code:
public class StartWorkflow extends AbstractWebScript {
/**
* The Alfresco Service Registry that gives access to all public content services in Alfresco.
*/
private ServiceRegistry serviceRegistry;
public void setServiceRegistry(ServiceRegistry serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
#Override
public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException {
// Create JSON object for the response
JSONObject obj = new JSONObject();
try {
// Check if parameter defName is present in the request
String wfDefFromReq = req.getParameter("defName");
if (wfDefFromReq == null) {
obj.put("resultCode", "1 (Error)");
obj.put("errorMessage", "Parameter defName not found.");
return;
}
// Get the WFL Service
WorkflowService workflowService = serviceRegistry.getWorkflowService();
// Build WFL Definition name
String wfDefName = "activiti$" + wfDefFromReq;
// Get WorkflowDefinition object
WorkflowDefinition wfDef = workflowService.getDefinitionByName(wfDefName);
// Check if such WorkflowDefinition exists
if (wfDef == null) {
obj.put("resultCode", "1 (Error)");
obj.put("errorMessage", "No workflow definition found for defName = " + wfDefName);
return;
}
// Get parameters from the request
Content reqContent = req.getContent();
if (reqContent == null) {
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Missing request body.");
}
String content;
content = reqContent.getContent();
if (content.isEmpty()) {
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Content is empty");
}
JSONTokener jsonTokener = new JSONTokener(content);
JSONObject json = new JSONObject(jsonTokener);
// Set the workflow description
Map<QName, Serializable> params = new HashMap();
params.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "Workflow started from JAVA API");
// Start the workflow
WorkflowPath wfPath = workflowService.startWorkflow(wfDef.getId(), params);
// Get params from the POST request
Map<QName, Serializable> reqParams = new HashMap();
Iterator<String> i = json.keys();
while (i.hasNext()) {
String paramName = i.next();
QName qName = QName.createQName(paramName);
String value = json.getString(qName.getLocalName());
reqParams.put(qName, value);
}
// Try to update the task properties
// Get the next active task which contains the properties to update
WorkflowTask wfTask = workflowService.getTasksForWorkflowPath(wfPath.getId()).get(0);
// Update properties
WorkflowTask updatedTask = workflowService.updateTask(wfTask.getId(), reqParams, null, null);
obj.put("resultCode", "0 (Success)");
obj.put("workflowId", wfPath.getId());
} catch (JSONException e) {
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
e.getLocalizedMessage());
} catch (IOException ioe) {
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"Error when parsing the request.",
ioe);
} finally {
// build a JSON string and send it back
String jsonString = obj.toString();
res.getWriter().write(jsonString);
}
}
}
Here is how I call the webscript:
curl -v -uadmin:admin -X POST -d #postParams.json localhost:8080/alfresco/s/workflow/startJava?defName=nameOfTheWFLDefinition -H "Content-Type:application/json"
In postParams.json file I have the required pairs for property/value which I need to update:
{
"cmprop:propOne" : "Value 1",
"cmprop:propTwo" : "Value 2",
"cmprop:propThree" : "Value 3"
}
The workflow is started, bpm:workflowDescription is set correctly, but the properties in the task are not visible to be set.
I made a JS script which I call when the workflow is started:
execution.setVariable('bpm_workflowDescription', 'Some String ' + execution.getVariable('cmprop:propOne'));
And actually the value for cmprop:propOne is used and the description is properly updated - which means that those properties are updated somewhere (on execution level maybe?) but I cannot figure out why they are not visible when I open the task.
I had success with starting the workflow and updating the properties using the JavaScript API with:
if (wfdef) {
// Get the params
wfparams = {};
if (jsonRequest) {
for ( var prop in jsonRequest) {
wfparams[prop] = jsonRequest[prop];
}
}
wfpackage = workflow.createPackage();
wfpath = wfdef.startWorkflow(wfpackage, wfparams);
The problem is that I only want to use the public Java API, please help.
Thanks!
Do you set your variables locally in your tasks? From what I see, it seems that you define your variables at the execution level, but not at the state level. If you take a look at the ootb adhoc.bpmn20.xml file (https://github.com/Activiti/Activiti-Designer/blob/master/org.activiti.designer.eclipse/src/main/resources/templates/adhoc.bpmn20.xml), you can notice an event listener that sets the variable locally:
<extensionElements>
<activiti:taskListener event="create" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
<activiti:field name="script">
<activiti:string>
if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate);
if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority;
</activiti:string>
</activiti:field>
</activiti:taskListener>
</extensionElements>
Usually, I just try to import all tasks for my custom model prefix. So for you, it should look like that:
import java.util.Set;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.DelegateTask;
import org.apache.log4j.Logger;
public class ImportVariables extends AbstractTaskListener {
private Logger logger = Logger.getLogger(ImportVariables.class);
#Override
public void notify(DelegateTask task) {
logger.debug("Inside ImportVariables.notify()");
logger.debug("Task ID:" + task.getId());
logger.debug("Task name:" + task.getName());
logger.debug("Task proc ID:" + task.getProcessInstanceId());
logger.debug("Task def key:" + task.getTaskDefinitionKey());
DelegateExecution execution = task.getExecution();
Set<String> executionVariables = execution.getVariableNamesLocal();
for (String variableName : executionVariables) {
// If the variable starts by "cmprop_"
if (variableName.startsWith("cmprop_")) {
// Publish it at the task level
task.setVariableLocal(variableName, execution.getVariableLocal(variableName));
}
}
}
}

Using AWS Java's SDKs, how can I terminate the CloudFormation stack of the current instance?

Uses on-line decomentation I come up with the following code to terminate the current EC2 Instance:
public class Ec2Utility {
static private final String LOCAL_META_DATA_ENDPOINT = "http://169.254.169.254/latest/meta-data/";
static private final String LOCAL_INSTANCE_ID_SERVICE = "instance-id";
static public void terminateMe() throws Exception {
TerminateInstancesRequest terminateRequest = new TerminateInstancesRequest().withInstanceIds(getInstanceId());
AmazonEC2 ec2 = new AmazonEC2Client();
ec2.terminateInstances(terminateRequest);
}
static public String getInstanceId() throws Exception {
//SimpleRestClient, is an internal wrapper on http client.
SimpleRestClient client = new SimpleRestClient(LOCAL_META_DATA_ENDPOINT);
HttpResponse response = client.makeRequest(METHOD.GET, LOCAL_INSTANCE_ID_SERVICE);
return IOUtils.toString(response.getEntity().getContent(), "UTF-8");
}
}
My issue is that my EC2 instance is under an AutoScalingGroup which is under a CloudFormationStack, that is because of my organisation deployment standards though this single EC2 is all there is there for this feature.
So, I want to terminate the entire CloudFormationStack from the JavaSDK, keep in mind, I don't have the CloudFormation Stack Name in advance as I didn't have the EC2 Instance Id so I will have to get it from the code using the API calls.
How can I do that, if I can do it?
you should be able to use the deleteStack method from cloud formation sdk
DeleteStackRequest request = new DeleteStackRequest();
request.setStackName(<stack_name_to_be_deleted>);
AmazonCloudFormationClient client = new AmazonCloudFormationClient (<credentials>);
client.deleteStack(request);
If you don't have the stack name, you should be able to retrieve from the Tag of your instance
DescribeInstancesRequest request =new DescribeInstancesRequest();
request.setInstanceIds(instancesList);
DescribeInstancesResult disresult = ec2.describeInstances(request);
List <Reservation> list = disresult.getReservations();
for (Reservation res:list){
List <Instance> instancelist = res.getInstances();
for (Instance instance:instancelist){
List <Tag> tags = instance.getTags();
for (Tag tag:tags){
if (tag.getKey().equals("aws:cloudformation:stack-name")) {
tag.getValue(); // name of the stack
}
}
At the end I've achieved the desired behaviour using the set of the following util functions I wrote:
/**
* Delete the CloudFormationStack with the given name.
*
* #param stackName
* #throws Exception
*/
static public void deleteCloudFormationStack(String stackName) throws Exception {
AmazonCloudFormationClient client = new AmazonCloudFormationClient();
DeleteStackRequest deleteStackRequest = new DeleteStackRequest().withStackName("");
client.deleteStack(deleteStackRequest);
}
static public String getCloudFormationStackName() throws Exception {
AmazonEC2 ec2 = new AmazonEC2Client();
String instanceId = getInstanceId();
List<Tag> tags = getEc2Tags(ec2, instanceId);
for (Tag t : tags) {
if (t.getKey().equalsIgnoreCase(TAG_KEY_STACK_NAME)) {
return t.getValue();
}
}
throw new Exception("Couldn't find stack name for instanceId:" + instanceId);
}
static private List<Tag> getEc2Tags(AmazonEC2 ec2, String instanceId) throws Exception {
DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest().withInstanceIds(instanceId);
DescribeInstancesResult describeInstances = ec2.describeInstances(describeInstancesRequest);
List<Reservation> reservations = describeInstances.getReservations();
if (reservations.isEmpty()) {
throw new Exception("DescribeInstances didn't returned reservation for instanceId:" + instanceId);
}
List<Instance> instances = reservations.get(0).getInstances();
if (instances.isEmpty()) {
throw new Exception("DescribeInstances didn't returned instance for instanceId:" + instanceId);
}
return instances.get(0).getTags();
}
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// Example of usage from the code:
deleteCloudFormationStack(getCloudFormationStackName());
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Consuming Java Web Service In C#

I wrote a Bottom up web service in java. The service code is like the following:
#WebService(serviceName="CPService", name="CPService")
#SOAPBinding(style=SOAPBinding.Style.DOCUMENT, use=SOAPBinding.Use.LITERAL, parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)
public class CPService {
#WebMethod(operationName = "SendSms")
#WebResult(name="SendResult")
public SendResult sendSms(#WebParam(name = "smses") Sms[] smses){
SendResult result = new SendResult();
if(smses == null || smses.length == 0){
result.OperationResult = OperationResult.MaxSizeExceed.getValue();
return result;
}
result.OperationResult = OperationResult.Success.getValue();
SmsResult[] smsResults = new SmsResult[smses.length];
int i = 0;
for (Sms sms : smses) {
smsResults[i++] = new SmsResult(sms.Index);
}
result.SmsResults = smsResults;
return result;
}
}
#XmlRootElement(name="SmsResult")
public class SmsResult {
#XmlElement(required = true, name="Index")
public int Index;
#XmlElement(required = true, name="BrtelId")
public String BrtelId;
public SmsResult(){}
public SmsResult(int index){
this.Index = index;
this.BrtelId = UUID.randomUUID().toString();
}
}
Then I created a C# based client and I tried to call service. Service call was Ok but return values was null. After some try I understand that the problem was related to deference between array serialization in C# and Java.
I want to know is there any way to handle WSDL generation process(for exmaple using XmlElementWrapper decorator)?

ICEFaces AJAX redirect to 404

EDIT: This occurs during any component AJAX call.
I am building a web application using ICEFaces 3.2.0 community along with Spring Security 3.2 Everything has been going very well up until a few days ago. I have an ACE AutoCompleteEntry component in the page with a backing bean attached to the value as the following example:
<ace:autoCompleteEntry id="autoCompleteState"
label="State"
labelPosition="top"
value="#{autoCompleteEntry.selectedText}"
rows="10" width="160"
filterMatchMode="startsWith">
<f:selectItems value="#{autoCompleteEntry.states}"/>
</ace:autoCompleteEntry>
The backing bean attached is as follows:
#ManagedBean(name=AutoCompleteEntry.BEAN_NAME)
#SessionScoped
public class AutoCompleteEntry implements Serializable {
public static final String BEAN_NAME = "autoCompleteEntry";
public static final String STATE_FILENAME = "State_Names.txt";
public static final String RESOURCE_PATH = "/resources/selectinputtext/";
public AutoCompleteEntry() {
}
public List<SelectItem> states;
public List<SelectItem> getStates() {
if(states == null) {
states = new ArrayList<SelectItem>();
for(String state : readStateFile()) {
states.add(new SelectItem(state));
}
}
return states;
}
private String selectedText = null;
public String getSelectedText() {return selectedText;}
public void setSelectedText(String selectedText) {this.selectedText = selectedText;}
private static List<String> readStateFile() {
InputStream fileIn = null;
BufferedReader in = null;
try {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
fileIn = ec.getResourceAsStream(AutoCompleteEntry.RESOURCE_PATH + STATE_FILENAME);
if(fileIn != null) {
in = new BufferedReader(new InputStreamReader(fileIn));
List<String> loadedStates = new ArrayList<String>(53);
String read;
while((read = in.readLine()) != null) {
loadedStates.add(read);
}
return loadedStates;
}
}catch (IOException failedRead) {
failedRead.printStackTrace();
}finally {
try {
if(in != null) {
in.close();
}
}catch (IOException failedClose) {
failedClose.printStackTrace();
}
}
List<String> errorReturn = new ArrayList<String>(1);
errorReturn.add("Error Loading State List");
return errorReturn;
}
}
The problem is that each time I attempt to test the component instead of bringing up a list of the States it redirects to an absolute path of my main page, which results in a 404. In developer tools I see an error of:
> Uncaught TypeError: Cannot read property 'value' of undefined
bridge.uncompressed.js.jsf:2701
namespace.onAfterUpdate.viewIDElement bridge.uncompressed.js.jsf:2701
apply bridge.uncompressed.js.jsf:122
(anonymous function) bridge.uncompressed.js.jsf:484
(anonymous function) bridge.uncompressed.js.jsf:363
(anonymous function) bridge.uncompressed.js.jsf:240
broadcast bridge.uncompressed.js.jsf:483
(anonymous function) bridge.uncompressed.js.jsf:1928
sendEvent jsf.js.jsf:1447
AjaxEngine.req.sendRequest jsf.js.jsf:1333
request jsf.js.jsf:1834
fullSubmit bridge.uncompressed.js.jsf:2309
submit bridge.uncompressed.js.jsf:2314
iceSubmit compat.uncompressed.js.jsf:1523
onclick
The developer tools log shows:
> [window] persisted focus for element "autoCompleteState_input"
bridge.uncompressed.js.jsf:1252
[window] full submit to localhost:8181/HHCA_Portal/pages/secure/HHCA.jsf
javax.faces.execute: #all
javax.faces.render: patientRecordsForm
javax.faces.source: autoCompleteState_input
view ID: v33tl98j
event type: unknown bridge.uncompressed.js.jsf:1252
XHR finished loading: "localhost:8181/HHCA_Portal/pages/secure/HHCA.jsf".
jsf.js.jsf:1334
AjaxEngine.req.sendRequest jsf.js.jsf:1334
request jsf.js.jsf:1834
fullSubmit bridge.uncompressed.js.jsf:2309
ice.ace.AjaxRequest ace-jquery.uncompressed.js.jsf:20854
ice.ace.ab ace-jquery.uncompressed.js.jsf:20779
ice.ace.Autocompleter.getUpdatedChoices autocompleteentry.js.jsf:695
ice.ace.Autocompleter.onObserverEvent autocompleteentry.js.jsf:637
(anonymous function)
I have spent many hours working on this and other issues, and I have run out of ideas. If someone has some kind of assistance I would really appreciate the help.
If you are using JSF 2 then you can add your own exception handler , this should be able to capture ajax requests.
<factory>
<exception-handler-factory>
test.MyExceptionHandlerFactory
</exception-handler-factory>
</factory>
see the examples here ,
http://balusc.blogspot.com/2012/03/full-ajax-exception-handler.html
http://wmarkito.wordpress.com/2012/04/05/adding-global-exception-handling-using-jsf-2-x-exceptionhandler/

Categories