Categories
discuss

Why is this Java generic method call ambiguous when only one method is valid when separate?

I am trying to understand why the compiler is unable to resolve the bar method call. I would expect bar(Xyz::new) to always select bar(Supplier) as bar(T extends Xyz) can never match due to the upper bound on Xyz.

public <T extends Xyz> void foo(T s) {}
public <T extends Xyz> void bar(T s) {}
public <T extends Xyz> void bar(Supplier<T> s) {}

public void example() {
    foo(Xyz::new); // not valid (does not extend Xyz)

    bar((Supplier<Xyz>) Xyz::new); // valid (explicitly a Supplier)
    bar(Xyz::new); // ambiguous - but only one method is valid?
}

public static class Xyz {}

If bar(T) is not applicable, even when alone (as shown with foo(T)), then surely the only option is bar(Supplier) making this a non-ambiguous overload.

Why is the bar call ambiguous, especially when the foo and bar(T) calls are not valid resolutions themselves?

Runnable example of above code: https://www.jdoodle.com/ia/kqP

Answer

You’re right that a smarter compiler should be able to resolve this unambiguously.

The way Java resolves method invocations is complex. It’s defined by the JLS, and I make it 7500 words purely to determine how to resolve a method. Pasted into a text editor, it was 15 pages.

The general approach is:

  1. Compile-Time Step 1: Determine Type to Search (no issue here)
  2. Compile-Time Step 2: Determine Method Signature
    1. Identify Potentially Applicable Methods
    2. Phase 1: Identify Matching Arity Methods Applicable by Strict Invocation
    3. Phase 2: Identify Matching Arity Methods Applicable by Loose Invocation
    4. Phase 3: Identify Methods Applicable by Variable Arity Invocation
    5. Choosing the Most Specific Method
    6. Method Invocation Type
  3. Compile-Time Step 3: Is the Chosen Method Appropriate?

I don’t understand anywhere close to all of the details and how it pertains to your specific case. If you care to dive into it then I’ve already linked the full spec. Hopefully this explanation is good enough for your purposes:

Ambiguousness is determined at step 2.6, but there is still a further appropriateness check at step 3. Your foo method must be failing at step 3. Your bar method never makes it that far because the compiler still considers both methods to be valid possibilities. A human can make the determination that the non-appropriateness resolves the ambiguity, but that’s not order the compiler does things. I could only speculate why – performance might be a factor.

Your code is operating at the intersection of generics, overloading and method references, all three of which were introduced at different times; it’s not massively surprising to me that the compiler would struggle.

Categories
discuss

How to access request headers on JAXRS classes generated by Swagger Codegen

I have a project with an Swagger API and its server code was generated by swagger-codegen-2.4.24 for language jaxrs.

The code generated has an abstract class suffixed “*ApiService” that defines a series of methods, each corresponding to each operation defined on the Swagger specification of the API.

Each method has a javax.ws.rs.core.SecurityContext interface local variable.

Now, on my custom class which extends “*ApiService”, that obviously has javax.ws.rs.core.SecurityContext class local variable, I need to fetch the value of request header “X-Forwarded-For”.

If I debug my custom class I see that SecurityContext interface is an instance of org.glassfish.jersey.server.internal.process.SecurityContextInjectee, which has the header I need.

How do I get that information, since I’m not able to work with SecurityContextInjectee since it’s private?

I realize that if classes generated by swagger-codegen added javax.servlet.http.HttpServletRequest class, besides SecurityContext, it would be possible to have access to the request parameters, but I didn’t see any jaxrs parameter that allows that.

Looking forward for your comments.

Answer

In every specification version you can define a header like one of the possible parameter locations.

So, one possible solution, will be to define the header in the methods you required in the request parameters sections:

parameters:
    -
        name: X-Forwarded-For
        description: X-Formarwed-For header.
        schema:
            type: string
        in: header

Or, in JSON notation:

    "parameters": [
        {
            "name": "X-Forwarded-For",
            "description": "X-Formarwed-For header.",
            "schema": {
                "type": "string"
            },
            "in": "header"
        }
    ]

I am aware that perhaps it is a less maintainable solution because you will need to include the header in every request, but maybe you could mitigate that fact with inheritance in your services implementation.

There is an open Github issue asking for the behavior you described, handling the header processing in a general way.

One suitable option, suggested as well in this related SO answer, could be modifying the Mustache templates used in the API code generation and include within them the required headers processing. Please, be aware that this will do your code less maintainable and you will have the risk of perform some change that breaks the compatibility with the official Swagger Codegen repository. I am not sure in Swagger Codegen, but in the OpenAPI generator there is an option to override the used templates without modifying the actual provided in the official distribution. Please, see this related SO question.

