/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library 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 2.1 of the License, or (at your option)
* any later version.
*
* This library 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.
*/
package com.liferay.portlet;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.portlet.PortalPreferences;
import com.liferay.portal.kernel.service.PortalPreferencesLocalServiceUtil;
import com.liferay.portal.kernel.service.persistence.PortalPreferencesUtil;
import com.liferay.portal.kernel.transaction.Propagation;
import com.liferay.portal.kernel.transaction.TransactionConfig;
import com.liferay.portal.kernel.transaction.TransactionInvokerUtil;
import com.liferay.portal.kernel.util.HashUtil;
import com.liferay.portal.kernel.util.SetUtil;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.Validator;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.portlet.ReadOnlyException;
import org.hibernate.StaleObjectStateException;
/**
* @author Brian Wing Shun Chan
* @author Alexander Chow
*/
public class PortalPreferencesImpl
extends BasePreferencesImpl
implements Cloneable, PortalPreferences, Serializable {
public static final TransactionConfig SUPPORTS_TRANSACTION_CONFIG;
static {
TransactionConfig.Builder builder = new TransactionConfig.Builder();
builder.setPropagation(Propagation.SUPPORTS);
builder.setReadOnly(true);
builder.setRollbackForClasses(
PortalException.class, SystemException.class);
SUPPORTS_TRANSACTION_CONFIG = builder.build();
}
public PortalPreferencesImpl() {
this(0, 0, null, Collections.<String, Preference>emptyMap(), false);
}
public PortalPreferencesImpl(
com.liferay.portal.kernel.model.PortalPreferences portalPreferences,
boolean signedIn) {
this(
portalPreferences.getOwnerId(), portalPreferences.getOwnerType(),
portalPreferences.getPreferences(),
PortletPreferencesFactoryImpl.createPreferencesMap(
portalPreferences.getPreferences()),
signedIn);
_portalPreferences =
(com.liferay.portal.kernel.model.PortalPreferences)
portalPreferences.clone();
}
public PortalPreferencesImpl(
long ownerId, int ownerType, String xml,
Map<String, Preference> preferences, boolean signedIn) {
super(ownerId, ownerType, xml, preferences);
_signedIn = signedIn;
}
@Override
public PortalPreferencesImpl clone() {
String originalXML = getOriginalXML();
if (_portalPreferences == null) {
return new PortalPreferencesImpl(
getOwnerId(), getOwnerType(), getOriginalXML(),
new HashMap<>(getOriginalPreferences()), isSignedIn());
}
if (Objects.equals(originalXML, _portalPreferences.getPreferences())) {
PortalPreferencesImpl portalPreferencesImpl =
new PortalPreferencesImpl(
getOwnerId(), getOwnerType(), originalXML,
new HashMap<>(getOriginalPreferences()), isSignedIn());
portalPreferencesImpl._portalPreferences =
(com.liferay.portal.kernel.model.PortalPreferences)
_portalPreferences.clone();
return portalPreferencesImpl;
}
return new PortalPreferencesImpl(_portalPreferences, isSignedIn());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof PortalPreferencesImpl)) {
return false;
}
PortalPreferencesImpl portalPreferences = (PortalPreferencesImpl)obj;
if ((getOwnerId() == portalPreferences.getOwnerId()) &&
(getOwnerType() == portalPreferences.getOwnerType()) &&
getPreferences().equals(portalPreferences.getPreferences())) {
return true;
}
else {
return false;
}
}
public long getMvccVersion() {
if (_portalPreferences == null) {
return -1;
}
return _portalPreferences.getMvccVersion();
}
@Override
public long getUserId() {
return _userId;
}
@Override
public String getValue(String namespace, String key) {
return getValue(namespace, key, null);
}
@Override
public String getValue(String namespace, String key, String defaultValue) {
key = _encodeKey(namespace, key);
return super.getValue(key, defaultValue);
}
@Override
public String[] getValues(String namespace, String key) {
return getValues(namespace, key, null);
}
@Override
public String[] getValues(
String namespace, String key, String[] defaultValue) {
key = _encodeKey(namespace, key);
return super.getValues(key, defaultValue);
}
@Override
public int hashCode() {
int hashCode = HashUtil.hash(0, getOwnerId());
hashCode = HashUtil.hash(hashCode, getOwnerType());
hashCode = HashUtil.hash(hashCode, getPreferences());
return hashCode;
}
@Override
public boolean isSignedIn() {
return _signedIn;
}
@Override
public void reset(final String key) throws ReadOnlyException {
if (isReadOnly(key)) {
throw new ReadOnlyException(key);
}
String[] values = super.getValues(key, null);
if (values == null) {
return;
}
Callable<Void> callable = new Callable<Void>() {
@Override
public Void call() {
Map<String, Preference> modifiedPreferences =
getModifiedPreferences();
modifiedPreferences.remove(key);
return null;
}
};
try {
retryableStore(callable, key);
}
catch (ConcurrentModificationException cme) {
throw cme;
}
catch (Throwable t) {
_log.error(t, t);
}
}
@Override
public void resetValues(String namespace) {
Map<String, Preference> preferences = getPreferences();
try {
for (Map.Entry<String, Preference> entry : preferences.entrySet()) {
String key = entry.getKey();
if (key.startsWith(namespace) && !isReadOnly(key)) {
reset(key);
}
}
}
catch (ConcurrentModificationException cme) {
throw cme;
}
catch (Throwable t) {
_log.error(t, t);
}
}
@Override
public void setSignedIn(boolean signedIn) {
_signedIn = signedIn;
}
@Override
public void setUserId(long userId) {
_userId = userId;
}
@Override
public void setValue(String namespace, String key, final String value) {
if (Validator.isNull(key) || key.equals(_RANDOM_KEY)) {
return;
}
final String encodedKey = _encodeKey(namespace, key);
try {
if (value == null) {
reset(encodedKey);
return;
}
String[] oldValues = super.getValues(encodedKey, null);
if ((oldValues != null) && (oldValues.length == 1) &&
value.equals(oldValues[0])) {
return;
}
Callable<Void> callable = new Callable<Void>() {
@Override
public Void call() throws ReadOnlyException {
PortalPreferencesImpl.super.setValue(encodedKey, value);
return null;
}
};
if (_signedIn) {
retryableStore(callable, encodedKey);
}
else {
callable.call();
}
}
catch (ConcurrentModificationException cme) {
throw cme;
}
catch (Throwable t) {
_log.error(t, t);
}
}
@Override
public void setValues(String namespace, String key, final String[] values) {
if (Validator.isNull(key) || key.equals(_RANDOM_KEY)) {
return;
}
final String encodedKey = _encodeKey(namespace, key);
try {
if (values == null) {
reset(encodedKey);
return;
}
if (values.length == 1) {
setValue(namespace, key, values[0]);
return;
}
String[] oldValues = super.getValues(encodedKey, null);
if (oldValues != null) {
Set<String> valuesSet = SetUtil.fromArray(values);
Set<String> oldValuesSet = SetUtil.fromArray(oldValues);
if (valuesSet.equals(oldValuesSet)) {
return;
}
}
Callable<Void> callable = new Callable<Void>() {
@Override
public Void call() throws ReadOnlyException {
PortalPreferencesImpl.super.setValues(encodedKey, values);
return null;
}
};
if (_signedIn) {
retryableStore(callable, encodedKey);
}
else {
callable.call();
}
}
catch (ConcurrentModificationException cme) {
throw cme;
}
catch (Throwable t) {
_log.error(t, t);
}
}
@Override
public void store() throws IOException {
try {
if (_portalPreferences == null) {
_portalPreferences =
PortalPreferencesLocalServiceUtil.updatePreferences(
getOwnerId(), getOwnerType(), this);
}
else {
PortalPreferencesWrapperCacheUtil.remove(
getOwnerId(), getOwnerType());
_portalPreferences.setPreferences(toXML());
PortalPreferencesLocalServiceUtil.updatePortalPreferences(
_portalPreferences);
_portalPreferences = _reload(getOwnerId(), getOwnerType());
}
}
catch (Throwable t) {
throw new IOException(t);
}
}
protected boolean isCausedByStaleObjectException(Throwable t) {
Throwable cause = t.getCause();
while (t != cause) {
if (t instanceof StaleObjectStateException) {
return true;
}
if (cause == null) {
return false;
}
t = cause;
cause = t.getCause();
}
return false;
}
protected void retryableStore(Callable<?> callable, String key)
throws Throwable {
String[] originalValues = super.getValues(key, null);
while (true) {
try {
callable.call();
store();
return;
}
catch (Exception e) {
if (isCausedByStaleObjectException(e)) {
long ownerId = getOwnerId();
int ownerType = getOwnerType();
com.liferay.portal.kernel.model.PortalPreferences
portalPreferences = _reload(ownerId, ownerType);
if (portalPreferences == null) {
continue;
}
PortalPreferencesImpl portalPreferencesImpl =
new PortalPreferencesImpl(
portalPreferences, isSignedIn());
if (!Arrays.equals(
originalValues,
portalPreferencesImpl.getValues(
key, (String[])null))) {
throw new ConcurrentModificationException();
}
reset();
setOriginalPreferences(
portalPreferencesImpl.getOriginalPreferences());
setOriginalXML(portalPreferences.getPreferences());
_portalPreferences = portalPreferences;
}
else {
throw e;
}
}
}
}
private String _encodeKey(String namespace, String key) {
if (Validator.isNull(namespace)) {
return key;
}
else {
return namespace.concat(StringPool.POUND).concat(key);
}
}
private com.liferay.portal.kernel.model.PortalPreferences _reload(
final long ownerId, final int ownerType)
throws Throwable {
return TransactionInvokerUtil.invoke(
SUPPORTS_TRANSACTION_CONFIG,
new Callable<com.liferay.portal.kernel.model.PortalPreferences>() {
@Override
public com.liferay.portal.kernel.model.PortalPreferences
call() {
return PortalPreferencesUtil.fetchByO_O(
ownerId, ownerType, false);
}
});
}
private static final String _RANDOM_KEY = "r";
private static final Log _log = LogFactoryUtil.getLog(
PortalPreferencesImpl.class);
private com.liferay.portal.kernel.model.PortalPreferences
_portalPreferences;
private boolean _signedIn;
private long _userId;
}