001/*
002 * Copyright (c) 2015 Maxim Yunusov
003 *    Licensed under the Apache License, Version 2.0 (the "License");
004 *    you may not use this file except in compliance with the License.
005 *    You may obtain a copy of the License at
006 *
007 *        http://www.apache.org/licenses/LICENSE-2.0
008 *
009 *    Unless required by applicable law or agreed to in writing, software
010 *    distributed under the License is distributed on an "AS IS" BASIS,
011 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 *    See the License for the specific language governing permissions and
013 *    limitations under the License.
014 */
015
016package org.maxur.perfmodel.backend.infrastructure;
017
018import org.iq80.leveldb.WriteBatch;
019import org.jvnet.hk2.annotations.Service;
020import org.maxur.perfmodel.backend.domain.ConflictException;
021import org.maxur.perfmodel.backend.domain.Project;
022import org.maxur.perfmodel.backend.domain.Repository;
023import org.maxur.perfmodel.backend.service.Benchmark;
024import org.maxur.perfmodel.backend.service.DataSource;
025import org.slf4j.Logger;
026
027import javax.inject.Inject;
028import java.io.IOException;
029import java.util.Collection;
030import java.util.Optional;
031
032import static java.lang.String.format;
033import static java.util.Optional.empty;
034import static org.slf4j.LoggerFactory.getLogger;
035
036/**
037 * Level Db Implementation of Project Repository.
038 *
039 * @author myunusov
040 * @version 1.0
041 * @since <pre>30.08.2015</pre>
042 */
043@Service
044public class ProjectRepositoryLevelDbImpl implements Repository<Project> {
045
046    private static final Logger LOGGER = getLogger(ProjectRepositoryLevelDbImpl.class);
047
048    public static final String ROOT_PREFIX = "/";
049
050    @Inject
051    private DataSource dataSource;
052
053    @Override
054    @Benchmark
055    public Optional<Project> get(final String key) {
056        try {
057            return dataSource.get(key);
058        } catch (IOException | ClassNotFoundException e) {
059            return throwError(e, "Cannot find project by id '%s'", key);
060        }
061    }
062
063    @Override
064    @Benchmark
065    public Collection<Project> findAll() {
066        try {
067            return dataSource.findAllByPrefix(ROOT_PREFIX);
068        } catch (IOException | ClassNotFoundException e) {
069            return throwError(e, "Cannot get all projects");
070        }
071    }
072
073    @Override
074    @Benchmark
075    public Optional<Project> findByName(final String name) {
076        try {
077            return dataSource.get(path(name));
078        } catch (IOException | ClassNotFoundException e) {
079            return throwError(e, "Cannot find project by name '%s'", name);
080        }
081    }
082
083    @Override
084    @Benchmark
085    public Optional<Project> remove(final String key) {
086        try (WriteBatch batch = dataSource.createWriteBatch()) {
087            final Optional<Project> result = dataSource.get(key);
088            if (result.isPresent()) {
089                dataSource.delete(key);
090                dataSource.delete(path(result.get().getName()));
091                dataSource.commit(batch);
092            }
093            return result;
094        } catch (IOException | ClassNotFoundException e) {
095            return throwError(e, "Cannot remove project '%s'", key);
096        }
097
098    }
099
100    @Override
101    @Benchmark
102    public Optional<Project> add(final Project value) throws ConflictException {
103        final String id = value.getId();
104        final String newName = value.getName();
105        try (WriteBatch batch = dataSource.createWriteBatch()) {
106            final Optional<Project> other = dataSource.get(id);
107            if (value.isSame(other)) {
108                return other;
109            }
110            value.checkUniqueId(other);
111            value.checkNamesakes(findByName(newName));
112            value.makeVersion();
113            dataSource.put(id, value);
114            dataSource.put(path(newName), value.brief());
115            dataSource.commit(batch);
116            return Optional.of(value);
117        } catch (IOException | ClassNotFoundException e) {
118            return throwError(e, "Cannot save project '%s'", newName);
119        }
120    }
121
122    @Override
123    @Benchmark
124    // idempotent
125    public Optional<Project> amend(final Project value) throws ConflictException  {
126        final String id = value.getId();
127        final String newName = value.getName();
128        value.incVersion();
129
130        try (WriteBatch batch = dataSource.createWriteBatch()) {
131            final Optional<Project> prev = dataSource.get(id);
132            if (!prev.isPresent()) {
133                return empty();
134            }
135            if (value.isSame(prev)) {
136                return prev;
137            }
138            value.checkConflictWith(prev);
139            final boolean mustBeRenamed = !prev.get().getName().equals(newName);
140            if (mustBeRenamed) {
141                value.checkNamesakes(findByName(newName));
142                dataSource.delete(path(prev.get().getName()));
143            }
144            dataSource.put(id, value);
145            dataSource.put(path(newName), value.brief());
146            dataSource.commit(batch);
147            return Optional.of(value);
148        } catch (IOException | ClassNotFoundException e) {
149            return throwError(e, "Cannot save project '%s'", newName);
150        }
151    }
152
153    private String path(final String name) {
154        return ROOT_PREFIX + name;
155    }
156
157    private static <T> T throwError(final Exception e, final String message, final String... args) {
158        final String msg = format(message, args);
159        LOGGER.error(msg, e);
160        throw new IllegalStateException(msg, e);
161    }
162
163}