package com.psddev.cms.db;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.psddev.dari.db.Database;
import com.psddev.dari.db.Modification;
import com.psddev.dari.db.ObjectType;
import com.psddev.dari.db.Query;
import com.psddev.dari.db.Record;
import com.psddev.dari.db.State;
import com.psddev.dari.util.ErrorUtils;
import com.psddev.dari.util.PeriodicValue;
import com.psddev.dari.util.PullThroughValue;
// CHECKSTYLE:OFF
/**
* Changes objects based on rules.
*
* <p>Some of the uses of this class are: A/B testing, internationalization,
* and delivery of a different layout for mobile.
*
* @deprecated No replacement.
*/
@Deprecated
public class Variation extends Record {
public static final String APPLIED_EXTRA = "cms.variation.applied";
private static final Logger LOGGER = LoggerFactory.getLogger(Variation.class);
@Indexed(unique = true)
@Required
private String name;
@Indexed(unique = true)
@Required
@ToolUi.Hidden
private double position;
@ToolUi.Note("Leave blank to allow on all content types.")
private Set<ObjectType> contentTypes;
@Required
private Rule rule;
@Required
private Operation operation;
/** Returns the name. Displayed in the tool UI. */
public String getName() {
return this.name;
}
/** Sets the name. */
public void setName(String name) {
this.name = name;
}
/** Returns the position. Determines the order during {@link #applyAll}. */
public double getPosition() {
return this.position;
}
/** Sets the position. */
public void setPosition(double position) {
this.position = position;
}
public Set<ObjectType> getContentTypes() {
if (contentTypes == null) {
contentTypes = new HashSet<ObjectType>();
}
return contentTypes;
}
public void setContentTypes(Set<ObjectType> contentTypes) {
this.contentTypes = contentTypes;
}
/** Returns the rule. */
public Rule getRule() {
return this.rule;
}
/** Sets the rule. */
public void setRule(Rule rule) {
this.rule = rule;
}
/** Returns the operation. */
public Operation getOperation() {
return this.operation;
}
/** Sets the operation. */
public void setOperation(Operation operation) {
this.operation = operation;
}
/**
* @deprecated No replacement.
*/
@Deprecated
public static class Data extends Modification<Object> {
private Map<String, Object> variations;
@InternalName("cms.variation.initialVariation")
private Variation initialVariation;
public Map<String, Object> getVariations() {
if (variations == null) {
variations = new HashMap<String, Object>();
}
return variations;
}
public void setVariations(Map<String, Object> variations) {
this.variations = variations;
}
public Variation getInitialVariation() {
return initialVariation;
}
public void setInitialVariation(Variation initialVariation) {
this.initialVariation = initialVariation;
}
}
/**
* {@link Variation} utility methods.
*
* @deprecated No replacement.
*/
@Deprecated
public static final class Static {
private Static() {
}
private static final PullThroughValue<PeriodicValue<List<Variation>>>
ALL = new PullThroughValue<PeriodicValue<List<Variation>>>() {
@Override
protected PeriodicValue<List<Variation>> produce() {
return new PeriodicValue<List<Variation>>() {
@Override
protected List<Variation> update() {
Query<Variation> query = Query.from(Variation.class).sortAscending("position").using(Database.Static.getDefaultOriginal());
Date cacheUpdate = getUpdateDate();
Date databaseUpdate = query.lastUpdate();
if (databaseUpdate == null || (cacheUpdate != null && !databaseUpdate.after(cacheUpdate))) {
List<Variation> variations = get();
return variations != null ? variations : Collections.<Variation>emptyList();
}
LOGGER.info("Loading variations");
return query.selectAll();
}
};
}
};
/**
* Applies all variations to the given {@code object} using the
* given {@code profile}.
*
* @throws IllegalArgumentException If the given {@code object}
* or {@code profile} is {@code null}.
*/
public static void applyAll(Object object, Profile profile) {
ErrorUtils.errorIfNull(object, "object");
ErrorUtils.errorIfNull(profile, "profile");
List<Variation> applied = getApplied(object);
for (Variation variation : ALL.get().get()) {
try {
if (!applied.contains(variation) &&
variation.getRule().evaluate(variation, profile, object)) {
applied.add(variation);
variation.getOperation().evaluate(variation, profile, object);
}
} catch (Throwable error) {
LOGGER.warn(String.format(
"Can't apply variation [%s] to [%s]!",
variation.getId(),
State.getInstance(object).getId()),
error);
}
}
}
/**
* Returns the list of variations that have been applied to
* the given {@code object} so far.
*
* @throws IllegalArgumentException If the given {@code object}
* is {@code null}.
*/
public static List<Variation> getApplied(Object object) {
ErrorUtils.errorIfNull(object, "object");
Map<String, Object> extras = State.getInstance(object).getExtras();
@SuppressWarnings("unchecked")
List<Variation> applied = (List<Variation>) extras.get(APPLIED_EXTRA);
if (applied == null) {
applied = new ArrayList<Variation>();
extras.put(APPLIED_EXTRA, applied);
}
return applied;
}
/**
* Returns a list of variations that can be applied to the instances
* of the given {@code type}.
*
* @return Never {@code null}.
*/
public static List<Variation> getApplicable(ObjectType type) {
List<Variation> applicable = new ArrayList<Variation>();
for (Variation variation : ALL.get().get()) {
Set<ObjectType> types = variation.getContentTypes();
if (types.isEmpty() || types.contains(type)) {
applicable.add(variation);
}
}
return applicable;
}
}
// --- Deprecated ---
/** @deprecated Use {@link Static#applyAll} instead. */
@Deprecated
public static void applyAll(Object object, Profile profile) {
Static.applyAll(object, profile);
}
/** @deprecated Use {@link Static#getApplied} instead. */
@Deprecated
public static List<Variation> getApplied(Object object) {
return Static.getApplied(object);
}
}