package com.psddev.cms.db; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.psddev.cms.tool.ToolPageContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.psddev.cms.tool.CmsTool; import com.psddev.dari.db.Modification; import com.psddev.dari.db.Predicate; import com.psddev.dari.db.PredicateParser; import com.psddev.dari.db.Query; import com.psddev.dari.db.Record; import com.psddev.dari.db.State; import com.psddev.dari.util.ObjectUtils; import com.psddev.dari.util.PeriodicValue; import com.psddev.dari.util.PullThroughValue; import com.psddev.dari.util.StorageItem; /** Group of pages that's regarded as one entity. */ @ToolUi.IconName("object-site") @Record.BootstrapPackages("Sites") public class Site extends Record implements Global, Managed { private static final String FIELD_PREFIX = "cms.site."; private static final Logger LOGGER = LoggerFactory.getLogger(Site.class); public static final String OWNER_FIELD = FIELD_PREFIX + "owner"; public static final String IS_GLOBAL_FIELD = FIELD_PREFIX + "isGlobal"; public static final String BLACKLIST_FIELD = FIELD_PREFIX + "blacklist"; public static final String CONSUMERS_FIELD = FIELD_PREFIX + "consumers"; @Indexed(unique = true) @Required private String name; @ToolUi.Tab("Dashboard") private List<CmsTool.ResourceItem> resources; @ToolUi.Tab("Dashboard") private CmsTool.CommonContentSettings commonContentSettings; @ToolUi.Tab("Dashboard") private CmsTool.BulkUploadSettings bulkUploadSettings; @ToolUi.Tab("Advanced") private String cmsCssClass; @ToolUi.Tab("Advanced") private StorageItem cmsLogo; @Indexed(unique = true) private List<String> urls; @Indexed private SiteCategory siteCategory; @ToolUi.Tab("Advanced") private Variation defaultVariation; @ToolUi.Tab("Advanced") private Boolean isAllSitesAccessible; @ToolUi.Tab("Advanced") private Set<Site> accessibleSites; /** @deprecated Use {@link Static#findAll} instead. */ @Deprecated public static List<Site> findAll() { return Static.findAll(); } /** @deprecated Use {@link Static#findByUrl} instead. */ @Deprecated public static Map.Entry<String, Site> findByUrl(String url) { return Static.findByUrl(url); } /** Returns the display name. */ public String getName() { return name; } /** Sets the display name. */ public void setName(String name) { this.name = name; } public List<CmsTool.ResourceItem> getResources() { if (resources == null) { resources = new ArrayList<CmsTool.ResourceItem>(); } return resources; } public void setResources(List<CmsTool.ResourceItem> resources) { this.resources = resources; } public CmsTool.CommonContentSettings getCommonContentSettings() { return commonContentSettings; } public void setCommonContentSettings(CmsTool.CommonContentSettings commonContentSettings) { this.commonContentSettings = commonContentSettings; } public CmsTool.BulkUploadSettings getBulkUploadSettings() { return bulkUploadSettings; } public void setBulkUploadSettings(CmsTool.BulkUploadSettings bulkUploadSettings) { this.bulkUploadSettings = bulkUploadSettings; } public String getCmsCssClass() { return cmsCssClass; } public void setCmsCssClass(String cmsCssClass) { this.cmsCssClass = cmsCssClass; } public StorageItem getCmsLogo() { return cmsLogo; } public void setCmsLogo(StorageItem cmsLogo) { this.cmsLogo = cmsLogo; } /** Returns the list of URLs that prefix all pages in this site. */ public List<String> getUrls() { if (urls == null) { urls = new ArrayList<String>(); } return urls; } /** Sets the list of URLs that prefix all pages in this site. */ public void setUrls(List<String> urls) { this.urls = urls; } public SiteCategory getSiteCategory() { return siteCategory; } public void setSiteCategory(SiteCategory siteCategory) { this.siteCategory = siteCategory; } public Variation getDefaultVariation() { return defaultVariation; } public void setDefaultVariation(Variation defaultVariation) { this.defaultVariation = defaultVariation; } /** Returns {@code true} if this site can access all other sites. */ public boolean isAllSitesAccessible() { return Boolean.TRUE.equals(isAllSitesAccessible); } /** Sets whether this site can access all other sites. */ public void setAllSitesAccessible(boolean isAllSitesAccessible) { this.isAllSitesAccessible = isAllSitesAccessible ? Boolean.TRUE : null; } /** Returns the list of other sites that this one can access. */ public Set<Site> getAccessibleSites() { if (accessibleSites == null) { accessibleSites = new LinkedHashSet<Site>(); } return accessibleSites; } /** Sets the list of other sites that this one can access. */ public void setAccessibleSites(Set<Site> accessibleSites) { this.accessibleSites = accessibleSites; } public String getPermissionId() { return "site/" + getId(); } public String getPrimaryUrl() { if (getUrls().size() > 0) { String url = getUrls().get(0); if (url.endsWith("/")) { url = url.substring(0, url.length() - 1); } return url; } return null; } /** Returns the raw path that is stored within other objects. */ public String getRawPath() { return getId() + ":"; } /** * Returns a predicate that filters out any objects not accessible * by this site. */ public Predicate itemsPredicate() { if (isAllSitesAccessible()) { return null; } else { Set<Site> consumers = new LinkedHashSet<Site>(); consumers.add(this); consumers.addAll(getAccessibleSites()); return PredicateParser.Static.parse( Site.OWNER_FIELD + " = ? or (" + Site.IS_GLOBAL_FIELD + " = ? and " + Site.BLACKLIST_FIELD + " != ?) or " + Site.CONSUMERS_FIELD + " = ?", consumers, Boolean.TRUE, this, consumers); } } @Override public String createManagedEditUrl(ToolPageContext page) { return page.cmsUrl("/admin/sites.jsp", "id", getId()); } /** Static utility methods. */ public static final class Static { private static final Comparator<Map.Entry<String, ?>> LONGEST_KEY_FIRST = new Comparator<Map.Entry<String, ?>>() { @Override public int compare(Map.Entry<String, ?> x, Map.Entry<String, ?> y) { int xLength = x.getKey().length(); int yLength = y.getKey().length(); return xLength > yLength ? -1 : xLength < yLength ? 1 : 0; } }; private static final PullThroughValue<PeriodicValue<List<Site>>> INSTANCES = new PullThroughValue<PeriodicValue<List<Site>>>() { @Override protected PeriodicValue<List<Site>> produce() { return new PeriodicValue<List<Site>>() { @Override protected List<Site> update() { Query<Site> query = Query.from(Site.class).sortAscending("name"); Date cacheUpdate = getUpdateDate(); Date databaseUpdate = query.lastUpdate(); if (databaseUpdate == null || (cacheUpdate != null && !databaseUpdate.after(cacheUpdate))) { List<Site> sites = get(); return sites != null ? sites : Collections.<Site>emptyList(); } LOGGER.info("Loading sites"); return query.selectAll(); } }; } }; private Static() { } /** Returns a cached list of all sites. */ public static List<Site> findAll() { return new ArrayList<Site>(INSTANCES.get().get()); } /** Finds a cached site associated with the given {@code url}. */ public static Map.Entry<String, Site> findByUrl(String url) { if (url == null) { return null; } URI requestUri; try { requestUri = new URI(url); } catch (URISyntaxException ex) { try { URL urlObject = new URL(url); requestUri = new URI(urlObject.getProtocol(), urlObject.getAuthority(), urlObject.getHost(), urlObject.getPort(), urlObject.getPath(), urlObject.getQuery(), urlObject.getRef()); } catch (MalformedURLException error2) { return null; } catch (URISyntaxException error2) { return null; } } Map<String, Site> checkUrlsMap = new HashMap<String, Site>(); for (Site site : INSTANCES.get().get()) { for (String siteUrl : site.getUrls()) { try { String checkUrl = requestUri.resolve(siteUrl).toString(); if (!checkUrl.endsWith("/")) { checkUrl += "/"; } checkUrlsMap.put(checkUrl, site); } catch (IllegalArgumentException e) { // the url is malformed, just skip it } } } List<Map.Entry<String, Site>> checkUrls = new ArrayList<Map.Entry<String, Site>>(checkUrlsMap.entrySet()); Collections.sort(checkUrls, LONGEST_KEY_FIRST); if (!url.endsWith("/")) { url += "/"; } for (Map.Entry<String, Site> entry : checkUrls) { if (url.startsWith(entry.getKey())) { return entry; } } return null; } /** * Returns {@code true} if the given {@code object} is accessible * by the given {@code site}. */ public static boolean isObjectAccessible(Site site, Object object) { State objectState = State.getInstance(object); Site.ObjectModification objectSiteMod = objectState.as(Site.ObjectModification.class); Site objectOwner = objectSiteMod.getOwner(); if (ObjectUtils.equals(site, objectOwner)) { return true; } else if (site == null) { return false; } else if (site.isAllSitesAccessible()) { return true; } else if (site.getAccessibleSites().contains(objectOwner)) { return true; } else { return (objectSiteMod.isGlobal() && !objectSiteMod.getBlacklist().contains(site)) || objectSiteMod.getConsumers().contains(site); } } } /** Modification that adds object accessibility information per site. */ public static class ObjectModification extends Modification<Object> { @Indexed @InternalName(OWNER_FIELD) @ToolUi.Hidden private Site owner; @Indexed @InternalName(IS_GLOBAL_FIELD) @ToolUi.Hidden private Boolean isGlobal; @Indexed @InternalName(BLACKLIST_FIELD) @ToolUi.Hidden private Set<Site> blacklist; @Indexed @InternalName(CONSUMERS_FIELD) @ToolUi.Hidden private Set<Site> consumers; /** Returns the owner that controls this object. */ public Site getOwner() { return owner; } /** Sets the owner that controls this object. */ public void setOwner(Site owner) { this.owner = owner; } /** * Returns {@code true} if this object can be accessed globally * by any site. */ public boolean isGlobal() { return Boolean.TRUE.equals(isGlobal); } /** Sets whether this object can be accessed globally by any site. */ public void setGlobal(boolean isGlobal) { this.isGlobal = isGlobal ? Boolean.TRUE : null; } /** * Returns the set of blacklisted sites that aren't allowed to * access this object. */ public Set<Site> getBlacklist() { if (blacklist == null) { blacklist = new LinkedHashSet<Site>(); } return blacklist; } /** * Sets the set of blacklisted sites that aren't allowed to * access this object. */ public void setBlacklist(Set<Site> blacklist) { this.blacklist = blacklist; } /** * Returns the set of consumer sites that are allowed to access * the object. */ public Set<Site> getConsumers() { if (consumers == null) { consumers = new LinkedHashSet<Site>(); } return consumers; } /** * Sets the set of consumer sites that are allowed to access * this object. */ public void setConsumers(Set<Site> consumers) { this.consumers = consumers; } } }