Worked on the interfaces and documentation.

Signed-off-by: Kai Zimmermann <kai.zimmermann@bosch-si.com>
This commit is contained in:
Kai Zimmermann
2016-05-22 08:48:47 +02:00
parent f0a78369f2
commit 9301de096c
55 changed files with 971 additions and 506 deletions

View File

@@ -171,8 +171,8 @@ public interface DeploymentManagement {
/**
* counts all actions associated to a specific target.
*
* @param spec
* the specification to filter the count result
* @param rsqlParam
* rsql query string
* @param target
* the target associated to the actions to count
* @return the count value of found actions associated to the target
@@ -271,8 +271,8 @@ public interface DeploymentManagement {
* Retrieves all {@link Action}s assigned to a specific {@link Target} and a
* given specification.
*
* @param specifiction
* the specification to narrow down the search
* @param rsqlParam
* rsql query string
* @param target
* the target which must be assigned to the actions
* @param pageable
@@ -280,7 +280,6 @@ public interface DeploymentManagement {
* @return a slice of actions assigned to the specific target and the
* specification
*/
// TODO fix this
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Slice<Action> findActionsByTarget(@NotNull String rsqlParam, @NotNull Target target, @NotNull Pageable pageable);

View File

@@ -59,10 +59,9 @@ public class DistributionSetAssignmentResult extends AssignmentResult<Target> {
return actions;
}
@SuppressWarnings("unchecked")
@Override
public List<Target> getAssignedEntity() {
return (List<Target>) targetManagement.findTargetByControllerID(assignedTargets);
return targetManagement.findTargetByControllerID(assignedTargets);
}
}

View File

