How to prevent public methods from being called from specific classes
Asked Answered
C

11

20

I have an existing class into which I want to add a method. But I want the method to be called only from a specific method from a specific class. Is there any way that I can prevent that call from other classes/methods?

For example, I have an existing class A

public final class A
{
    //other stuff available for all classes/methods

    //I want to add a method that does its job only if called from a specific method of a class, for example:

    public void method()
    {
        //proceed if called from Class B.anotherMethod() else throw Exception
    }
}

One way of doing this is getting the StackTrace inside the method() and then confirming the parent method?

What I am looking for is a solution that is more clean and advisable solution like a pattern or something.

Chere answered 30/3, 2011 at 13:15 Comment(2)
You can try using method(thisobject) in your class B and extending the function in class A with a parameter, letting it check if thisobject is BMickey
StackTrace would work, but its a runtime solution for a design time problem.Tombolo
K
10

To be honest, you have painted yourself into a corner here.

If classes A and B are not related and not members of the same package, then visibility won't solve the problem. (And even if it did, reflection can be used to subvert the visibility rules.)

Static code analysis won't solve the problem if the code can use reflection to call the method.

Passing and checking B.this as an extra parameter to A.method(...) doesn't help because some other class C could pass a B instance.

This leaves only the stacktrace approach1... or giving up and relying on the good sense of the programmer2 not to call methods that they shouldn't.


The ideal solution is to revisit the design and/or coding decisions that got you into this mess.


1 - See other answers for examples that use annotations, a security manager, etc to conceal the stacktrace stuff from the application programmer. But note that under the hood you are adding probably hundreds, possibly thousands of instructions overhead per method call.

2 - Do not underestimate the programmer's good sense. Most programmers, when they see advice not to call some method, are likely to follow that advice.

Keeleykeelhaul answered 30/3, 2011 at 14:9 Comment(0)
A
7

The right way to do this would be a SecurityManager.

Define a permission which all code which wants to call A.method() has to have, and then make sure only B and A have that permission (this also means that no class has AllPermission).

In A, you check this with System.getSecurityManager().checkPermission(new BMethodPermission()), and in B you call the method inside of AccessController.doPrivileged(...).

Of course, this requires that a security manager is installed (and it uses suitable policies) - if it isn't, all code is trusted and everyone can call everything (if necessary, with Reflection).

Aronarondel answered 30/3, 2011 at 17:51 Comment(5)
You'd probably want a doPrivileged in there as well. What a mess.Devland
@Tom: I have a doPrivileged in there, I think.Fayalite
Oh, so you do. Still a mess (and requires modification of the public method, and bizarre privilege allocation).Devland
@Tom: Yes, you are right, this should not be done simply for modularity purposes, but only if the actual security concept calls for it.Fayalite
There is a follow-up question over at https://mcmap.net/q/663395/-java-securitymanager-how-to-ensure-a-method-is-run-only-by-another-method/14955 where people are politely asking for a code sample.Athalla
W
6

You might consider using an interface. If you're passing in the calling class, you can confirm that the class is of the appropriate type.

Alternatively, if you're using Java, you can use "default" or "package" level access (e.g. void method() vs. public void method()). This will allow your method to be called by any class inside the package and does not require that you pass the class to the method.

Woe answered 30/3, 2011 at 13:25 Comment(1)
I was thinking something along the lines of an interface as wellRolon
M
2

The only way to check for sure at run time is to take a stack trace. Even if its private you can access the method via reflections.

A simpler way to do this would be to check usages in your IDE. (provided its not called via reflections)

Mccarver answered 30/3, 2011 at 13:23 Comment(1)
+1 - You could also define a custom Annotation to express the restriction, and write a static analysis tool to check that the restriction is not violated. (But reflection can subvert this, of course.)Keeleykeelhaul
T
2

As others have mentioned, using the stack trace is one way to implement the functionality that you are looking for. Generally, if one needs to "block" callers from a public method, it could be a sign of poor design. As a rule of thumb, use access modifiers that restrict the scope as much as possible. However, making a method package-private or protected is not always an option. Sometimes, one may want to group some classes in a separate package. In that case, the default (package-private) access is too restrictive, and it usually does not make sense to subclass, so protected is not helpful either.

If restricting calling to certain classes is desired, you can create a method like:

public static void checkPermission(Class... expectedCallerClasses) {
    StackTraceElement callerTrace = Thread.currentThread().getStackTrace()[3];
    for (Class expectedClass : expectedCallerClasses) {
        if (callerTrace.getClassName().equals(expectedClass.getName())) {
            return;
        }
    }
    throw new RuntimeException("Bad caller.");
}

Using it is very simple: just specify what class(es) can call the method. For example,

public void stop() {
    checkPermission(ShutdownHandler.class);
    running = false;
}

So, if the stop method gets called by a class other than ShutdownHandler, checkPermission will throw an IllegalStateException.

You may wonder why checkPermission is hard-coded to use the fourth element of the stack trace. This is because Thread#getStackTrace() makes the most recently called method the first element. So,

  • getStackTrace()[0] would be the call to getStackTrace itself.
  • getStackTrace()[1] would be the call to checkPermission.
  • getStackTrace()[2] would be the call to stop.
  • getStackTrace()[3] would be the method that called stop. This is what we are interested in.

You mentioned that you want methods to be called from a specific class and method, but checkPermission only checks for class names. Adding the functionality to check for method names requires only a few modifications, so I'm going to leave that as an exercise.

Telesthesia answered 2/1, 2017 at 3:9 Comment(0)
B
1

Make proper use of protected

