blob: 63a64fcd17b3221bf35a9ec3567280955b92def6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.igfs;
import java.util.concurrent.Callable;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.lang.IgniteOutClosure;
import org.jetbrains.annotations.Nullable;
/**
* Provides ability to execute IGFS code in a context of a specific user.
*/
public abstract class IgfsUserContext {
/** Thread local to hold the current user context. */
private static final ThreadLocal<String> userStackThreadLocal = new ThreadLocal<>();
/**
* Executes given callable in the given user context.
* The main contract of this method is that {@link #currentUser()} method invoked
* inside closure always returns 'user' this callable executed with.
* @param user the user name to invoke closure on behalf of.
* @param c the closure to execute
* @param <T> The type of closure result.
* @return the result of closure execution.
* @throws IllegalArgumentException if user name is null or empty String or if the closure is null.
*/
public static <T> T doAs(String user, final IgniteOutClosure<T> c) {
if (F.isEmpty(user))
throw new IllegalArgumentException("Failed to use null or empty user name.");
final String ctxUser = userStackThreadLocal.get();
if (F.eq(ctxUser, user))
return c.apply(); // correct context is already there
userStackThreadLocal.set(user);
try {
return c.apply();
}
finally {
userStackThreadLocal.set(ctxUser);
}
}
/**
* Same contract that {@link #doAs(String, IgniteOutClosure)} has, but accepts
* callable that throws checked Exception.
* The Exception is not ever wrapped anyhow.
* If your Callable throws Some specific checked Exceptions, the recommended usage pattern is:
* <pre name="code" class="java">
* public Foo myOperation() throws MyCheckedException1, MyCheckedException2 {
* try {
* return IgfsUserContext.doAs(user, new Callable<Foo>() {
* &#64;Override public Foo call() throws MyCheckedException1, MyCheckedException2 {
* return makeSomeFoo(); // do the job
* }
* });
* }
* catch (MyCheckedException1 | MyCheckedException2 | RuntimeException | Error e) {
* throw e;
* }
* catch (Exception e) {
* throw new AssertionError("Must never go there.");
* }
* }
* </pre>
* @param user the user name to invoke closure on behalf of.
* @param c the Callable to execute
* @param <T> The type of callable result.
* @return the result of closure execution.
* @throws IllegalArgumentException if user name is null or empty String or if the closure is null.
*/
public static <T> T doAs(String user, final Callable<T> c) throws Exception {
if (F.isEmpty(user))
throw new IllegalArgumentException("Failed to use null or empty user name.");
final String ctxUser = userStackThreadLocal.get();
if (F.eq(ctxUser, user))
return c.call(); // correct context is already there
userStackThreadLocal.set(user);
try {
return c.call();
}
finally {
userStackThreadLocal.set(ctxUser);
}
}
/**
* Gets the current context user.
* If this method is invoked outside of any {@link #doAs(String, IgniteOutClosure)} on the call stack, it will
* return null. Otherwise it will return the user name set in the most lower
* {@link #doAs(String, IgniteOutClosure)} call on the call stack.
* @return The current user, may be null.
*/
@Nullable public static String currentUser() {
return userStackThreadLocal.get();
}
}