/*******************************************************************************
* Copyright (c) 2010-2014 SAP AG and others.
* 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
*
* Contributors:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.skalli.services.entity;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import org.eclipse.skalli.model.EntityBase;
import org.eclipse.skalli.model.EntityFilter;
import org.eclipse.skalli.model.Issue;
import org.eclipse.skalli.model.Severity;
import org.eclipse.skalli.model.ValidationException;
import org.eclipse.skalli.services.event.EventService;
import org.eclipse.skalli.services.extension.DataMigration;
import org.eclipse.skalli.services.persistence.PersistenceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base implementation of an entity service.
*
* Derived classes must implement the {@link #getEntityClass()} method returning the class
* of the supported entity type and should provide a validation mechanism by overwriting
* {@link #validateEntity(EntityBase, Severity)}.
* <p>
* <b>IMPORTANT NOTE:</b><br>
* Derived classes must declare the following references in their service component descriptors:
* <pre>
* <reference
* name="PersistenceService"
* cardinality="1..1"
* interface="org.eclipse.skalli.services.persistence.PersistenceService"
* policy="dynamic"
* bind="bindPersistenceService"
* unbind="unbindPersistenceService" />
* <reference
* name="EventService"
* interface="org.eclipse.skalli.services.event.EventService"
* cardinality="0..1"
* policy="dynamic"
* bind="bindEventService"
* unbind="unbindEventService" />
* </pre>
* The second reference is optional if the entity service does not want to send {@link EventEntityUpdate update
* events} when an entity is {@link #persist(EntityBase, String) persisted}.
*/
public abstract class EntityServiceBase<T extends EntityBase> implements EntityService<T> {
private static final Logger LOG = LoggerFactory.getLogger(EntityServiceBase.class);
private PersistenceService persistenceService;
private EventService eventService;
protected void bindPersistenceService(PersistenceService persistenceService) {
this.persistenceService = persistenceService;
LOG.info(MessageFormat.format("bindPersistenceService({0})", persistenceService)); //$NON-NLS-1$
}
protected void unbindPersistenceService(PersistenceService persistenceService) {
LOG.info(MessageFormat.format("unbindPersistenceService({0})", persistenceService)); //$NON-NLS-1$
this.persistenceService = null;
}
protected void bindEventService(EventService eventService) {
this.eventService = eventService;
LOG.info(MessageFormat.format("bindEventService({0})", eventService)); //$NON-NLS-1$
}
protected void unbindEventService(EventService eventService) {
LOG.info(MessageFormat.format("unbindEventService({0})", eventService)); //$NON-NLS-1$
this.eventService = null;
}
/**
* Returns the (required) persistence service the entity service should use
* to retrieve or store entities.
*
* @throws IllegalStateException if no persistence service is registered.
*/
protected PersistenceService getPersistenceService() {
if (persistenceService == null) {
throw new IllegalStateException(MessageFormat.format(
"ERROR: No implementation for required service {0} registered", PersistenceService.class.getName()));
}
return persistenceService;
}
/**
* Ensures that an entity can be persisted. Throws <code>ValidationException</code>
* if the entity has {@link Severity#FATAL fatal} issues.
*
* @param entity the entity to validate.
*
* @throws ValidationException if the entity has fatal issues that prevents
* the entity from being persisted.
*/
protected abstract void validateEntity(T entity) throws ValidationException;
/**
* Validates an entity with its default validators.
*
* @param entity the entity to validate.
* @param minSeverity the minimal severity of issues to report.
* @return a set of issues, or an empty set.
*/
protected abstract SortedSet<Issue> validateEntity(T entity, Severity minSeverity);
@Override
public Set<UUID> keySet() {
return getPersistenceService().keySet(getEntityClass());
}
@Override
public T getByUUID(UUID uuid) {
return getPersistenceService().getEntity(getEntityClass(), uuid);
}
@Override
public List<T> getAll() {
return getPersistenceService().getEntities(getEntityClass());
}
public List<T> getAll(EntityFilter<T> filter) {
return getPersistenceService().getEntities(getEntityClass(), filter);
}
@Override
public int size() {
return getPersistenceService().size(getEntityClass());
}
@Override
public synchronized void persist(T entity, String userId) throws ValidationException {
if (entity.getUuid() == null) {
entity.setUuid(UUID.randomUUID());
}
validateEntity(entity);
getPersistenceService().persist(getEntityClass(), entity, userId);
if (eventService != null) {
eventService.fireEvent(new EventEntityUpdate(getEntityClass(), entity, userId));
}
}
@Override
public T loadEntity(Class<T> entityClass, UUID uuid) {
return getPersistenceService().loadEntity(entityClass, uuid);
}
@Override
public Map<String, Class<?>> getAliases() {
return new HashMap<String, Class<?>>();
}
@Override
public Set<ClassLoader> getClassLoaders() {
return new HashSet<ClassLoader>();
}
@Override
public Set<DataMigration> getMigrations() {
return new HashSet<DataMigration>();
}
@Override
public SortedSet<Issue> validate(T entity, Severity minSeverity) {
SortedSet<Issue> issues = new TreeSet<Issue>();
issues.addAll(validateEntity(entity, minSeverity));
validateIssues(entity.getUuid(), minSeverity, issues);
return issues;
}
@Override
public SortedSet<Issue> validateAll(Severity minSeverity) {
SortedSet<Issue> issues = new TreeSet<Issue>();
for (T entity : getAll()) {
issues.addAll(validateEntity(entity, minSeverity));
validateIssues(entity.getUuid(), minSeverity, issues);
}
return issues;
}
/**
* Validates the given set of issues. Ensures that all entries apply to the given
* <code>entityId</code> and have a severity equal or greater than the given
* <code>minSeverity</code>. Removes invalid entries from the issue set and logs
* the cause. Sets the timestamp of the issues to the current system time unless
* already defined.
*
* @param entityId the unique identifier of the entity that caused the given issues.
* @param minSeverity the minimal severity of issues to report.
* @param issues set of issues to validate.
*/
protected void validateIssues(UUID entityId, Severity minSeverity, SortedSet<Issue> issues) {
ArrayList<Issue> invalidIssues = new ArrayList<Issue>();
for (Issue issue : issues) {
// don't accept issues for other entities
if (!issue.getEntityId().equals(entityId)) {
invalidIssues.add(issue);
LOG.warn(MessageFormat.format("Invalid issue detected (requested entity={0} but found entity={1})",
entityId, issue.getEntityId()));
}
// we cannot guarantee that 3rd-party validators honor minSeverity, so
// to be sure we filter out issues with a severity less than minSeverity
if (minSeverity.compareTo(issue.getSeverity()) < 0) {
invalidIssues.add(issue);
LOG.warn(MessageFormat.format(
"Invalid issue detected (requested minSeverity={0} but found severity={1})", minSeverity,
issue.getSeverity()));
}
// if the issue has no timestamp, set the current system time
long timestamp = issue.getTimestamp();
if (timestamp <= 0) {
issue.setTimestamp(System.currentTimeMillis());
}
}
// remove invalid issues from the result and log them
for (Issue invalidIssue : invalidIssues) {
issues.remove(invalidIssue);
}
}
}