Java tutorial
/* * The MIT License * * Copyright 2015 Ahseya. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.github.horrorho.liquiddonkey.cloud; import com.github.horrorho.liquiddonkey.cloud.data.Auth; import java.io.IOException; import java.time.Instant; import java.util.Objects; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import net.jcip.annotations.GuardedBy; import net.jcip.annotations.Immutable; import net.jcip.annotations.ThreadSafe; import org.apache.http.client.HttpClient; import org.apache.http.client.HttpResponseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Authenticator. * <p> * Thread-safe authenticator. * * @author Ahseya */ @ThreadSafe public final class Authenticator { public static Authenticator from(HttpClient client, String id, String password) throws IOException { Auth auth = Auth.from(client, id, password); return from(id, password, auth); } public static Authenticator from(Auth auth) { return new Authenticator("", "", auth); } public static Authenticator from(String id, String password, Auth auth) { return new Authenticator(id, password, auth); } private static final Logger logger = LoggerFactory.getLogger(Authenticator.class); private final String id; private final String password; private final Lock lock; @GuardedBy("lock") private volatile Token token; @GuardedBy("lock") private volatile String invalid; Authenticator(String id, String password, Lock lock, Token token, String invalid) { this.id = id; this.password = password; this.token = token; this.lock = Objects.requireNonNull(lock); this.invalid = invalid; } Authenticator(String id, String password, Auth auth) { this(id, password, new ReentrantLock(false), Token.from(auth), null); } public Token get() throws HttpResponseException { testIsInvalid(); return token; } public Token reauthenticate(HttpClient client, Token expired) throws IOException { logger.trace("<< reauthenticate() < dsPrsID: {} timestamp: {}", expired.auth().dsPrsID(), expired.timestamp()); testIsInvalid(); lock.lock(); try { if (token.timestamp().isAfter(expired.timestamp())) { logger.debug("-- reauthenticate() > expired token"); } else { logger.debug("-- reauthenticate() > reauthenticating"); authenticate(client); } logger.trace(">> reauthenticate() > dsPrsID: {} timestamp: {}", token.auth().dsPrsID(), token.timestamp()); return token; } finally { lock.unlock(); } } @GuardedBy("lock") void authenticate(HttpClient client) throws IOException { if (id == null || id.isEmpty() || password == null || password.isEmpty()) { invalid = "Unable to re-authenticate expired token: missing appleId/ password."; } else { try { Auth auth = Auth.from(client, id, password); if (auth.dsPrsID().equals(token.auth().dsPrsID())) { token = Token.from(auth); } else { logger.error("-- reauthentication() > mismatched dsPrsID: {} > {}", token.auth().dsPrsID(), auth.dsPrsID()); invalid = "Unable to re-authenticate expired token: account mismatch."; } } catch (HttpResponseException ex) { if (ex.getStatusCode() == 401) { invalid = "Unable to re-authenticate expired token: invalid appleId/ password."; } } } testIsInvalid(); } void testIsInvalid() throws HttpResponseException { if (isInvalid()) { throw new HttpResponseException(401, invalid); } } public boolean isInvalid() { return invalid != null; } public String dsPrsID() { return token.auth().dsPrsID(); } @Immutable @ThreadSafe public static final class Token { static Token from(Auth auth) { return new Token(auth, Instant.now()); } private final Auth auth; private final Instant timestamp; Token(Auth auth, Instant timestamp) { this.auth = auth; this.timestamp = timestamp; } public Auth auth() { return auth; } public Instant timestamp() { return timestamp; } } }