Feature target metadata (#757)

* Defined the model for target matadata and the corresponding repository layer/management
* Added target metadata quotas incl enforcement
* Extended Target Mgmt REST API to allow for metadata CRUD operations
* Added migration scripts for each database
* Added back reference to target metadata in JpaTarget
* Added tests for target management, Mgmt REST API, target metadata RSQL, and REST documentation
* Updated asciidocs for target rest documentation
* Fix Allure imports and annotations
* Fix review findings

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com>
Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>
This commit is contained in:
Bondar Bogdan
2018-10-29 11:28:34 +01:00
committed by Stefan Behl
parent 1cbde47370
commit 0cf4f8e8b9
37 changed files with 1886 additions and 216 deletions

View File

@@ -457,7 +457,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<DistributionSetMetadata> createMetaData(final long dsId, final Collection<MetaData> md) {
md.forEach(meta -> checkAndThrowAlreadyIfDistributionSetMetadataExists(
md.forEach(meta -> checkAndThrowIfDistributionSetMetadataAlreadyExists(
new DsMetadataCompositeKey(dsId, meta.getKey())));
assertMetaDataQuota(dsId, md.size());
@@ -478,8 +478,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
private void assertSoftwareModuleQuota(final Long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxSoftwareModulesPerDistributionSet(),
SoftwareModule.class, DistributionSet.class,
softwareModuleRepository::countByAssignedToId);
SoftwareModule.class, DistributionSet.class, softwareModuleRepository::countByAssignedToId);
}
@Override
@@ -676,7 +675,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
return distributionSetRepository.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable);
}
private void checkAndThrowAlreadyIfDistributionSetMetadataExists(final DsMetadataCompositeKey metadataId) {
private void checkAndThrowIfDistributionSetMetadataAlreadyExists(final DsMetadataCompositeKey metadataId) {
if (distributionSetMetadataRepository.exists(metadataId)) {
throw new EntityAlreadyExistsException(
"Metadata entry with key '" + metadataId.getKey() + "' already exists");

View File

@@ -26,6 +26,7 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTagBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetBuilder;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
@@ -57,10 +58,15 @@ public class JpaEntityFactory implements EntityFactory {
private SoftwareModuleMetadataBuilder softwareModuleMetadataBuilder;
@Override
public MetaData generateMetadata(final String key, final String value) {
public MetaData generateDsMetadata(final String key, final String value) {
return new JpaDistributionSetMetadata(key, StringUtils.trimWhitespace(value));
}
@Override
public MetaData generateTargetMetadata(final String key, final String value) {
return new JpaTargetMetadata(key, StringUtils.trimWhitespace(value));
}
@Override
public DistributionSetTypeBuilder distributionSetType() {
return distributionSetTypeBuilder;

View File

@@ -26,13 +26,16 @@ import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.repository.FilterParams;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetMetadataFields;
import org.eclipse.hawkbit.repository.TimestampCalculator;
import org.eclipse.hawkbit.repository.builder.TargetCreate;
import org.eclipse.hawkbit.repository.builder.TargetUpdate;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetCreate;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetUpdate;
@@ -40,15 +43,21 @@ import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -83,8 +92,12 @@ public class JpaTargetManagement implements TargetManagement {
private final EntityManager entityManager;
private final QuotaManagement quotaManagement;
private final TargetRepository targetRepository;
private final TargetMetadataRepository targetMetadataRepository;
private final RolloutGroupRepository rolloutGroupRepository;
private final DistributionSetRepository distributionSetRepository;
@@ -107,7 +120,8 @@ public class JpaTargetManagement implements TargetManagement {
private final Database database;
JpaTargetManagement(final EntityManager entityManager, final TargetRepository targetRepository,
JpaTargetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository,
final DistributionSetRepository distributionSetRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
@@ -116,7 +130,9 @@ public class JpaTargetManagement implements TargetManagement {
final TenantAware tenantAware, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) {
this.entityManager = entityManager;
this.quotaManagement = quotaManagement;
this.targetRepository = targetRepository;
this.targetMetadataRepository = targetMetadataRepository;
this.rolloutGroupRepository = rolloutGroupRepository;
this.distributionSetRepository = distributionSetRepository;
this.targetFilterQueryRepository = targetFilterQueryRepository;
@@ -135,6 +151,11 @@ public class JpaTargetManagement implements TargetManagement {
return targetRepository.findByControllerId(controllerId);
}
private JpaTarget getByControllerIdAndThrowIfNotFound(final String controllerId) {
return targetRepository.findByControllerId(controllerId).map(JpaTarget.class::cast)
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
}
@Override
public List<Target> getByControllerID(final Collection<String> controllerIDs) {
return Collections.unmodifiableList(
@@ -146,6 +167,121 @@ public class JpaTargetManagement implements TargetManagement {
return targetRepository.count();
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<TargetMetadata> createMetaData(final String controllerId, final Collection<MetaData> md) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
md.forEach(meta -> checkAndThrowIfTargetMetadataAlreadyExists(
new TargetMetadataCompositeKey(target.getId(), meta.getKey())));
assertMetaDataQuota(target.getId(), md.size());
final JpaTarget updatedTarget = touch(target);
return Collections.unmodifiableList(md.stream()
.map(meta -> targetMetadataRepository
.save(new JpaTargetMetadata(meta.getKey(), meta.getValue(), updatedTarget)))
.collect(Collectors.toList()));
}
private void checkAndThrowIfTargetMetadataAlreadyExists(final TargetMetadataCompositeKey metadataId) {
if (targetMetadataRepository.exists(metadataId)) {
throw new EntityAlreadyExistsException(
"Metadata entry with key '" + metadataId.getKey() + "' already exists");
}
}
private void assertMetaDataQuota(final Long targetId, final int requested) {
QuotaHelper.assertAssignmentQuota(targetId, requested, quotaManagement.getMaxMetaDataEntriesPerTarget(),
TargetMetadata.class, Target.class, targetMetadataRepository::countByTargetId);
}
private JpaTarget touch(final JpaTarget target) {
// merge base target so optLockRevision gets updated and audit
// log written because modifying metadata is modifying the base
// target itself for auditing purposes.
final JpaTarget result = entityManager.merge(target);
result.setLastModifiedAt(0L);
return targetRepository.save(result);
}
private JpaTarget touch(final String controllerId) {
return touch(getByControllerIdAndThrowIfNotFound(controllerId));
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public TargetMetadata updateMetaData(final String controllerId, final MetaData md) {
// check if exists otherwise throw entity not found exception
final JpaTargetMetadata toUpdate = (JpaTargetMetadata) getMetaDataByControllerId(controllerId, md.getKey())
.orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, md.getKey()));
toUpdate.setValue(md.getValue());
// touch it to update the lock revision because we are modifying the
// target indirectly
touch(controllerId);
return targetMetadataRepository.save(toUpdate);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteMetaData(final String controllerId, final String key) {
final JpaTargetMetadata metadata = (JpaTargetMetadata) getMetaDataByControllerId(controllerId, key)
.orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, key));
touch(controllerId);
targetMetadataRepository.delete(metadata.getId());
}
@Override
public Page<TargetMetadata> findMetaDataByControllerId(final Pageable pageable, final String controllerId) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
return convertMdPage(
targetMetadataRepository
.findAll(
(Specification<JpaTargetMetadata>) (root, query, cb) -> cb
.equal(root.get(JpaTargetMetadata_.target).get(JpaTarget_.id), targetId),
pageable),
pageable);
}
private static Page<TargetMetadata> convertMdPage(final Page<JpaTargetMetadata> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
}
@Override
public Page<TargetMetadata> findMetaDataByControllerIdAndRsql(final Pageable pageable, final String controllerId,
final String rsqlParam) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
final Specification<JpaTargetMetadata> spec = RSQLUtility.parse(rsqlParam, TargetMetadataFields.class,
virtualPropertyReplacer, database);
return convertMdPage(targetMetadataRepository.findAll((Specification<JpaTargetMetadata>) (root, query, cb) -> cb
.and(cb.equal(root.get(JpaTargetMetadata_.target).get(JpaTarget_.id), targetId),
spec.toPredicate(root, query, cb)),
pageable), pageable);
}
@Override
public Optional<TargetMetadata> getMetaDataByControllerId(final String controllerId, final String key) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
return Optional.ofNullable(targetMetadataRepository.findOne(new TargetMetadataCompositeKey(targetId, key)));
}
@Override
public Slice<Target> findAll(final Pageable pageable) {
return convertPage(criteriaNoCountDao.findAll(pageable, JpaTarget.class), pageable);
@@ -178,8 +314,7 @@ public class JpaTargetManagement implements TargetManagement {
public Target update(final TargetUpdate u) {
final JpaTargetUpdate update = (JpaTargetUpdate) u;
final JpaTarget target = (JpaTarget) targetRepository.findByControllerId(update.getControllerId())
.orElseThrow(() -> new EntityNotFoundException(Target.class, update.getControllerId()));
final JpaTarget target = getByControllerIdAndThrowIfNotFound(update.getControllerId());
update.getName().ifPresent(target::setName);
update.getDescription().ifPresent(target::setDescription);
@@ -214,8 +349,7 @@ public class JpaTargetManagement implements TargetManagement {
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteByControllerID(final String controllerID) {
final Target target = targetRepository.findByControllerId(controllerID)
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerID));
final Target target = getByControllerIdAndThrowIfNotFound(controllerID);
targetRepository.delete(target.getId());
}
@@ -418,8 +552,7 @@ public class JpaTargetManagement implements TargetManagement {
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Target unAssignTag(final String controllerID, final long targetTagId) {
final JpaTarget target = (JpaTarget) targetRepository.findByControllerId(controllerID)
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerID));
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerID);
final TargetTag tag = targetTagRepository.findById(targetTagId)
.orElseThrow(() -> new EntityNotFoundException(TargetTag.class, targetTagId));
@@ -635,16 +768,14 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public Map<String, String> getControllerAttributes(final String controllerId) {
final JpaTarget target = (JpaTarget) getByControllerID(controllerId)
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
return target.getControllerAttributes();
}
@Override
public void requestControllerAttributes(final String controllerId) {
final JpaTarget target = (JpaTarget) getByControllerID(controllerId)
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
target.setRequestControllerAttributes(true);
@@ -655,8 +786,7 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public boolean isControllerAttributesRequested(final String controllerId) {
final JpaTarget target = (JpaTarget) getByControllerID(controllerId)
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
return target.isRequestControllerAttributes();
}

View File

@@ -449,7 +449,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
TargetManagement targetManagement(final EntityManager entityManager, final TargetRepository targetRepository,
TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository,
final DistributionSetRepository distributionSetRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
@@ -457,10 +458,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final ApplicationEventPublisher eventPublisher, final ApplicationContext applicationContext,
final TenantAware tenantAware, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties) {
return new JpaTargetManagement(entityManager, targetRepository, rolloutGroupRepository,
distributionSetRepository, targetFilterQueryRepository, targetTagRepository, criteriaNoCountDao,
eventPublisher, applicationContext, tenantAware, afterCommit, virtualPropertyReplacer,
properties.getDatabase());
return new JpaTargetManagement(entityManager, quotaManagement, targetRepository, targetMetadataRepository,
rolloutGroupRepository, distributionSetRepository, targetFilterQueryRepository, targetTagRepository,
criteriaNoCountDao, eventPublisher, applicationContext, tenantAware, afterCommit,
virtualPropertyReplacer, properties.getDatabase());
}
/**

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH 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
*/
package org.eclipse.hawkbit.repository.jpa;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link TargetMetadata} repository.
*/
@Transactional(readOnly = true)
public interface TargetMetadataRepository
extends PagingAndSortingRepository<JpaTargetMetadata, TargetMetadataCompositeKey>,
JpaSpecificationExecutor<JpaTargetMetadata> {
/**
* Counts the meta data entries that match the given target ID.
*
* @param id
* of the target.
*
* @return The number of matching meta data entries.
*/
long countByTargetId(@Param("id") Long id);
}

View File

@@ -54,6 +54,7 @@ import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
@@ -167,6 +168,10 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@Column(name = "request_controller_attributes", nullable = false)
private boolean requestControllerAttributes = true;
@CascadeOnDelete
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, targetEntity = JpaTargetMetadata.class)
private List<TargetMetadata> metadata;
/**
* Constructor.
*
@@ -351,6 +356,14 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return requestControllerAttributes;
}
public List<TargetMetadata> getMetadata() {
if (metadata == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(metadata);
}
@Override
public String toString() {
return "JpaTarget [controllerId=" + controllerId + ", revision=" + getOptLockRevision() + ", id=" + getId()

View File

@@ -0,0 +1,109 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH 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
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
/**
* Meta data for {@link Target}.
*
*/
@IdClass(TargetMetadataCompositeKey.class)
@Entity
@Table(name = "sp_target_metadata")
public class JpaTargetMetadata extends AbstractJpaMetaData implements TargetMetadata {
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "target_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_target"))
private JpaTarget target;
public JpaTargetMetadata() {
// default public constructor for JPA
}
/**
* Creates a single metadata entry with the given key and value.
*
* @param key
* of the meta data entry
* @param value
* of the meta data entry
*/
public JpaTargetMetadata(final String key, final String value) {
super(key, value);
}
/**
* Creates a single metadata entry with the given key and value for the
* given {@link Target}.
*
* @param key
* of the meta data entry
* @param value
* of the meta data entry
* @param target
* the meta data entry is associated with
*/
public JpaTargetMetadata(final String key, final String value, final Target target) {
super(key, value);
this.target = (JpaTarget) target;
}
public TargetMetadataCompositeKey getId() {
return new TargetMetadataCompositeKey(target.getId(), getKey());
}
public void setTarget(final Target target) {
this.target = (JpaTarget) target;
}
@Override
public Target getTarget() {
return target;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((target == null) ? 0 : target.hashCode());
return result;
}
@Override
// exception squid:S2259 - obj is checked for null in super
@SuppressWarnings("squid:S2259")
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
final JpaTargetMetadata other = (JpaTargetMetadata) obj;
if (target == null) {
if (other.target != null) {
return false;
}
} else if (!target.equals(other.target)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH 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
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serializable;
import java.util.Objects;
/**
* The Target Metadata composite key which contains the meta data key and the ID
* of the Target itself.
*
*/
public final class TargetMetadataCompositeKey implements Serializable {
private static final long serialVersionUID = 1L;
private String key;
private Long target;
public TargetMetadataCompositeKey() {
// Default constructor for JPA.
}
/**
* @param target
* the target Id for this meta data
* @param key
* the key of the meta data
*/
public TargetMetadataCompositeKey(final Long target, final String key) {
this.target = target;
this.key = key;
}
public String getKey() {
return key;
}
public void setKey(final String key) {
this.key = key;
}
public Long getTargetId() {
return target;
}
public void setTargetId(final Long target) {
this.target = target;
}
@Override
public int hashCode() {
return Objects.hash(target, key);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TargetMetadataCompositeKey other = (TargetMetadataCompositeKey) obj;
if (target == null) {
if (other.target != null) {
return false;
}
} else if (!target.equals(other.target)) {
return false;
}
if (key == null) {
if (other.key != null) {
return false;
}
} else if (!key.equals(other.key)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,9 @@
CREATE TABLE sp_target_metadata
(
meta_key VARCHAR(128) NOT NULL,
meta_value VARCHAR(4000),
target_id BIGINT NOT NULL,
PRIMARY KEY (meta_key, target_id)
);
ALTER TABLE sp_target_metadata ADD CONSTRAINT fk_metadata_target FOREIGN KEY (target_id) REFERENCES sp_target (id) ON DELETE CASCADE;

View File

@@ -0,0 +1,12 @@
create table sp_target_metadata (
meta_key varchar(128) not null,
meta_value varchar(4000),
target_id bigint not null,
primary key (target_id, meta_key)
);
alter table sp_target_metadata
add constraint fk_metadata_target
foreign key (target_id)
references sp_target
on delete cascade;

View File

@@ -0,0 +1,12 @@
create table sp_target_metadata (
meta_key varchar(128) not null,
meta_value varchar(4000),
target_id bigint not null,
primary key (target_id, meta_key)
);
alter table sp_target_metadata
add constraint fk_metadata_target
foreign key (target_id)
references sp_target (id)
on delete cascade;

View File

@@ -0,0 +1,9 @@
CREATE TABLE sp_target_metadata
(
meta_key VARCHAR(128) NOT NULL,
meta_value VARCHAR(4000) NULL,
target_id NUMERIC(19) NOT NULL,
PRIMARY KEY (meta_key, target_id)
);
ALTER TABLE sp_target_metadata ADD CONSTRAINT fk_metadata_target FOREIGN KEY (target_id) REFERENCES sp_target (id) ON DELETE CASCADE;