Java tutorial
/* * Copyright 2012 - 2013 Silvio Wangler (silvio.wangler@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ch.silviowangler.dox; import ch.silviowangler.dox.api.*; import ch.silviowangler.dox.api.Domain; import ch.silviowangler.dox.api.rest.DocumentClass; import ch.silviowangler.dox.document.DocumentInspectorFactory; import ch.silviowangler.dox.domain.*; import ch.silviowangler.dox.domain.Attribute; import ch.silviowangler.dox.domain.AttributeDataType; import ch.silviowangler.dox.domain.Range; import ch.silviowangler.dox.domain.security.DoxUser; import ch.silviowangler.dox.repository.*; import ch.silviowangler.dox.repository.security.DoxUserRepository; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.nio.file.Files; import java.util.*; import static ch.silviowangler.dox.domain.AttributeDataType.*; import static ch.silviowangler.dox.domain.DomainUtils.containsWildcardCharacters; import static ch.silviowangler.dox.domain.DomainUtils.replaceWildcardCharacters; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.newArrayListWithCapacity; import static com.google.common.collect.Maps.newHashMapWithExpectedSize; import static org.springframework.transaction.annotation.Propagation.REQUIRED; import static org.springframework.transaction.annotation.Propagation.SUPPORTS; import static org.springframework.util.Assert.*; import ch.silviowangler.dox.api.rest.DocumentClass; /** * @author Silvio Wangler * @since 0.1 */ @Service("documentService") public class DocumentServiceImpl implements DocumentService, InitializingBean { private static final String DD_MM_YYYY = "dd.MM.yyyy"; private static final String YYYY_MM_DD = "yyyy-MM-dd"; public static final String CACHE_DOCUMENT_COUNT = "documentCount"; public static final int PAGE_NUMBER_NOT_RETRIEVABLE = -1; private final Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private DocumentClassRepository documentClassRepository; @Autowired private DocumentRepository documentRepository; @Autowired private DomainRepository domainRepository; @Autowired private AttributeRepository attributeRepository; @Autowired private IndexStoreRepository indexStoreRepository; @Value("#{systemEnvironment['DOX_STORE']}") private File archiveDirectory; @Autowired private Properties mimeTypes; @Autowired private IndexMapEntryRepository indexMapEntryRepository; @Autowired private DocumentInspectorFactory documentInspectorFactory; @Autowired private ClientRepository clientRepository; @Autowired private DoxUserRepository doxUserRepository; @Override public void afterPropertiesSet() throws Exception { notNull(archiveDirectory, "Archive directory must not be null. Please make sure you have properly set environment variable DOX_STORE"); isTrue(archiveDirectory.isDirectory(), "Archive store must be a directory ['" + this.archiveDirectory + "']"); isTrue(archiveDirectory.canRead(), "Archive store must be readable ['" + this.archiveDirectory + "']"); isTrue(archiveDirectory.canWrite(), "Archive store must be writable ['" + this.archiveDirectory + "']"); notEmpty(mimeTypes, "No mime types have been set"); } @Override @Transactional(readOnly = true) public List<DocumentClass> findAllDocumentClasses() { List<DocumentClass> documentClasses = newArrayList(); List<String> clients = getClients(); Iterable<ch.silviowangler.dox.domain.DocumentClass> documentClassEntities = documentClassRepository .findAllByClients(clients); for (ch.silviowangler.dox.domain.DocumentClass documentClassEntity : documentClassEntities) { DocumentClass documentClass = toDocumentClassWithAttributesApi(documentClassEntity); documentClass.setAttributes(newArrayList( toAttributeApi(attributeRepository.findAttributesForDocumentClass(documentClassEntity)))); documentClasses.add(documentClass); } return documentClasses; } @Override @Transactional public void deleteDocument(Long id) { logger.info("About delete document reference with id {}", id); notNull(id, "id must not be null"); Document document = documentRepository.findOne(id); if (document != null) { User user = getPrincipal(); if (!user.getUsername().equals(document.getUserReference())) { logger.warn( "User '{}' tries to delete document with id {} but it belongs to user '{}' and can therefore no be deleted by that user", new Object[] { user.getUsername(), document.getId(), document.getUserReference() }); throw new AccessDeniedException("Document " + document.getId() + " is not owned by you. You can only delete your own documents"); } indexStoreRepository.delete(document.getIndexStore()); document.setIndexStore(null); documentRepository.delete(id); logger.info("Document reference {} successfully deleted", id); } } private User getPrincipal() { return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); } @Override @Cacheable(CACHE_DOCUMENT_COUNT) @Transactional(propagation = SUPPORTS, readOnly = true) public long retrieveDocumentReferenceCount() { return documentRepository.count(); } @Override @Transactional(propagation = SUPPORTS, readOnly = true) @PreAuthorize("hasRole('ROLE_USER')") public SortedSet<ch.silviowangler.dox.api.Attribute> findAttributes( ch.silviowangler.dox.api.DocumentClass documentClass) throws DocumentClassNotFoundException { ch.silviowangler.dox.domain.DocumentClass docClass = findDocumentClass(documentClass.getShortName()); List<Attribute> attributes = attributeRepository.findAttributesForDocumentClass(docClass); return toAttributeApi(attributes); } @Override @Transactional(propagation = SUPPORTS, readOnly = true) @PreAuthorize("hasRole('ROLE_USER')") public Set<ch.silviowangler.dox.api.DocumentClass> findDocumentClasses() { Set<ch.silviowangler.dox.api.DocumentClass> result = new HashSet<>(); Iterable<ch.silviowangler.dox.domain.DocumentClass> documentClasses = documentClassRepository.findAll(); for (ch.silviowangler.dox.domain.DocumentClass documentClass : documentClasses) { logger.debug("Processing document class '{}' with id {}", documentClass.getShortName(), documentClass.getId()); result.add(toDocumentClassApi(documentClass)); } logger.info("Found {} document classes in DOX", result.size()); return result; } @Override @Transactional(propagation = SUPPORTS, readOnly = true) @PreAuthorize("hasRole('ROLE_USER')") public List<DocumentReference> findDocumentReferences(String queryString) { return findDocumentReferencesInternal(queryString, null, null); } @Override @Transactional(propagation = SUPPORTS, readOnly = true) @PreAuthorize("hasRole('ROLE_USER')") public List<DocumentReference> findDocumentReferences(String queryString, Locale locale) { return findDocumentReferencesInternal(queryString, null, locale); } @Override @Transactional(propagation = SUPPORTS, readOnly = true) @PreAuthorize("hasRole('ROLE_USER')") public List<DocumentReference> findDocumentReferencesForCurrentUser(String queryString) { User user = getPrincipal(); return findDocumentReferencesInternal(queryString, user.getUsername(), null); } @Override @Transactional(propagation = SUPPORTS, readOnly = true) @PreAuthorize("hasRole('ROLE_USER')") public List<DocumentReference> findDocumentReferencesForCurrentUser(String queryString, Locale locale) { User user = getPrincipal(); return findDocumentReferencesInternal(queryString, user.getUsername(), locale); } private List<DocumentReference> findDocumentReferencesInternal(String queryString, String username, Locale locale) { logger.debug("About to find document references for query string '{}'", queryString); List<Document> documents; List<String> clients = getClients(); if (username == null) { if (containsWildcardCharacters(queryString)) { String value = replaceWildcardCharacters(queryString); documents = indexMapEntryRepository.findByValueLike(value.toUpperCase(), value, clients); } else { documents = indexMapEntryRepository.findByValue(queryString.toUpperCase(), queryString, clients); } } else { if (containsWildcardCharacters(queryString)) { String value = replaceWildcardCharacters(queryString); documents = indexMapEntryRepository.findByValueLikeAndUserReference(value.toUpperCase(), value, username, clients); } else { documents = indexMapEntryRepository.findByValueAndUserReference(queryString.toUpperCase(), queryString, username, clients); } } List<DocumentReference> documentReferences = newArrayListWithCapacity(documents.size()); logger.info("Found {} documents for query string '{}'", documents.size(), queryString); for (Document document : documents) { logger.trace("Found document with id {}", document.getId()); documentReferences.add(toDocumentReference(document, locale)); } return documentReferences; } private List<String> getClients() { User user = getPrincipal(); DoxUser doxUser = doxUserRepository.findByUsername(user.getUsername()); List<String> clients = newArrayListWithCapacity(doxUser.getClients().size()); for (Client client : doxUser.getClients()) { clients.add(client.getShortName()); } return clients; } @Override @Transactional(propagation = SUPPORTS, readOnly = true) @PreAuthorize("hasRole('ROLE_USER')") public Set<DocumentReference> findDocumentReferences(Map<TranslatableKey, DescriptiveIndex> queryParams, String documentClassShortName) throws DocumentClassNotFoundException { logger.debug("Trying to find document references in document class '{}' using params '{}'", documentClassShortName, queryParams); ch.silviowangler.dox.domain.DocumentClass documentClass = findDocumentClass(documentClassShortName); List<Attribute> attributes = attributeRepository.findAttributesForDocumentClass(documentClass); List<Document> documents = documentRepository.findDocuments( toEntityMap(fixDataTypesOfIndices(queryParams, attributes)), toAttributeMap(attributes), documentClass); HashSet<DocumentReference> documentReferences = new HashSet<>(documents.size()); for (Document document : documents) { logger.trace("Found document with id {}", document.getId()); documentReferences.add(toDocumentReference(document, null)); } return documentReferences; } @Override @Transactional(propagation = SUPPORTS, readOnly = true) @PreAuthorize("hasRole('ROLE_USER')") public PhysicalDocument findPhysicalDocument(Long id) throws DocumentNotFoundException, DocumentNotInStoreException { DocumentReference doc = findDocumentReference(id); PhysicalDocument document = new PhysicalDocument(); try { BeanUtils.copyProperties(document, doc); } catch (IllegalAccessException | InvocationTargetException e) { logger.error("Unable to copy properties", e); } File file = new File(this.archiveDirectory, doc.getHash()); if (!file.exists()) { logger.error("Unable to find file for document hash '{}' on path '{}'", doc.getHash(), file.getAbsolutePath()); throw new DocumentNotInStoreException(doc.getHash(), doc.getId()); } try { document.setContent(FileUtils.readFileToByteArray(file)); } catch (IOException e) { logger.error("Unable to read content of file '{}'", file.getAbsolutePath(), e); } return document; } @Override @Transactional(propagation = SUPPORTS, readOnly = true) @PreAuthorize("hasRole('ROLE_USER')") public DocumentReference findDocumentReference(Long id) throws DocumentNotFoundException { logger.info("About to find document by using id {}", id); if (documentReferenceExists(id)) { logger.info("Found document for id {}", id); Document document = documentRepository.findOne(id); DocumentReference documentReference = toDocumentReference(document, null); return documentReference; } else { logger.warn("No document found for id {}", id); throw new DocumentNotFoundException(id); } } @Override @CacheEvict(value = CACHE_DOCUMENT_COUNT, allEntries = true) @Transactional(propagation = REQUIRED, readOnly = false) @PreAuthorize("hasRole('ROLE_USER')") public DocumentReference importDocument(PhysicalDocument physicalDocumentApi) throws ValidationException, DocumentDuplicationException, DocumentClassNotFoundException { final String documentClassShortName = physicalDocumentApi.getDocumentClass().getShortName(); ch.silviowangler.dox.domain.DocumentClass documentClassEntity = findDocumentClass(documentClassShortName); List<Attribute> attributes = attributeRepository.findAttributesForDocumentClass(documentClassEntity); logger.debug("Found {} attributes for document class '{}'", attributes.size(), documentClassShortName); verifyMandatoryAttributes(physicalDocumentApi, attributes); verifyUnknownKeys(physicalDocumentApi, documentClassShortName, attributes); try { verifyDomainValues(physicalDocumentApi, attributes); } catch (ValueNotInDomainException e) { ch.silviowangler.dox.domain.Domain domain = domainRepository.findByShortName(e.getDomainName()); if (domain.isStrict()) { logger.warn( "Domain '{}' is defined as strict domain and can therefore only accept predefined value", e.getDomainName()); throw e; } logger.debug("Adding value '{}' to domain '{}'", e.getValue(), e.getDomainName()); domain.getValues().add(e.getValue()); domainRepository.save(domain); verifyDomainValues(physicalDocumentApi, attributes); } physicalDocumentApi.setIndices(fixDataTypesOfIndices(physicalDocumentApi.getIndices(), attributes)); final String mimeType = investigateMimeType(physicalDocumentApi.getFileName()); final String hash = DigestUtils.sha256Hex(physicalDocumentApi.getContent()); final Document documentByHash = documentRepository.findByHash(hash); if (documentByHash != null) { throw new DocumentDuplicationException(documentByHash.getId(), hash); } IndexStore indexStore = new IndexStore(); updateIndices(physicalDocumentApi, indexStore); User user = getPrincipal(); Document document = new Document(hash, documentClassEntity, PAGE_NUMBER_NOT_RETRIEVABLE, mimeType, physicalDocumentApi.getFileName(), indexStore, user.getUsername()); document.setClient(clientRepository.findByShortName(physicalDocumentApi.getClient())); indexStore.setDocument(document); document = documentRepository.save(document); indexStoreRepository.save(indexStore); updateIndexMapEntries(toEntityMap(physicalDocumentApi.getIndices()), document); File target = new File(this.archiveDirectory, hash); try { FileUtils.writeByteArrayToFile(target, physicalDocumentApi.getContent()); } catch (IOException e) { logger.error("Unable to write file to store at {}", this.archiveDirectory.getAbsolutePath(), e); } try { final long size = Files.size(target.toPath()); document.setFileSize(size); final int numberOfPages = documentInspectorFactory.findDocumentInspector(mimeType) .retrievePageCount(target); document.setPageCount(numberOfPages); document = documentRepository.save(document); } catch (IOException e) { logger.error("Unable to calculate file size of file {}", target.getAbsolutePath(), e); } DocumentReference docRef = toDocumentReference(document, null); return docRef; } @Override @Transactional(propagation = REQUIRED, readOnly = false) @PreAuthorize("hasRole('ROLE_USER')") public DocumentReference updateIndices(DocumentReference reference) throws DocumentNotFoundException { DocumentReference documentReferenceApi = findDocumentReference(reference.getId()); documentReferenceApi.getIndices().putAll(reference.getIndices()); Document document = documentRepository.findOne(reference.getId()); ch.silviowangler.dox.domain.DocumentClass documentClassEntity = document.getDocumentClass(); List<Attribute> attributes = attributeRepository.findAttributesForDocumentClass(documentClassEntity); documentReferenceApi.setIndices(fixDataTypesOfIndices(documentReferenceApi.getIndices(), attributes)); updateIndices(documentReferenceApi, document.getIndexStore()); indexStoreRepository.save(document.getIndexStore()); updateIndexMapEntries(toEntityMap(documentReferenceApi.getIndices()), document); return findDocumentReference(reference.getId()); } @Override @Transactional(propagation = SUPPORTS, readOnly = true) @PreAuthorize("hasRole('ROLE_USER')") public Set<DocumentReference> retrieveAllDocumentReferences() { logger.info("Retrieving all document references from repository"); Iterable<Document> documents = documentRepository.findAll(); Set<DocumentReference> documentReferences = Sets.newHashSet(); for (Document document : documents) { logger.debug("Processing document {}", document); documentReferences.add(toDocumentReference(document, null)); } logger.info("Done retrieving all document references from repository. Fetched {} document references", documentReferences.size()); return documentReferences; } private void verifyDomainValues(PhysicalDocument physicalDocument, List<Attribute> attributes) throws ValueNotInDomainException { for (Attribute attribute : attributes) { final String attributeShortName = attribute.getShortName(); final TranslatableKey key = new TranslatableKey(attributeShortName); if (attribute.getDomain() != null && physicalDocument.getIndices().containsKey(key)) { final String attributeValue = String.valueOf(physicalDocument.getIndices().get(key).getValue()); logger.debug("Analyzing domain value on attribute '{}' for value '{}'", attributeShortName, attributeValue); if (!attribute.getDomain().getValues().contains(attributeValue)) { logger.error("Attribute '{}' belongs to a domain. This domain does not contain the value '{}'", attributeShortName, attributeValue); throw new ValueNotInDomainException("Value is not part of this domain", attributeValue, attribute.getDomain().getValues(), attribute.getDomain().getShortName()); } } } logger.info("All domains and their values have been respected"); } private String getStringRepresentation(Object indexValue) { String indexValueToStore; if (indexValue instanceof DateTime) { indexValueToStore = ((DateTime) indexValue).toString(DD_MM_YYYY); } else { indexValueToStore = String.valueOf(indexValue); } logger.debug("String representation of index value '{}' is '{}'", indexValue, indexValueToStore); return indexValueToStore; } private String investigateMimeType(final String fileName) { logger.debug("Trying to find media type (mime type) for file name '{}'", fileName); int i = fileName.lastIndexOf('.'); String extension = null; if (i > 0 && i < fileName.length() - 1) { extension = fileName.substring(i + 1).toLowerCase(); } if (this.mimeTypes.containsKey(extension)) { return (String) this.mimeTypes.get(extension); } logger.error("No media type (mime type) registered for file extension '{}' (original file name '{}')", extension, fileName); throw new UnsupportedOperationException("No mime type registered for file extension " + fileName); } private Map<TranslatableKey, DescriptiveIndex> fixDataTypesOfIndices( final Map<TranslatableKey, DescriptiveIndex> indexes, List<Attribute> attributes) { Map<TranslatableKey, DescriptiveIndex> resultMap = Maps.newHashMap(indexes); // copy elements for (Attribute attribute : attributes) { final TranslatableKey key = new TranslatableKey(attribute.getShortName()); if (resultMap.containsKey(key) && resultMap.get(key).getValue() != null) { if (!isAssignableType(attribute.getDataType(), resultMap.get(key).getValue().getClass())) { logger.debug("Attribute '{}' is not assignable to '{}'", key, attribute.getDataType()); resultMap.put(key, new DescriptiveIndex( makeAssignable(attribute.getDataType(), resultMap.get(key).getValue()))); } } else { logger.debug("Ignoring attribute '{}' since it was not mentioned in the index map", key); } } return resultMap; } private Map<String, Object> toEntityMap(final Map<TranslatableKey, DescriptiveIndex> indices) { Map<String, Object> entityMap = Maps.newHashMapWithExpectedSize(indices.size()); for (TranslatableKey key : indices.keySet()) { entityMap.put(key.getKey(), indices.get(key).getValue()); } return entityMap; } @SuppressWarnings("unchecked") private Object makeAssignable(AttributeDataType desiredDataType, Object valueToConvert) { if (valueToConvert instanceof ch.silviowangler.dox.api.Range && isRangeCompatible(desiredDataType)) { logger.debug("Found a range parameter. Skip this one"); if (DATE.equals(desiredDataType)) { ch.silviowangler.dox.api.Range<DateTime> original = (ch.silviowangler.dox.api.Range<DateTime>) valueToConvert; return new Range<>(original.getFrom(), original.getTo()); } else if (DOUBLE.equals(desiredDataType)) { ch.silviowangler.dox.api.Range<BigDecimal> original = (ch.silviowangler.dox.api.Range<BigDecimal>) valueToConvert; return new Range<>(original.getFrom(), original.getTo()); } else if (INTEGER.equals(desiredDataType)) { ch.silviowangler.dox.api.Range<Integer> original = (ch.silviowangler.dox.api.Range<Integer>) valueToConvert; return new Range<>(original.getFrom(), original.getTo()); } else if (LONG.equals(desiredDataType)) { ch.silviowangler.dox.api.Range<Long> original = (ch.silviowangler.dox.api.Range<Long>) valueToConvert; return new Range<>(original.getFrom(), original.getTo()); } else if (SHORT.equals(desiredDataType)) { ch.silviowangler.dox.api.Range<Short> original = (ch.silviowangler.dox.api.Range<Short>) valueToConvert; return new Range<>(original.getFrom(), original.getTo()); } throw new IllegalArgumentException(); } if (DATE.equals(desiredDataType) && valueToConvert instanceof String) { final String stringValueToConvert = (String) valueToConvert; String regexPattern; if (stringValueToConvert.matches("\\d{4}-\\d{2}-\\d{2}")) { regexPattern = YYYY_MM_DD; } else if (stringValueToConvert.matches("\\d{2}\\.\\d{2}\\.\\d{4}")) { regexPattern = DD_MM_YYYY; } else { logger.error("Unsupported format of a date string '{}'", stringValueToConvert); throw new UnsupportedOperationException("Unknown date format " + stringValueToConvert); } return DateTimeFormat.forPattern(regexPattern).parseDateTime(stringValueToConvert); } else if (DATE.equals(desiredDataType) && valueToConvert instanceof Date) { return new DateTime(valueToConvert); } else if (DOUBLE.equals(desiredDataType) && valueToConvert instanceof Double) { return BigDecimal.valueOf((Double) valueToConvert); } else if (DOUBLE.equals(desiredDataType) && valueToConvert instanceof String && ((String) valueToConvert).matches("(\\d.*|\\d.*\\.\\d{1,2})")) { return new BigDecimal((String) valueToConvert); } else if (DOUBLE.equals(desiredDataType) && valueToConvert instanceof Integer) { return BigDecimal.valueOf(Long.parseLong(String.valueOf(valueToConvert))); } else if (DOUBLE.equals(desiredDataType) && valueToConvert instanceof Long) { return BigDecimal.valueOf((Long) valueToConvert); } else if (CURRENCY.equals(desiredDataType) && valueToConvert instanceof String) { String value = (String) valueToConvert; return new AmountOfMoney(value); } else if (CURRENCY.equals(desiredDataType) && valueToConvert instanceof Money) { Money money = (Money) valueToConvert; return new AmountOfMoney(money.getCurrency(), money.getAmount()); } else if (CURRENCY.equals(desiredDataType) && valueToConvert instanceof Map) { Map money = (Map) valueToConvert; return new AmountOfMoney(Currency.getInstance((String) money.get("currency")), new BigDecimal((String) money.get("amount"))); } logger.error("Unable to convert data type '{}' and value '{}' (class: '{}')", new Object[] { desiredDataType, valueToConvert, valueToConvert.getClass().getCanonicalName() }); throw new IllegalArgumentException("Unable to convert data type '" + desiredDataType + "' and value '" + valueToConvert + "' (Target class: '" + valueToConvert.getClass().getCanonicalName() + "')"); } private boolean isRangeCompatible(AttributeDataType desiredDataType) { return Lists.newArrayList(DATE, DOUBLE, INTEGER, LONG, SHORT).contains(desiredDataType); } @SuppressWarnings("unchecked") private boolean isAssignableType(AttributeDataType desiredDataType, Class currentType) { if (desiredDataType == DATE) { return currentType.isAssignableFrom(DateTime.class); } else if (desiredDataType == STRING) { return currentType.isAssignableFrom(String.class); } else if (desiredDataType == DOUBLE) { return currentType.isAssignableFrom(BigDecimal.class); } else if (desiredDataType == CURRENCY) { return currentType.isAssignableFrom(AmountOfMoney.class); } else { logger.error("Unknown data type '{}'", desiredDataType); throw new IllegalArgumentException("Unknown data type " + desiredDataType); } } private void updateIndices(DocumentReference documentReference, IndexStore indexStore) { for (TranslatableKey key : documentReference.getIndices().keySet()) { Attribute attribute = attributeRepository.findByShortName(key.getKey()); assert attribute != null : "Attribute " + key + " must be there"; final Object value = documentReference.getIndices().get(key).getValue(); try { final String propertyName = attribute.getMappingColumn().toLowerCase(); logger.debug("About to set column '{}' using value '{}' on index store", propertyName, value); setFieldValue(indexStore, propertyName, value); } catch (IllegalAccessException | NoSuchFieldException e) { logger.error("Error setting property '{}' with value '{}'", new Object[] { key, value, e }); } } } private void verifyUnknownKeys(PhysicalDocument physicalDocument, String documentClassShortName, List<Attribute> attributes) throws ValidationException { for (TranslatableKey key : physicalDocument.getIndices().keySet()) { boolean exists = false; for (Attribute attribute : attributes) { if (attribute.getShortName().equals(key.getKey())) { exists = true; continue; } } if (!exists) { logger.warn("Key '{}' does not belong to document class '{}'", documentClassShortName); throw new ValidationException( "Key " + key + " does not belong to document class " + documentClassShortName); } } } private void verifyMandatoryAttributes(PhysicalDocument physicalDocument, List<Attribute> attributes) throws ValidationException { for (Attribute attribute : attributes) { if (!attribute.isOptional()) { logger.trace("Analyzing mandatory attribute '{}'", attribute.getShortName()); if (!physicalDocument.getIndices().containsKey(new TranslatableKey(attribute.getShortName()))) { logger.warn("Attribute '{}' is required for document class(es) '{}' and not was not provided.", attribute.getShortName(), attribute.getDocumentClasses()); throw new ValidationException("Attribute " + attribute.getShortName() + " is mandatory"); } } } } private boolean documentReferenceExists(Long id) { return documentRepository.exists(id); } private DocumentReference toDocumentReference(Document document, Locale locale) { final DocumentReference documentReference = new DocumentReference(document.getHash(), document.getId(), document.getPageCount(), document.getMimeType(), toDocumentClassApi(document.getDocumentClass()), toIndexMap(document.getIndexStore(), attributeRepository.findAttributesForDocumentClass(document.getDocumentClass()), locale), document.getOriginalFilename(), document.getUserReference(), document.getFileSize()); documentReference.setCreationDate(document.getCreationDate()); documentReference.setClient(document.getClient().getShortName()); return documentReference; } private Map<TranslatableKey, DescriptiveIndex> toIndexMap(IndexStore indexStore, List<Attribute> attributes, Locale locale) { Map<TranslatableKey, DescriptiveIndex> indices = newHashMapWithExpectedSize(attributes.size()); for (Attribute attribute : attributes) { DescriptiveIndex index = new DescriptiveIndex(); index.setAttribute(toAttributeApi(attribute)); try { final String fieldName = attribute.getMappingColumn(); final Object propertyValue = getFieldValue(indexStore, fieldName); final TranslatableKey key = new TranslatableKey(attribute.getShortName()); if (attribute.getDataType() == CURRENCY) { AmountOfMoney amountOfMoney = (AmountOfMoney) propertyValue; if (locale == null) { index.setValue((amountOfMoney == null) ? null : new Money(amountOfMoney.getCurrency(), amountOfMoney.getAmount())); } else { index.setValue((amountOfMoney == null) ? null : amountOfMoney.getCurrency() + " " + amountOfMoney.getAmount()); } } else { index.setValue(propertyValue); } indices.put(key, index); } catch (IllegalAccessException | NoSuchFieldException e) { logger.error("Error setting property '{}'", attribute.getShortName(), e); } } return indices; } private Object getFieldValue(IndexStore indexStore, String fieldName) throws NoSuchFieldException, IllegalAccessException { final Field field = indexStore.getClass().getDeclaredField(fieldName.toUpperCase()); field.setAccessible(true); return field.get(indexStore); } private void setFieldValue(IndexStore indexStore, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { final Field field = indexStore.getClass().getDeclaredField(fieldName.toUpperCase()); field.setAccessible(true); if (field.getType() == LocalDate.class && value.getClass() == DateTime.class) { field.set(indexStore, ((DateTime) value).toLocalDate()); } else { field.set(indexStore, value); } } private ch.silviowangler.dox.api.DocumentClass toDocumentClassApi( ch.silviowangler.dox.domain.DocumentClass documentClass) { ch.silviowangler.dox.api.DocumentClass docClassApi = new ch.silviowangler.dox.api.DocumentClass( documentClass.getShortName()); docClassApi.setClient(documentClass.getClient().getShortName()); return docClassApi; } private DocumentClass toDocumentClassWithAttributesApi( ch.silviowangler.dox.domain.DocumentClass documentClass) { return new DocumentClass(documentClass.getShortName(), documentClass.getClient().getShortName()); } private Map<String, Attribute> toAttributeMap(List<Attribute> attributes) { Map<String, Attribute> map = newHashMapWithExpectedSize(attributes.size()); for (Attribute attribute : attributes) { map.put(attribute.getShortName(), attribute); } return map; } private SortedSet<ch.silviowangler.dox.api.Attribute> toAttributeApi(List<Attribute> attributes) { SortedSet<ch.silviowangler.dox.api.Attribute> result = new TreeSet<>(); for (Attribute attribute : attributes) { ch.silviowangler.dox.api.Attribute attr = toAttributeApi(attribute); result.add(attr); } return result; } private ch.silviowangler.dox.api.Attribute toAttributeApi(Attribute attribute) { return new ch.silviowangler.dox.api.Attribute(attribute.getShortName(), attribute.isOptional(), attribute.getDomain() != null ? toDomainApi(attribute.getDomain()) : null, ch.silviowangler.dox.api.AttributeDataType.valueOf(attribute.getDataType().toString()), attribute.isUpdateable(), attribute.getMappingColumn()); } private Domain toDomainApi(ch.silviowangler.dox.domain.Domain domain) { Domain domainApi = new Domain(); domainApi.setShortName(domain.getShortName()); for (String domainValue : domain.getValues()) { domainApi.getValues().add(domainValue); } return domainApi; } private ch.silviowangler.dox.domain.DocumentClass findDocumentClass(String documentClassShortName) throws DocumentClassNotFoundException { ch.silviowangler.dox.domain.DocumentClass documentClassEntity = documentClassRepository .findByShortName(documentClassShortName); if (documentClassEntity == null) { logger.error("No such document class with name '{}' found", documentClassShortName); throw new DocumentClassNotFoundException(documentClassShortName); } return documentClassEntity; } private void updateIndexMapEntries(final Map<String, Object> indices, Document document) { List<IndexMapEntry> indexMapEntries = indexMapEntryRepository.findByDocument(document); indexMapEntryRepository.delete(indexMapEntries); for (String key : indices.keySet()) { final Object value = indices.get(key); String valueToStore = getStringRepresentation(value); IndexMapEntry indexMapEntry = new IndexMapEntry(key, valueToStore.toUpperCase(), document); indexMapEntryRepository.save(indexMapEntry); } } }