Skip to Main Content

Java EE (Java Enterprise Edition) General Discussion

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

Filters in RESTful Java

unknown-1175939Jul 26 2016 — edited Oct 21 2016

by Thamizharasu

Filters are one of the important features provided by the JAX-RS framework, and they are used in various contexts when RESTful web services are developed.

Filters are used to modify request and response entities, headers, and other parameters. In this article, we will explore different types of filters and different ways to use them. All the code examples are based on the Jersey framework.

Note: The source code for all the examples in this article is available on GitHub.

Types of Filters

Filters are classified under two major categories: server (container) filters and client filters. These two categories are further divided into request filters and response filters, as shown in Figure 1.

f1.jpg

Figure 1. Types of filters

Container Filters

The following sections describe container filters and an available annotation.

Container Response Filters

When we want to change the response header or modify some parameters after the resource method is executed, we can use a container response filter. For that, we have to provide an implementation of the ContainerResponseFilter interface, as shown in Listing 1.

public class RestSkolResponseFilter implements ContainerResponseFilter {

@Override

public void filter(ContainerRequestContext containerRequestContext,

        ContainerResponseContext containerResponseContext) throws IOException {

 containerResponseContext.getHeaders().add("X-Powered-By", "RESTSkol");

}

}

Listing 1. Example container response filter

This has to be registered as a provider in our resource configuration. In the example in Listing 1, we have injected a parameter called X-Powered-By in the response. (This example is inspired from the Jersey website). In this way, we can easily inject any header value that is common across all our responses.

Container Request Filters

A container request filter is useful when we want to enforce some kind of validation before executing the resource method. So a container request filter will act as a shield for our resource methods. We can enforce any kind of validation, and if the validation fails we can abort the resource execution at that point.

In the example in Listing 2, we are enforcing some kind of validation for the API key in our resource method call. If the expected API key is not available in the request header, then further call execution is stopped and a response is generated from this layer itself.

public class APIKeyCheckRequestFilter implements

ContainerRequestFilter { private static final String API\_KEY = "X-API-KEY";

@Override

public void filter(ContainerRequestContext containerRequestContext) throws

  IOException { final String apiKey =

  containerRequestContext.getHeaders().getFirst(API\_KEY); System.out.println("API KEY: " + apiKey);

  if (apiKey == null ||

       apiKey.isEmpty()) {

       containerRequestContext

             .abortWith(

               Response

                      .status(Response.Status.UNAUTHORIZED)

             .entity("Please provide a valid API Key")

             .build());

 }

}

}

Listing 2. Example container request filter

@PreMatching Annotation

By default, the container request filter implementation is executed after the appropriate request execution method is found. Here we will not have control over finding or choosing the required request method.

To achieve this behavior, we have the @PreMatching annotation for container request filters. If any container request filter is annotated with @PreMatching, then we can influence the request method. In the implementation shown in Listing 3, we are delegating the /books/v1 request to the /books/v2 resource. This might be useful when we want to deprecate any of the resource implementations.

@PreMatching

public class PreMatchingFilter implements ContainerRequestFilter {

@Override

public void filter(ContainerRequestContext containerRequestContext) throws

  IOException { final URI absolutePath =

  containerRequestContext.getUriInfo().getAbsolutePath(); String path =

  absolutePath.getPath();

  System.out.println("Path: " + path);

  if (path.contains("/books/v1")) {

   path = path.replace("/books/v1", "/books/v2");

 }

  System.out.println("After replaced Path: " + path);

 try {

   containerRequestContext.setRequestUri(new URI(path));

 } catch (URISyntaxException e) {

      e.printStackTrace();

 }

}

}

Listing 3. Example of using the @PreMatching annotation

Client Filters

Two variants of client filters are available: client request filters and client response filters. These filters are applicable only at the client API layer. This means that when we use the Jersey (JAX-RS) client API to call the REST endpoints, we can add these filters as part of the Client object.

Client Request Filters

This filter is the starting point of the filter workflow. If the client request filter is registered with the Client object, then this will be triggered first before the request has been sent to the server. So we can use this filter when we want to enforce some kind of validation at the client layer itself.

The purpose of the client request filter shown in Listing 4 is to validate when something is calling the PATCH method. We can enforce a validation to prevent such unsupported methods. If this call is not validated successfully, we can abort the request and the complete workflow will come to an end. The request will be passed to the server level once this validation is satisfied.

public class RestSkolClientRequestFilter implements ClientRequestFilter {

private static final Logger logger =

LogManager.getLogger(RestSkolClientRequestFilter.class);

@Override

public void filter(ClientRequestContext clientRequestContext) throws IOException {

  logger.info("Client request called");

  final String methodName =

  clientRequestContext.getMethod(); if

  (methodName.equals("PATCH")) {

  clientRequestContext.abortWith(          Response.status(Response.Status.METHOD\_NOT\_ALLOWED)

            .entity("HTTP Patch is NOT supported")

            .build());

 }

}

}

Listing 4. Example client request filter

Client Response Filters

A client response filter is the final step in the filter workflow. Once the execution is completed at the server level, the server layer will generate either a successful response or a failure response. This response will be passed to the client layer. A client response filter is the one that will be executed once the response is received from the server. We can validate or inject further entity data into the response at the client response filter level. Then, the response will be passed back to the caller.

How to Use Client Filters

As I already mentioned, client filters (both request and response) should be registered with the Client object, as shown in Listing 5.

final Client client = ClientBuilder.newBuilder()

 .register(RestSkolClientRequestFilter.class)

 .register(RestSkolClientResponseFilter.class)

 .build();

