org.eclipse.hono.service.credentials.impl.FileBasedCredentialsService.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.hono.service.credentials.impl.FileBasedCredentialsService.java

Source

/**
 * Copyright (c) 2017 Bosch Software Innovations GmbH.
 * <p>
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * <p>
 * Contributors:
 * Bosch Software Innovations GmbH - initial creation
 */

package org.eclipse.hono.service.credentials.impl;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.file.FileSystem;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import org.eclipse.hono.service.credentials.BaseCredentialsService;
import org.eclipse.hono.util.CredentialsResult;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Repository;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

import static java.net.HttpURLConnection.*;
import static org.eclipse.hono.util.CredentialsConstants.*;

/**
 * A credentials service that keeps all data in memory but is backed by a file.
 * <p>
 * On startup this adapter loads all added credentials from a file. On shutdown all
 * credentials kept in memory are written to the file.
 */
@Repository
@ConfigurationProperties(prefix = "hono.credentials")
@Profile({ "default", "credentials-file" })
public class FileBasedCredentialsService extends BaseCredentialsService {

    private static final String ARRAY_CREDENTIALS = "credentials";
    private static final String FIELD_TENANT = "tenant";

    // <tenantId, <authId, credentialsData[]>>
    private Map<String, Map<String, JsonArray>> credentials = new HashMap<>();
    // the name of the file used to persist the registry content
    private String filename = "credentials.json";
    private boolean saveToFile = false;
    private boolean running = false;
    private boolean dirty = false;

    /**
     * Sets the path to the file that the content of the credentials registry should be persisted
     * periodically.
     * <p>
     * Default value is <em>credentials.json</em>.
     *
     * @param filename The name of the file to persist to (can be a relative or absolute path).
     * @throws IllegalStateException if this registry is already running.
     */
    public final void setFilename(final String filename) {
        if (running) {
            throw new IllegalStateException("credentials registry already started");
        }
        this.filename = filename;
    }

    /**
     * Sets whether the content of the credentials registry should be persisted to the file system
     * periodically.
     * <p>
     * Default value is {@code true}.
     *
     * @param enabled {@code true} if credentials registry content should be persisted.
     * @throws IllegalStateException if this credentials registry is already running.
     */
    public final void setSaveToFile(final boolean enabled) {
        if (running) {
            throw new IllegalStateException("credentials registry already started");
        }
        this.saveToFile = enabled;
    }

    @Override
    protected void doStart(final Future<Void> startFuture) throws Exception {

        if (!running) {
            if (filename != null) {
                loadCredentialsData();
                if (saveToFile) {
                    log.info("saving credentials to file every 3 seconds");
                    vertx.setPeriodic(3000, saveIdentities -> {
                        saveToFile(Future.future());
                    });
                } else {
                    log.info("persistence is disabled, will not save credentials to file");
                }
            }
        }
        running = true;
        startFuture.complete();
    }

    protected void loadCredentialsData() {
        if (filename != null) {
            final FileSystem fs = vertx.fileSystem();
            log.debug("trying to load credentials information from file {}", filename);
            if (fs.existsBlocking(filename)) {
                final AtomicInteger credentialsCount = new AtomicInteger();
                fs.readFile(filename, readAttempt -> {
                    if (readAttempt.succeeded()) {
                        JsonArray allObjects = new JsonArray(new String(readAttempt.result().getBytes()));
                        for (Object obj : allObjects) {
                            JsonObject tenant = (JsonObject) obj;
                            String tenantId = tenant.getString(FIELD_TENANT);
                            Map<String, JsonArray> credentialsMap = new HashMap<>();
                            for (Object credentialsObj : tenant.getJsonArray(ARRAY_CREDENTIALS)) {
                                JsonObject credentials = (JsonObject) credentialsObj;
                                JsonArray authIdCredentials;
                                if (credentialsMap.containsKey(credentials.getString(FIELD_AUTH_ID))) {
                                    authIdCredentials = credentialsMap.get(credentials.getString(FIELD_AUTH_ID));
                                } else {
                                    authIdCredentials = new JsonArray();
                                }
                                authIdCredentials.add(credentials);
                                credentialsMap.put(credentials.getString(FIELD_AUTH_ID), authIdCredentials);
                                credentialsCount.incrementAndGet();
                            }
                            credentials.put(tenantId, credentialsMap);
                        }
                        log.info("successfully loaded {} credentials from file [{}]", credentialsCount.get(),
                                filename);
                    } else {
                        log.warn("could not load credentials from file [{}]", filename, readAttempt.cause());
                    }
                });
            } else {
                log.debug("credentials file {} does not exist (yet)", filename);
            }
        }
    }

