/*
* #%L
* BroadleafCommerce Open Admin Platform
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* 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.
* #L%
*/
package org.broadleafcommerce.openadmin.dto;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.broadleafcommerce.common.util.BLCMapUtils;
import org.broadleafcommerce.common.util.TypedClosure;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Generic DTO for a domain object. Each property of the domain object is represented by the 'properties' instance variable
* which allows for further display metadata to be stored.
*
* @author jfischer
* @see {@link Property}
*
*/
public class Entity implements Serializable {
protected static final long serialVersionUID = 1L;
protected String[] type;
protected Property[] properties;
protected boolean isDirty = false;
protected Date deployDate;
protected Boolean isDeleted = false;
protected Boolean isInactive = false;
protected Boolean isActive = false;
protected boolean multiPartAvailableOnThread = false;
protected boolean isValidationFailure = false;
protected Map<String, List<String>> validationErrors = new HashMap<String, List<String>>();
protected List<String> globalValidationErrors = new ArrayList<String>();
protected Map<String, Property> pMap = null;
public String[] getType() {
return type;
}
public void setType(String[] type) {
if (type != null && type.length > 0) {
Arrays.sort(type);
}
this.type = type;
}
public Map<String, Property> getPMap() {
if (pMap == null) {
pMap = BLCMapUtils.keyedMap(properties, new TypedClosure<String, Property>() {
@Override
public String getKey(Property value) {
return value.getName();
}
});
}
return pMap;
}
public Property[] getProperties() {
return properties;
}
public void setProperties(Property[] properties) {
this.properties = properties;
pMap = null;
}
public void mergeProperties(String prefix, Entity entity) {
int j = 0;
Property[] merged = new Property[properties.length + entity.getProperties().length];
for (Property property : properties) {
merged[j] = property;
j++;
}
for (Property property : entity.getProperties()) {
property.setName(prefix!=null?prefix+"."+property.getName():""+property.getName());
merged[j] = property;
j++;
}
properties = merged;
}
/**
* Replaces all property values in this entity with the values from the given entity. This also resets the {@link #pMap}
*
* @param entity
*/
public void overridePropertyValues(Entity entity) {
for (Property property : entity.getProperties()) {
Property myProperty = findProperty(property.getName());
if (myProperty != null) {
myProperty.setValue(property.getValue());
myProperty.setRawValue(property.getRawValue());
}
}
pMap = null;
}
public Property findProperty(String name) {
if (properties == null) {
return null;
}
Arrays.sort(properties, new Comparator<Property>() {
@Override
public int compare(Property o1, Property o2) {
if (o1 == null && o2 == null) {
return 0;
} else if (o1 == null) {
return 1;
} else if (o2 == null) {
return -1;
}
return o1.getName().compareTo(o2.getName());
}
});
Property searchProperty = new Property();
searchProperty.setName(name);
int index = Arrays.binarySearch(properties, searchProperty, new Comparator<Property>() {
@Override
public int compare(Property o1, Property o2) {
if (o1 == null && o2 == null) {
return 0;
} else if (o1 == null) {
return 1;
} else if (o2 == null) {
return -1;
}
return o1.getName().compareTo(o2.getName());
}
});
if (index >= 0) {
return properties[index];
}
return null;
}
public void addProperty(Property property) {
boolean exists = findProperty(property.getName()) != null;
Property[] allProps = getProperties();
Property[] newProps = new Property[exists?allProps.length:allProps.length + 1];
int count = 0;
for (int j=0;j<allProps.length;j++) {
if (!allProps[j].getName().equals(property.getName())) {
newProps[count] = allProps[j];
count++;
}
}
newProps[newProps.length - 1] = property;
setProperties(newProps);
}
public Property removeProperty(String name) {
boolean exists = findProperty(name) != null;
Property response = null;
if (exists) {
Property[] allProps = getProperties();
Property[] newProps = new Property[allProps.length - 1];
int count = 0;
for (int j = 0; j < allProps.length; j++) {
if (!allProps[j].getName().equals(name)) {
newProps[count] = allProps[j];
count++;
} else {
response = allProps[j];
}
}
setProperties(newProps);
}
return response;
}
/**
* Adds a single validation error to this entity. This will also set the entire
* entity in an error state by invoking {@link #setValidationFailure(boolean)}.
*
* @param fieldName - the field that is in error. This works on top-level properties (like a 'manufacturer' field on a
* Product entity) but can also work on properties gleaned from a related entity (like
* 'defaultSku.weight.weightUnitOfMeasure' on a Product entity)
* @param errorOrErrorKey - the error message to present to a user. Could be the actual error message or a key to a
* property in messages.properties to support different locales
*/
public void addValidationError(String fieldName, String errorOrErrorKey) {
Map<String, List<String>> fieldErrors = getPropertyValidationErrors();
List<String> errorMessages = fieldErrors.get(fieldName);
if (errorMessages == null) {
errorMessages = new ArrayList<String>();
fieldErrors.put(fieldName, errorMessages);
}
errorMessages.add(errorOrErrorKey);
setValidationFailure(true);
}
public boolean isDirty() {
return isDirty;
}
public void setDirty(boolean dirty) {
isDirty = dirty;
}
public boolean isMultiPartAvailableOnThread() {
return multiPartAvailableOnThread;
}
public void setMultiPartAvailableOnThread(boolean multiPartAvailableOnThread) {
this.multiPartAvailableOnThread = multiPartAvailableOnThread;
}
/**
*
* @return if this entity has failed validation. This will also check the {@link #getPropertyValidationErrors()} map and
* {@link #getGlobalValidationErrors()} if this boolean has not been explicitly set
*/
public boolean isValidationFailure() {
if (MapUtils.isNotEmpty(getPropertyValidationErrors()) || CollectionUtils.isNotEmpty(getGlobalValidationErrors())) {
isValidationFailure = true;
}
return isValidationFailure;
}
public void setValidationFailure(boolean validationFailure) {
isValidationFailure = validationFailure;
}
/**
* @deprecated use {@link #getPropertyValidationErrors()} instead
* @return
*/
@Deprecated
public Map<String, List<String>> getValidationErrors() {
return getPropertyValidationErrors();
}
/**
* Validation error map where the key corresponds to the property that failed validation (which could be dot-separated)
* and the value corresponds to a list of the error messages, in the case of multiple errors on the same field.
*
* For instance, you might have a configuration where the field is both a Required validator and a regex validator.
* The validation map in this case might contain something like:
*
* defaultSku.name => ['This field is required', 'Cannot have numbers in name']
*
* @return a map keyed by property name to the list of error messages for that property
*/
public Map<String, List<String>> getPropertyValidationErrors() {
return validationErrors;
}
/**
* @deprecated use {@link #setPropertyValidationErrors(Map)} instead
*/
@Deprecated
public void setValidationErrors(Map<String, List<String>> validationErrors) {
setPropertyValidationErrors(validationErrors);
}
/**
* Completely reset the validation errors for this Entity. In most cases it is more appropriate to use the convenience
* method for adding a single error via {@link #addValidationError(String, String)}. This will also set the entire
* entity in an error state by invoking {@link #setValidationFailure(boolean)}.
*
* @param validationErrors
* @see #addValidationError(String, String)
*/
public void setPropertyValidationErrors(Map<String, List<String>> validationErrors) {
if (MapUtils.isNotEmpty(validationErrors)) {
setValidationFailure(true);
}
this.validationErrors = validationErrors;
}
/**
* Adds a validation error to this entity that is not tied to any specific property. If you need to tie this to a
* property then you should use {@link #addValidationError(String, String)} instead.
* @param errorOrErrorKey
*/
public void addGlobalValidationError(String errorOrErrorKey) {
setValidationFailure(true);
globalValidationErrors.add(errorOrErrorKey);
}
/**
* Similar to {@link #addGlobalValidationError(String)} except with a list of errors
* @param errorOrErrorKeys
*/
public void addGlobalValidationErrors(List<String> errorOrErrorKeys) {
setValidationFailure(true);
globalValidationErrors.addAll(errorOrErrorKeys);
}
public List<String> getGlobalValidationErrors() {
return globalValidationErrors;
}
public void setGlobalValidationErrors(List<String> globalValidationErrors) {
this.globalValidationErrors = globalValidationErrors;
}
public Boolean getActive() {
return isActive;
}
public void setActive(Boolean active) {
isActive = active;
}
public Boolean getDeleted() {
return isDeleted;
}
public void setDeleted(Boolean deleted) {
isDeleted = deleted;
}
public Boolean getInactive() {
return isInactive;
}
public void setInactive(Boolean inactive) {
isInactive = inactive;
}
public Date getDeployDate() {
return deployDate;
}
public void setDeployDate(Date deployDate) {
this.deployDate = deployDate;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Entity{");
sb.append("isValidationFailure=").append(isValidationFailure);
sb.append(", isDirty=").append(isDirty);
sb.append(", properties=").append(Arrays.toString(properties));
sb.append(", type=").append(Arrays.toString(type));
sb.append('}');
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (!getClass().isAssignableFrom(o.getClass())) return false;
Entity entity = (Entity) o;
if (isDirty != entity.isDirty) return false;
if (isValidationFailure != entity.isValidationFailure) return false;
if (multiPartAvailableOnThread != entity.multiPartAvailableOnThread) return false;
if (deployDate != null ? !deployDate.equals(entity.deployDate) : entity.deployDate != null) return false;
if (isActive != null ? !isActive.equals(entity.isActive) : entity.isActive != null) return false;
if (isDeleted != null ? !isDeleted.equals(entity.isDeleted) : entity.isDeleted != null) return false;
if (isInactive != null ? !isInactive.equals(entity.isInactive) : entity.isInactive != null) return false;
if (pMap != null ? !pMap.equals(entity.pMap) : entity.pMap != null) return false;
if (!Arrays.equals(properties, entity.properties)) return false;
if (!Arrays.equals(type, entity.type)) return false;
if (validationErrors != null ? !validationErrors.equals(entity.validationErrors) : entity.validationErrors !=
null)
return false;
return true;
}
@Override
public int hashCode() {
int result = type != null ? Arrays.hashCode(type) : 0;
result = 31 * result + (properties != null ? Arrays.hashCode(properties) : 0);
result = 31 * result + (isDirty ? 1 : 0);
result = 31 * result + (deployDate != null ? deployDate.hashCode() : 0);
result = 31 * result + (isDeleted != null ? isDeleted.hashCode() : 0);
result = 31 * result + (isInactive != null ? isInactive.hashCode() : 0);
result = 31 * result + (isActive != null ? isActive.hashCode() : 0);
result = 31 * result + (multiPartAvailableOnThread ? 1 : 0);
result = 31 * result + (isValidationFailure ? 1 : 0);
result = 31 * result + (validationErrors != null ? validationErrors.hashCode() : 0);
result = 31 * result + (pMap != null ? pMap.hashCode() : 0);
return result;
}
}