1

I'm implementing a Java interface with a lot of methods with Object parameters, which in my case are really Strings containing user names:

public interface TwoFactorAuthProvider {
    boolean requiresTwoFactorAuth(Object principal);
    ... //many more methods with the same kind of parameter
}

I'm trying to use implicit conversion to convert these to User objects in my implementation:

class TwoFactorAuthProviderImpl(userRepository: UserRepository) 
    extends TwoFactorAuthProvider {

    def requiresTwoFactorAuth(user: User): Boolean = {
        ...
    }
}

When I define the conversion in the companion object of my class, it is picked up just fine and my class compiles:

object TwoFactorAuthProviderImpl {
    implicit def toUser(principal: Any): User = {
        null //TODO: do something useful
    }
}

However, to be able to do the conversion, I need access to the user repository, which the TwoFactorAuthProviderImpl instance has, but the companion object does not. I thought I could possibly use an implicit parameter to pass it:

implicit def toUser(principal: Any)(implicit repo: UserRepository): User = {
    val email = principal.asInstanceOf[String]
    repo.findByEmail(email)
}

But with the implicit parameter, the conversion is no longer picked up by the compiler (complaining that I'm not implementing the interface).

Is there a way to get the implicit conversion that I want, or is this outside the scope of what you can do with implicits?

Henning
  • 16,063
  • 3
  • 51
  • 65

2 Answers2

4

This should work just fine - can you supply the exact compilation error? Not implementing what interface? It looks like you would have to declare as follows:

class TwoFactorAuthProviderImpl(implicit userRepository: UserRepository) 

Here's an example for the REPL to show that implicits can have implicits; I'm using paste mode to ensure that module X is the companion object of the class X

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class X(i: Int, s: String)
object X { implicit def Int_Is_X(i: Int)(implicit s: String) = X(i, s) }

// Exiting paste mode, now interpreting.

defined class X
defined module X

scala> val i: X = 4
<console>:9: error: value X is not a member of object $iw
       val i: X = 4
                  ^

But if we add an implicit string in scope

scala> implicit val s = "Foo"
s: java.lang.String = Foo

scala> val i: X = 4
i: X = X(4,Foo)

Implicits advice

Don't go overboard with implicit conversions - I think you are going too far in this sense - the principal is implicitly a mechanism by which you can discover a user, it is not implicitly a user itself. I'd be tempted to do something like this instead:

implicit def Principal_Is_UserDiscoverable(p: String) = new {
  def findUser(implicit repo: UserRepository) = repo.findUser(p)
}

Then you can do "oxbow".findUser

oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • The compiler says "class TwoFactorAuthProviderImpl needs to be abstract, since: method requiresTwoFactorAuth in trait TwoFactorAuthProvider of type (x$1: Any)Boolean is not defined (Note that Any does not match com.ontopofthings.usermanagement.entities.User)". – Henning Feb 17 '12 at 11:27
  • `userRepository` should be picked up, as is a member of `TwoFactorAuthProviderImpl`, right? – Henning Feb 17 '12 at 11:29
  • It's not `implicit` though. If a parameter is marked as being `implicit`, then only things marked as `implicit` can be implicitly sent to it. As for the compiler error, unless you post a short piece of code demonstrating the exact problem *in isolation*, it's really difficult for us to tell. The answer to the question "Can implicits have implicits?" is a resounding "yes". The answer to "What is wrong with my code?" is "I'm not sure, you have not posted enough for me to tell" – oxbow_lakes Feb 17 '12 at 11:40
  • 1
    PS - note my edit. I think if you mark the `userRepository` parameter as `implicit`, it will get passed through into your implicit lookup as desired – oxbow_lakes Feb 17 '12 at 11:41
  • Thanks! Indeed I had to mark `userRepository` as implicit to have it passed to the conversion. Also, the parameters of my functions have to remain `principal: Any`, but thanks to the conversion I can then use them as though they were `User`s. I'll post a summary of the working code in my answer. – Henning Feb 17 '12 at 11:54
  • +1, is that a structurally typed dynamic finder you have there with the implicit @oxbow? Had no idea you could do that. – virtualeyes Feb 17 '12 at 13:04
1

Thanks to Oxbow's answer, I now have it working, this is only for reference.

First of all, a value that should be passed as an implicit must itself be marked implicit:

class TwoFactorAuthProviderImpl(implicit userRepository: UserRepository) ...

Second, implicit conversions are nice and all, but a method implementation signature must still match the signature of its declaration. So this does not compile, even though there is a conversion from Any to User:

def requiresTwoFactorAuth(principal: User): Boolean = { ... }

But leaving the parameter as Any, as in the declaration, and then using it as a user works just fine:

def requiresTwoFactorAuth(principal: Any): Boolean = {
    principal.getSettings().getPhoneUsedForTwoFactorAuthentication() != null
}

Also, the conversion really doesn't have to be in the companion object in this case, so in the end, I left the implicit parameters out.

The full source code:

class TwoFactorAuthProviderImpl(userRepository: UserRepository) 
    extends TwoFactorAuthProvider  {

    private implicit def toUser(principal: Any): User = {
        val email = principal.asInstanceOf[String]
        userRepository.findByEmail(email)
    }

    def requiresTwoFactorAuth(principal: Any): Boolean = {
        //using principal as a User
        principal.getSettings().getPhoneUsedForTwoFactorAuthentication() != null
    }

    ...
}
Community
  • 1
  • 1
Henning
  • 16,063
  • 3
  • 51
  • 65