
com.semanticcms.core.servlet.SemanticCMS Maven / Gradle / Ivy
/*
* semanticcms-core-servlet - Java API for modeling web page content and relationships in a Servlet environment.
* Copyright (C) 2014, 2015, 2016 AO Industries, Inc.
* [email protected]
* 7262 Bull Pen Cir
* Mobile, AL 36695
*
* This file is part of semanticcms-core-servlet.
*
* semanticcms-core-servlet is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* semanticcms-core-servlet is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with semanticcms-core-servlet. If not, see .
*/
package com.semanticcms.core.servlet;
import com.aoindustries.servlet.http.Dispatcher;
import com.aoindustries.util.PropertiesUtils;
import com.aoindustries.util.WrappedException;
import com.semanticcms.core.model.Book;
import com.semanticcms.core.model.PageRef;
import java.io.IOException;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
/**
* The SemanticCMS application context.
*
* TODO: Consider custom EL resolver for this variable: http://stackoverflow.com/questions/5016965/how-to-add-a-custom-variableresolver-in-pure-jsp
*/
public class SemanticCMS {
//
static final String ATTRIBUTE_NAME = "semanticCMS";
private static final Object instanceLock = new Object();
/**
* Gets the SemanticCMS instance, creating it if necessary.
*/
public static SemanticCMS getInstance(ServletContext servletContext) {
try {
synchronized(instanceLock) {
SemanticCMS semanticCMS = (SemanticCMS)servletContext.getAttribute(SemanticCMS.ATTRIBUTE_NAME);
if(semanticCMS == null) {
semanticCMS = new SemanticCMS(servletContext);
servletContext.setAttribute(SemanticCMS.ATTRIBUTE_NAME, semanticCMS);
}
return semanticCMS;
}
} catch(IOException e) {
throw new WrappedException(e);
}
}
private SemanticCMS(ServletContext servletContext) throws IOException {
this.demoMode = Boolean.parseBoolean(servletContext.getInitParameter(DEMO_MODE_INIT_PARAM));
this.rootBook = initBooks(servletContext);
}
//
//
private static final String DEMO_MODE_INIT_PARAM = "com.semanticcms.core.servlet.SemanticCMS.demoMode";
private final boolean demoMode;
/**
* When true, a cursory attempt will be made to hide sensitive information for demo mode.
*/
public boolean getDemoMode() {
return demoMode;
}
//
//
private static final String BOOKS_PROPERTIES_RESOURCE = "/WEB-INF/books.properties";
private static final String BOOKS_ATTRIBUTE_NAME = "books";
private static final String MISSING_BOOKS_ATTRIBUTE_NAME = "missingBooks";
private static final String ROOT_BOOK_ATTRIBUTE_NAME = "rootBook";
private static String getProperty(Properties booksProps, Set
//
/**
* The parameter name used for views.
*/
public static final String VIEW_PARAM = "view";
/**
* The default view is the content view and will have the empty view name.
*/
public static final String DEFAULT_VIEW_NAME = "content";
private final Object viewsLock = new Object();
/**
* The views by name in order added.
*/
private final Map viewsByName = new LinkedHashMap();
private static final Set viewGroups = Collections.unmodifiableSet(EnumSet.allOf(View.Group.class));
/**
* Gets all view groups.
*/
public Set getViewGroups() {
return viewGroups;
}
/**
* Gets the views in order added.
*/
public Map getViewsByName() {
return Collections.unmodifiableMap(viewsByName);
}
/**
* The views in order.
*/
private final SortedSet views = new TreeSet();
/**
* Gets the views, ordered by view group then display.
*
* @see View#compareTo(com.semanticcms.core.servlet.View)
*/
public SortedSet getViews() {
return Collections.unmodifiableSortedSet(views);
}
/**
* Registers a new view.
*
* @throws IllegalStateException if a view is already registered with the name.
*/
public void addView(View view) throws IllegalStateException {
String name = view.getName();
synchronized(viewsLock) {
if(viewsByName.containsKey(name)) throw new IllegalStateException("View already registered: " + name);
if(viewsByName.put(name, view) != null) throw new AssertionError();
if(!views.add(view)) throw new AssertionError();
}
}
//
//
/**
* The default theme is used when no other theme is registered.
*/
public static final String DEFAULT_THEME_NAME = "base";
/**
* The themes in order added.
*/
private final Map themes = new LinkedHashMap();
/**
* Gets the themes, in the order added.
*/
public Map getThemes() {
synchronized(themes) {
// Not returning a copy since themes are normally only registered on app start-up.
return Collections.unmodifiableMap(themes);
}
}
/**
* Registers a new theme.
*
* @throws IllegalStateException if a theme is already registered with the name.
*/
public void addTheme(Theme theme) throws IllegalStateException {
String name = theme.getName();
synchronized(themes) {
if(themes.containsKey(name)) throw new IllegalStateException("Theme already registered: " + name);
if(themes.put(name, theme) != null) throw new AssertionError();
}
}
//
//
/**
* The CSS links in the order added.
*/
private final Set cssLinks = new LinkedHashSet();
/**
* Gets the CSS links, in the order added.
*/
public Set getCssLinks() {
synchronized(cssLinks) {
// Not returning a copy since CSS links are normally only registered on app start-up.
return Collections.unmodifiableSet(cssLinks);
}
}
/**
* Registers a new CSS link.
*
* @throws IllegalStateException if the link is already registered.
*/
public void addCssLink(String cssLink) throws IllegalStateException {
synchronized(cssLinks) {
if(!cssLinks.add(cssLink)) throw new IllegalStateException("CSS link already registered: " + cssLink);
}
}
//
//
/**
* The scripts in the order added.
*/
private final Map scripts = new LinkedHashMap();
/**
* Gets the scripts, in the order added.
*/
public Map getScripts() {
synchronized(scripts) {
// Not returning a copy since scripts are normally only registered on app start-up.
return Collections.unmodifiableMap(scripts);
}
}
/**
* Registers a new script. When a script is added multiple times,
* the src must be consistent between adds. Also, a src may not be
* added under different names.
*
* @param name the name of the script, independent of version and src
* @param src the src of the script.
*
* @throws IllegalStateException if the script already registered but with a different src.
*/
public void addScript(String name, String src) throws IllegalStateException {
synchronized(scripts) {
String existingSrc = scripts.get(name);
if(existingSrc != null) {
if(!src.equals(existingSrc)) {
throw new IllegalStateException(
"Script already registered but with a different src:"
+ " name=" + name
+ " src=" + src
+ " existingSrc=" + existingSrc
);
}
} else {
// Make sure src not provided by another script
if(scripts.values().contains(src)) {
throw new IllegalArgumentException("Non-unique global script src: " + src);
}
if(scripts.put(name, src) != null) throw new AssertionError();
}
}
}
//
//
/**
* The head includes in the order added.
*/
private final Set headIncludes = new LinkedHashSet();
/**
* Gets the head includes, in the order added.
*/
public Set getHeadIncludes() {
synchronized(headIncludes) {
// Not returning a copy since head includes are normally only registered on app start-up.
return Collections.unmodifiableSet(headIncludes);
}
}
/**
* Registers a new head include.
*
* @throws IllegalStateException if the link is already registered.
*/
public void addHeadInclude(String headInclude) throws IllegalStateException {
synchronized(headIncludes) {
if(!headIncludes.add(headInclude)) throw new IllegalStateException("headInclude already registered: " + headInclude);
}
}
//
//
/**
* Resolves the link CSS class for the given types of elements.
*/
public static interface LinkCssClassResolver {
/**
* Gets the CSS class to use in links to the given element.
* When null is returned, any resolvers for super classes will also be invoked.
*
* @return The CSS class name or {@code null} when none configured for the provided element.
*/
String getCssLinkClass(E element);
}
/**
* The CSS classes used in links.
*/
private final Map,LinkCssClassResolver>> linkCssClassResolverByElementType = new LinkedHashMap,LinkCssClassResolver>>();
/**
* Gets the CSS class to use in links to the given element.
* Also looks for match on parent classes up to and including Element itself.
*
* @return The CSS class or {@code null} when element is null or no class registered for it or any super class.
*
* @see #getLinkCssClass(java.lang.Class)
*/
public String getLinkCssClass(E element) {
if(element == null) return null;
Class extends com.semanticcms.core.model.Element> elementType = element.getClass();
synchronized(linkCssClassResolverByElementType) {
while(true) {
@SuppressWarnings("unchecked")
LinkCssClassResolver super E> linkCssClassResolver = (LinkCssClassResolver super E>)linkCssClassResolverByElementType.get(elementType);
if(linkCssClassResolver != null) {
String linkCssClass = linkCssClassResolver.getCssLinkClass(element);
if(linkCssClass != null) return linkCssClass;
}
if(elementType == com.semanticcms.core.model.Element.class) return null;
elementType = elementType.getSuperclass().asSubclass(com.semanticcms.core.model.Element.class);
}
}
}
/**
* Registers a new CSS resolver to use in link to the given type of element.
*
* @throws IllegalStateException if the element type is already registered.
*/
public void addLinkCssClassResolver(
Class elementType,
LinkCssClassResolver super E> cssLinkClassResolver
) throws IllegalStateException {
synchronized(linkCssClassResolverByElementType) {
if(linkCssClassResolverByElementType.containsKey(elementType)) throw new IllegalStateException("Link CSS class already registered: " + elementType);
if(linkCssClassResolverByElementType.put(elementType, cssLinkClassResolver) != null) throw new AssertionError();
}
}
/**
* Registers a new CSS class to use in link to the given type of element.
*
* @throws IllegalStateException if the element type is already registered.
*/
public void addLinkCssClass(
Class elementType,
final String cssLinkClass
) throws IllegalStateException {
addLinkCssClassResolver(
elementType,
new LinkCssClassResolver() {
@Override
public String getCssLinkClass(E element) {
return cssLinkClass;
}
}
);
}
//
//
/**
* Resolves the list item CSS class for the given types of nodes.
*/
public static interface ListItemCssClassResolver {
/**
* Gets the CSS class to use in list items to the given node.
* When null is returned, any resolvers for super classes will also be invoked.
*
* @return The CSS class name or {@code null} when none configured for the provided node.
*/
String getListItemCssClass(N node);
}
/**
* The CSS classes used in list items.
*/
private final Map,ListItemCssClassResolver>> listItemCssClassResolverByNodeType = new LinkedHashMap,ListItemCssClassResolver>>();
/**
* Gets the CSS class to use in list items to the given node.
* Also looks for match on parent classes up to and including Node itself.
*
* @return The CSS class or {@code null} when node is null or no class registered for it or any super class.
*
* @see #getListItemCssClass(java.lang.Class)
*/
public String getListItemCssClass(N node) {
if(node == null) return null;
Class extends com.semanticcms.core.model.Node> nodeType = node.getClass();
synchronized(listItemCssClassResolverByNodeType) {
while(true) {
@SuppressWarnings("unchecked")
ListItemCssClassResolver super N> listItemCssClassResolver = (ListItemCssClassResolver super N>)listItemCssClassResolverByNodeType.get(nodeType);
if(listItemCssClassResolver != null) {
String listItemCssClass = listItemCssClassResolver.getListItemCssClass(node);
if(listItemCssClass != null) return listItemCssClass;
}
if(nodeType == com.semanticcms.core.model.Node.class) return null;
nodeType = nodeType.getSuperclass().asSubclass(com.semanticcms.core.model.Node.class);
}
}
}
/**
* Registers a new CSS resolver to use in list items to the given type of node.
*
* @throws IllegalStateException if the node type is already registered.
*/
public void addListItemCssClassResolver(
Class nodeType,
ListItemCssClassResolver super N> listItemCssClassResolver
) throws IllegalStateException {
synchronized(listItemCssClassResolverByNodeType) {
if(listItemCssClassResolverByNodeType.containsKey(nodeType)) throw new IllegalStateException("List item CSS class already registered: " + nodeType);
if(listItemCssClassResolverByNodeType.put(nodeType, listItemCssClassResolver) != null) throw new AssertionError();
}
}
/**
* Registers a new CSS class to use in list items to the given type of node.
*
* @throws IllegalStateException if the node type is already registered.
*/
public void addListItemCssClass(
Class nodeType,
final String listItemCssClass
) throws IllegalStateException {
addListItemCssClassResolver(
nodeType,
new ListItemCssClassResolver() {
@Override
public String getListItemCssClass(N node) {
return listItemCssClass;
}
}
);
}
//
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy