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