/******************************************************************************* * Copyright 2014 Miami-Dade County * * Licensed 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.sharegov.cirm.user; import java.util.Date; import mjson.Json; import org.sharegov.cirm.utils.ThreadLocalStopwatch; /** * Thread safe simple FailoverUserProvider Implementation. * * Uses originalUP User Provider until failure and then replicateUP for a * duration of DEFAULT_FALLBACK_SECS (or specified) before trying originalUP * again. If fallBackSecs is set to zero, the originalUP will be tried for each * query before the replicateUP even after failure. * * * It makes no assumption that userProvider and replicateUP return equal * results. * * @author Thomas Hilpold * */ public class FailoverUserProvider implements UserProvider { public static boolean DBG = true; public static int DEFAULT_FALLBACK_SECS = 30; // use failover before retry // original secs private volatile UserProvider originalUP; // null allowed private volatile UserProvider replicateUP; // null allowed private volatile Date lastFailureTime = new Date(0); private volatile UserProvider activeUP = null; // must use accessor method // (thread safety) /** * Time to use failover before checking userProvider again. */ private volatile int fallbackSecs; /** * Creates a Failoveruserprovider with DEFAULT_FALLBACK_SECS. Null allowed * for one UP, but not both. * * @param userProvider * the original user provider * @param replicateUP * a replicate of the original to use, when original fails. */ public FailoverUserProvider(UserProvider originalUP, UserProvider replicateUP) { this(originalUP, replicateUP, DEFAULT_FALLBACK_SECS); } /** * Creates a Failoveruserprovider. Null allowed for one UP, but not both. * * @param userProvider * the original user provider * @param replicateUP * a replicate of the original to use, when original fails. * @param useFailoverSecs * use 0 to always try userP first */ public FailoverUserProvider(UserProvider originalUP, UserProvider replicateUP, int fallbackSecs) { if (originalUP == null && replicateUP == null) throw new IllegalArgumentException( "Initialized with two NULL providers. Giving up."); if (originalUP == null) System.err .println("FailoverUserProvider: Initialized with null original. Using only replicate."); if (replicateUP == null) System.err .println("FailoverUserProvider: Initialized with null replicate. Using only original."); if (fallbackSecs < 0) throw new IllegalArgumentException( "fallbackSecs < 0; 0 or greater allowed."); this.originalUP = originalUP; this.replicateUP = replicateUP; this.fallbackSecs = fallbackSecs; if (DBG) ThreadLocalStopwatch.getWatch().time( "FailOverUserProvider: Initialized (Fallback secs: " + fallbackSecs + ")"); } public boolean authenticate(String username, String password) { try { return getActiveUserProvider().authenticate(username, password); } catch (Exception e) { failed(e); return replicateUP.authenticate(username, password); } } @Override public Json find(String attribute, String value) { try { return getActiveUserProvider().find(attribute, value); } catch (Exception e) { failed(e); return replicateUP.find(attribute, value); } } @Override public Json find(Json prototype) { try { return getActiveUserProvider().find(prototype); } catch (Exception e) { failed(e); return replicateUP.find(prototype); } } @Override public Json find(Json prototype, int resultLimit) { try { return getActiveUserProvider().find(prototype, resultLimit); } catch (Exception e) { failed(e); return replicateUP.find(prototype, resultLimit); } } public Json findGroups(String id) { try { return getActiveUserProvider().findGroups(id); } catch (Exception e) { failed(e); return replicateUP.findGroups(id); } } @Override public Json get(String id) { try { return getActiveUserProvider().get(id); } catch (Exception e) { failed(e); return replicateUP.get(id); } } public Json populate(Json user) { try { return getActiveUserProvider().populate(user); } catch (Exception e) { failed(e); return replicateUP.populate(user); } } @Override public String getIdAttribute() { try { return getActiveUserProvider().getIdAttribute(); } catch (Exception e) { failed(e); return replicateUP.getIdAttribute(); } } protected UserProvider getActiveUserProvider() { UserProvider nextUP; // thread safety if (originalUP == null) { if (DBG) ThreadLocalStopwatch .getWatch() .time("FailOverUserProvider: original null, only using replicate."); nextUP = replicateUP; } else if (replicateUP == null) { if (DBG) ThreadLocalStopwatch .getWatch() .time("FailOverUserProvider: replicate null, only using original."); nextUP = originalUP; } else { // Use replicate if original failed until fallback over and next // failure UserProvider lastUP = activeUP; nextUP = isFallBackNow() ? originalUP : replicateUP; activeUP = nextUP; if (DBG && lastUP == replicateUP && nextUP == originalUP) ThreadLocalStopwatch.getWatch().time( "FailOverUserProvider: Falling back to originalUP"); else if (DBG && (lastUP == null || lastUP == originalUP) && nextUP == replicateUP) ThreadLocalStopwatch.getWatch().time( "FailOverUserProvider: Using replicateUP"); } return nextUP; } /** * If the fallback time passed since last failure, use original again (fall * back). * * @return */ protected boolean isFallBackNow() { return fallbackSecs == 0 || (new Date().getTime() - lastFailureTime.getTime() > fallbackSecs * 1000L); } protected void failed(Exception e) { if (DBG) ThreadLocalStopwatch.getWatch().time( "FailOverUserProvider: original failed, trying replicate. Error was: " + e); lastFailureTime = new Date(); } public UserProvider getOriginalUP() { return originalUP; } public void setOriginalUP(UserProvider originalUP) { this.originalUP = originalUP; } public UserProvider getReplicateUP() { return replicateUP; } public void setReplicateUP(UserProvider replicateUP) { this.replicateUP = replicateUP; } public int getFallbackSecs() { return fallbackSecs; } public void setFallbackSecs(int fallbackSecs) { this.fallbackSecs = fallbackSecs; } /** * @return date with time 0, if not yet failed. */ public Date getLastFailureTime() { return lastFailureTime; } }