Listing 5. Example of registering client filters

Then these client filters will be enforced during the method call.

Filter Execution Workflow and Output

Figure 2 depicts the complete workflow or steps of the filter execution path.

f2.png

Figure 2. Steps in the filter execution path

The following output shows the execution order that occurs if all of the above filter implementation is in place. Check out the source code and run the project locally to see the following output locally. The output should exact match the order depicted in Figure 2.

[INFO ] 2016-04-12 23:29:38.212 [main] RestSkolClientRequestFilter - Client request called

[INFO ] 2016-04-12 23:29:38.509 [http-bio-8080-exec-1] PreMatchingFilter - Pre matching container request filter

[INFO ] 2016-04-12 23:29:38.519 [http-bio-8080-exec-1] APIKeyCheckRequestFilter - Container request filter

[INFO ] 2016-04-12 23:29:38.530 [http-bio-8080-exec-1] RestSkolResponseFilter - Container response filter

[INFO ] 2016-04-12 23:29:38.935 [main] RestSkolClientResponseFilter - Client response filter

Conclusion

The source code for all the examples in this article is available at https://github.com/cloudskol/restskol. Contact me if you need any further details. I will try to answer your questions as much as possible!

See Also

For more detailed information about filters, see the Jersey documentation.

About the Author

Thamizharasu is a Java developer living in Chennai, India. He is enthusiastic about programming and Java EE. Thamizharasu is also involved in JSR activities (JSONB) and currently is serving as a leader of Madras Java User Group. He evangelizes programming and frameworks on his blog and on Twitter.

Join the Conversation

Join the Java community conversation on Facebook, Twitter, and the Oracle Java Blog!

Comments

Frank Kulash

Hi, gg
Can some one please tell how can i pass multiple values to a bind parameter in a query having a equal to sign?
Sorry, a bind variable can only have one value at a time. That value can be a string, such as the 13-character string '100, 101, 102', but if you use if you want to use it in a SQL statement like

WHERE  dc_code IN (:p_pick_value)

it won't check if dc_code is any of the three numbers 100, 101 and 102; it will check if dc_code is the 13-character string. To treat that string as a comma-delimited list of numbers requires dynamic SQL.

BluShadow
Answer

undefined (0 Bytes)
Or the string get's passed as e.g. ',100,101,102,' and then the where clause could be...

WHERE :p_pick_value like '%,'||dc_code||',%'

but that does have it's limitations, such as lack of index usage, or limits on the size of the string (if many values are being passed).
Or, the input string needs to be split with SQL to separate values (various methods to do that, including a connect by statement) before joining to the results.

SQL> ed
Wrote file afiedt.buf

  1  select *
  2  from emp
  3  where empno in (
  4    with t as (select '&input_string' as txt from dual)
  5    select REGEXP_SUBSTR (txt, '[^,]+', 1, level)
  6    from t
  7    connect by level <= length(regexp_replace(txt,'[^,]*'))+1
  8*   )
SQL> /
Enter value for input_string: 7369,7844,7788
old   4:   with t as (select '&input_string' as txt from dual)
new   4:   with t as (select '7369,7844,7788' as txt from dual)


     EMPNO ENAME      JOB              MGR HIREDATE                    SAL       COMM     DEPTNO
---------- ---------- --------- ---------- -------------------- ---------- ---------- ----------
      7369 SMITH      CLERK           7902 17-DEC-1980 00:00:00        800                    20
      7844 TURNER     SALESMAN        7698 08-SEP-1981 00:00:00       1500          0         30
      7788 SCOTT      ANALYST         7566 19-APR-1987 00:00:00       3000                    20

Whichever way though, the SQL's would need changing to cater for it... no real way of getting the "=" to match multiple values.

Marked as Answer by ms · Feb 8 2022
ms

Thank you so much Frank and Blushadow.

User_H3J7U
SQL> alter session set sql_translation_profile=stp_test1;

Session altered.

SQL> var p_pick_value varchar2(1000 char)
SQL> exec :p_pick_value := '11,22,33'

PL/SQL procedure successfully completed.

SQL> with t(n) as (select level from dual connect by level<=30)
  2  select * from t where n = :p_pick_value;

         N
----------
        11
        22
create or replace package stp_test1 as
procedure translate_sql(sql_text       in clob,
                       translated_text out clob);
end stp_test1;
/

create or replace package body stp_test1 as
procedure translate_sql(sql_text       in clob,
                       translated_text out clob) is
  rex varchar2(512 byte) := '((([_#$[:alnum:]]+|"[^"]+")\.){0,2}([_#$[:alnum:]]+|"[^"]+"))\s*=\s*:p_pick_value(\s|$)';
begin
  if regexp_like(sql_text, rex, 'i') then
    translated_text := regexp_replace(sql_text, rex,
                                    '\1 in (select "." from xmltable(:p_pick_value columns "." number))',1,1,'i');
  end if;
end translate_sql;
end stp_test1;
/

begin
 dbms_sql_translator.create_profile('STP_TEST1');
 dbms_sql_translator.set_attribute ('STP_TEST1', 'FOREIGN_SQL_SYNTAX', 'FALSE');
 dbms_sql_translator.set_attribute ('STP_TEST1', 'RAISE_TRANSLATION_ERROR', 'TRUE');
 dbms_sql_translator.set_attribute ('STP_TEST1', 'TRANSLATOR', 'STP_TEST1');
end;
/

--exec dbms_sql_translator.drop_profile('STP_TEST1')
--drop package stp_test1;
1 - 4

Post Details

Added on Jul 26 2016
6 comments
53,527 views