Bibulous answered 30/3, 2011 at 13:18 Comment(5)
If I make the method protected then I cannot use it because Class B is not a subclass of A. Also Class A is final.Lolland
@Swaranga Sarma - protected extends to classes in the same package.Splanchnic
You can design it such way.. other wise there are poor hack available using stacktraceBibulous
If you can restructure your package structure a bit, you can make use of "package-private" default visibility. See my answer.Tombolo
@birryree...my Class B aleady exists and it is in a different packageChere
T
1

The standard way to do this in java is to put Class B and Class A in the same package (maybe a subpackage of your current application) and use the default visibility.

The default java visibility is "package-private" which means everything in that package can see your method, but nothing outside that package can access it.

See Also:
Is there a way to simulate the C++ 'friend' concept in Java?

Tombolo answered 30/3, 2011 at 13:23 Comment(0)
S
1

Assuming that you only need to apply this restriction to classes within your project, static analysis could work for you - for example an ArchUnit test:

package net.openid.conformance.archunit;

import com.google.gson.JsonElement;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.AccessTarget;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import net.openid.conformance.testmodule.OIDFJSON;
import org.junit.Test;

import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo;
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.*;
import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner;
import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;

public class PreventGetAs {
    @Test
    public void doNotCallJsonElementGetAs() {
        JavaClasses importedClasses = new ClassFileImporter().importPackages("net.openid.conformance");

        JavaClasses allExceptOIDFJSON = importedClasses.that(DescribedPredicate.not(nameContaining("OIDFJSON")));

        ArchRule rule = noClasses().should().callMethodWhere(
            target(nameMatching("getAs[^J].*")) // ignores getAsJsonObject/getAsJsonPrimitive/etc which are fine
                .and(target(owner(assignableTo(JsonElement.class)))
        )).because("the getAs methods perform implicit conversions that might not be desirable - use OIDFJSON wrapper instead");

        rule.check(allExceptOIDFJSON);
    }
}
Spoor answered 20/12, 2020 at 17:28 Comment(0)
S
0

You can do it by using annotations and reflection. I will report a similar case, i.e. the case where you can let the method being called only by specific methods from extenal classes. Suppose that the class that must be "protected" by a whatsoever invocation of the its public methods is Invoked, while Invoker is the class tha has a method enabled to invoke one or more methods from Invoked. Then, you can do something like reported in the following.

public class Invoked{

  @Retention(RetentionPolicy.RUNTIME)
  @Target(ElementType.METHOD)
  public static @interface CanInvoke{} 


   public void methodToBeInvoked() {
    boolean canExecute=false;
    try {
        //get the caller class
        StackTraceElement element = (new Throwable()).getStackTrace()[1];
        String className = element.getClassName();
        Class<?> callerClass = Class.forName(className);
        //check if caller method is annotated
        for (Method m : callerClass.getDeclaredMethods()) {
            if (m.getName().equals(methodName)) {
                if(Objects.nonNull(m.getAnnotation(EnabledToMakeOperationRemoved.class))){
                    canExecute = true;
                    break;
                }
            }
        }

    } catch (SecurityException | ClassNotFoundException ex e) {
        //In my case does nothing
    }
    if(canExecute){
      //do something
    }
    else{
      //throw exception
    }
   }
}

and the Invoker class is

public class Invoker{
   private Invoked i;

   @Invoked.CanInvoke
   public void methodInvoker(){
     i.methodToBeInvoked();
   }

}

Note that the method that is enabled to invoke is annotated with the CanInvoke annotation.

The case that you requested is similar. You annotate the classes/method that cannot call the public method and then you set to true the canExecute variable only if the method/class is not annotated.

Schiller answered 15/5, 2014 at 15:53 Comment(0)
F
0

You can use a tool like Macker and add it to your build process to check some rules are respected, like

<?xml version="1.0"?>
<macker>    
    <ruleset name="Simple example">
        <access-rule>
            <deny>
                <from class="**Print*" />
                <to class="java.**" />
            </deny>
        </access-rule>
    </ruleset>
</macker>

It will NOT prevent you from writing wrong code but if you use Maven or another build system it can raise an error during your build process.

This tools work at a "class" level not at a "method" level but I do not see the point of preventing the call of only one method from a certain class ...

Ferino answered 8/9, 2015 at 15:58 Comment(0)
S
0

I realise your use case states 'specific method in specific class', but I don't think you can reliably solve this at design time (and I can't think of a use case where this would have to be enforced anyway).

The following example creates an easy design time solution for restricting the access of a class' method to a particular class. It can, however, be easily extended to multiple allowed classes.

It is achieved by defining a public inner class with a private constructor that acts as a key to the method at hand. In the following example the class Bar has a method that should only be called from an instance of the Foo class.

Class Foo:

public class Foo
{
    public Foo()
    {   
        Bar bar = new Bar();
        bar.method(new FooPrivateKey());
    }

    public class FooPrivateKey
    {
        private FooPrivateKey()
        {   }
    }  
}

Class Bar:

public class Bar
{
    public Bar()
    {

    }

    public void method(FooPrivateKey fooPrivateKey)
    {
        if(fooPrivateKey == null)
        {   throw new IllegalArgumentException("This method should only be called from the Foo class.");}

        //Do originally intended work.
    }
}

I don't think this is by any means safe for things like reflection or even things like FooPrivateKey.class.newInstance(), but this will at least warn the programmer a little more obtrusively than a simple comment or documentation, while you don't have to look in to more complicated things like what was suggested by people like Roberto Trunfio and Ronan Quillevere (which are perfectly viable answers as well, just too complicated for most situations in my opinion).

I hope this is sufficient for your use case.

Solid answered 16/6, 2016 at 12:56 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.