package br.gov.servicos.editor.conteudo;
import br.gov.servicos.editor.frontend.Siorg;
import br.gov.servicos.editor.git.ConteudoMetadados;
import br.gov.servicos.editor.git.Metadados;
import br.gov.servicos.editor.git.RepositorioGit;
import br.gov.servicos.editor.git.Revisao;
import br.gov.servicos.editor.security.UserProfile;
import br.gov.servicos.editor.utils.EscritorDeArquivos;
import br.gov.servicos.editor.utils.LeitorDeArquivos;
import br.gov.servicos.editor.utils.ReformatadorXml;
import com.github.slugify.Slugify;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.experimental.FieldDefaults;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.w3c.dom.Document;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.dom.DOMSource;
import java.io.FileNotFoundException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import static br.gov.servicos.editor.config.CacheConfig.METADADOS;
import static br.gov.servicos.editor.utils.Unchecked.Supplier.uncheckedSupplier;
import static java.lang.String.format;
import static lombok.AccessLevel.PRIVATE;
import static lombok.AccessLevel.PROTECTED;
import static org.eclipse.jgit.lib.Constants.MASTER;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
@FieldDefaults(level = PRIVATE, makeFinal = true)
@CacheConfig(cacheNames = METADADOS, keyGenerator = "geradorDeChavesParaCacheDeCommitsRecentes")
@AllArgsConstructor
public class ConteudoVersionado {
@Getter
String id;
@Getter
TipoPagina tipo;
@Getter(PROTECTED)
RepositorioGit repositorio;
LeitorDeArquivos leitorDeArquivos;
EscritorDeArquivos escritorDeArquivos;
Slugify slugify;
ReformatadorXml reformatadorXml;
Siorg siorg;
public Path getCaminho() {
return Paths.get(tipo.getCaminhoPasta().toString(), getId() + '.' + tipo.getExtensao());
}
public String getBranchRef() {
return R_HEADS + tipo.prefixo() + getId();
}
public Path getCaminhoAbsoluto() {
return repositorio.getCaminhoAbsoluto().resolve(getCaminho()).toAbsolutePath();
}
public Path getCaminhoRelativo() {
return repositorio.getCaminhoAbsoluto().relativize(getCaminhoAbsoluto());
}
public boolean existe() {
synchronized (RepositorioGit.class) {
return existeNoMaster() || existeNoBranch();
}
}
public boolean existeNoMaster() {
synchronized (RepositorioGit.class) {
return repositorio.comRepositorioAbertoNoBranch(getBranchMasterRef(),
() -> getCaminhoAbsoluto().toFile().exists());
}
}
public boolean existeNoBranch() {
synchronized (RepositorioGit.class) {
return repositorio.existeBranch(getBranchRef())
&& repositorio.comRepositorioAbertoNoBranch(getBranchRef(),
() -> getCaminhoAbsoluto().toFile().exists());
}
}
@Cacheable
public Metadados getMetadados() {
synchronized (RepositorioGit.class) {
return internalGetMetadados();
}
}
@CacheEvict
public void salvar(UserProfile profile, String conteudo) {
synchronized (RepositorioGit.class) {
repositorio.comRepositorioAbertoNoBranch(getBranchRef(), () -> {
salvarConteudo(profile, getBranchRef(), conteudo);
return null;
});
}
}
@CacheEvict
public void remover(UserProfile profile) {
synchronized (RepositorioGit.class) {
repositorio.comRepositorioAbertoNoBranch(getBranchMasterRef(), () -> {
repositorio.pull();
repositorio.deleteLocalBranch(getBranchRef());
repositorio.deleteRemoteBranch(getBranchRef());
if (isPublicado()) {
repositorio.remove(getCaminhoRelativo());
repositorio.commit(getCaminhoRelativo(), "Remove '" + getId() + '\'', profile);
repositorio.push(getBranchMasterRef());
}
return null;
});
}
}
@SneakyThrows
@CacheEvict
public void publicar(UserProfile profile) {
synchronized (RepositorioGit.class) {
if (!existeNoBranch()) {
return;
}
String conteudo = getConteudoRaw();
repositorio.comRepositorioAbertoNoBranch(getBranchMasterRef(), () -> {
salvarConteudo(profile, getBranchMasterRef(), conteudo);
repositorio.deleteLocalBranch(getBranchRef());
repositorio.deleteRemoteBranch(getBranchRef());
return null;
});
}
}
@CacheEvict
public void descartarAlteracoes() {
synchronized (RepositorioGit.class) {
if (!existeNoBranch()) {
return;
}
repositorio.comRepositorioAbertoNoBranch(getBranchMasterRef(), () -> {
repositorio.deleteLocalBranch(getBranchRef());
repositorio.deleteRemoteBranch(getBranchRef());
return null;
});
}
}
@SneakyThrows
@CacheEvict
public void despublicarAlteracoes(UserProfile profile) {
synchronized (RepositorioGit.class) {
if (!existeNoMaster()) return;
String conteudo = getConteudoRaw();
salvar(profile, conteudo);
despublicar(profile);
}
}
@CacheEvict
public String renomear(UserProfile profile, String novoNome) {
synchronized (RepositorioGit.class) {
String novoId = slugify.slugify(novoNome);
String novoBranch = tipo.prefixo() + novoId;
repositorio.comRepositorioAbertoNoBranch(getBranchRef(), uncheckedSupplier(() -> {
repositorio.pull();
alterarConteudo(profile, novoNome, getBranchRef());
if (!getId().equals(novoId)) {
repositorio.moveBranchPara(novoBranch);
renomearConteudo(profile, novoId, novoBranch);
repositorio.deleteRemoteBranch(getBranchRef());
}
return null;
}));
repositorio.comRepositorioAbertoNoBranch(getBranchMasterRef(), uncheckedSupplier(() -> {
repositorio.pull();
if (isPublicado()) {
alterarConteudo(profile, novoNome, getBranchMasterRef());
if (!getId().equals(novoId)) {
renomearConteudo(profile, novoId, getBranchMasterRef());
}
}
return null;
}));
return novoId;
}
}
@CacheEvict
public String getConteudoRaw() throws FileNotFoundException {
synchronized (RepositorioGit.class) {
return repositorio.comRepositorioAbertoNoBranch(getBranchRef(), () -> {
repositorio.pull();
return leitorDeArquivos.ler(getCaminhoAbsoluto().toFile());
}).orElseThrow(
() -> new FileNotFoundException("Não foi possível encontrar o " + getTipo().getNome() + " referente ao arquivo '" + getCaminhoAbsoluto() + '\'')
);
}
}
public String getOrgaoId() {
return getMetadados().getConteudo().getOrgaoId();
}
@SneakyThrows
protected ConteudoMetadados getConteudoParaMetadados() {
return tipo.metadados(getConteudoRaw(), siorg);
}
protected Optional<Revisao> getRevisaoMaisRecenteDoMaster() {
if (!existeNoMaster()) {
return Optional.empty();
}
return repositorio.getRevisaoMaisRecenteDoBranch(getBranchMasterRef(), getCaminhoRelativo());
}
protected Optional<Revisao> getRevisaoMaisRecenteDoBranch() {
if (!existeNoBranch()) {
return Optional.empty();
}
return repositorio.getRevisaoMaisRecenteDoBranch(getBranchRef(), getCaminhoRelativo());
}
protected Metadados internalGetMetadados() {
return new Metadados()
.withId(getId())
.withPublicado(getRevisaoMaisRecenteDoMaster().orElse(null))
.withEditado(getRevisaoMaisRecenteDoBranch().orElse(null))
.withConteudo(getConteudoParaMetadados());
}
private boolean isPublicado() {
return Files.exists(getCaminhoAbsoluto());
}
private String getBranchMasterRef() {
return R_HEADS + MASTER;
}
private void despublicar(UserProfile profile) {
repositorio.comRepositorioAbertoNoBranch(getBranchMasterRef(), () -> {
repositorio.remove(getCaminhoRelativo());
repositorio.commit(getCaminhoRelativo(), "Despublica '" + getId() + '\'', profile);
repositorio.push(getBranchMasterRef());
return null;
});
}
@SneakyThrows
private String mudarNomeConteudo(String novoNome) {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(getCaminhoAbsoluto().toFile());
doc.getElementsByTagName("nome").item(0).setTextContent(novoNome);
return reformatadorXml.formata(new DOMSource(doc));
}
private void salvarConteudo(UserProfile profile, String branch, String conteudo, String mensagemBase) {
String mensagem = format("%s '%s'", mensagemBase, getId());
repositorio.pull();
escritorDeArquivos.escrever(getCaminhoAbsoluto(), conteudo);
repositorio.add(getCaminhoRelativo());
repositorio.commit(getCaminhoRelativo(), mensagem, profile);
repositorio.push(branch);
}
private void salvarConteudo(UserProfile profile, String branch, String conteudo) {
salvarConteudo(profile, branch, conteudo, getCaminhoAbsoluto().toFile().exists() ? "Altera" : "Cria");
}
@SneakyThrows
private void renomearConteudo(UserProfile profile, String nomeNovoArquivo, String branch) {
String mensagem = format("Renomeia '%s' para '%s'", getId(), nomeNovoArquivo);
Path novoCaminho = getCaminhoRelativo().resolveSibling(nomeNovoArquivo + ".xml");
Files.move(getCaminhoAbsoluto(), getCaminhoAbsoluto().resolveSibling(nomeNovoArquivo + ".xml"));
repositorio.remove(getCaminhoRelativo());
repositorio.commit(getCaminhoRelativo(), mensagem, profile);
repositorio.add(novoCaminho);
repositorio.commit(novoCaminho, mensagem, profile);
repositorio.push(branch);
}
private void alterarConteudo(UserProfile profile, String novoNome, String branch) {
String conteudo = mudarNomeConteudo(novoNome);
salvarConteudo(profile, branch, conteudo);
}
}