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