@@ -86,6 +86,9 @@ public interface DistributionSetManagement {
/**
* Count all {@link DistributionSet}s in the repository that are not marked
* as deleted.
*
* @param type
* to look for
*
* @return number of {@link DistributionSet}s
*/
@@ -289,8 +292,8 @@ public interface DistributionSetManagement {
*
* @param distributionSetId
* the distribution set id to retrieve the meta data from
* @param spec
* the specification to filter the result
* @param rsqlParam
* rsql query string
* @param pageable
* the page request to page the result
* @return a paged result of all meta data entries for a given distribution
@@ -343,8 +346,8 @@ public interface DistributionSetManagement {
/**
* finds all {@link DistributionSet}s.
*
* @param spec
* the specification to add for the search query.
* @param rsqlParam
* rsql query string
* @param pageReq
* the pagination parameter
* @param deleted
@@ -432,8 +435,8 @@ public interface DistributionSetManagement {
/**
* Generic predicate based query for {@link DistributionSetType}.
*
* @param spec
* of the search
* @param rsqlParam
* rsql query string
* @param pageable
* parameter for paging
*

View File

@@ -130,8 +130,8 @@ public interface TagManagement {
/**
* Retrieves all DistributionSet tags based on the given specification.
*
* @param spec
* the specification for the query
* @param rsqlParam
* rsql query string
* @param pageable
* pagination parameter
* @return the found {@link DistributionSetTag}s, never {@code null}
@@ -159,8 +159,8 @@ public interface TagManagement {
/**
* Retrieves all target tags based on the given specification.
*
* @param spec
* the specification for the query
* @param rsqlParam
* rsql query string
* @param pageable
* pagination parameter
* @return the found {@link Target}s, never {@code null}
@@ -245,12 +245,48 @@ public interface TagManagement {
*/
DistributionSetTag generateDistributionSetTag();
/**
* Generates a {@link TargetTag} without persisting it.
*
* @param name
* of the tag
* @param description
* of the tag
* @param colour
* of the tag
* @return {@link TargetTag} object
*/
TargetTag generateTargetTag(String name, String description, String colour);
/**
* Generates a {@link TargetTag} without persisting it.
*
* @param name
* of the tag
* @return {@link TargetTag} object
*/
TargetTag generateTargetTag(String name);
/**
* Generates a {@link DistributionSetTag} without persisting it.
*
* @param name
* of the tag
* @param description
* of the tag
* @param colour
* of the tag
* @return {@link DistributionSetTag} object
*/
DistributionSetTag generateDistributionSetTag(String name, String description, String colour);
/**
* Generates a {@link DistributionSetTag} without persisting it.
*
* @param name
* of the tag
* @return {@link DistributionSetTag} object
*/
DistributionSetTag generateDistributionSetTag(String name);
}
}

View File

@@ -395,7 +395,7 @@ public class JpaControllerManagement implements ControllerManagement {
// retrieves after the other we don't want to store to protect to
// overflood action status in
// case controller retrieves a action multiple times.
if (resultList.isEmpty() || resultList.get(0)[1] != Status.RETRIEVED) {
if (resultList.isEmpty() || !Status.RETRIEVED.equals(resultList.get(0)[1])) {
// document that the status has been retrieved
actionStatusRepository
.save(new JpaActionStatus(action, Status.RETRIEVED, System.currentTimeMillis(), message));

View File

@@ -81,7 +81,7 @@ public class JpaTenantConfigurationManagement implements EnvironmentAware, Tenan
final TenantConfigurationKey configurationKey, final Class<T> propertyType,
final TenantConfiguration tenantConfiguration) {
if (tenantConfiguration != null) {
return TenantConfigurationValue.<T> builder().isGlobal(false).createdBy(tenantConfiguration.getCreatedBy())
return TenantConfigurationValue.<T> builder().global(false).createdBy(tenantConfiguration.getCreatedBy())
.createdAt(tenantConfiguration.getCreatedAt())
.lastModifiedAt(tenantConfiguration.getLastModifiedAt())
.lastModifiedBy(tenantConfiguration.getLastModifiedBy())
@@ -89,7 +89,7 @@ public class JpaTenantConfigurationManagement implements EnvironmentAware, Tenan
} else if (configurationKey.getDefaultKeyName() != null) {
return TenantConfigurationValue.<T> builder().isGlobal(true).createdBy(null).createdAt(null)
return TenantConfigurationValue.<T> builder().global(true).createdBy(null).createdAt(null)
.lastModifiedAt(null).lastModifiedBy(null)
.value(getGlobalConfigurationValue(configurationKey, propertyType)).build();
}
@@ -149,8 +149,7 @@ public class JpaTenantConfigurationManagement implements EnvironmentAware, Tenan
final Class<T> clazzT = (Class<T>) value.getClass();
return TenantConfigurationValue.<T> builder().isGlobal(false)
.createdBy(updatedTenantConfiguration.getCreatedBy())
return TenantConfigurationValue.<T> builder().global(false).createdBy(updatedTenantConfiguration.getCreatedBy())
.createdAt(updatedTenantConfiguration.getCreatedAt())
.lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt())
.lastModifiedBy(updatedTenantConfiguration.getLastModifiedBy())

View File

@@ -59,8 +59,11 @@ public class JpaDistributionSetTag extends AbstractJpaTag implements Distributio
super(name, description, colour);
}
/**
* Default constructor for JPA.
*/
public JpaDistributionSetTag() {
super();
// Default constructor for JPA.
}
@Override

View File

@@ -158,7 +158,6 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout {
this.totalTargets = totalTargets;
}
@Override
public int getRolloutGroupsTotal() {
return rolloutGroupsTotal;
}

View File

@@ -32,10 +32,6 @@ public class JpaSoftwareModuleType extends AbstractJpaNamedEntity implements Sof
@Column(name = "type_key", nullable = false, length = 64)
private String key;
public void setMaxAssignments(final int maxAssignments) {
this.maxAssignments = maxAssignments;
}
@Column(name = "max_ds_assignments", nullable = false)
private int maxAssignments;
@@ -87,10 +83,15 @@ public class JpaSoftwareModuleType extends AbstractJpaNamedEntity implements Sof
}
/**
* Default Constructor.
* Default Constructor for JPA.
*/
public JpaSoftwareModuleType() {
super();
// Default Constructor for JPA.
}
@Override
public void setMaxAssignments(final int maxAssignments) {
this.maxAssignments = maxAssignments;
}
@Override
@@ -108,7 +109,6 @@ public class JpaSoftwareModuleType extends AbstractJpaNamedEntity implements Sof
return deleted;
}
@Override
public void setDeleted(final boolean deleted) {
this.deleted = deleted;
}

