This is the original method:
#GET
#Produces({"application/json"})
public Response getTermClouds(#Context SecurityContext secCtxt, #Context UriInfo ui)
{
return null
}
I want to copy this method but add a new String parameter,and the annotations of the new method is the same as before,like this:
#GET
#Produces({"application/json"})
public Response getTermClouds(#Context SecurityContext secCtxt, #Context UriInfo ui,String newParam)
{
return null
}
I use javassist to do it,i don`t want to add a "get" annotation and then add a "produces" annotation,because there may be many other annotations which are unkown.How to do it as a common way?
When You try adding a new parameter to a method , Javassist does not allow to add an extra parameter to an existing method,Instead of doing that, a new method receiving the extra parameter as well as the other parameters is added to the same class.
A copy of the the CtMethod object can be obtained by CtNewMethod.copy().
Try this to create a copy of Your previous method. And can you explain exactly what you want to be done with annotations?
I realize this is old now, but I ran into the same problem trying to add parameters to Spring web handler methods, and I've figured it out. You need to copy the attributes of the old class to the new one. You'll also likely want to remove them from the old one to prevent possible conflicts. The code looks like this:
//Create a new method with the same name as the old one
CtMethod mNew = CtNewMethod.copy(mOrig, curClass, null);
//Copy all attributes from the old method to the new one. This includes annotations
for(Object attribute: mOrig.getMethodInfo().getAttributes()) {
m.getMethodInfo().addAttribute((AttributeInfo)attribute);
}
//Remove the method and parameter annotations from the old method
mOrig.getMethodInfo().removeAttribute(AnnotationsAttribute.visibleTag);
mOrig.getMethodInfo().removeAttribute(ParameterAnnotationsAttribute.visibleTag);
//Add the new String parameter to the new method
m.addParameter(cp.getOrNull("java.lang.String"));
//Add a new empty annotation entry for the new parameter (not sure this part is necessary for you.
//In my case, I was adding a new parameter to the beginning, so the old ones needed to be offset.
ParameterAnnotationsAttribute paa = (ParameterAnnotationsAttribute)m.getMethodInfo().getAttribute(ParameterAnnotationsAttribute.visibleTag);
Annotation[][] oldAnnos = paa.getAnnotations();
Annotation[][] newAnnos = new Annotation[oldAnnos.length + 1][];
newAnnos[oldAnnos.length] = new Annotation[] {};
System.arraycopy(oldAnnos, 0, newAnnos, 0, oldAnnos.length);
paa.setAnnotations(newAnnos);
//Rename the old method and add the new one to the class
mOrig.setName(mOrig.getName() + "_replaced");
curClass.addMethod(m);
Related
I am writing a Unit Test for a class as follows:
#Test
void testCreateStackResources()
{
List<StackResource> stackResourceListExpected = new ArrayList<>();
StackResource stackResource = new StackResource();
stackResource.setLogicalResourceId("Sample-Logical-ID");
stackResourceListExpected.add(stackResource);
ListStackResourcesResult listStackResourcesResult = new ListStackResourcesResult();
StackResourceSummary stackResourceSummary = new StackResourceSummary();
stackResourceSummary.setLogicalResourceId("Sample-Logical-ID");
listStackResourcesResult.setStackResourceSummaries((Collection<StackResourceSummary>) stackResourceSummary); // Problem in this line
Mockito.when(amazonCloudFormation.listStackResources(Mockito.any(ListStackResourcesRequest.class))).thenReturn(listStackResourcesResult);
List<StackResource> stackResourceListResult = cloudFormationManager.createStackResources(Mockito.anyString());
Assert.assertEquals(stackResourceListExpected, stackResourceListResult);
}
Now, when I run this code, it gives me an error that I can't cast StackResourceSummary to a Collection in Java.
java.lang.ClassCastException: com.amazonaws.services.cloudformation.model.StackResourceSummary cannot be cast to java.util.Collection
On the other hand, if I make an array list before, add the object of StackResourceSummary to the list and then run the UT, it gives me the
objc[3648]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/bin/java (0x10d19c4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x10ea194e0). One of the two will be used. Which one is undefined.
This is very weird behaviour. I don't know why can't I cast this to a collection? Please help. Thanks!
PS: There is a seperate class called ListStackResourcesResult which has a setter as follows:
public void setStackResourceSummaries(java.util.Collection<StackResourceSummary> stackResourceSummaries) {
if (stackResourceSummaries == null) {
this.stackResourceSummaries = null;
return;
}
this.stackResourceSummaries = new com.amazonaws.internal.SdkInternalList<StackResourceSummary>(stackResourceSummaries);
}
And I am trying to use this method above.
That is because StackResourceSummary does not extend or implement anything related to a collection.
http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/cloudformation/model/StackResourceSummary.html
What you need to to is create a collection and add your instance of StackResourceSummary to it. For example like so:
List<StackResourceSummary> stackResourceSummaries = new ArrayList<StackResourceSummary>();
stackResourceSummaries.add(stackResourceSummary)
or maybe like so
Arrays.asList(stackResourceSummary)
or use a third party lib like guava collections.
Then you should use that collection as an argument.
listStackResourcesResult.setStackResourceSummaries(stackResourceSummaries); // Problem gone in this line
Background: In several Java frameworks like Spring there is the possibility to have methods that are called with injected parameter values. A good example is a controller action in Spring Web/MVC that receives a POST value and has a redirect attribute.
Example:
#RequestMapping(value = "/testform", method = RequestMethod.POST)
public ModelAndView testform(#RequestParam(value = "postvalue") String postvalue, RedirectAttributes attributes)
{
if(postvalue.equals("Test"))
{
// Do stuff with attributes
}
return new ModelAndView("addresses");
}
For example when I would like to use something similar in an own application (No Spring included available) - I end up with something like that (Hacked together):
Strign actionname = "mymethod";
Controller controller = new SampleController();
for(Method method : controller.getClass().getDeclaredMethods())
{
String name = method.getName();
if(name.equals(actionname))
{
int parametercount = method.getParameterCount();
if(parametercount == 0) // No parameter
{
ModelAndView view = (ModelAndView) method.invoke(controller);
// Do stuff
}
else if(parametercount == 1) // 1 String parameter
{
ModelAndView view = (ModelAndView) method.invoke(controller, new String("parameter1"));
// Do stuff
}
else if(parametercount == 2) // 2 String parameters
{
ModelAndView view = (ModelAndView) method.invoke(controller, new String("parameter1"), new String("parameter2"));
// Do stuff
}
else // Error
{
// Unsupported method
}
break;
}
}
Problem: This solution only supports void, 1-parameter and 2-parameter methods that take a string as argument - nothing else
Question: How does Java and Spring allow such a feature? Does Spring have a huge array of method.invoke(...) that are suitable for the most common methods or is there a more dynamic solution to this problem?
Final solution: I ended up with this (unfinished) solution based on Seelenvirtuose answer:
else if(parametercount == 2)
{
Object[] parameters = new Object[2];
parameters[0] = new String("Hello");
parameters[1] = new String("world!");
method.invoke(controller, parameters);
}
Aside from any injection dependency frameworks in general (and Spring specifically), you seem to ask how to reflectively call methods with an arbitrary number of parameters.
As you can see in the invoke method's signature, you provide all parameters in an array. So you simply should assemble an argument array and provide that:
Object[] arguments = createArguments(parametercount); // create array of correct size
(ModelAndView) method.invoke(controller, arguments);
Note, that varargs are treated like an arry. Oh, and please respect the comments about string behaviors.
In principle Spring does the same thing, just more sophisticated.
Especially they don't look for names (at least for many things) but for annotations. You can get the annotations of classes, methods, fields and so on.
The other thing they'll use is that invoke takes an vararg, which is basically an array, so instead of having one if branch for each number of parameters, they pass just an array with the correct number of elements.
I am using the below code to do the authorizatin checks.
PDPrincipal whoIsit = new PDPrincipal(userId,configURL);
PDPermission whatTheyWant = new PDPermission(objectSpaceName,"TbvA");
boolean haveAccess = whoIsit.implies(whatTheyWant);
However the implies method on com.tivoli.mts.PDPrincipal has been deprecated and has been replaced by implies method from the new PdPrincipal class from different package.
com.tivoli.pd.jazn.PDPrincipal
the new method is as follows.
public boolean implies(javax.security.auth.Subject subject)
the new method takes a Subject.
Can you please let me know how can I change my code to use the new method? How do i construct the Subject or can i get the Subject from somewhere?
Thanks,
Rohit
I was able to work out a solution for this hence sharing it here so that anyone else facing the same issue can use this code.
I found that the new com.tivoli.pd.jazn.PDPermission class has a method implies which takes in a PdAuthorization context and a com.tivoli.pd.jazn.PDPrincipal object which does the same authorization checks that the previous class com.tivoli.mts.PDPrincipal use to do.
Mentioned below is how the same authorization can be done. With this code you need not implement the JAAS code.
First construct the PdAuthorizationContext as shown below. Make sure to define a static PdAuthorizationContext object so that it can be reused untill you close it. Constructing PDAuthorizationContext for every authorization check is resource intensive and not recommended. close the context at the end of your logic
URL configURL = new URL("file:" + String locationToTamConfigFile);
PDAuthorizationContext pdAuthCtx = new PDAuthorizationContext(configURL);
Next Construct the new PDPrincipal and the PdPermission objects as shown below and call the implies method
com.tivoli.pd.jazn.PDPrincipal pdPrincipal = new com.tivoli.pd.jazn.PDPrincipal(pdAuthCtx,userId);
com.tivoli.pd.jazn.PDPermission pdPermission = new com.tivoli.pd.jazn.PDPermission(objectSpaceName,"TbvA");
boolean newimpliesTry = pdPermission.implies(pdAuthCtx,pdPrincipal);
With Deadbolt's module we can check the restrictedResource with a ressource name and parameters in the view.
For example in my view, I have it, and it works well:
#{deadbolt.restrictedResource resourceKeys:['Domain'] , resourceParameters:['domainid':domain.id]}
<li>${domain.title}</li>
#{/deadbolt.restrictedResource}
But in my controller, I just can check the ressource name but I don't find a way to check it in my RestrictedResourcesHandler passing the domainid with.
I am looking for a solution to do something like that:
#RestrictedResource(name = {"Domain"}, params = {domainid})
public static void showDomain(String domainid)
{
}
Thanks in advance
It's not possible to have dynamic information in an annotation, but you can use params to define the name of an incoming value in the request. However, this information isn't passed into the handler at the moment because it expects a map. While you can pass in a map of parameters from the restrictedResource tag, you can't do this from an annotation so an empty map is passed into the handler.
Your best approach here is to pull a well-known parameter name from the request object. I need to have a rethink about the best way to do this without breaking backwards compatibility.
Steve (author of Deadbolt)
I've found a way the solved the problem, not the best I think, but it is the Steve Chaloner's solution (Deadbolt's creator), and it works.
For example, if your Controller's method argument is named "id", and you want to check this id inside your checkAccess method :
// Controller's method :
#RestrictedResource(name = {"Domain"})
public static void showDomain(String id){}
Just check at the beginning of your checkAccess method the Map "resourceParameters" is empty, and use the request object to get the parameters:
public AccessResult checkAccess(List<String> resourceNames,
Map<String, String> resourceParameters)
{
Map<String, String> hashm = new HashMap<String,String>();
if(resourceParameters != null && !resourceParameters.isEmpty()){
hashm = resourceParameters;
}else if(Http.Request.current().routeArgs!= null && !Http.Request.current().routeArgs.isEmpty()){
hashm = Http.Request.current().routeArgs;
}
}
Then just have to foreach your hashmap inside your checkAccess method to get your Controller's method argument and check the Access as you wish.
for (Map.Entry<String,String> mp : hashm.entrySet())
{
// Get the id argument
if(mp.getKey().equals("id"))
{
// Do something with the value..
mp.getValue()
}
}
In my client I would like to specify strategies to be used in the class produced by builder.
However, I can't pass these strategy objects to builder, because their initialization is partially handled by builder. Yet, I still have to communicate builder which object to use and which additional parameters to pass it.
Here's Builder class
public class MarketGeneratorBuilder {
private MarketGenerator.Parameters parameters;
public MarketGeneratorBuilder(MarketGenerator.Parameters parameters) {
this.parameters = parameters;
}
public MarketGenerator build() {
return new MarketGenerator(
parameters,
new GoodsGenerator(
new UniformDistribution(
new ValueRange(0,parameters.getNumberOfLevels()-1)
)
),
new ITGenerator(),
new OTGenerator(),
new IOTGenerator(
new UniformDistribution(
new ValueRange(1,parameters.getNumberOfLevels()-2)
),
new BundlesGenerator(
new ForwardMarkovDistribution(
new MarkovDistribution.Parameters(
new ValueRange(0,parameters.getNumberOfLevels()-1),
0.1,
0.1
)
),
new UniformDistribution(
new ValueRange(1,parameters.getNumberOfGoodsToCreate()-1)
)
),
new BundlesGenerator(
new BackwardMarkovDistribution(
new MarkovDistribution.Parameters(
new ValueRange(0,parameters.getNumberOfLevels()-1),
0.2,
0.2
)
),
new UniformDistribution(
new ValueRange(1,parameters.getNumberOfGoodsToCreate()-1)
)
)
)
);
}
}
Distributions (UniformDistribution, ForwardMarkovDistribution, ForwardMarkovDistribution and potentially more to come) are hardcoded now, but should be chosen by client. The value range is defined by Builder (the ValueRange objects). Yet, some distributions take additional parameters (ForwardMarkovDistribution takes ValueRange, alpha, beta), which should be defined by client.
The only solution I can see is to pull ValueRange from constructor into setter. But it seems wrong as its an essencial field for the object. Also, I would like client code to not contain internal logic of the builder. More like,
DistributionType levelDistribution = new DistributionType(Distributions.UNIFORM);
DistributionType goodsDistribution = new DistributionsType(Distributions.MARKOV_FORWARD, 0.1,0.1);
But in this case I dont understand how to enforce data integrity. Meaning that when the client chooses Distributions.UNIFORM there are no more parameters to pass. Or in case of Distributions.MARKOV_FORWARD he has to pass alpha and beta.
So, I was hoping you could point me to a better pattern. Thanks!
For full flexibility, add a provider/factory for each of the distributions:
interface DistributionProvider<D extends Distribution> {
D create(ValueRange vr);
}
class UniformDistributionProvider implements DistributionProvider<UniformDistribution> {
UniformDistribution create(ValueRange vr) {
return new UniformDistribution(vr);
}
}
class ForwardMarkovDistributionProvider implements DistributionProvider<ForwardMarkovDistribution> {
private final MarkovDistribution.Parameters params;
ForwardMarkovDistributionProvider(MarkovDistribution.Parameters pParams) {
params = pParams;
}
ForwardMarkovDistribution create(ValueRange vr) {
return new ForwardMarkovDistribution(vr, params);
}
}
// etc.
(Remove the ValueRange from MarkovDistribution.Parameters.)
Basically, each of the providers stores all necessary parameters for the distribution, except the ValueRange.
Then clients can instantiate the provider for the distribution they want, configuring it with the appropriate values.
The builder would get those instances of DistributionProvider and use these to create the distributions, passing a ValueRange.
I see a couple of different patterns here.
You could have a couple of different build methods. buildUniform() and buildMarkov(float alpha, float beta).
You could have a couple of different builder classes: MarketGeneratorMarkovBuilder. They could have a base class with a protected build method. [Not sure I like this one]
You could add the alpha and beta values to the parameters class and throw an exception if they aren't available for the MARKOV_FORWARD and throw exception if they are available for UNIFORM.
If the ValueRange is constant for the entire builder then I see no reason why, as you mention, you couldn't add it to the parameters or put a setter on the builder to inject it.
You might consider using dependency injection (in Spring fashion) for all of the constant sub classes. The builder could have setters for all of them instead of being hard coded.
Hope this helps.