001/*
002 * acme4j - Java ACME client
003 *
004 * Copyright (C) 2015 Richard "Shred" Körber
005 *   http://acme4j.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.acme4j.provider;
015
016import java.net.HttpURLConnection;
017import java.net.URI;
018import java.util.Collections;
019import java.util.HashMap;
020import java.util.Map;
021import java.util.Objects;
022import java.util.function.Function;
023
024import org.shredzone.acme4j.Session;
025import org.shredzone.acme4j.challenge.Challenge;
026import org.shredzone.acme4j.challenge.Dns01Challenge;
027import org.shredzone.acme4j.challenge.Http01Challenge;
028import org.shredzone.acme4j.connector.Connection;
029import org.shredzone.acme4j.connector.DefaultConnection;
030import org.shredzone.acme4j.connector.HttpConnector;
031import org.shredzone.acme4j.exception.AcmeException;
032import org.shredzone.acme4j.toolbox.JSON;
033
034/**
035 * Abstract implementation of {@link AcmeProvider}. It consists of a challenge
036 * registry and a standard {@link HttpConnector}.
037 * <p>
038 * Implementing classes must implement at least {@link AcmeProvider#accepts(URI)}
039 * and {@link AbstractAcmeProvider#resolve(URI)}.
040 */
041public abstract class AbstractAcmeProvider implements AcmeProvider {
042
043    private static final Map<String, Function<Session, Challenge>> CHALLENGES = challengeMap();
044
045    @Override
046    public Connection connect() {
047        return new DefaultConnection(createHttpConnector());
048    }
049
050    @Override
051    public JSON directory(Session session, URI serverUri) throws AcmeException {
052        try (Connection conn = connect()) {
053            conn.sendRequest(resolve(serverUri), session);
054            conn.accept(HttpURLConnection.HTTP_OK);
055
056            // use nonce header if there is one, saves a HEAD request...
057            conn.updateSession(session);
058
059            return conn.readJsonResponse();
060        }
061    }
062
063    private static Map<String, Function<Session, Challenge>> challengeMap() {
064        Map<String, Function<Session, Challenge>> map = new HashMap<>();
065
066        map.put(Dns01Challenge.TYPE, Dns01Challenge::new);
067        map.put(Http01Challenge.TYPE, Http01Challenge::new);
068
069        return Collections.unmodifiableMap(map);
070    }
071
072    @Override
073    public Challenge createChallenge(Session session, String type) {
074        Objects.requireNonNull(session, "session");
075        Objects.requireNonNull(type, "type");
076
077        Function<Session, Challenge> constructor = CHALLENGES.get(type);
078        if (constructor == null) {
079            return null;
080        }
081
082        return constructor.apply(session);
083    }
084
085    /**
086     * Creates a {@link HttpConnector}.
087     * <p>
088     * Subclasses may override this method to configure the {@link HttpConnector}.
089     */
090    protected HttpConnector createHttpConnector() {
091        return new HttpConnector();
092    }
093
094}