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 =
084                            (com.liferay.portal.model.PortalPreferences)
085                                    portalPreferences.clone();
086            }
087    
088            public PortalPreferencesImpl(
089                    long ownerId, int ownerType, String xml,
090                    Map<String, Preference> preferences, boolean signedIn) {
091    
092                    super(ownerId, ownerType, xml, preferences);
093    
094                    _signedIn = signedIn;
095            }
096    
097            @Override
098            public PortalPreferencesImpl clone() {
099                    if (_portalPreferences == null) {
100                            return new PortalPreferencesImpl(
101                                    getOwnerId(), getOwnerType(), getOriginalXML(),
102                                    new HashMap<>(getOriginalPreferences()), isSignedIn());
103                    }
104    
105                    return new PortalPreferencesImpl(_portalPreferences, isSignedIn());
106            }
107    
108            @Override
109            public boolean equals(Object obj) {
110                    if (this == obj) {
111                            return true;
112                    }
113    
114                    if (!(obj instanceof PortalPreferencesImpl)) {
115                            return false;
116                    }
117    
118                    PortalPreferencesImpl portalPreferences = (PortalPreferencesImpl)obj;
119    
120                    if ((getOwnerId() == portalPreferences.getOwnerId()) &&
121                            (getOwnerType() == portalPreferences.getOwnerType()) &&
122                            getPreferences().equals(portalPreferences.getPreferences())) {
123    
124                            return true;
125                    }
126                    else {
127                            return false;
128                    }
129            }
130    
131            @Override
132            public long getUserId() {
133                    return _userId;
134            }
135    
136            @Override
137            public String getValue(String namespace, String key) {
138                    return getValue(namespace, key, null);
139            }
140    
141            @Override
142            public String getValue(String namespace, String key, String defaultValue) {
143                    key = _encodeKey(namespace, key);
144    
145                    return super.getValue(key, defaultValue);
146            }
147    
148            @Override
149            public String[] getValues(String namespace, String key) {
150                    return getValues(namespace, key, null);
151            }
152    
153            @Override
154            public String[] getValues(
155                    String namespace, String key, String[] defaultValue) {
156    
157                    key = _encodeKey(namespace, key);
158    
159                    return super.getValues(key, defaultValue);
160            }
161    
162            @Override
163            public int hashCode() {
164                    int hashCode = HashUtil.hash(0, getOwnerId());
165    
166                    hashCode = HashUtil.hash(hashCode, getOwnerType());
167                    hashCode = HashUtil.hash(hashCode, getPreferences());
168    
169                    return hashCode;
170            }
171    
172            @Override
173            public boolean isSignedIn() {
174                    return _signedIn;
175            }
176    
177            @Override
178            public void reset(final String key) throws ReadOnlyException {
179                    if (isReadOnly(key)) {
180                            throw new ReadOnlyException(key);
181                    }
182    
183                    String[] values = super.getValues(key, null);
184    
185                    if (values == null) {
186                            return;
187                    }
188    
189                    Callable<Void> callable = new Callable<Void>() {
190    
191                            @Override
192                            public Void call() {
193                                    Map<String, Preference> modifiedPreferences =
194                                            getModifiedPreferences();
195    
196                                    modifiedPreferences.remove(key);
197    
198                                    return null;
199                            }
200    
201                    };
202    
203                    try {
204                            retryableStore(callable, key);
205                    }
206                    catch (ConcurrentModificationException cme) {
207                            throw cme;
208                    }
209                    catch (Throwable t) {
210                            _log.error(t, t);
211                    }
212            }
213    
214            @Override
215            public void resetValues(String namespace) {
216                    Map<String, Preference> preferences = getPreferences();
217    
218                    try {
219                            for (Map.Entry<String, Preference> entry : preferences.entrySet()) {
220                                    String key = entry.getKey();
221    
222                                    if (key.startsWith(namespace) && !isReadOnly(key)) {
223                                            reset(key);
224                                    }
225                            }
226                    }
227                    catch (ConcurrentModificationException cme) {
228                            throw cme;
229                    }
230                    catch (Throwable t) {
231                            _log.error(t, t);
232                    }
233            }
234    
235            @Override
236            public void setSignedIn(boolean signedIn) {
237                    _signedIn = signedIn;
238            }
239    
240            @Override
241            public void setUserId(long userId) {
242                    _userId = userId;
243            }
244    
245            @Override
246            public void setValue(String namespace, String key, final String value) {
247                    if (Validator.isNull(key) || key.equals(_RANDOM_KEY)) {
248                            return;
249                    }
250    
251                    final String encodedKey = _encodeKey(namespace, key);
252    
253                    try {
254                            if (value == null) {
255                                    reset(encodedKey);
256    
257                                    return;
258                            }
259    
260                            String[] oldValues = super.getValues(encodedKey, null);
261    
262                            if ((oldValues != null) && (oldValues.length == 1) &&
263                                    value.equals(oldValues[0])) {
264    
265                                    return;
266                            }
267    
268                            Callable<Void> callable = new Callable<Void>() {
269    
270                                    @Override
271                                    public Void call() throws ReadOnlyException {
272                                            PortalPreferencesImpl.super.setValue(encodedKey, value);
273    
274                                            return null;
275                                    }
276    
277                            };
278    
279                            if (_signedIn) {
280                                    retryableStore(callable, encodedKey);
281                            }
282                            else {
283                                    callable.call();
284                            }
285                    }
286                    catch (ConcurrentModificationException cme) {
287                            throw cme;
288                    }
289                    catch (Throwable t) {
290                            _log.error(t, t);
291                    }
292            }
293    
294            @Override
295            public void setValues(String namespace, String key, final String[] values) {
296                    if (Validator.isNull(key) || key.equals(_RANDOM_KEY)) {
297                            return;
298                    }
299    
300                    final String encodedKey = _encodeKey(namespace, key);
301    
302                    try {
303                            if (values == null) {
304                                    reset(encodedKey);
305    
306                                    return;
307                            }
308    
309                            if (values.length == 1) {
310                                    setValue(namespace, key, values[0]);
311    
312                                    return;
313                            }
314    
315                            String[] oldValues = super.getValues(encodedKey, null);
316    
317                            if (oldValues != null) {
318                                    Set<String> valuesSet = SetUtil.fromArray(values);
319                                    Set<String> oldValuesSet = SetUtil.fromArray(oldValues);
320    
321                                    if (valuesSet.equals(oldValuesSet)) {
322                                            return;
323                                    }
324                            }
325    
326                            Callable<Void> callable = new Callable<Void>() {
327    
328                                    @Override
329                                    public Void call() throws ReadOnlyException {
330                                            PortalPreferencesImpl.super.setValues(encodedKey, values);
331    
332                                            return null;
333                                    }
334    
335                            };
336    
337                            if (_signedIn) {
338                                    retryableStore(callable, encodedKey);
339                            }
340                            else {
341                                    callable.call();
342                            }
343                    }
344                    catch (ConcurrentModificationException cme) {
345                            throw cme;
346                    }
347                    catch (Throwable t) {
348                            _log.error(t, t);
349                    }
350            }
351    
352            @Override
353            public void store() throws IOException {
354                    try {
355                            if (_portalPreferences == null) {
356                                    _portalPreferences =
357                                            PortalPreferencesLocalServiceUtil.updatePreferences(
358                                                    getOwnerId(), getOwnerType(), this);
359                            }
360                            else {
361                                    PortalPreferencesWrapperCacheUtil.remove(
362                                            getOwnerId(), getOwnerType());
363    
364                                    _portalPreferences.setPreferences(toXML());
365    
366                                    PortalPreferencesLocalServiceUtil.updatePortalPreferences(
367                                            _portalPreferences);
368    
369                                    _portalPreferences = _reload(getOwnerId(), getOwnerType());
370                            }
371                    }
372                    catch (Throwable t) {
373                            throw new IOException(t);
374                    }
375            }
376    
377            protected boolean isCausedByStaleObjectException(Throwable t) {
378                    Throwable cause = t.getCause();
379    
380                    while (t != cause) {
381                            if (t instanceof StaleObjectStateException) {
382                                    return true;
383                            }
384    
385                            if (cause == null) {
386                                    return false;
387                            }
388    
389                            t = cause;
390    
391                            cause = t.getCause();
392                    }
393    
394                    return false;
395            }
396    
397            protected void retryableStore(Callable<?> callable, String key)
398                    throws Throwable {
399    
400                    String[] originalValues = super.getValues(key, null);
401    
402                    while (true) {
403                            try {
404                                    callable.call();
405    
406                                    store();
407    
408                                    return;
409                            }
410                            catch (Exception e) {
411                                    if (isCausedByStaleObjectException(e)) {
412                                            long ownerId = getOwnerId();
413                                            int ownerType = getOwnerType();
414    
415                                            com.liferay.portal.model.PortalPreferences
416                                                    portalPreferences = _reload(ownerId, ownerType);
417    
418                                            if (portalPreferences == null) {
419                                                    continue;
420                                            }
421    
422                                            PortalPreferencesImpl portalPreferencesImpl =
423                                                    new PortalPreferencesImpl(
424                                                            portalPreferences, isSignedIn());
425    
426                                            if (!Arrays.equals(
427                                                            originalValues,
428                                                            portalPreferencesImpl.getValues(
429                                                                    key, (String[])null))) {
430    
431                                                    throw new ConcurrentModificationException();
432                                            }
433    
434                                            reset();
435    
436                                            setOriginalPreferences(
437                                                    portalPreferencesImpl.getOriginalPreferences());
438    
439                                            setOriginalXML(portalPreferences.getPreferences());
440    
441                                            _portalPreferences = portalPreferences;
442                                    }
443                                    else {
444                                            throw e;
445                                    }
446                            }
447                    }
448            }
449    
450            private String _encodeKey(String namespace, String key) {
451                    if (Validator.isNull(namespace)) {
452                            return key;
453                    }
454                    else {
455                            return namespace.concat(StringPool.POUND).concat(key);
456                    }
457            }
458    
459            private com.liferay.portal.model.PortalPreferences _reload(
460                            final long ownerId, final int ownerType)
461                    throws Throwable {
462    
463                    return TransactionInvokerUtil.invoke(
464                            SUPPORTS_TRANSACTION_ATTRIBUTE,
465                            new Callable<com.liferay.portal.model.PortalPreferences>() {
466    
467                                    @Override
468                                    public com.liferay.portal.model.PortalPreferences call() {
469                                            return PortalPreferencesUtil.fetchByO_O(
470                                                    ownerId, ownerType, false);
471                                    }
472    
473                            });
474            }
475    
476            private static final String _RANDOM_KEY = "r";
477    
478            private static final Log _log = LogFactoryUtil.getLog(
479                    PortalPreferencesImpl.class);
480    
481            private com.liferay.portal.model.PortalPreferences _portalPreferences;
482            private boolean _signedIn;
483            private long _userId;
484    
485    }