    @Override
    protected void doStop(final Future<Void> stopFuture) {

        if (running) {
            Future<Void> stopTracker = Future.future();
            stopTracker.setHandler(stopAttempt -> {
                running = false;
                stopFuture.complete();
            });

            if (saveToFile) {
                saveToFile(stopTracker);
            } else {
                stopTracker.complete();
            }
        } else {
            stopFuture.complete();
        }
    }

    protected void saveToFile(final Future<Void> writeResult) {

        if (!dirty) {
            log.trace("credentials registry does not need to be persisted");
            return;
        }

        final FileSystem fs = vertx.fileSystem();
        if (!fs.existsBlocking(filename)) {
            fs.createFileBlocking(filename);
        }
        final AtomicInteger idCount = new AtomicInteger();
        JsonArray tenants = new JsonArray();
        for (Entry<String, Map<String, JsonArray>> entry : credentials.entrySet()) {
            JsonArray credentialsArray = new JsonArray();
            for (Entry<String, JsonArray> credentialEntry : entry.getValue().entrySet()) { // authId -> full json attributes object
                JsonArray singleAuthIdCredentials = credentialEntry.getValue(); // from one authId

                credentialsArray.add(singleAuthIdCredentials);
                idCount.incrementAndGet();
            }
            tenants.add(
                    new JsonObject().put(FIELD_TENANT, entry.getKey()).put(ARRAY_CREDENTIALS, credentialsArray));
        }
        fs.writeFile(filename, Buffer.factory.buffer(tenants.encodePrettily()), writeAttempt -> {
            if (writeAttempt.succeeded()) {
                dirty = false;
                log.trace("successfully wrote {} credentials to file {}", idCount.get(), filename);
                writeResult.complete();
            } else {
                log.warn("could not write credentials to file {}", filename, writeAttempt.cause());
                writeResult.fail(writeAttempt.cause());
            }
        });
    }

    @Override
    public final void getCredentials(final String tenantId, final String type, final String authId,
            final Handler<AsyncResult<CredentialsResult>> resultHandler) {
        CredentialsResult credentialsResult = getCredentialsResult(tenantId, authId, type);
        resultHandler.handle(Future.succeededFuture(credentialsResult));
    }

    @Override
    public void addCredentials(final String tenantId, final JsonObject otherKeys,
            final Handler<AsyncResult<CredentialsResult>> resultHandler) {
        // TODO: implement
        CredentialsResult credentialsResult = CredentialsResult.from(HTTP_NOT_IMPLEMENTED);
        resultHandler.handle(Future.succeededFuture(credentialsResult));
    }

    @Override
    public void updateCredentials(final String tenantId, final JsonObject otherKeys,
            final Handler<AsyncResult<CredentialsResult>> resultHandler) {
        // TODO: implement
        CredentialsResult credentialsResult = CredentialsResult.from(HTTP_NOT_IMPLEMENTED);
        resultHandler.handle(Future.succeededFuture(credentialsResult));
    }

    @Override
    public void removeCredentials(final String tenantId, final String deviceId, final String type,
            final String authId, final Handler<AsyncResult<CredentialsResult>> resultHandler) {
        // TODO: implement
        CredentialsResult credentialsResult = CredentialsResult.from(HTTP_NOT_IMPLEMENTED);
        resultHandler.handle(Future.succeededFuture(credentialsResult));
    }

    protected CredentialsResult getCredentialsResult(final String tenantId, final String authId,
            final String type) {
        JsonObject data = getCredentials(tenantId, authId, type);
        if (data != null) {
            JsonObject resultPayload = getResultPayload(data.getString(FIELD_DEVICE_ID), data.getString(FIELD_TYPE),
                    data.getString(FIELD_AUTH_ID), data.getBoolean(FIELD_ENABLED),
                    data.getJsonArray(FIELD_SECRETS));
            return CredentialsResult.from(HTTP_OK, resultPayload);
        } else {
            return CredentialsResult.from(HTTP_NOT_FOUND);
        }
    }

    private JsonObject getCredentials(final String tenantId, final String authId, final String type) {
        Objects.nonNull(tenantId);
        Objects.nonNull(authId);
        Objects.nonNull(type);

        final Map<String, JsonArray> credentialsForTenant = credentials.get(tenantId);
        if (credentialsForTenant != null) {
            JsonArray authIdCredentials = credentialsForTenant.get(authId);
            if (authIdCredentials == null) {
                return null;
            }

            for (Object authIdCredentialEntry : authIdCredentials) {
                JsonObject authIdCredential = (JsonObject) authIdCredentialEntry;
                // return the first matching type entry for this authId
                if (type.equals(authIdCredential.getString(FIELD_TYPE))) {
                    return authIdCredential;
                }
            }
        }
        return null;
    }

    /**
     * Removes all credentials from the registry.
     */
    public final void clear() {
        dirty = true;
        credentials.clear();
    }

    @Override
    public String toString() {
        return String.format("%s[filename=%s]", FileBasedCredentialsService.class.getSimpleName(), filename);
    }
}