JpaSoftwareModule#metadata made map (#2412)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-05-21 16:59:25 +03:00
committed by GitHub
parent 452d8618d7
commit 64ee8b9906
37 changed files with 413 additions and 895 deletions

View File

@@ -26,7 +26,6 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTagBuilder;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.springframework.validation.annotation.Validated;
/**

View File

@@ -11,8 +11,8 @@ package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.repository.model.Action.Status.DOWNLOADED;
import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED;
import static org.eclipse.hawkbit.repository.model.Target.CONTROLLER_ATTRIBUTE_KEY_SIZE;
import static org.eclipse.hawkbit.repository.model.Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE;
import static org.eclipse.hawkbit.repository.model.Target.CONTROLLER_ATTRIBUTE_MAX_KEY_SIZE;
import static org.eclipse.hawkbit.repository.model.Target.CONTROLLER_ATTRIBUTE_MAX_VALUE_SIZE;
import java.net.URI;
import java.time.Duration;
@@ -618,11 +618,11 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
}
private static boolean isAttributeKeyValid(final String key) {
return key != null && key.length() <= CONTROLLER_ATTRIBUTE_KEY_SIZE && PATTERN.matcher(key).matches();
return key != null && key.length() <= CONTROLLER_ATTRIBUTE_MAX_KEY_SIZE && PATTERN.matcher(key).matches();
}
private static boolean isAttributeValueValid(final String value) {
return value == null || (value.length() <= CONTROLLER_ATTRIBUTE_VALUE_SIZE && PATTERN.matcher(value).matches());
return value == null || (value.length() <= CONTROLLER_ATTRIBUTE_MAX_VALUE_SIZE && PATTERN.matcher(value).matches());
}
private static void copy(final Map<String, String> src, final Map<String, String> trg) {

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.repository.jpa.management;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -19,7 +18,6 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jakarta.persistence.EntityManager;
@@ -29,7 +27,6 @@ import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields;
import org.eclipse.hawkbit.repository.builder.GenericSoftwareModuleMetadataUpdate;
import org.eclipse.hawkbit.repository.builder.GenericSoftwareModuleUpdate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleCreate;
@@ -272,53 +269,61 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<SoftwareModuleMetadata> putMetaData(final Collection<SoftwareModuleMetadataCreate> create) {
if (!create.isEmpty()) {
// check if all metadata entries refer to the same software module
final Long id = ((JpaSoftwareModuleMetadataCreate) create.iterator().next()).getSoftwareModuleId();
if (createJpaMetadataCreateStream(create).allMatch(c -> id.equals(c.getSoftwareModuleId()))) {
assertSoftwareModuleExists(id);
assertMetaDataQuota(id, create.size());
// touch to update revision and last modified timestamp
JpaManagementHelper.touch(entityManager, softwareModuleRepository, (JpaSoftwareModule) get(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id)));
return createJpaMetadataCreateStream(create).map(this::saveMetadata).toList();
} else {
// group by software module id to minimize database access
final Map<Long, List<JpaSoftwareModuleMetadataCreate>> groups = createJpaMetadataCreateStream(create)
.collect(Collectors.groupingBy(JpaSoftwareModuleMetadataCreate::getSoftwareModuleId));
return groups.entrySet().stream().flatMap(e -> {
final List<JpaSoftwareModuleMetadataCreate> group = e.getValue();
public void createMetadata(final Collection<SoftwareModuleMetadataCreate> create) {
// group by software module id to minimize database access
create.stream()
.map(JpaSoftwareModuleMetadataCreate.class::cast)
.collect(Collectors.groupingBy(JpaSoftwareModuleMetadataCreate::getSoftwareModuleId))
.forEach((id, createsForSoftwareModule) -> {
assertSoftwareModuleExists(id);
assertMetaDataQuota(e.getKey(), group.size());
assertMetadataQuota(id, createsForSoftwareModule.size());
// touch to update revision and last modified timestamp
JpaManagementHelper.touch(entityManager, softwareModuleRepository, (JpaSoftwareModule) get(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id)));
return group.stream().map(this::saveMetadata);
}).toList();
}
}
JpaManagementHelper.touch(
entityManager, softwareModuleRepository,
(JpaSoftwareModule) get(id).orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id)));
createsForSoftwareModule.forEach(this::saveMetadata);
});
}
return Collections.emptyList();
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public List<SoftwareModuleMetadata> getMetadata(final long id) {
assertSoftwareModuleExists(id);
return (List)softwareModuleMetadataRepository.findAll(metadataBySoftwareModuleIdSpec(id));
}
@Override
public SoftwareModuleMetadata getMetadata(final long id, final String key) {
assertSoftwareModuleExists(id);
return findMetadata(id, key).orElseThrow(() -> new EntityNotFoundException("SoftwareModuleMetadata", id + ":" + key));
}
@Override
public Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleIdAndTargetVisible(final Pageable pageable, final long id) {
assertSoftwareModuleExists(id);
return JpaManagementHelper.convertPage(softwareModuleMetadataRepository.findBySoftwareModuleIdAndTargetVisible(
id, true, PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT)), pageable);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModuleMetadata updateMetaData(final SoftwareModuleMetadataCreate c) {
public SoftwareModuleMetadata updateMetadata(final SoftwareModuleMetadataCreate c) {
final JpaSoftwareModuleMetadataCreate create = (JpaSoftwareModuleMetadataCreate) c;
final Long id = create.getSoftwareModuleId();
assertSoftwareModuleExists(id);
assertMetaDataQuota(id, 1);
assertMetadataQuota(id, 1);
// touch to update revision and last modified timestamp
JpaManagementHelper.touch(entityManager, softwareModuleRepository, (JpaSoftwareModule) get(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id)));
JpaManagementHelper.touch(
entityManager, softwareModuleRepository,
(JpaSoftwareModule) get(id).orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id)));
return saveMetadata(create);
}
@@ -326,13 +331,13 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModuleMetadata updateMetaData(final SoftwareModuleMetadataUpdate u) {
public SoftwareModuleMetadata updateMetadata(final SoftwareModuleMetadataUpdate u) {
final GenericSoftwareModuleMetadataUpdate update = (GenericSoftwareModuleMetadataUpdate) u;
// check if exists otherwise throw entity not found exception
final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) findMetaDataBySoftwareModuleId(
final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) findMetadata(
update.getSoftwareModuleId(), update.getKey())
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleMetadata.class, update.getSoftwareModuleId(), update.getKey()));
.orElseThrow(() -> new EntityNotFoundException("SoftwareModuleMetadata", update.getSoftwareModuleId() + ":" + update.getKey()));
update.getValue().ifPresent(metadata::setValue);
update.isTargetVisible().ifPresent(metadata::setTargetVisible);
@@ -345,9 +350,9 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteMetaData(final long id, final String key) {
final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) findMetaDataBySoftwareModuleId0(id, key)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleMetadata.class, id, key));
public void deleteMetadata(final long id, final String key) {
final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) findMetadata(id, key)
.orElseThrow(() -> new EntityNotFoundException("SoftwareModuleMetadata", id + ":" + key));
JpaManagementHelper.touch(entityManager, softwareModuleRepository, metadata.getSoftwareModule());
softwareModuleMetadataRepository.deleteById(metadata.getId());
@@ -424,45 +429,6 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
.map(SoftwareModule.class::cast);
}
@Override
public Optional<SoftwareModuleMetadata> findMetaDataBySoftwareModuleId(final long id, final String key) {
return findMetaDataBySoftwareModuleId0(id, key);
}
private Optional<SoftwareModuleMetadata> findMetaDataBySoftwareModuleId0(final long id, final String key) {
assertSoftwareModuleExists(id);
return softwareModuleMetadataRepository.findById(new SwMetadataCompositeKey(id, key))
.map(SoftwareModuleMetadata.class::cast);
}
@Override
public Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleId(final Pageable pageable, final long id) {
assertSoftwareModuleExists(id);
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleMetadataRepository,
Collections.singletonList(metadataBySoftwareModuleIdSpec(id)), pageable
);
}
@Override
public Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleIdAndTargetVisible(final Pageable pageable,
final long id) {
assertSoftwareModuleExists(id);
return JpaManagementHelper.convertPage(softwareModuleMetadataRepository.findBySoftwareModuleIdAndTargetVisible(
id, true, PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT)), pageable);
}
@Override
public Page<SoftwareModuleMetadata> findMetaDataByRsql(final long id, final String rsqlParam, final Pageable pageable) {
assertSoftwareModuleExists(id);
final List<Specification<JpaSoftwareModuleMetadata>> specList = Arrays
.asList(RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleMetadataFields.class,
virtualPropertyReplacer, database), metadataBySoftwareModuleIdSpec(id));
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleMetadataRepository, specList, pageable);
}
@Override
public Slice<SoftwareModule> findByType(final long typeId, final Pageable pageable) {
assertSoftwareModuleTypeExists(typeId);
@@ -490,18 +456,6 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
return softwareModuleRepository.count(SoftwareModuleSpecification.byAssignedToDs(distributionSetId));
}
@Override
public long countMetaDataBySoftwareModuleId(final long id) {
assertSoftwareModuleExists(id);
return softwareModuleMetadataRepository.countBySoftwareModuleId(id);
}
private static Stream<JpaSoftwareModuleMetadataCreate> createJpaMetadataCreateStream(
final Collection<SoftwareModuleMetadataCreate> create) {
return create.stream().map(JpaSoftwareModuleMetadataCreate.class::cast);
}
private static Specification<JpaSoftwareModuleMetadata> metadataBySoftwareModuleIdSpec(final long id) {
return (root, query, cb) -> cb.equal(root.get(JpaSoftwareModuleMetadata_.softwareModule).get(AbstractJpaBaseEntity_.id), id);
}
@@ -526,23 +480,30 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
return softwareModuleMetadataRepository.save(create.build());
}
private void assertSoftwareModuleMetadataDoesNotExist(final Long id,
final JpaSoftwareModuleMetadataCreate md) {
private Optional<SoftwareModuleMetadata> findMetadata(final long id, final String key) {
assertSoftwareModuleExists(id);
return softwareModuleMetadataRepository.findById(new SwMetadataCompositeKey(id, key))
.map(SoftwareModuleMetadata.class::cast);
}
private void assertSoftwareModuleMetadataDoesNotExist(final Long id, final JpaSoftwareModuleMetadataCreate md) {
if (softwareModuleMetadataRepository.existsById(new SwMetadataCompositeKey(id, md.getKey()))) {
throw new EntityAlreadyExistsException("Metadata entry with key '" + md.getKey() + "' already exists!");
}
}
/**
* Asserts the meta data quota for the software module with the given ID.
* Asserts the meta-data quota for the software module with the given ID.
*
* @param id The software module ID.
* @param requested Number of meta data entries to be created.
* @param requested Number of meta-data entries to be created.
*/
private void assertMetaDataQuota(final Long id, final int requested) {
private void assertMetadataQuota(final Long id, final int requested) {
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
QuotaHelper.assertAssignmentQuota(id, requested, maxMetaData, SoftwareModuleMetadata.class,
SoftwareModule.class, softwareModuleMetadataRepository::countBySoftwareModuleId);
QuotaHelper.assertAssignmentQuota(
id, requested, maxMetaData, SoftwareModuleMetadata.class, SoftwareModule.class,
softwareModuleMetadataRepository::countBySoftwareModuleId);
}
private void assertSoftwareModuleExists(final Long id) {

View File

@@ -1,85 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import java.util.Objects;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.eclipse.hawkbit.repository.model.MetaData;
/**
* Meta data for entities.
*/
@MappedSuperclass
public abstract class AbstractJpaMetaData implements MetaData {
@Serial
private static final long serialVersionUID = 1L;
@Id
@Column(name = "meta_key", nullable = false, length = MetaData.KEY_MAX_SIZE, updatable = false)
@Size(min = 1, max = MetaData.KEY_MAX_SIZE)
@NotNull
private String key;
@Column(name = "meta_value", length = MetaData.VALUE_MAX_SIZE)
@Size(max = MetaData.VALUE_MAX_SIZE)
@Basic
private String value;
protected AbstractJpaMetaData(final String key, final String value) {
this.key = key;
this.value = value;
}
protected AbstractJpaMetaData() {
// Default constructor needed for JPA entities
}
@Override
public String getKey() {
return key;
}
public void setKey(final String key) {
this.key = key;
}
@Override
public String getValue() {
return value;
}
public void setValue(final String value) {
this.value = value;
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override
public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final AbstractJpaMetaData that = (AbstractJpaMetaData) o;
return Objects.equals(key, that.key) && Objects.equals(value, that.value);
}
}

View File

@@ -116,8 +116,8 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
name = "sp_ds_metadata",
joinColumns = { @JoinColumn(name = "ds", nullable = false) },
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_metadata_ds"))
@MapKeyColumn(name = "meta_key", length = DistributionSet.METADATA_KEY_SIZE)
@Column(name = "meta_value", length = DistributionSet.METADATA_VALUE_SIZE)
@MapKeyColumn(name = "meta_key", length = DistributionSet.METADATA_MAX_KEY_SIZE)
@Column(name = "meta_value", length = DistributionSet.METADATA_MAX_VALUE_SIZE)
private Map<String, String> metadata;
@Column(name = "complete")

View File

@@ -13,10 +13,13 @@ import java.io.Serial;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import jakarta.persistence.CascadeType;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
@@ -24,6 +27,7 @@ import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.NamedAttributeNode;
import jakarta.persistence.NamedEntityGraph;
import jakarta.persistence.OneToMany;
@@ -69,8 +73,6 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
@Serial
private static final long serialVersionUID = 1L;
private static final String DELETED_PROPERTY = "deleted";
@Setter
@ManyToOne
@JoinColumn(name = "sm_type", nullable = false, updatable = false,
@@ -92,11 +94,16 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
@Column(name = "encrypted")
private boolean encrypted;
@ToString.Exclude
@OneToMany(mappedBy = "softwareModule", fetch = FetchType.LAZY,
cascade = { CascadeType.REMOVE },
targetEntity = JpaSoftwareModuleMetadata.class)
private List<JpaSoftwareModuleMetadata> metadata;
// no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent.
@Getter
@ElementCollection
@CollectionTable(
name = "sp_sm_metadata",
joinColumns = { @JoinColumn(name = "sm", nullable = false) },
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_sm_metadata_sm"))
@MapKeyColumn(name = "meta_key", length = SoftwareModule.METADATA_KEY_MAX_SIZE)
@Column(name = "meta_value", length = SoftwareModule.METADATA_VALUE_MAX_SIZE)
private Map<String, String> metadata;
@Column(name = "locked")
private boolean locked;

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity;
@@ -21,11 +22,11 @@ import jakarta.persistence.IdClass;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
@@ -34,31 +35,42 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
*/
@NoArgsConstructor // Default constructor for JPA
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@IdClass(SwMetadataCompositeKey.class)
@Entity
@Table(name = "sp_sm_metadata")
public class JpaSoftwareModuleMetadata extends AbstractJpaMetaData implements SoftwareModuleMetadata {
public class JpaSoftwareModuleMetadata implements SoftwareModuleMetadata {
@Serial
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "sm", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_sm_metadata_sm"))
@JoinColumn(
name = "sm", nullable = false, updatable = false,
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_sm_metadata_sm"))
private JpaSoftwareModule softwareModule;
@Id
@Column(name = "meta_key", nullable = false, length = SoftwareModule.METADATA_KEY_MAX_SIZE, updatable = false)
@Size(min = 1, max = SoftwareModule.METADATA_KEY_MAX_SIZE)
@NotNull
private String key;
@Column(name = "meta_value", length = SoftwareModule.METADATA_VALUE_MAX_SIZE)
@Size(max = SoftwareModule.METADATA_VALUE_MAX_SIZE)
@Basic
private String value;
@Column(name = "target_visible")
private boolean targetVisible;
public JpaSoftwareModuleMetadata(final String key, final SoftwareModule softwareModule, final String value) {
super(key, value);
this.softwareModule = (JpaSoftwareModule) softwareModule;
this(key, softwareModule, value, false);
}
public JpaSoftwareModuleMetadata(final String key, final SoftwareModule softwareModule, final String value, final boolean targetVisible) {
super(key, value);
this.key = key;
this.value = value;
this.softwareModule = (JpaSoftwareModule) softwareModule;
this.targetVisible = targetVisible;
}

View File

@@ -37,7 +37,6 @@ import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.NamedAttributeNode;
import jakarta.persistence.NamedEntityGraph;
import jakarta.persistence.NamedEntityGraphs;
import jakarta.persistence.NamedSubgraph;
import jakarta.persistence.OneToMany;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.Table;
@@ -187,8 +186,8 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
name = "sp_target_attributes",
joinColumns = { @JoinColumn(name = "target", nullable = false) },
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_attributes_target"))
@MapKeyColumn(name = "attribute_key", length = Target.CONTROLLER_ATTRIBUTE_KEY_SIZE)
@Column(name = "attribute_value", length = Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE)
@MapKeyColumn(name = "attribute_key", length = Target.CONTROLLER_ATTRIBUTE_MAX_KEY_SIZE)
@Column(name = "attribute_value", length = Target.CONTROLLER_ATTRIBUTE_MAX_VALUE_SIZE)
private Map<String, String> controllerAttributes;
// no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent
@@ -198,8 +197,8 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
name = "sp_target_metadata",
joinColumns = { @JoinColumn(name = "target", nullable = false) },
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_metadata_target"))
@MapKeyColumn(name = "meta_key", length = Target.CONTROLLER_METADATA_KEY_SIZE)
@Column(name = "meta_value", length = Target.CONTROLLER_METADATA_VALUE_SIZE)
@MapKeyColumn(name = "meta_key", length = Target.METADATA_MAX_KEY_SIZE)
@Column(name = "meta_value", length = Target.METADATA_MAX_VALUE_SIZE)
private Map<String, String> metadata;
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaAction.class)