
org.sakaiproject.config.impl.StoredConfigService Maven / Gradle / Ivy
/******************************************************************************
* $URL: $
* $Id: $
******************************************************************************
*
* Copyright (c) 2003-2014 The Apereo Foundation
*
* Licensed under the Educational Community 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://opensource.org/licenses/ecl2
*
* 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.
*
*****************************************************************************/
package org.sakaiproject.config.impl;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.util.Calendar;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.IllegalClassException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasypt.encryption.pbe.PBEStringEncryptor;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.component.api.ServerConfigurationService.ConfigData;
import org.sakaiproject.component.api.ServerConfigurationService.ConfigItem;
import org.sakaiproject.component.api.ServerConfigurationService.ConfigurationListener;
import org.sakaiproject.component.api.ServerConfigurationService.ConfigurationProvider;
import org.sakaiproject.component.impl.ConfigItemImpl;
import org.sakaiproject.config.api.HibernateConfigItem;
import org.sakaiproject.config.api.HibernateConfigItemDao;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* KNL-1063
* Manages the persistence details of a ConfigItem in the form of a HibernateConfigItem.
* This service does take into consideration multiple application servers and partitions
* the properties appropriately using the serverId.
*
* DO NOT USE THIS SERVICE to change properties use ServerConfigurationService.
*
* Once SCS has been updated with property changes they will then persisted no need to
* do anything else.
*
* @author Earle Nietzel
* Created on Mar 8, 2013
*/
public class StoredConfigService implements ConfigurationListener, ConfigurationProvider {
// enables persistence of ConfigItems that SCS knows about
public static final String SAKAI_CONFIG_STORE_ENABLE = "sakai.config.store.enable";
// registers with SCS as a provider of config
public static final String SAKAI_CONFIG_PROVIDE_ENABLE = "sakai.config.provide.enable";
public static final boolean SAKAI_CONFIG_PROVIDE_ENABLE_DEFAULT = true;
// enables the database poller to provide config during a running system
public static final String SAKAI_CONFIG_POLL_ENABLE = "sakai.config.poll.enable";
// how often to poll for config
public static final String SAKAI_CONFIG_POLL_SECONDS = "sakai.config.poll.seconds";
// use unexpanded values hence raw
public static final String SAKAI_CONFIG_USE_RAW = "sakai.config.use.raw";
// config that should never be persisted
public static final String SAKAI_CONFIG_NEVER_PERSIST = "sakai.config.never.persist";
private final Log log = LogFactory.getLog(StoredConfigService.class);
private ScheduledExecutorService scheduler;
private ServerConfigurationService serverConfigurationService;
private HibernateConfigItemDao dao;
private PBEStringEncryptor textEncryptor;
private Set neverPersistItems;
private String node;
public void init() {
log.info("init()");
node = serverConfigurationService.getServerId();
if (StringUtils.isBlank(node)) {
log.error("init(); node cannot be blank, StoredConfigService is disabled");
return;
}
// get configured config to skip
String[] doNotPersistConfig = serverConfigurationService.getStrings(SAKAI_CONFIG_NEVER_PERSIST);
List tmpdoNotPersist;
if (doNotPersistConfig == null) {
// SCS can return a null here if config is not found
tmpdoNotPersist = new ArrayList();
} else {
tmpdoNotPersist = Arrays.asList(doNotPersistConfig);
}
// always add [email protected]
tmpdoNotPersist.add("[email protected]");
// TODO add more stuff here like serverId, DB password
// Setup list of items we should never persist
neverPersistItems = Collections.unmodifiableSet(new HashSet(tmpdoNotPersist));
// delete items that should never persisted
for (String item : neverPersistItems) {
deleteHibernateConfigItem(item);
}
// enables storing of all config that SCS knows about
if (serverConfigurationService.getBoolean(SAKAI_CONFIG_STORE_ENABLE, false)) {
learnConfig(serverConfigurationService.getConfigData().getItems());
serverConfigurationService.registerListener(this);
}
if (serverConfigurationService.getBoolean(SAKAI_CONFIG_POLL_ENABLE, false)) {
final int pollDelaySeconds = serverConfigurationService.getInt(SAKAI_CONFIG_POLL_SECONDS, 60);
// setup an ExecutorService
scheduler = Executors.newSingleThreadScheduledExecutor();
// schedule task for every pollDelaySeconds
scheduler.scheduleWithFixedDelay(
new Runnable() {
Date pollDate;
@Override
public void run() {
pollDate = storedConfigPoller(pollDelaySeconds, pollDate);
}
},
pollDelaySeconds < 120 ? 120 : pollDelaySeconds, // minimally wait 120 seconds for sakai to start
pollDelaySeconds, TimeUnit.SECONDS
);
log.info("init() " + SAKAI_CONFIG_POLL_ENABLE + " is enabled and polling every " + pollDelaySeconds + " seconds");
}
}
public void destroy() {
// terminate the scheduler
if (scheduler != null) {
scheduler.shutdown();
}
}
private Date storedConfigPoller(int delay, Date then) {
Calendar calendar = Calendar.getInstance();
Date now = calendar.getTime();
if (then == null) {
// on start set then
calendar.add(Calendar.SECOND, -(delay));
then = calendar.getTime();
}
List polled = findPollOn(then, now);
int registered = 0;
for (HibernateConfigItem item : polled) {
if (item.isRegistered()) { // registering items that are not marked as registered leads to bad things in SCS
ConfigItem cItem = serverConfigurationService.getConfigItem(item.getName());
if (!item.similar(cItem)) { // only register items that are not similar, reduces history on items in SCS
serverConfigurationService.registerConfigItem(createConfigItem(item));
registered++;
}
} else {
log.warn("storedConfigPoller() item " + item.getName() + " is not registered skipping");
}
}
if (log.isDebugEnabled()) {
log.debug("storedConfigPoller() Polling found " + polled.size() + " config item(s) (from " +
new SimpleDateFormat("HH:mm:ss").format(then) + " to " +
new SimpleDateFormat("HH:mm:ss").format(now) + "), " + registered + " item(s) registered");
}
return now;
}
/**
* Creates a HibernateConfigItem for every item in the list and persists it for this node.
*
* @param items List of ConfigItem
*/
private void learnConfig(List items) {
if (items == null) {
return;
}
int total = items.size();
int updated = 0;
int created = 0;
for (ConfigItem item : items) {
if (item == null) {
continue; // skip this item
}
HibernateConfigItem hItem = null;
int rows = countByName(item.getName());
if (rows == 0) {
// new item, create it
hItem = createHibernateConfigItem(item);
created++;
} else {
// sync config with files when store enabled and not provide enabled
if (!serverConfigurationService.getBoolean(SAKAI_CONFIG_PROVIDE_ENABLE, SAKAI_CONFIG_PROVIDE_ENABLE_DEFAULT)) {
hItem = findByName(item.getName());
hItem = updateHibernateConfigItem(hItem, item);
if (hItem != null) {
// if hItem is not null it was updated
updated++;
}
}
}
saveOrUpdate(hItem);
}
log.info("initItems() processed " + total + " config items, updated " + updated + " created " + created);
}
/**
* Persists or updates (if it already exists) a HibernateConfigItem to the database for this node
*
* @param hItem a HibernateConfigItem
*/
public void saveOrUpdate(HibernateConfigItem hItem) {
if (hItem == null) {
return;
}
String name = hItem.getName();
String type = hItem.getType();
if (name == null || name.isEmpty()) {
log.warn("saveOrUpdate() item name is missing");
return;
} else if (!(ServerConfigurationService.TYPE_STRING.equals(type)
|| ServerConfigurationService.TYPE_INT.equals(type)
|| ServerConfigurationService.TYPE_BOOLEAN.equals(type)
|| ServerConfigurationService.TYPE_ARRAY.equals(type))) {
log.warn("saveOrUpdate() item type is incorrect");
return;
}
dao.saveOrUpdate(hItem);
}
/**
* Get all registered HibernateConfigItem as ConfigItems for this node
*
* @return a List of ConfigItem
*/
public List getConfigItems() {
List configItems = new ArrayList();
List regItems = findRegistered();
for (HibernateConfigItem hItem : regItems) {
ConfigItem item = createConfigItem(hItem);
if (item != null) {
configItems.add(item);
if (log.isDebugEnabled()) {
log.debug("getConfigItems() " + item.toString());
}
}
}
return configItems;
}
/**
* Creates an equivalent ConfigItem from a HibernateConfigItem
*
* @param hItem a HibernateConfigItem
* @return a ConfigItem
* @throws IllegalClassException this can occur during deserialization when creating a ConfigItem
*/
public ConfigItem createConfigItem(HibernateConfigItem hItem) throws IllegalClassException {
if (hItem == null) {
return null;
}
String value;
if (serverConfigurationService.getBoolean(SAKAI_CONFIG_USE_RAW, false)
&& StringUtils.isNotBlank(hItem.getRawValue())
) {
value = hItem.getRawValue();
} else {
value = hItem.getValue();
}
// create new ConfigItem
ConfigItem item = new ConfigItemImpl(
hItem.getName(),
deSerializeValue(value, hItem.getType(), hItem.isSecured()),
hItem.getType(),
hItem.getDescription(),
this.getClass().getName(),
deSerializeValue(hItem.getDefaultValue(), hItem.getType(), hItem.isSecured()),
0, // requested
0, // changed
null, // history
hItem.isRegistered(),
hItem.isDefaulted(),
hItem.isSecured(),
hItem.isDynamic()
);
if (log.isDebugEnabled()) {
log.debug("createConfigItem() " + item.toString());
}
return item;
}
/**
* Creates an equivalent HibernateConfigItem from a ConfigItem
*
* @param item a ConfigItem
* @return a HibernateConfigItem
* @throws IllegalClassException thrown when the item.getValue doesn't return the right type
*/
public HibernateConfigItem createHibernateConfigItem(ConfigItem item) throws IllegalClassException {
if (item == null || neverPersistItems.contains(item.getName())) {
return null;
}
if (log.isDebugEnabled()) {
log.debug("createHibernateConfigItem() New ConfigItem = " + item.toString());
}
String serialValue;
String serialDefaultValue;
String serialRawValue;
try {
serialValue = serializeValue(item.getValue(), item.getType(), item.isSecured());
serialDefaultValue = serializeValue(item.getDefaultValue(), item.getType(), item.isSecured());
serialRawValue = serializeValue(getRawProperty(item.getName()), ServerConfigurationService.TYPE_STRING, item.isSecured());
} catch (IllegalClassException ice) {
log.error("createHibernateConfigItem() IllegalClassException " + ice.getMessage() + " skip ConfigItem " + item.toString(), ice);
return null;
}
HibernateConfigItem hItem = new HibernateConfigItem(node,
item.getName(),
serialValue,
serialRawValue,
item.getType(),
item.getDescription(),
item.getSource(),
serialDefaultValue,
item.isRegistered(),
item.isDefaulted(),
item.isSecured(),
item.isDynamic());
if (log.isDebugEnabled()) {
log.debug("createHibernateConfigItem() Created HibernateConfigItem = " + hItem.toString());
}
return hItem;
}
/**
* Updates a HibernateConfigItem with a ConfigItem's data; the name, node
*
* @param hItem a HibernateConfigItem
* @param item a ConfigItem
* @return a HibernateConfigItem if it was updated or null if it was not updated
* @throws IllegalClassException thrown when the item.getValue doesn't return the right type
*/
public HibernateConfigItem updateHibernateConfigItem(HibernateConfigItem hItem, ConfigItem item) throws IllegalClassException {
if (hItem == null || item == null) {
return null;
}
HibernateConfigItem updatedItem = null;
// check if updating is needed, update it
if (!hItem.similar(item)) {
// if they are not similar update it
if (log.isDebugEnabled()) {
log.debug("updateHibernateConfigItem() Before " + hItem.toString());
}
Object value = deSerializeValue(hItem.getValue(), hItem.getType(), hItem.isSecured());
Object defaultValue = deSerializeValue(hItem.getDefaultValue(), hItem.getType(), hItem.isSecured());
try {
if (value == null) {
if (item.getValue() != null) {
// different update
hItem.setValue(serializeValue(item.getValue(), item.getType(), item.isSecured()));
}
} else if (!value.equals(item.getValue())) {
// different update
hItem.setValue(serializeValue(item.getValue(), item.getType(), item.isSecured()));
}
if (defaultValue == null) {
if (item.getDefaultValue() != null) {
// different update
hItem.setDefaultValue(serializeValue(item.getDefaultValue(), item.getType(), item.isSecured()));
}
} else if (!defaultValue.equals(item.getDefaultValue())) {
// different update
hItem.setDefaultValue(serializeValue(item.getDefaultValue(), item.getType(), item.isSecured()));
}
} catch (IllegalClassException ice) {
log.error("updateHibernateConfigItem() IllegalClassException " + ice.getMessage() + " skip ConfigItem " + item.toString(), ice);
return null;
}
hItem.setType(item.getType());
hItem.setDefaulted(item.isDefaulted());
hItem.setSecured(item.isSecured());
hItem.setRegistered(item.isRegistered());
hItem.setSource(item.getSource());
hItem.setDescription(item.getDescription());
hItem.setDynamic(item.isDynamic());
hItem.setModified(Calendar.getInstance().getTime());
if (log.isDebugEnabled()) {
log.debug("updateHibernateConfigItem() After " + hItem.toString());
}
updatedItem = hItem;
}
// check if raw value needs updating
// raw values are always strings
String rawValue = (String) deSerializeValue(hItem.getRawValue(), ServerConfigurationService.TYPE_STRING, hItem.isSecured());
if (!StringUtils.equals(rawValue, getRawProperty(hItem.getName()))) {
// different update
hItem.setRawValue(serializeValue(getRawProperty(hItem.getName()), ServerConfigurationService.TYPE_STRING, item.isSecured()));
updatedItem = hItem;
}
// only return a hItem if it was updated
return updatedItem;
}
/**
* Delete a HibernateConfigItem
*
* @param name The HibernateConfigItem that should be deleted
*/
public void deleteHibernateConfigItem(String name) {
if (StringUtils.isBlank(name)) {
return;
}
HibernateConfigItem hItem = findByName(name);
if (hItem != null) {
log.info("deleteHibernateConfigItem() delete HibernateConfigItem = " + hItem);
dao.delete(hItem);
}
}
/* (non-Javadoc)
* @see org.sakaiproject.component.api.ServerConfigurationService.ConfigurationListener#changed(org.sakaiproject.component.api.ServerConfigurationService.ConfigItem, org.sakaiproject.component.api.ServerConfigurationService.ConfigItem)
*/
@Override
public void changed(ConfigItem item, ConfigItem previous) {
// a ConfigItem has changed
if (item == null) {
return;
}
HibernateConfigItem hItem = findByName(item.getName());
if (hItem == null) {
// new hItem
hItem = createHibernateConfigItem(item);
} else {
// existing hItem, update it
hItem = updateHibernateConfigItem(hItem, item);
}
saveOrUpdate(hItem);
}
/* (non-Javadoc)
* @see org.sakaiproject.component.api.ServerConfigurationService.ConfigurationListener#changing(org.sakaiproject.component.api.ServerConfigurationService.ConfigItem, org.sakaiproject.component.api.ServerConfigurationService.ConfigItem)
*/
@Override
public ConfigItem changing(ConfigItem currentConfigItem, ConfigItem newConfigItem) {
// no need to do anything before item has changed
return null;
}
@Override
public List registerConfigItems(ConfigData configData) {
if (serverConfigurationService.getBoolean(SAKAI_CONFIG_PROVIDE_ENABLE, SAKAI_CONFIG_PROVIDE_ENABLE_DEFAULT)) {
return getConfigItems();
}
return Collections.emptyList();
}
/**
* Find a HibernateConfigItem with a matching name for this node
*
* @param name the name of the item to find
* @return a HibernateConfigItem matching the name for this node or null if none found
*/
public HibernateConfigItem findByName(String name) {
HibernateConfigItem item = null;
List list = dao.findAllByCriteriaByNode(node, name, null, null, null, null);
if (list.size() == 1) {
item = list.get(0);
}
return item;
}
/**
* Number of properties with a matching name on this node
*
* @param name the name of the item to count
* @return a positive integer or -1 if name or node is null
*/
public int countByName(String name) {
return dao.countByNodeAndName(node, name);
}
/**
* Number of all properties on this node
*
* @return a positive integer or -1 if node is null
*/
public int countAll() {
return dao.countByNode(node);
}
/**
* Find all HibernateConfigItem(s) for this node
*
* @return a List of HibernateConfigItem(s)
*/
public List findAll() {
return dao.findAllByCriteriaByNode(node, null, null, null, null, null);
}
/**
* Find all Secured HibernateConfigItem(s) for this node
*
* @return a List of HibernateConfigItem(s)
*/
public List findSecured() {
return dao.findAllByCriteriaByNode(node, null, null, null, null, true);
}
/**
* Find all Registered HibernateConfigItem(s) for this node
*
* @return a List of HibernateConfigItem(s)
*/
public List findRegistered() {
return dao.findAllByCriteriaByNode(node, null, null, true, null, null);
}
/**
* Find all Dynamic HibernateConfigItem(s) for this node
*
* @return a List of HibernateConfigItem(s)
*/
public List findDynamic() {
return dao.findAllByCriteriaByNode(node, null, null, null, true, null);
}
/**
* Find all Defaulted HibernateConfigItem(s) for this node
*
* @return a List of HibernateConfigItem(s)
*/
public List findDefaulted() {
return dao.findAllByCriteriaByNode(node, null, true, null, null, null);
}
/**
* Find all HibernateConfigItem(s) that have a pollOn during a specified time
* If a date is null then that date is left open then time is unbounded in that direction
* If both dates are null then every config item that has a pollOn date is returned
*
* @param after select items after this timestamp
* @param before select items before this timestamp
* @return a List of HibernateConfigItem(s)
*/
public List findPollOn(Date after, Date before) {
return dao.findPollOnByNode(node, after, before);
}
private String getRawProperty(String name) {
if (StringUtils.isBlank(name)) {
return null;
}
String value = null;
ConfigItem ci = serverConfigurationService.getConfigItem(name); // null if it does not exist
if (ci != null) {
String rawValue = serverConfigurationService.getRawProperty(name);
value = StringUtils.trimToNull(rawValue);
}
return value;
}
private Object deSerializeValue(String value, String type, boolean secured) throws IllegalClassException {
if (value == null || type == null) {
return null;
}
String string;
if (secured) {
// sanity check should be Base64 encoded
if (Base64.isBase64(value)) {
string = textEncryptor.decrypt(value);
} else {
log.warn("deSerializeValue() Invalid value found attempting to decrypt a secured property, check your secured properties");
string = value;
}
} else {
string = value;
}
Object obj;
if (ServerConfigurationService.TYPE_STRING.equals(type)) {
obj = string;
} else if (ServerConfigurationService.TYPE_INT.equals(type)) {
obj = Integer.valueOf(string);
} else if (ServerConfigurationService.TYPE_BOOLEAN.equals(type)) {
obj = Boolean.valueOf(string);
} else if (ServerConfigurationService.TYPE_ARRAY.equals(type)) {
obj = string.split(HibernateConfigItem.ARRAY_SEPARATOR);
} else {
throw new IllegalClassException("deSerializeValue() invalid TYPE, while deserializing");
}
return obj;
}
private String serializeValue(Object obj, String type, boolean secured) throws IllegalClassException {
if (obj == null || type == null) {
return null;
}
String string;
if (ServerConfigurationService.TYPE_STRING.equals(type)) {
if (obj instanceof String) {
string = String.valueOf(obj);
} else {
throw new IllegalClassException(String.class, obj);
}
} else if (ServerConfigurationService.TYPE_INT.equals(type)) {
if (obj instanceof Integer) {
string = Integer.toString((Integer) obj);
} else {
throw new IllegalClassException(Integer.class, obj);
}
} else if (ServerConfigurationService.TYPE_BOOLEAN.equals(type)) {
if (obj instanceof Boolean) {
string = Boolean.toString((Boolean) obj);
} else {
throw new IllegalClassException(Boolean.class, obj);
}
} else if (ServerConfigurationService.TYPE_ARRAY.equals(type)) {
if (obj instanceof String[]) {
string = StringUtils.join((String[]) obj, HibernateConfigItem.ARRAY_SEPARATOR);
} else {
throw new IllegalClassException("serializeValue() expected an array of type String[]");
}
} else {
throw new IllegalClassException("serializeValue() invalid TYPE, while serializing");
}
if (secured) {
string = textEncryptor.encrypt(string);
}
return string;
}
public void setServerConfigurationService(ServerConfigurationService serverConfigurationService) {
this.serverConfigurationService = serverConfigurationService;
}
public void setDao(HibernateConfigItemDao dao) {
this.dao = dao;
}
public void setTextEncryptor(PBEStringEncryptor textEncryptor) {
this.textEncryptor = textEncryptor;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy