001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet;
016    
017    import com.liferay.portal.kernel.exception.PortalException;
018    import com.liferay.portal.kernel.exception.SystemException;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.transaction.Propagation;
022    import com.liferay.portal.kernel.transaction.TransactionAttribute;
023    import com.liferay.portal.kernel.transaction.TransactionInvokerUtil;
024    import com.liferay.portal.kernel.util.HashUtil;
025    import com.liferay.portal.kernel.util.SetUtil;
026    import com.liferay.portal.kernel.util.StringPool;
027    import com.liferay.portal.kernel.util.Validator;
028    import com.liferay.portal.service.PortalPreferencesLocalServiceUtil;
029    import com.liferay.portal.service.persistence.PortalPreferencesUtil;
030    
031    import java.io.IOException;
032    import java.io.Serializable;
033    
034    import java.util.Arrays;
035    import java.util.Collections;
036    import java.util.ConcurrentModificationException;
037    import java.util.HashMap;
038    import java.util.Map;
039    import java.util.Set;
040    import java.util.concurrent.Callable;
041    
042    import javax.portlet.ReadOnlyException;
043    
044    import org.hibernate.StaleObjectStateException;
045    
046    /**
047     * @author Brian Wing Shun Chan
048     * @author Alexander Chow
049     */
050    public class PortalPreferencesImpl
051            extends BasePreferencesImpl
052            implements Cloneable, PortalPreferences, Serializable {
053    
054            public static final TransactionAttribute SUPPORTS_TRANSACTION_ATTRIBUTE;
055    
056            static {
057                    TransactionAttribute.Builder builder =
058                            new TransactionAttribute.Builder();
059    
060                    builder.setPropagation(Propagation.SUPPORTS);
061                    builder.setReadOnly(true);
062                    builder.setRollbackForClasses(
063                            PortalException.class, SystemException.class);
064    
065                    SUPPORTS_TRANSACTION_ATTRIBUTE = builder.build();
066            }
067    
068            public PortalPreferencesImpl() {
069                    this(0, 0, null, Collections.<String, Preference>emptyMap(), false);
070            }
071    
072            public PortalPreferencesImpl(
073                    com.liferay.portal.model.PortalPreferences portalPreferences,
074                    boolean signedIn) {
075    
076                    this(
077                            portalPreferences.getOwnerId(), portalPreferences.getOwnerType(),
078                            portalPreferences.getPreferences(),
079                            PortletPreferencesFactoryImpl.createPreferencesMap(
080                                    portalPreferences.getPreferences()),
081                            signedIn);
082    
083                    _portalPreferences = (com.liferay.portal.model.PortalPreferences)
084                            portalPreferences.clone();
085            }
086    
087            public PortalPreferencesImpl(
088                    long ownerId, int ownerType, String xml,
089                    Map<String, Preference> preferences, boolean signedIn) {
090    
091                    super(ownerId, ownerType, xml, preferences);
092    
093                    _signedIn = signedIn;
094            }
095    
096            @Override
097            public PortalPreferencesImpl clone() {
098                    if (_portalPreferences == null) {
099                            return new PortalPreferencesImpl(
100                                    getOwnerId(), getOwnerType(), getOriginalXML(),
101                                    new HashMap<>(getOriginalPreferences()), isSignedIn());
102                    }
103    
104                    return new PortalPreferencesImpl(_portalPreferences, isSignedIn());
105            }
106    
107            @Override
108            public boolean equals(Object obj) {
109                    if (this == obj) {
110                            return true;
111                    }
112    
113                    if (!(obj instanceof PortalPreferencesImpl)) {
114                            return false;
115                    }
116    
117                    PortalPreferencesImpl portalPreferences = (PortalPreferencesImpl)obj;
118    
119                    if ((getOwnerId() == portalPreferences.getOwnerId()) &&
120                            (getOwnerType() == portalPreferences.getOwnerType()) &&
121                            getPreferences().equals(portalPreferences.getPreferences())) {
122    
123                            return true;
124                    }
125                    else {
126                            return false;
127                    }
128            }
129    
130            @Override
131            public long getUserId() {
132                    return _userId;
133            }
134    
135            @Override
136            public String getValue(String namespace, String key) {
137                    return getValue(namespace, key, null);
138            }
139    
140            @Override
141            public String getValue(String namespace, String key, String defaultValue) {
142                    key = _encodeKey(namespace, key);
143    
144                    return super.getValue(key, defaultValue);
145            }
146    
147            @Override
148            public String[] getValues(String namespace, String key) {
149                    return getValues(namespace, key, null);
150            }
151    
152            @Override
153            public String[] getValues(
154                    String namespace, String key, String[] defaultValue) {
155    
156                    key = _encodeKey(namespace, key);
157    
158                    return super.getValues(key, defaultValue);
159            }
160    
161            @Override
162            public int hashCode() {
163                    int hashCode = HashUtil.hash(0, getOwnerId());
164    
165                    hashCode = HashUtil.hash(hashCode, getOwnerType());
166                    hashCode = HashUtil.hash(hashCode, getPreferences());
167    
168                    return hashCode;
169            }
170    
171            @Override
172            public boolean isSignedIn() {
173                    return _signedIn;
174            }
175    
176            @Override
177            public void reset(final String key) throws ReadOnlyException {
178                    if (isReadOnly(key)) {
179                            throw new ReadOnlyException(key);
180                    }
181    
182                    String[] values = super.getValues(key, null);
183    
184                    if (values == null) {
185                            return;
186                    }
187    
188                    Callable<Void> callable = new Callable<Void>() {
189    
190                            @Override
191                            public Void call() {
192                                    Map<String, Preference> modifiedPreferences =
193                                            getModifiedPreferences();
194    
195                                    modifiedPreferences.remove(key);
196    
197                                    return null;
198                            }
199    
200                    };
201    
202                    try {
203                            retryableStore(callable, key);
204                    }
205                    catch (ConcurrentModificationException cme) {
206                            throw cme;
207                    }
208                    catch (Throwable t) {
209                            _log.error(t, t);
210                    }
211            }
212    
213            @Override
214            public void resetValues(String namespace) {
215                    Map<String, Preference> preferences = getPreferences();
216    
217                    try {
218                            for (Map.Entry<String, Preference> entry : preferences.entrySet()) {
219                                    String key = entry.getKey();
220    
221                                    if (key.startsWith(namespace) && !isReadOnly(key)) {
222                                            reset(key);
223                                    }
224                            }
225                    }
226                    catch (ConcurrentModificationException cme) {
227                            throw cme;
228                    }
229                    catch (Throwable t) {
230                            _log.error(t, t);
231                    }
232            }
233    
234            @Override
235            public void setSignedIn(boolean signedIn) {
236                    _signedIn = signedIn;
237            }
238    
239            @Override
240            public void setUserId(long userId) {
241                    _userId = userId;
242            }
243    
244            @Override
245            public void setValue(String namespace, String key, final String value) {
246                    if (Validator.isNull(key) || key.equals(_RANDOM_KEY)) {
247                            return;
248                    }
249    
250                    final String encodedKey = _encodeKey(namespace, key);
251    
252                    try {
253                            if (value == null) {
254                                    reset(encodedKey);
255    
256                                    return;
257                            }
258    
259                            String[] oldValues = super.getValues(encodedKey, null);
260    
261                            if ((oldValues != null) && (oldValues.length == 1) &&
262                                    value.equals(oldValues[0])) {
263    
264                                    return;
265                            }
266    
267                            Callable<Void> callable = new Callable<Void>() {
268    
269                                    @Override
270                                    public Void call() throws ReadOnlyException {
271                                            PortalPreferencesImpl.super.setValue(encodedKey, value);
272    
273                                            return null;
274                                    }
275    
276                            };
277    
278                            if (_signedIn) {
279                                    retryableStore(callable, encodedKey);
280                            }
281                            else {
282                                    callable.call();
283                            }
284                    }
285                    catch (ConcurrentModificationException cme) {
286                            throw cme;
287                    }
288                    catch (Throwable t) {
289                            _log.error(t, t);
290                    }
291            }
292    
293            @Override
294            public void setValues(String namespace, String key, final String[] values) {
295                    if (Validator.isNull(key) || key.equals(_RANDOM_KEY)) {
296                            return;
297                    }
298    
299                    final String encodedKey = _encodeKey(namespace, key);
300    
301                    try {
302                            if (values == null) {
303                                    reset(encodedKey);
304    
305                                    return;
306                            }
307    
308                            if (values.length == 1) {
309                                    setValue(namespace, key, values[0]);
310    
311                                    return;
312                            }
313    
314                            String[] oldValues = super.getValues(encodedKey, null);
315    
316                            if (oldValues != null) {
317                                    Set<String> valuesSet = SetUtil.fromArray(values);
318                                    Set<String> oldValuesSet = SetUtil.fromArray(oldValues);
319    
320                                    if (valuesSet.equals(oldValuesSet)) {
321                                            return;
322                                    }
323                            }
324    
325                            Callable<Void> callable = new Callable<Void>() {
326    
327                                    @Override
328                                    public Void call() throws ReadOnlyException {
329                                            PortalPreferencesImpl.super.setValues(encodedKey, values);
330    
331                                            return null;
332                                    }
333    
334                            };
335    
336                            if (_signedIn) {
337                                    retryableStore(callable, encodedKey);
338                            }
339                            else {
340                                    callable.call();
341                            }
342                    }
343                    catch (ConcurrentModificationException cme) {
344                            throw cme;
345                    }
346                    catch (Throwable t) {
347                            _log.error(t, t);
348                    }
349            }
350    
351            @Override
352            public void store() throws IOException {
353                    try {
354                            if (_portalPreferences == null) {
355                                    _portalPreferences =
356                                            PortalPreferencesLocalServiceUtil.updatePreferences(
357                                                    getOwnerId(), getOwnerType(), this);
358                            }
359                            else {
360                                    PortalPreferencesWrapperCacheUtil.remove(
361                                            getOwnerId(), getOwnerType());
362    
363                                    _portalPreferences.setPreferences(toXML());
364    
365                                    PortalPreferencesLocalServiceUtil.updatePortalPreferences(
366                                            _portalPreferences);
367    
368                                    _portalPreferences = _reload(getOwnerId(), getOwnerType());
369                            }
370                    }
371                    catch (Throwable t) {
372                            throw new IOException(t);
373                    }
374            }
375    
376            protected boolean isCausedByStaleObjectException(Throwable t) {
377                    Throwable cause = t.getCause();
378    
379                    while (t != cause) {
380                            if (t instanceof StaleObjectStateException) {
381                                    return true;
382                            }
383    
384                            if (cause == null) {
385                                    return false;
386                            }
387    
388                            t = cause;
389    
390                            cause = t.getCause();
391                    }
392    
393                    return false;
394            }
395    
396            protected void retryableStore(Callable<?> callable, String key)
397                    throws Throwable {
398    
399                    String[] originalValues = super.getValues(key, null);
400    
401                    while (true) {
402                            try {
403                                    callable.call();
404    
405                                    store();
406    
407                                    return;
408                            }
409                            catch (Exception e) {
410                                    if (isCausedByStaleObjectException(e)) {
411                                            long ownerId = getOwnerId();
412                                            int ownerType = getOwnerType();
413    
414                                            com.liferay.portal.model.PortalPreferences
415                                                    portalPreferences = _reload(ownerId, ownerType);
416    
417                                            if (portalPreferences == null) {
418                                                    continue;
419                                            }
420    
421                                            PortalPreferencesImpl portalPreferencesImpl =
422                                                    new PortalPreferencesImpl(
423                                                            portalPreferences, isSignedIn());
424    
425                                            if (!Arrays.equals(
426                                                            originalValues,
427                                                            portalPreferencesImpl.getValues(
428                                                                    key, (String[])null))) {
429    
430                                                    throw new ConcurrentModificationException();
431                                            }
432    
433                                            reset();
434    
435                                            setOriginalPreferences(
436                                                    portalPreferencesImpl.getOriginalPreferences());
437    
438                                            setOriginalXML(portalPreferences.getPreferences());
439    
440                                            _portalPreferences = portalPreferences;
441                                    }
442                                    else {
443                                            throw e;
444                                    }
445                            }
446                    }
447            }
448    
449            private String _encodeKey(String namespace, String key) {
450                    if (Validator.isNull(namespace)) {
451                            return key;
452                    }
453                    else {
454                            return namespace.concat(StringPool.POUND).concat(key);
455                    }
456            }
457    
458            private com.liferay.portal.model.PortalPreferences _reload(
459                            final long ownerId, final int ownerType)
460                    throws Throwable {
461    
462                    return TransactionInvokerUtil.invoke(
463                            SUPPORTS_TRANSACTION_ATTRIBUTE,
464                            new Callable<com.liferay.portal.model.PortalPreferences>() {
465    
466                                    @Override
467                                    public com.liferay.portal.model.PortalPreferences call() {
468                                            return PortalPreferencesUtil.fetchByO_O(
469                                                    ownerId, ownerType, false);
470                                    }
471    
472                            });
473            }
474    
475            private static final String _RANDOM_KEY = "r";
476    
477            private static final Log _log = LogFactoryUtil.getLog(
478                    PortalPreferencesImpl.class);
479    
480            private com.liferay.portal.model.PortalPreferences _portalPreferences;
481            private boolean _signedIn;
482            private long _userId;
483    
484    }