/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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.apereo.portal.portlet.container.services; import com.google.common.base.Function; import com.google.common.collect.Maps; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; import javax.portlet.PortletPreferences; import javax.portlet.ReadOnlyException; import javax.portlet.ValidatorException; import org.apereo.portal.portlet.dao.jpa.PortletPreferenceImpl; import org.apereo.portal.portlet.om.IPortletPreference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; /** * Base class for portlet preferences * */ public abstract class AbstractPortletPreferencesImpl<C> implements PortletPreferences { protected final Logger logger = LoggerFactory.getLogger(getClass()); private final boolean render; private Map<String, IPortletPreference> targetPortletPreferences; private Map<String, IPortletPreference> basePortletPreferences; private Map<String, IPortletPreference> compositePreferences; private Map<String, String[]> compositePreferencesView; private boolean modified = false; public AbstractPortletPreferencesImpl(boolean render) { this.render = render; } protected C getInitializationContext() { return null; } protected abstract Object getLogDescription(); protected abstract void loadTargetPortletPreferences( C initContext, Map<String, IPortletPreference> targetPortletPreferences); protected abstract void loadBasePortletPreferences( C initContext, Map<String, IPortletPreference> basePortletPreferences); protected abstract boolean storeInternal() throws IOException, ValidatorException; protected boolean isReadOnly(IPortletPreference portletPreference) { return portletPreference != null && portletPreference.isReadOnly(); } private final void initPrefs() { if (this.compositePreferencesView != null) { return; } final C initContext = this.getInitializationContext(); this.targetPortletPreferences = new LinkedHashMap<String, IPortletPreference>(); this.loadTargetPortletPreferences(initContext, this.targetPortletPreferences); this.basePortletPreferences = new LinkedHashMap<String, IPortletPreference>(); this.loadBasePortletPreferences(initContext, this.basePortletPreferences); this.compositePreferences = new LinkedHashMap<String, IPortletPreference>(this.basePortletPreferences); this.compositePreferences.putAll(this.targetPortletPreferences); this.compositePreferencesView = Maps.transformValues( this.compositePreferences, new Function<IPortletPreference, String[]>() { @Override public String[] apply(IPortletPreference input) { return input.getValues(); } }); } private final void clearPrefs() { this.targetPortletPreferences = null; this.basePortletPreferences = null; this.compositePreferences = null; this.compositePreferencesView = null; } protected final Map<String, IPortletPreference> getTargetPortletPreferences() { this.initPrefs(); return this.targetPortletPreferences; } protected final Map<String, IPortletPreference> getBasePortletPreferences() { this.initPrefs(); return this.basePortletPreferences; } protected final Map<String, IPortletPreference> getCompositePortletPreferences() { this.initPrefs(); return this.compositePreferences; } protected final Map<String, String[]> getCompositePortletPreferencesView() { this.initPrefs(); return this.compositePreferencesView; } protected final IPortletPreference getPortletPreference(String key) { Assert.notNull(key, "Preference Key cannot be null"); final Map<String, IPortletPreference> targetPortletPreferences = this.getTargetPortletPreferences(); final IPortletPreference portletPreference = targetPortletPreferences.get(key); if (portletPreference != null) { return portletPreference; } final Map<String, IPortletPreference> basePortletPreferences = this.getBasePortletPreferences(); return basePortletPreferences.get(key); } @Override public final boolean isReadOnly(String key) { final IPortletPreference portletPreference = this.getPortletPreference(key); return portletPreference != null && portletPreference.isReadOnly(); } @Override public final String getValue(String key, String def) { final IPortletPreference portletPreference = this.getPortletPreference(key); if (portletPreference != null) { final String[] values = portletPreference.getValues(); if (values == null || values.length == 0) { return def; } return values[0]; } return def; } @Override public final String[] getValues(String key, String[] def) { final IPortletPreference portletPreference = this.getPortletPreference(key); if (portletPreference != null) { return portletPreference.getValues(); } return def; } @Override public final void setValue(String key, String value) throws ReadOnlyException { this.setValues(key, new String[] {value}); } @Override public final void setValues(String key, String[] values) throws ReadOnlyException { Assert.notNull(key, "Preference Key cannot be null"); final Map<String, IPortletPreference> targetPortletPreferences = this.getTargetPortletPreferences(); //Check if there is a base preference for the key final Map<String, IPortletPreference> basePortletPreferences = this.getBasePortletPreferences(); final IPortletPreference basePreference = basePortletPreferences.get(key); if (basePreference != null) { if (this.isReadOnly(basePreference)) { throw new ReadOnlyException("Preference '" + key + "' is read only"); } //if the set value matches base value, delete any target pref if (Arrays.equals(values, basePreference.getValues())) { this.reset(key); return; } } IPortletPreference portletPreference = targetPortletPreferences.get(key); //No target preference exists yet, create it and then update the composite map if (portletPreference == null) { portletPreference = new PortletPreferenceImpl(key, false, values != null ? values.clone() : null); targetPortletPreferences.put(key, portletPreference); final Map<String, IPortletPreference> compositePortletPreferences = this.getCompositePortletPreferences(); compositePortletPreferences.put(key, portletPreference); this.modified = true; } //Update the existing preference if the values array is different else if (!Arrays.equals(values, portletPreference.getValues())) { portletPreference.setValues(values != null ? values.clone() : null); this.modified = true; } } @Override public final Enumeration<String> getNames() { final Map<String, String[]> compositePortletPreferencesView = this.getCompositePortletPreferencesView(); return Collections.enumeration(compositePortletPreferencesView.keySet()); } @Override public final Map<String, String[]> getMap() { return this.getCompositePortletPreferencesView(); } @Override public final void reset(String key) throws ReadOnlyException { final Map<String, IPortletPreference> basePortletPreferences = this.getBasePortletPreferences(); final IPortletPreference basePreference = basePortletPreferences.get(key); if (this.isReadOnly(basePreference)) { throw new ReadOnlyException("Preference '" + key + "' is read only"); } final Map<String, IPortletPreference> targetPortletPreferences = this.getTargetPortletPreferences(); final IPortletPreference removed = targetPortletPreferences.remove(key); //There was a target preference with that key, update the composite preferences map if (removed != null) { final Map<String, IPortletPreference> compositePortletPreferences = this.getCompositePortletPreferences(); if (basePreference != null) { compositePortletPreferences.put(key, basePreference); } else { compositePortletPreferences.remove(key); } this.modified = true; } } @Override public final void store() throws IOException, ValidatorException { if (this.render) { throw new IllegalStateException("store is not allowed during RENDER phase."); } if (!modified) { logger.debug( "Skipping store of portlet preferences, nothing has changed: {}", getLogDescription()); return; } final boolean stored = storeInternal(); if (stored) { logger.debug( "Store of portlet preferences resulted in a change, clear preferences cache: {}", getLogDescription()); this.clearPrefs(); } this.modified = false; } }