Although it seems that is no longer the case, at least in older versions of Jersey in which the class was public, you could try accessing the requestContext internal variable in org.glassfish.jersey.server.internal.process.SecurityContextInjectee by reflection as well, although I think that workaround makes your application very implementation dependent. In any case, perhaps you could define an utility method like this that you could reuse in your services implementation:

public static String getXForwardedForHeaderValue(final SecurityContext securityContext) {
  SecurityContextInjectee securityContextImpl = (SecurityContextInjectee) securityContext;
  Field requestContextField = SecurityContextInjectee.class.getDeclaredField("requestContext");
  requestContextField.setAccessible(true);
  ContainerRequestContext requestContext = requestContextField.get(securityContextImpl);
  String xForwardedForHeaderValue = requestContext.getHeaderString("X-Forwarded-For");
  return xForwardedForHeaderValue;
}

Finally, another possibility could be using a filter that process your header. If required you could pass the header value using for instance a thread local variable to the underlying services. The idea would be something like the following.

First, define a convenient object that wraps your ThreadLocal value:

public class XForwardedForHeaderHolder{

    private static final ThreadLocal<String> value = new ThreadLocal<String>();

    public static void setXForwardedForHeader(String xForwardedFor) {
        value.set(xForwardedFor);
    }

    public static String getXForwardedForHeader() {
        return value.get();
    }

    public static void clean() {
        value.remove();
    }
}

Next, create a ContainerRequestFilter. This filter will read header from the information received in the HTTP request being processed:

import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;
 
@Provider
public class XForwardedForHeaderRequestFilter implements ContainerRequestFilter {
 
    @Override
    public void filter(ContainerRequestContext requestContext)
        throws IOException {
 
        String xForwardedForHeaderValue = requestContext.getHeaderString("X-Forwarded-For");
        XForwardedForHeaderHolder.setXForwardedForHeader(
            xForwardedForHeaderValue
        );
    }
}

Finally, consume the value in your services implementation:

String xForwardedForHeaderValue = XForwardedForHeaderHolder.getXForwardedForHeader();
// Clean up
XForwardedForHeaderHolder.clean();

A word of caution: on one hand, the filter registration should work properly but it could depend on the JAXRS version you are using and Swagger itself; on the other, the solution assume that the filter will provide, in the thread local variable, the right header for every request to the underlying services, in other words, that there are not any threading related issue. I think it should be the case, but it is something that need to be tested.

Categories
discuss

Draw a horizontal and vertical line on mouse hover in chart js

I am stuck with a problem on chart js while creating line chart. I want to create a chart with the specified data and also need to have horizontal and vertical line while I hover on intersection point. I am able to create vertical line on hover but can not find any solution where I can draw both the line. Here is my code to draw vertical line on hover.

    window.lineOnHover = function(){        
        Chart.defaults.LineWithLine = Chart.defaults.line;
        Chart.controllers.LineWithLine = Chart.controllers.line.extend({
        draw: function(ease) {
          Chart.controllers.line.prototype.draw.call(this, ease);

          if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
             var activePoint = this.chart.tooltip._active[0],
                 ctx = this.chart.ctx,
                 x = activePoint.tooltipPosition().x,
                 topY = this.chart.legend.bottom,
                 bottomY = this.chart.chartArea.bottom;

             // draw line
             ctx.save();
             ctx.beginPath();
             ctx.moveTo(x, topY);
             ctx.lineTo(x, bottomY);
             ctx.lineWidth = 1;
             ctx.setLineDash([3,3]);
             ctx.strokeStyle = '#FF4949';
             ctx.stroke();
             ctx.restore();
          }
        }
        });
    }


//create chart
var backhaul_wan_mos_chart = new Chart(backhaul_wan_mos_chart, {
    type: 'LineWithLine',
    data: {
        labels: ['Aug 1', 'Aug 2', 'Aug 3', 'Aug 4', 'Aug 5', 'Aug 6', 'Aug 7', 'Aug 8'],
        datasets: [{
                label: 'Series 1',
                data: [15, 16, 17, 18, 16, 18, 17, 14, 19, 16, 15, 15, 17],
                pointRadius: 0,
                fill: false,
                borderDash: [3, 3],
                borderColor: '#0F1731',
//                    backgroundColor: '#FF9CE9',
//                    pointBackgroundColor: ['#FB7BDF'],
                borderWidth: 1
            }],
//                lineAtIndex: 2,
    },
    options: {
        tooltips: {
            intersect: false
        },
        legend: {
            display: false
        },
        scales: {
            xAxes: [{
                    gridLines: {
                        offsetGridLines: true
                    },
                    ticks: {
                        fontColor: '#878B98',
                        fontStyle: "600",
                        fontSize: 10,
                        fontFamily: "Poppins"
                    }
                }],
            yAxes: [{
                    display: true,
                    stacked: true,
                    ticks: {
                        min: 0,
                        max: 50,
                        stepSize: 10,
                        fontColor: '#878B98',
                        fontStyle: "500",
                        fontSize: 10,
                        fontFamily: "Poppins"
                    }
                }]
        },
        responsive: true,
    }
});

my output of the code is as follow in WAN MoS Score graphenter image description here

So I want to have an horizontal line with the same vertical line together when I hover on the intersection (plotted) point..

Please help my guys..Thanks in advance.

Answer

You can just add a second draw block for the y coordinate that you get from the tooltip, first you move to the left of the chartArea that you can get the same way you got bottom and top and then you move to the right on the same Y

Chart.defaults.LineWithLine = Chart.defaults.line;
Chart.controllers.LineWithLine = Chart.controllers.line.extend({
  draw: function(ease) {
    Chart.controllers.line.prototype.draw.call(this, ease);

    if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
      var activePoint = this.chart.tooltip._active[0],
        ctx = this.chart.ctx,
        x = activePoint.tooltipPosition().x,
        y = activePoint.tooltipPosition().y,
        topY = this.chart.legend.bottom,
        bottomY = this.chart.chartArea.bottom,
        left = this.chart.chartArea.left,
        right = this.chart.chartArea.right;


      // Set line opts
      ctx.save();
      ctx.lineWidth = 1;
      ctx.setLineDash([3, 3]);
      ctx.strokeStyle = '#FF4949';

      // draw vertical line      
      ctx.beginPath();
      ctx.moveTo(x, topY);
      ctx.lineTo(x, bottomY);
      ctx.stroke();

      // Draw horizontal line
      ctx.beginPath();
      ctx.moveTo(left, y);
      ctx.lineTo(right, y);
      ctx.stroke();

      ctx.restore();
    }
  }
});

var options = {
  type: 'LineWithLine',
  data: {
    labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
    datasets: [{
        label: '# of Votes',
        data: [12, 19, 3, 5, 2, 3],
        borderWidth: 1
      },
      {
        label: '# of Points',
        data: [7, 11, 5, 8, 3, 7],
        borderWidth: 1
      }
    ]
  },
  options: {
  }
}

var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
  <canvas id="chartJSContainer" width="600" height="400"></canvas>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
</body>

Edit:

You should use a custom plugin for this since you dont draw everytime you move the cursor and you can enforce this by using a custom plugin:

const options = {
  type: 'line',
  data: {
    labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
    datasets: [{
        label: '# of Votes',
        data: [12, 19, 3, 5, 2, 3],
        borderWidth: 1
      },
      {
        label: '# of Points',
        data: [7, 11, 5, 8, 3, 7],
        borderWidth: 1
      }
    ]
  },
  options: {
    plugins: {
      corsair: {
        dash: [2, 2],
        color: 'red',
        width: 3
      }
    }
  },
  plugins: [{
    id: 'corsair',
    afterInit: (chart) => {
      chart.corsair = {
        x: 0,
        y: 0
      }
    },
    afterEvent: (chart, evt) => {
      const {
        chartArea: {
          top,
          bottom,
          left,
          right
        }
      } = chart;
      const {
        x,
        y
      } = evt;
      if (x < left || x > right || y < top || y > bottom) {
        chart.corsair = {
          x,
          y,
          draw: false
        }
        chart.draw();
        return;
      }

      chart.corsair = {
        x,
        y,
        draw: true
      }

      chart.draw();
    },
    afterDatasetsDraw: (chart, _, opts) => {
      const {
        ctx,
        chartArea: {
          top,
          bottom,
          left,
          right
        }
      } = chart;
      const {
        x,
        y,
        draw
      } = chart.corsair;

      if (!draw) {
        return;
      }

      ctx.lineWidth = opts.width || 0;
      ctx.setLineDash(opts.dash || []);
      ctx.strokeStyle = opts.color || 'black'

      ctx.save();
      ctx.beginPath();
      ctx.moveTo(x, bottom);
      ctx.lineTo(x, top);
      ctx.moveTo(left, y);
      ctx.lineTo(right, y);
      ctx.stroke();
      ctx.restore();
    }
  }]
}

const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
  <canvas id="chartJSContainer" width="600" height="400"></canvas>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
</body>
Categories
discuss

ESLINT: Prevent the use of double exclamation (!!)

What is the rule and configuration required to stop eslint (when using eslint --fix) from changing:

return regex.test(foo) ? true : false

into this:

return !!regex.test(postalCode)

While I understand what this rule is doing, I don’t like it. I thought it might be one of these — however, it’s either neither, or I just fail to understand how to configure them correctly.

// eslintrc.js

module.exports = {
  rules: {
    'no-implicit-coercion': [2, { string: false, boolean: false, number: false }],
    'no-extra-boolean-cast': [2, { enforceForLogicalOperands: true }],
  }
}

Answer

Looks like no-unneeded-ternary is converting this, as it is ultimately superfluous and can be written more cleanly and succinctly without a ternary. That said, as Salman A points out in the comments, it is somewhat curious that the fix itself is leveraging what is considered a bad practice of the !! Boolean casting.

Categories
discuss

Receiving 403 instead of 404 when calling non existing endpoint

This is a typical part of Spring Security configuration:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().and().cors().disable();
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    http.authorizeRequests().antMatchers("/login", "/api/v1/auth/**").permitAll();
    http.authorizeRequests().anyRequest().authenticated();
}

I have a problem with http.authorizeRequests().anyRequest().authenticated().

After adding it, when I call non-existing endpoints, for example: GET: /api/v1/not-existing, I receive 403 instead of expected 404 response.

I want to protect all my resources but I want to get 404 when calling not existing resources.

How can I fix it?

Answer

I am okay with this behaviour . If an user is not authenticated , why bother to worry about telling him more information about your system. Just like if an user does not have permission to view your harddisk , why need to let him can discover your harddisk directory tree structure .

If you really want to return 404 , you need to customize AuthenticationEntryPoint and AccessDeniedHandler in the ExceptionTranslationFilter . Both of them will be invoked if an user does not have enough permission to visit an endpoint (i.e. AccessDeniedException happen). The former is for the anonymous user and the latter is for the non-anonymous user (i.e. user that is authenticated successfully but without enough permission)

Both of their default implementation (i.e Http403ForbiddenEntryPoint and AccessDeniedHandlerImpl) simply return 403 now . You have to customize them such that they will first check if there are existing endpoints to serve the current HttpServletRequest and return 404 if no. You can do it by looping through the HandlerMapping inside the DispatcherServlet and check if any of the HandlerMapping can handle the current HttpServletRequest.

First create an object that do this check :

public class HttpRequestEndpointChecker {

    private DispatcherServlet servlet;

    public HttpRequestEndpointChecker(DispatcherServlet servlet) {
        this.servlet = servlet;
    }

    public boolean isEndpointExist(HttpServletRequest request) {

        for (HandlerMapping handlerMapping : servlet.getHandlerMappings()) {
            try {
                HandlerExecutionChain foundHandler = handlerMapping.getHandler(request);
                if (foundHandler != null) {
                    return true;
                }
            } catch (Exception e) {
                return false;
            }
        }
        return false;
    }
}   

Then customize AuthenticationEntryPoint and AccessDeniedHandler to use this object for checking :

public  class MyAccessDeniedHandler extends AccessDeniedHandlerImpl {
    private HttpRequestEndpointChecker endpointChecker;

    public MyAccessDeniedHandler(HttpRequestEndpointChecker endpointChecker) {
        this.endpointChecker = endpointChecker;
    }

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException accessDeniedException) throws IOException, ServletException {

        if (!endpointChecker.isEndpointExist(request)) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "Resource not found");
        } else {
            super.handle(request, response, accessDeniedException);
        }
    }
}
public class MyAuthenticationEntryPoint extends Http403ForbiddenEntryPoint {

        private HttpRequestEndpointChecker endpointChecker;

        public MyAuthenticationEntryPoint(HttpRequestEndpointChecker endpointChecker) {
            this.endpointChecker = endpointChecker;
        }

        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                AuthenticationException authException) throws IOException {
            if (!endpointChecker.isEndpointExist(request)) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND, "Resource not found");
            } else {
                super.commence(request, response, authException);
            }
        }
}

And configure them :

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DispatcherServlet dispatcherServlet;

    @Autowired
    private HttpRequestEndpointChecker endpointChecker;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
            ..............
            ..............
            http.exceptionHandling()
                .authenticationEntryPoint(new MyAuthenticationEntryPoint(endpointChecker))
                .accessDeniedHandler(new MyAccessDeniedHandler(endpointChecker));

    }

    @Bean
    public HttpRequestEndpointChecker endpointChecker() {
        return new HttpRequestEndpointChecker(dispatcherServlet);
    }

}
Source: stackoverflow
Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Privacy Policy, and Copyright Policy. Content is available under CC BY-SA 3.0 unless otherwise noted. The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 © No Copyrights, All Questions are retrived from public domain..