View File

@@ -37,6 +37,14 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity imple
// Default constructor for JPA.
}
/**
* Public constructor.
*
* @param name
* of the {@link TargetFilterQuery}.
* @param query
* of the {@link TargetFilterQuery}.
*/
public JpaTargetFilterQuery(final String name, final String query) {
this.name = name;
this.query = query;

View File

@@ -60,7 +60,7 @@ public class JpaTargetTag extends AbstractJpaTag implements TargetTag {
}
public JpaTargetTag() {
super();
// Default constructor for JPA.
}
@Override

View File

@@ -18,11 +18,19 @@ import org.springframework.data.jpa.domain.Specification;
* Spring Data JPQL Specifications.
*
*/
public class TargetFilterQuerySpecification {
public final class TargetFilterQuerySpecification {
private TargetFilterQuerySpecification() {
// utility class
}
/**
* {@link Specification} for retrieving {@link JpaTargetFilterQuery}s based
* on is {@link JpaTargetFilterQuery#getName()}.
*
* @param searchText
* of the filter
* @return the {@link JpaTargetFilterQuery} {@link Specification}
*/
public static Specification<JpaTargetFilterQuery> likeName(final String searchText) {
return (targetFilterQueryRoot, query, cb) -> {
final String searchTextToLower = searchText.toLowerCase();

View File

@@ -189,12 +189,12 @@ public interface Action extends TenantAwareBaseEntity {
CANCELING,
/**
* Action has been presented to the target.
* Action has been send to the target.
*/
RETRIEVED,
/**
* Action needs download by this target which has now started.
* Action requests download by this target which has now started.
*/
DOWNLOAD,

View File

@@ -0,0 +1,68 @@
/**
* 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.model;
import java.time.LocalDateTime;
/**
* The poll time object which holds all the necessary information around the
* target poll time, e.g. the last poll time, the next poll time and the overdue
* poll time.
*
*/
public class PollStatus {
private final LocalDateTime lastPollDate;
private final LocalDateTime nextPollDate;
private final LocalDateTime overdueDate;
private final LocalDateTime currentDate;
public PollStatus(final LocalDateTime lastPollDate, final LocalDateTime nextPollDate,
final LocalDateTime overdueDate, final LocalDateTime currentDate) {
this.lastPollDate = lastPollDate;
this.nextPollDate = nextPollDate;
this.overdueDate = overdueDate;
this.currentDate = currentDate;
}
/**
* calculates if the target poll time is overdue and the target has not been
* polled in the configured poll time interval.
*
* @return {@code true} if the current time is after the poll time overdue
* date otherwise {@code false}.
*/
public boolean isOverdue() {
return currentDate.isAfter(overdueDate);
}
/**
* @return the lastPollDate
*/
public LocalDateTime getLastPollDate() {
return lastPollDate;
}
public LocalDateTime getNextPollDate() {
return nextPollDate;
}
public LocalDateTime getOverdueDate() {
return overdueDate;
}
public LocalDateTime getCurrentDate() {
return currentDate;
}
@Override
public String toString() {
return "PollTime [lastPollDate=" + lastPollDate + ", nextPollDate=" + nextPollDate + ", overdueDate="
+ overdueDate + ", currentDate=" + currentDate + "]";
}
}

View File

@@ -9,8 +9,10 @@
package org.eclipse.hawkbit.repository.model;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus.Status;
/**
* Software update operations in large scale IoT scenarios with hundred of
@@ -57,20 +59,46 @@ public interface Rollout extends NamedEntity {
*/
RolloutStatus getStatus();
/**
* @return {@link ActionType} of the rollout.
*/
ActionType getActionType();
/**
* @param actionType
* of the rollout.
*/
void setActionType(ActionType actionType);
/**
* @return time in {@link TimeUnit#MILLISECONDS} after which
* {@link #isForced()} switches to <code>true</code> in case of
* {@link ActionType#TIMEFORCED}.
*/
long getForcedTime();
/**
* @param forcedTime
* in {@link TimeUnit#MILLISECONDS} after which
* {@link #isForced()} switches to <code>true</code> in case of
* {@link ActionType#TIMEFORCED}.
*/
void setForcedTime(long forcedTime);
/**
* @return number of {@link Target}s in this rollout.
*/
long getTotalTargets();
int getRolloutGroupsTotal();
/**
* @return number of {@link RolloutGroup}s.
*/
int getRolloutGroupsCreated();
/**
* @return all states with the respective target count in that
* {@link Status}.
*/
TotalTargetCountStatus getTotalTargetCountStatus();
/**

View File

@@ -182,4 +182,4 @@ public interface RolloutGroup extends NamedEntity {
return beanName;
}
}
}
}

View File

@@ -0,0 +1,91 @@
/**
* 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.model;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
/**
* Builder to build easily the {@link RolloutGroupConditions}.
*
*/
public class RolloutGroupConditionBuilder {
private final RolloutGroupConditions conditions = new RolloutGroupConditions();
/**
* @return completed {@link RolloutGroupConditions}.
*/
public RolloutGroupConditions build() {
return conditions;
}
/**
* Sets the finish condition and expression on the builder.
*
* @param condition
* the finish condition
* @param expression
* the finish expression
* @return the builder itself
*/
public RolloutGroupConditionBuilder successCondition(final RolloutGroupSuccessCondition condition,
final String expression) {
conditions.setSuccessCondition(condition);
conditions.setSuccessConditionExp(expression);
return this;
}
/**
* Sets the success action and expression on the builder.
*
* @param action
* the success action
* @param expression
* the error expression
* @return the builder itself
*/
public RolloutGroupConditionBuilder successAction(final RolloutGroupSuccessAction action, final String expression) {
conditions.setSuccessAction(action);
conditions.setSuccessActionExp(expression);
return this;
}
/**
* Sets the error condition and expression on the builder.
*
* @param condition
* the error condition
* @param expression
* the error expression
* @return the builder itself
*/
public RolloutGroupConditionBuilder errorCondition(final RolloutGroupErrorCondition condition,
final String expression) {
conditions.setErrorCondition(condition);
conditions.setErrorConditionExp(expression);
return this;
}
/**
* Sets the error action and expression on the builder.
*
* @param action
* the error action
* @param expression
* the error expression
* @return the builder itself
*/
public RolloutGroupConditionBuilder errorAction(final RolloutGroupErrorAction action, final String expression) {
conditions.setErrorAction(action);
conditions.setErrorActionExp(expression);
return this;
}
}

View File

@@ -0,0 +1,93 @@
/**
* 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.model;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
/**
* Object which holds all {@link RolloutGroup} conditions together which can
* easily built.
*/
public class RolloutGroupConditions {
private RolloutGroupSuccessCondition successCondition;
private String successConditionExp;
private RolloutGroupSuccessAction successAction;
private String successActionExp;
private RolloutGroupErrorCondition errorCondition;
private String errorConditionExp;
private RolloutGroupErrorAction errorAction;
private String errorActionExp;
public RolloutGroupSuccessCondition getSuccessCondition() {
return successCondition;
}
public void setSuccessCondition(final RolloutGroupSuccessCondition finishCondition) {
successCondition = finishCondition;
}
public String getSuccessConditionExp() {
return successConditionExp;
}
public void setSuccessConditionExp(final String finishConditionExp) {
successConditionExp = finishConditionExp;
}
public RolloutGroupSuccessAction getSuccessAction() {
return successAction;
}
public void setSuccessAction(final RolloutGroupSuccessAction successAction) {
this.successAction = successAction;
}
public String getSuccessActionExp() {
return successActionExp;
}
public void setSuccessActionExp(final String successActionExp) {
this.successActionExp = successActionExp;
}
public RolloutGroupErrorCondition getErrorCondition() {
return errorCondition;
}
public void setErrorCondition(final RolloutGroupErrorCondition errorCondition) {
this.errorCondition = errorCondition;
}
public String getErrorConditionExp() {
return errorConditionExp;
}
public void setErrorConditionExp(final String errorConditionExp) {
this.errorConditionExp = errorConditionExp;
}
public RolloutGroupErrorAction getErrorAction() {
return errorAction;
}
public void setErrorAction(final RolloutGroupErrorAction errorAction) {
this.errorAction = errorAction;
}
public String getErrorActionExp() {
return errorActionExp;
}
public void setErrorActionExp(final String errorActionExp) {
this.errorActionExp = errorActionExp;
}
}

View File

@@ -8,22 +8,53 @@
*/
package org.eclipse.hawkbit.repository.model;
/**
* {@link SoftwareModuleType} is an abstract definition used in
* {@link DistributionSetType}s and includes additional {@link SoftwareModule}
* specific information.
*
*/
public interface SoftwareModuleType extends NamedEntity {
/**
* @return business key of this {@link SoftwareModuleType}.
*/
String getKey();
/**
* @param key
* of this {@link SoftwareModuleType}.
*/
void setKey(String key);
/**
* @return maximum assignments of an {@link SoftwareModule} of this type to
* a {@link DistributionSet}.
*/
int getMaxAssignments();
/**
* @param maxAssignments
* of an {@link SoftwareModule} of this type to a
* {@link DistributionSet}.
*/
void setMaxAssignments(int maxAssignments);
/**
* @return <code>true</code> if the type is deleted and only kept for
* history purposes.
*/
boolean isDeleted();
void setDeleted(boolean deleted);
/**
* @return get color code to by used in management UI views.
*/
String getColour();
void setColour(String colour);
/**
* @param colour
* code to by used in management UI views.
*/
void setColour(final String colour);
}

View File

@@ -60,6 +60,8 @@ public class TargetIdName implements Serializable {
}
@Override
// Exception squid:S864 - generated code
@SuppressWarnings("squid:S864")
public int hashCode() {
final int prime = 31;
int result = 1;

View File

@@ -14,14 +14,14 @@ package org.eclipse.hawkbit.repository.model;
* @param <T>
* type of the configuration value
*/
public class TenantConfigurationValue<T> {
public final class TenantConfigurationValue<T> {
private T value;
private Long lastModifiedAt;
private String lastModifiedBy;
private Long createdAt;
private String createdBy;
private boolean isGlobal = true;
private boolean global = true;
private TenantConfigurationValue() {
}
@@ -41,7 +41,7 @@ public class TenantConfigurationValue<T> {
* @return true, if is global
*/
public boolean isGlobal() {
return isGlobal;
return global;
}
/**
@@ -126,13 +126,13 @@ public class TenantConfigurationValue<T> {
/**
* set the is global attribute.
*
* @param isGlobal
* @param global
* true when there is no tenant specific value, false
* otherwise
* @return the tenant configuration value builder
*/
public TenantConfigurationValueBuilder<T> isGlobal(final boolean isGlobal) {
this.configuration.isGlobal = isGlobal;
public TenantConfigurationValueBuilder<T> global(final boolean global) {
this.configuration.global = global;
return this;
}

View File

@@ -15,7 +15,8 @@ import java.util.Map;
/**
*
* Store all states with the target count of a rollout or rolloutgroup.
* Store target count of a {@link Rollout} or {@link RolloutGroup} for every
* {@link Status}.
*
*/
public class TotalTargetCountStatus {
@@ -24,7 +25,35 @@ public class TotalTargetCountStatus {
* Status of the total target counts.
*/
public enum Status {
SCHEDULED, RUNNING, ERROR, FINISHED, CANCELLED, NOTSTARTED
/**
* Action is scheduled.
*/
SCHEDULED,
/**
* Action is still running.
*/
RUNNING,
/**
* Action failed.
*/
ERROR,
/**
* Action is completed.
*/
FINISHED,
/**
* Action is canceled.
*/
CANCELLED,
/**
* Action is not started yet.
*/
NOTSTARTED
}
private final Map<Status, Long> statusTotalCountMap = new EnumMap<>(Status.class);
@@ -35,7 +64,7 @@ public class TotalTargetCountStatus {
*
* @param targetCountActionStatus
* the action state map
* @param totalTargets
* @param totalTargetCount
* the total target count
*/
public TotalTargetCountStatus(final List<TotalTargetCountActionStatus> targetCountActionStatus,
@@ -81,8 +110,7 @@ public class TotalTargetCountStatus {
* @param statusTotalCountMap
* the map
* @param rolloutStatusCountItems
* all target statut with total count
* @return <true> some state is populated <false> nothing is happend
* all target {@link Status} with total count
*/
private final void mapActionStatusToTotalTargetCountStatus(
final List<TotalTargetCountActionStatus> targetCountActionStatus) {
@@ -93,33 +121,40 @@ public class TotalTargetCountStatus {
statusTotalCountMap.put(Status.RUNNING, 0L);
Long notStartedTargetCount = totalTargetCount;
for (final TotalTargetCountActionStatus item : targetCountActionStatus) {
switch (item.getStatus()) {
case SCHEDULED:
statusTotalCountMap.put(Status.SCHEDULED, item.getCount());
break;
case ERROR:
statusTotalCountMap.put(Status.ERROR, item.getCount());
break;
case FINISHED:
statusTotalCountMap.put(Status.FINISHED, item.getCount());
break;
case RETRIEVED:
case RUNNING:
case WARNING:
case DOWNLOAD:
case CANCELING:
final Long runningItemsCount = statusTotalCountMap.get(Status.RUNNING) + item.getCount();
statusTotalCountMap.put(Status.RUNNING, runningItemsCount);
break;
case CANCELED:
statusTotalCountMap.put(Status.CANCELLED, item.getCount());
break;
default:
throw new IllegalArgumentException("State " + item.getStatus() + "is not valid");
}
convertStatus(item);
notStartedTargetCount -= item.getCount();
}
statusTotalCountMap.put(TotalTargetCountStatus.Status.NOTSTARTED, notStartedTargetCount);
}
// Exception squid:MethodCyclomaticComplexity - simple state conversion, not
// really complex.
@SuppressWarnings("squid:MethodCyclomaticComplexity")
private void convertStatus(final TotalTargetCountActionStatus item) {
switch (item.getStatus()) {
case SCHEDULED:
statusTotalCountMap.put(Status.SCHEDULED, item.getCount());
break;
case ERROR:
statusTotalCountMap.put(Status.ERROR, item.getCount());
break;
case FINISHED:
statusTotalCountMap.put(Status.FINISHED, item.getCount());
break;
case RETRIEVED:
case RUNNING:
case WARNING:
case DOWNLOAD:
case CANCELING:
final Long runningItemsCount = statusTotalCountMap.get(Status.RUNNING) + item.getCount();
statusTotalCountMap.put(Status.RUNNING, runningItemsCount);
break;
case CANCELED:
statusTotalCountMap.put(Status.CANCELLED, item.getCount());
break;
default:
throw new IllegalArgumentException("State " + item.getStatus() + "is not valid");
}
}
}