package com.psddev.cms.db;
import com.psddev.dari.db.CachingDatabase;
import com.psddev.dari.db.ObjectField;
import com.psddev.dari.db.ObjectType;
import com.psddev.dari.db.Record;
import com.psddev.dari.db.Query;
import com.psddev.dari.db.ReferentialText;
import com.psddev.dari.db.State;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Production Guide class to hold information about Content types and their associated fields
*
*/
@Record.LabelFields({ "documentedType/name" })
@Record.BootstrapPackages("Production Guides")
public class GuideType extends Record {
private static final Logger LOGGER = LoggerFactory
.getLogger(GuideType.class);
@ToolUi.Note("Content type for Production Guide information. After selecting, click Save to automatically generate field entries")
@Required
@Indexed
ObjectType documentedType;
@ToolUi.Note("Production Guide information about this content type")
@ToolUi.Hidden
// No plan for this yet - may not be needed
ReferentialText description;
@ToolUi.Note("Production Guide field descriptions for this content type (Note that any fields within an embedded type should be defined separately in a Guide for the embedded type)")
private List<GuideField> fieldDescriptions;
public ObjectType getDocumentedType() {
return documentedType;
}
public void setDocumentedType(ObjectType documentedType) {
this.documentedType = documentedType;
}
public ReferentialText getDescription() {
return description;
}
public void setDescription(ReferentialText description) {
this.description = description;
}
public List<GuideField> getFieldDescriptions() {
return fieldDescriptions;
}
/*
* Add a field description entry. Assumes that entry doesn't already exist.
*/
public void addFieldDescription(GuideField fieldDescription) {
if (fieldDescriptions == null) {
fieldDescriptions = new ArrayList<GuideField>();
}
fieldDescriptions.add(fieldDescription);
}
public void setFieldDescriptions(List<GuideField> fieldDescriptions) {
this.fieldDescriptions = fieldDescriptions;
}
public ReferentialText getFieldDescription(String fieldName, String fieldDisplayName,
boolean createIfMissing) {
ReferentialText desc = null;
if (fieldDescriptions != null) {
for (GuideField gf : fieldDescriptions) {
if (gf.getFieldName().equals(fieldName)) {
// we take this opportunity to synch the display name
if (createIfMissing && fieldDisplayName != null) {
gf.setDisplayName(fieldDisplayName);
}
return gf.getDescription();
}
}
}
if (createIfMissing) {
setFieldDescription(fieldName, fieldDisplayName, null);
}
return desc;
}
public GuideField getGuideField(String fieldName) {
if (fieldDescriptions != null) {
for (GuideField gf : fieldDescriptions) {
if (gf.getFieldName().equals(fieldName)) {
return gf;
}
}
}
return null;
}
public void setFieldDescription(String fieldName, String fieldDisplayName,
ReferentialText description) {
ReferentialText desc = null;
if (fieldDescriptions != null) {
for (GuideField gf : fieldDescriptions) {
if (gf.getFieldName().equals(fieldName)) {
if (fieldDisplayName != null) {
gf.setDisplayName(fieldDisplayName);
}
gf.setDescription(description);
return;
}
}
}
// if didn't already exist
GuideField gf = new GuideField();
gf.setFieldName(fieldName);
if (fieldDisplayName != null) {
gf.setDisplayName(fieldDisplayName);
}
gf.setDescription(description);
addFieldDescription(gf);
}
public void generateFieldDescriptionList() {
// Create an entry for each field
ObjectType type = getDocumentedType();
if (type != null) {
List<ObjectField> fields = type.getFields();
for (ObjectField field : fields) {
getFieldDescription(field.getInternalName(), field.getDisplayName(), true);
}
// Now identify any potentially out of date fields
if (this.getFieldDescriptions() != null && !this.getFieldDescriptions().isEmpty()) {
for (GuideField fieldDescription : this.getFieldDescriptions()) {
if (fieldDescription.getFieldName() != null && !fieldDescription.getFieldName().isEmpty()) {
if (type.getField(fieldDescription.getFieldName()) == null) {
fieldDescription.setDisplayName(fieldDescription.getFieldName() + " - INTERNAL FIELDNAME NO LONGER FOUND IN OBJECT TYPE. EVALUATE FOR CONTENT TRANSFER/REMOVAL.");
}
}
}
}
}
}
/*
* (non-Javadoc)
*
* @see com.psddev.dari.db.Record#beforeSave()
*/
public void beforeSave() {
generateFieldDescriptionList();
}
@Record.Embedded
@Record.LabelFields({ "displayName" })
public static class GuideField extends Record {
@Required
@Indexed
@ToolUi.Note("Internal fieldname in this object type. This is used to query the object and must exactly match the internal name. Extreme care should be taken if changing.")
String fieldName;
@ToolUi.Note("Displayed fieldname (note that changing this value does not change the value on the edit form)")
String displayName;
@ToolUi.Note("Production Guide information about this field")
ReferentialText description;
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public ReferentialText getDescription() {
return description;
}
public void setDescription(ReferentialText description) {
this.description = description;
}
}
public static final class Static {
public static ReferentialText getFieldDescription(State state,
String fieldName) {
if (state != null) {
ObjectType typeDefinition = state.getType();
GuideType guide = getGuideType(typeDefinition);
if (guide != null) {
return guide.getFieldDescription(fieldName, null, false);
}
}
return null;
}
/**
* Retrieve the field's minimum value for Production Guide display. For
* these purposes, we suppress some of the "less-useful" max/min values
* as they can be confusing to the editors
*
*/
public static Object getFieldMinimumValue(ObjectField field) {
Object minVal = field.getMinimum();
if (minVal != null) {
Class javaTypeClass = minVal.getClass();
if (javaTypeClass.equals(long.class) ||
javaTypeClass.equals(Long.class) &&
(Long) minVal == Long.MIN_VALUE) {
return null;
}
if (javaTypeClass.equals(int.class) ||
javaTypeClass.equals(Integer.class) &&
(Integer) minVal == Integer.MIN_VALUE) {
return null;
}
if (javaTypeClass.equals(short.class) ||
javaTypeClass.equals(Short.class) &&
(Short) minVal == Short.MIN_VALUE) {
return null;
}
if (javaTypeClass.equals(byte.class) ||
javaTypeClass.equals(Byte.class) &&
(Byte) minVal == Byte.MIN_VALUE) {
return null;
}
}
return minVal;
}
/**
* Retrieve the field's maximum value for Production Guide display. For
* these purposes, we suppress some of the "less-useful" max/min values
* as they can be confusing to the editors
*
*/
public static Object getFieldMaximumValue(ObjectField field) {
Object maxVal = field.getMaximum();
if (maxVal != null) {
Class javaTypeClass = maxVal.getClass();
if (javaTypeClass.equals(long.class) ||
javaTypeClass.equals(Long.class) &&
(Long) maxVal == Long.MAX_VALUE) {
return null;
}
if (javaTypeClass.equals(int.class) ||
javaTypeClass.equals(Integer.class) &&
(Integer) maxVal == Integer.MAX_VALUE) {
return null;
}
if (javaTypeClass.equals(short.class) ||
javaTypeClass.equals(Short.class) &&
(Short) maxVal == Short.MAX_VALUE) {
return null;
}
if (javaTypeClass.equals(byte.class) ||
javaTypeClass.equals(Byte.class) &&
(Byte) maxVal == Byte.MAX_VALUE) {
return null;
}
}
return maxVal;
}
/**
* Query to get a T/F as to whether the given {@code fieldName} has any
* information we include in the field description (e.g. used to
* determine whether ? link is displayed in UI
*/
public static boolean hasFieldGuideInfo(State state, String fieldName) {
ObjectField field = state.getField(fieldName);
if (field.isRequired()) {
return true;
} else if (getFieldMaximumValue(field) != null) {
return true;
} else if (getFieldMinimumValue(field) != null) {
return true;
}
ReferentialText desc = getFieldDescription(state, fieldName);
if (desc != null && !desc.isEmpty()) {
return true;
}
return false;
}
/**
* Retrieve the existing GuideType instance for a given {@code objectType}.
* If none exists, null is returned
*/
public static GuideType getGuideType(ObjectType objectType) {
return Query.from(GuideType.class)
.where("documentedType = ?", objectType.getId()).first();
}
/**
* Retrieve a GuideType instance for the parent type of a given {@code field}, creating one if it
* doesn't already exist.
*/
public static GuideType findOrCreateGuide(ObjectField field) {
GuideType guide = Query.from(GuideType.class)
.where("documentedType = ?", field.getParentType().getId()).first();
if (guide == null) {
guide = createGuide(field.getParentType());
}
return guide;
}
/**
* Retrieve a GuideType instance for the given {@code documentedType}, creating one if it
* doesn't already exist.
*/
public static GuideType findOrCreateGuide(ObjectType documentedType) {
GuideType guide = Query.from(GuideType.class)
.where("documentedType = ?", documentedType.getId()).first();
if (guide == null) {
guide = createGuide(documentedType);
}
return guide;
}
/**
* Create a GuideType instance for the given {@code documentedType}. To allow for thread/transaction safety, this
* is synchronized first queries to ensure it hasn't already been created.
*/
public static synchronized GuideType createGuide(
ObjectType documentedType) {
Query<GuideType> query = Query.from(GuideType.class)
.where("documentedType = ?", documentedType.getId());
query.as(CachingDatabase.QueryOptions.class).setDisabled(true);
GuideType guide = query.first();
if (guide == null) {
LOGGER.info("Creating a production guide instance for type: " + documentedType);
guide = new GuideType();
guide.setDocumentedType(documentedType);
guide.saveImmediately();
}
return guide;
}
public static void setDescription(ObjectField field,
ReferentialText descText) {
GuideType guide = Static.findOrCreateGuide(field);
guide.setFieldDescription(field.getInternalName(), field.getDisplayName(), descText);
guide.saveImmediately();
}
/**
* Generate a GuideType instance for every content type usable in the
* templates
*/
public static void createDefaultTypeGuides() {
List<Template> templates = Query.from(Template.class).selectAll();
for (Template template : templates) {
for (ObjectType t : template.getContentTypes()) {
findOrCreateGuide(t);
// Create guides for the types referenced within this type
if (t.getFields() != null) {
for (ObjectField field : t.getFields()) {
Set<ObjectType> types = field.getTypes();
if (types != null) {
for (ObjectType type : types) {
if (type.getFields() != null && !type.getFields().isEmpty()) {
findOrCreateGuide(type);
}
}
}
}
}
}
}
}
}
}