diff --git a/grails-app/controllers/docweb/UrlMappings.groovy b/grails-app/controllers/docweb/UrlMappings.groovy index d660276..72ccb0b 100644 --- a/grails-app/controllers/docweb/UrlMappings.groovy +++ b/grails-app/controllers/docweb/UrlMappings.groovy @@ -9,6 +9,8 @@ class UrlMappings { } } + "/componenteDigital/update/$id?(.$format)?"(controller: 'componenteDigital', action: 'update', method: 'POST') + "/"(view:"/index") "500"(view:'/error') "404"(view:'/notFound') diff --git a/grails-app/controllers/working/docweb/ComponenteDigitalController.groovy b/grails-app/controllers/working/docweb/ComponenteDigitalController.groovy new file mode 100644 index 0000000..589182a --- /dev/null +++ b/grails-app/controllers/working/docweb/ComponenteDigitalController.groovy @@ -0,0 +1,130 @@ +package working.docweb + +import java.security.MessageDigest + +import static org.springframework.http.HttpStatus.* +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class ComponenteDigitalController { + + static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"] + + def index(Integer max) { + params.max = Math.min(max ?: 10, 100) + respond ComponenteDigital.list(params), model:[componenteDigitalCount: ComponenteDigital.count()] + } + + def show(ComponenteDigital componenteDigital) { + respond componenteDigital + } + + def create() { + respond new ComponenteDigital(params) + } + + @Transactional + def save(ComponenteDigital componenteDigital) { + if (componenteDigital == null) { + transactionStatus.setRollbackOnly() + notFound() + return + } + + def file = request.getFile('myFile') + if (file != null && !file.empty) { + componenteDigital.nomeOriginal = file.originalFilename + componenteDigital.formato = file.contentType + componenteDigital.tamanho = file.size + componenteDigital.fixidade = MessageDigest.getInstance("SHA-256").digest(file.bytes).encodeHex().toString() + componenteDigital.armazenamento = "/tmp/${UUID.randomUUID() as String}" + file.transferTo(new File(componenteDigital.armazenamento)) + } + + if (!componenteDigital.validate()) { + transactionStatus.setRollbackOnly() + respond componenteDigital.errors, view:'create' + return + } + + componenteDigital.save flush:true + + request.withFormat { + form multipartForm { + flash.message = message(code: 'default.created.message', args: [message(code: 'componenteDigital.label', default: 'ComponenteDigital'), componenteDigital.id]) + redirect componenteDigital + } + '*' { respond componenteDigital, [status: CREATED] } + } + } + + def edit(ComponenteDigital componenteDigital) { + respond componenteDigital + } + + @Transactional + def update(ComponenteDigital componenteDigital) { + if (componenteDigital == null) { + transactionStatus.setRollbackOnly() + notFound() + return + } + + //throw new Exception(request.class.toString()) + def file = request.getFile('myFile') + if (file != null && !file.empty) { + componenteDigital.nomeOriginal = file.originalFilename + componenteDigital.formato = file.contentType + componenteDigital.tamanho = file.size + componenteDigital.fixidade = MessageDigest.getInstance("SHA-256").digest(file.bytes).encodeHex().toString() + componenteDigital.armazenamento = "/tmp/${UUID.randomUUID() as String}" + file.transferTo(new File(componenteDigital.armazenamento)) + } + + if (componenteDigital.validate()) { + transactionStatus.setRollbackOnly() + respond componenteDigital.errors, view:'edit' + return + } + + componenteDigital.save flush:true + + request.withFormat { + form multipartForm { + flash.message = message(code: 'default.updated.message', args: [message(code: 'componenteDigital.label', default: 'ComponenteDigital'), componenteDigital.id]) + redirect componenteDigital + } + '*'{ respond componenteDigital, [status: OK] } + } + } + + @Transactional + def delete(ComponenteDigital componenteDigital) { + + if (componenteDigital == null) { + transactionStatus.setRollbackOnly() + notFound() + return + } + + componenteDigital.delete flush:true + + request.withFormat { + form multipartForm { + flash.message = message(code: 'default.deleted.message', args: [message(code: 'componenteDigital.label', default: 'ComponenteDigital'), componenteDigital.id]) + redirect action:"index", method:"GET" + } + '*'{ render status: NO_CONTENT } + } + } + + protected void notFound() { + request.withFormat { + form multipartForm { + flash.message = message(code: 'default.not.found.message', args: [message(code: 'componenteDigital.label', default: 'ComponenteDigital'), params.id]) + redirect action: "index", method: "GET" + } + '*'{ render status: NOT_FOUND } + } + } +} diff --git a/grails-app/domain/working/docweb/ComponenteDigital.groovy b/grails-app/domain/working/docweb/ComponenteDigital.groovy new file mode 100644 index 0000000..90217cb --- /dev/null +++ b/grails-app/domain/working/docweb/ComponenteDigital.groovy @@ -0,0 +1,73 @@ +package working.docweb + +class ComponenteDigital { + + ComponenteDigital(Documento documento){ + this.documento = documento + } + + /*** + * Referência para o documento ao qual o componente digital pertence + */ + Documento documento + + /*** + * Nome original do componente digital no momento em que foi inserido no repositório, + * antes de ser renomeado com o identificador do repositório. + */ + String nomeOriginal + + /*** + * Propriedades técnicas de um componente digital, aplicáveis à maioria dos formatos, + * tais como: nível de composição, tamanho, software de criação e inibidores. + */ + String caracteristicasTecnicas + + /*** + * Identificação do formato de arquivo do componente digital + * + * + */ + String formato + + /*** + * Informações sobre a localização e o suporte do componente digital, bem como os recursos necessários para + * armazenamento permanente. + */ + String armazenamento + + /*** + * Informações sobre o ambiente de software necessário para apresentar e/ou usar os componentes digitais, + * incluindo a aplicação e o sistema operacional. + */ + String ambienteSofware + + /*** + * Informações sobre os componentes de hardware necessários para operar o software referenciado em 5.6, + * incluindo periféricos. + */ + String ambienteHardware + + /*** + * Informações utilizadas para verificar se o componente digital sofreu mudanças não documentadas + */ + String fixidade + + /*** + * Tamanho, em bytes, do espaço ocupado pela componente digital no sistema de arquivo + */ + Long tamanho = 0 + + static belongsTo = [documento: Documento] + + static constraints = { + nomeOriginal blank: false, size: 3..100 + formato blank: false + armazenamento blank: false + fixidade blank: false, size: 64..64 + caracteristicasTecnicas nullable: true + ambienteSofware nullable: true + ambienteHardware nullable: true + } + +} diff --git a/grails-app/domain/working/docweb/Documento.groovy b/grails-app/domain/working/docweb/Documento.groovy index 5a6e449..eaaf8a9 100644 --- a/grails-app/domain/working/docweb/Documento.groovy +++ b/grails-app/domain/working/docweb/Documento.groovy @@ -106,6 +106,13 @@ class Documento { */ Integer quantidadeFolhas + /*** + * Identificador dos componentes digitais que integram o documento + */ + Set componentesDigitais + + static hasMany = [componentesDigitais: ComponenteDigital] + static constraints = { protocolo nullable: true, validator: { val, doc -> Documento.validarProtocolo(val, doc) } diff --git a/grails-app/init/docweb/BootStrap.groovy b/grails-app/init/docweb/BootStrap.groovy index 6e993d2..69bff9b 100644 --- a/grails-app/init/docweb/BootStrap.groovy +++ b/grails-app/init/docweb/BootStrap.groovy @@ -1,5 +1,7 @@ package docweb +import working.docweb.ComponenteDigital +import working.docweb.Documento import working.docweb.Especie import working.docweb.TipoDocumento @@ -7,9 +9,12 @@ class BootStrap { def init = { servletContext -> + def tipoDoc + def especie + //Carga inicial de Tipos de documentos if(!TipoDocumento.count()) { - new TipoDocumento(nome: "Tipo de Documento 001").save(failOnError: true) + tipoDoc = new TipoDocumento(nome: "Tipo de Documento 001").save(failOnError: true) new TipoDocumento(nome: "Tipo de Documento 002").save(failOnError: true) new TipoDocumento(nome: "Tipo de Documento 003").save(failOnError: true) new TipoDocumento(nome: "Tipo de Documento 004").save(failOnError: true) @@ -18,12 +23,32 @@ class BootStrap { //Carga inicial de Espécie Documental if(!Especie.count()) { - new Especie(nome: "Aviso").save(failOnError: true) + especie = new Especie(nome: "Aviso").save(failOnError: true) new Especie(nome: "Declaração").save(failOnError: true) new Especie(nome: "Despacho").save(failOnError: true) new Especie(nome: "Memorando").save(failOnError: true) new Especie(nome: "Ofício").save(failOnError: true) } + + //Carga inicial de Documentos + if(!Documento.count()) { + new Documento( + numero: 'Carta: AB/11.000/2008', + protocolo: '0000000.00000000/0000-00', + status: Documento.Status.RASCUNHO, + nivelAcesso: Documento.NivelAcesso.PUBLICO, + titulo: 'Título do Documento', + meio: Documento.TipoMeio.DIGITAL, + genero: Documento.Genero.TEXTUAL, + descricao: 'Descrição do documento', + tipo: tipoDoc, + especie: especie, + idioma: 'pt-BR', + possuiAnexo: false, + dataProducao: new Date(), + localizacao: 'Depósito 201, estante 8, prateleira 2' + ).save(failOnError: true) + } } def destroy = { } diff --git a/grails-app/views/componenteDigital/create.gsp b/grails-app/views/componenteDigital/create.gsp new file mode 100644 index 0000000..5bfe07d --- /dev/null +++ b/grails-app/views/componenteDigital/create.gsp @@ -0,0 +1,39 @@ + + + + + + <g:message code="default.create.label" args="[entityName]" /> + + + + +
+

+ +
${flash.message}
+
+ + + + +
+ + Upload Form: +
+
+ +
+
+
+ + diff --git a/grails-app/views/componenteDigital/edit.gsp b/grails-app/views/componenteDigital/edit.gsp new file mode 100644 index 0000000..b5ddb49 --- /dev/null +++ b/grails-app/views/componenteDigital/edit.gsp @@ -0,0 +1,41 @@ + + + + + + <g:message code="default.edit.label" args="[entityName]" /> + + + + +
+

+ +
${flash.message}
+
+ + + + + +
+ + Upload Form: +
+
+ +
+
+
+ + diff --git a/grails-app/views/componenteDigital/index.gsp b/grails-app/views/componenteDigital/index.gsp new file mode 100644 index 0000000..9bed01b --- /dev/null +++ b/grails-app/views/componenteDigital/index.gsp @@ -0,0 +1,28 @@ + + + + + + <g:message code="default.list.label" args="[entityName]" /> + + + + +
+

+ +
${flash.message}
+
+ + + +
+ + \ No newline at end of file diff --git a/grails-app/views/componenteDigital/show.gsp b/grails-app/views/componenteDigital/show.gsp new file mode 100644 index 0000000..2dd8113 --- /dev/null +++ b/grails-app/views/componenteDigital/show.gsp @@ -0,0 +1,31 @@ + + + + + + <g:message code="default.show.label" args="[entityName]" /> + + + + +
+

+ +
${flash.message}
+
+ + +
+ + +
+
+
+ + diff --git a/src/test/groovy/working/docweb/ComponenteDigitalControllerSpec.groovy b/src/test/groovy/working/docweb/ComponenteDigitalControllerSpec.groovy new file mode 100644 index 0000000..d0dee23 --- /dev/null +++ b/src/test/groovy/working/docweb/ComponenteDigitalControllerSpec.groovy @@ -0,0 +1,158 @@ +package working.docweb + +import grails.test.mixin.* +import spock.lang.* + +@TestFor(ComponenteDigitalController) +@Mock(ComponenteDigital) +class ComponenteDigitalControllerSpec extends Specification { + + def populateValidParams(params) { + assert params != null + + // TODO: Populate valid properties like... + //params["name"] = 'someValidName' + //assert false, "TODO: Provide a populateValidParams() implementation for this generated test suite" + params["documento"] = new Documento() + params["nomeOriginal"] = "arquivo1.pdf" + params["caracteristicasTecnicas"] = "PDF version 1.0" + params["formato"] = "pdf" + params["armazenamento"] = "[repositorio]/2017/01/01/29831028309182903.pdf" + params["fixidade"] = "a6c6v7f839a6c6v7f839a6c6v7f839a6c6v7f839a6c6v7f839a6c6v7f8394444" + } + + void "Test the index action returns the correct model"() { + + when:"The index action is executed" + controller.index() + + then:"The model is correct" + !model.componenteDigitalList + model.componenteDigitalCount == 0 + } + + void "Test the create action returns the correct model"() { + when:"The create action is executed" + controller.create() + + then:"The model is correctly created" + model.componenteDigital!= null + } + + void "Test the save action correctly persists an instance"() { + + when:"The save action is executed with an invalid instance" + request.contentType = FORM_CONTENT_TYPE + request.method = 'POST' + def componenteDigital = new ComponenteDigital() + componenteDigital.validate() + controller.save(componenteDigital) + + then:"The create view is rendered again with the correct model" + model.componenteDigital!= null + view == 'create' + + when:"The save action is executed with a valid instance" + response.reset() + populateValidParams(params) + componenteDigital = new ComponenteDigital(params) + + controller.save(componenteDigital) + + then:"A redirect is issued to the show action" + response.redirectedUrl == '/componenteDigital/show/1' + controller.flash.message != null + ComponenteDigital.count() == 1 + } + + void "Test that the show action returns the correct model"() { + when:"The show action is executed with a null domain" + controller.show(null) + + then:"A 404 error is returned" + response.status == 404 + + when:"A domain instance is passed to the show action" + populateValidParams(params) + def componenteDigital = new ComponenteDigital(params) + controller.show(componenteDigital) + + then:"A model is populated containing the domain instance" + model.componenteDigital == componenteDigital + } + + void "Test that the edit action returns the correct model"() { + when:"The edit action is executed with a null domain" + controller.edit(null) + + then:"A 404 error is returned" + response.status == 404 + + when:"A domain instance is passed to the edit action" + populateValidParams(params) + def componenteDigital = new ComponenteDigital(params) + controller.edit(componenteDigital) + + then:"A model is populated containing the domain instance" + model.componenteDigital == componenteDigital + } + + void "Test the update action performs an update on a valid domain instance"() { + when:"Update is called for a domain instance that doesn't exist" + request.contentType = FORM_CONTENT_TYPE + request.method = 'PUT' + controller.update(null) + + then:"A 404 error is returned" + response.redirectedUrl == '/componenteDigital/index' + flash.message != null + + when:"An invalid domain instance is passed to the update action" + response.reset() + def componenteDigital = new ComponenteDigital() + componenteDigital.validate() + controller.update(componenteDigital) + + then:"The edit view is rendered again with the invalid instance" + view == 'edit' + model.componenteDigital == componenteDigital + + when:"A valid domain instance is passed to the update action" + response.reset() + populateValidParams(params) + componenteDigital = new ComponenteDigital(params).save(flush: true) + controller.update(componenteDigital) + + then:"A redirect is issued to the show action" + componenteDigital != null + response.redirectedUrl == "/componenteDigital/show/$componenteDigital.id" + flash.message != null + } + + void "Test that the delete action deletes an instance if it exists"() { + when:"The delete action is called for a null instance" + request.contentType = FORM_CONTENT_TYPE + request.method = 'DELETE' + controller.delete(null) + + then:"A 404 is returned" + response.redirectedUrl == '/componenteDigital/index' + flash.message != null + + when:"A domain instance is created" + response.reset() + populateValidParams(params) + def componenteDigital = new ComponenteDigital(params).save(flush: true) + + then:"It exists" + ComponenteDigital.count() == 1 + + when:"The domain instance is passed to the delete action" + controller.delete(componenteDigital) + + then:"The instance is deleted" + ComponenteDigital.count() == 0 + response.redirectedUrl == '/componenteDigital/index' + flash.message != null + } +} diff --git a/src/test/groovy/working/docweb/ComponenteDigitalSpec.groovy b/src/test/groovy/working/docweb/ComponenteDigitalSpec.groovy new file mode 100644 index 0000000..5065f9b --- /dev/null +++ b/src/test/groovy/working/docweb/ComponenteDigitalSpec.groovy @@ -0,0 +1,49 @@ +package working.docweb + +import grails.test.mixin.TestFor +import spock.lang.Specification + +/** + * See the API for {@link grails.test.mixin.domain.DomainClassUnitTestMixin} for usage instructions + */ +@TestFor(ComponenteDigital) +class ComponenteDigitalSpec extends Specification { + + ComponenteDigital componente + + def setup() { + componente = new ComponenteDigital(); + } + + def cleanup() { + } + + void "Componente digital não deve ser válido"() { + when: "Quando as propriedades do componente digital assumirem domínios inválidos" + componente.documento = null + componente.nomeOriginal = "" + componente.formato = "" + componente.armazenamento = "" + componente.fixidade = "" + + then: "Então o componente digital não será válido" + !componente.validate() + componente.errors.hasFieldErrors("documento") + componente.errors.hasFieldErrors("nomeOriginal") + componente.errors.hasFieldErrors("formato") + componente.errors.hasFieldErrors("armazenamento") + componente.errors.hasFieldErrors("fixidade") + } + + void "Componente digital deve ser válido"() { + when: "Quando as propriedades do componente digital assumirem domínios válidos" + componente.documento = new Documento() + componente.nomeOriginal = "arquivo1.pdf" + componente.formato = "pdf" + componente.armazenamento = "[repositorio]/2017/01/01/29831028309182903.pdf" + componente.fixidade = "a6c6v7f839a6c6v7f839a6c6v7f839a6c6v7f839a6c6v7f839a6c6v7f8394444" + + then: "Então o componente digital será válido" + componente.validate() + } +} diff --git a/src/test/groovy/working/docweb/DocumentoSpec.groovy b/src/test/groovy/working/docweb/DocumentoSpec.groovy index d65edac..ad927aa 100644 --- a/src/test/groovy/working/docweb/DocumentoSpec.groovy +++ b/src/test/groovy/working/docweb/DocumentoSpec.groovy @@ -12,7 +12,7 @@ import javax.swing.text.Document @TestFor(Documento) class DocumentoSpec extends Specification { - def doc + Documento doc def setup() { doc = new Documento() @@ -29,7 +29,7 @@ class DocumentoSpec extends Specification { doc.protocolo = null doc.validate() - then: "então o documento não é considerado válido" + then: "então o documento é considerado válido" !doc.errors?.hasFieldErrors("protocolo")