001    /**
002     * Copyright (c) 2000-2012 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.portal.service.impl;
016    
017    import com.liferay.portal.kernel.exception.PortalException;
018    import com.liferay.portal.kernel.exception.SystemException;
019    import com.liferay.portal.kernel.lar.PortletDataHandlerKeys;
020    import com.liferay.portal.kernel.lar.UserIdStrategy;
021    import com.liferay.portal.kernel.log.Log;
022    import com.liferay.portal.kernel.log.LogFactoryUtil;
023    import com.liferay.portal.kernel.staging.MergeLayoutPrototypesThreadLocal;
024    import com.liferay.portal.kernel.util.FileUtil;
025    import com.liferay.portal.kernel.util.GetterUtil;
026    import com.liferay.portal.kernel.util.ListUtil;
027    import com.liferay.portal.kernel.util.SystemProperties;
028    import com.liferay.portal.kernel.util.UnicodeProperties;
029    import com.liferay.portal.kernel.util.Validator;
030    import com.liferay.portal.kernel.uuid.PortalUUIDUtil;
031    import com.liferay.portal.kernel.workflow.WorkflowThreadLocal;
032    import com.liferay.portal.model.Group;
033    import com.liferay.portal.model.Layout;
034    import com.liferay.portal.model.LayoutConstants;
035    import com.liferay.portal.model.LayoutPrototype;
036    import com.liferay.portal.model.LayoutSet;
037    import com.liferay.portal.model.LayoutSetPrototype;
038    import com.liferay.portal.model.Lock;
039    import com.liferay.portal.model.UserGroup;
040    import com.liferay.portal.model.impl.VirtualLayout;
041    import com.liferay.portal.security.permission.PermissionChecker;
042    import com.liferay.portal.security.permission.PermissionThreadLocal;
043    import com.liferay.portal.service.GroupLocalServiceUtil;
044    import com.liferay.portal.service.LayoutLocalServiceUtil;
045    import com.liferay.portal.service.LayoutPrototypeLocalServiceUtil;
046    import com.liferay.portal.service.LayoutSetLocalServiceUtil;
047    import com.liferay.portal.service.LayoutSetPrototypeLocalServiceUtil;
048    import com.liferay.portal.service.LockLocalServiceUtil;
049    import com.liferay.portal.service.UserGroupLocalServiceUtil;
050    import com.liferay.portal.service.UserLocalServiceUtil;
051    import com.liferay.portal.service.persistence.LayoutSetUtil;
052    import com.liferay.portal.service.persistence.LayoutUtil;
053    import com.liferay.portal.util.PropsValues;
054    import com.liferay.portlet.sites.util.SitesUtil;
055    
056    import java.io.File;
057    
058    import java.lang.reflect.Method;
059    
060    import java.util.Arrays;
061    import java.util.Date;
062    import java.util.LinkedHashMap;
063    import java.util.List;
064    import java.util.Map;
065    
066    import org.aopalliance.intercept.MethodInterceptor;
067    import org.aopalliance.intercept.MethodInvocation;
068    
069    import org.springframework.core.annotation.Order;
070    
071    /**
072     * @author Raymond Augé
073     * @author Jorge Ferrer
074     */
075    @Order(2)
076    public class LayoutLocalServiceVirtualLayoutsAdvice
077            implements MethodInterceptor {
078    
079            public Object invoke(MethodInvocation methodInvocation) throws Throwable {
080                    PermissionChecker permissionChecker =
081                            PermissionThreadLocal.getPermissionChecker();
082    
083                    if (permissionChecker == null) {
084                            return methodInvocation.proceed();
085                    }
086    
087                    Method method = methodInvocation.getMethod();
088    
089                    String methodName = method.getName();
090    
091                    Object[] arguments = methodInvocation.getArguments();
092    
093                    Class<?>[] parameterTypes = method.getParameterTypes();
094    
095                    boolean workflowEnabled = WorkflowThreadLocal.isEnabled();
096    
097                    if (methodName.equals("getLayout") &&
098                            (Arrays.equals(parameterTypes, _TYPES_L) ||
099                             Arrays.equals(parameterTypes, _TYPES_L_B_L))) {
100    
101                            Layout layout = (Layout)methodInvocation.proceed();
102    
103                            if (Validator.isNull(layout.getSourcePrototypeLayoutUuid())) {
104                                    return layout;
105                            }
106    
107                            Group group = layout.getGroup();
108                            LayoutSet layoutSet = layout.getLayoutSet();
109    
110                            try {
111                                    MergeLayoutPrototypesThreadLocal.setInProgress(true);
112                                    WorkflowThreadLocal.setEnabled(false);
113    
114                                    mergeLayoutProtypeLayout(group, layout);
115                                    mergeLayoutSetProtypeLayouts(group, layoutSet);
116                            }
117                            finally {
118                                    MergeLayoutPrototypesThreadLocal.setInProgress(false);
119                                    WorkflowThreadLocal.setEnabled(workflowEnabled);
120                            }
121                    }
122                    else if (methodName.equals("getLayouts") &&
123                                     (Arrays.equals(parameterTypes, _TYPES_L_B_L) ||
124                                      Arrays.equals(parameterTypes, _TYPES_L_B_L_B_I_I))) {
125    
126                            long groupId = (Long)arguments[0];
127                            boolean privateLayout = (Boolean)arguments[1];
128                            long parentLayoutId = (Long)arguments[2];
129    
130                            try {
131                                    Group group = GroupLocalServiceUtil.getGroup(groupId);
132    
133                                    LayoutSet layoutSet = LayoutSetLocalServiceUtil.getLayoutSet(
134                                            groupId, privateLayout);
135    
136                                    try {
137                                            MergeLayoutPrototypesThreadLocal.setInProgress(true);
138                                            WorkflowThreadLocal.setEnabled(false);
139    
140                                            mergeLayoutSetProtypeLayouts(group, layoutSet);
141                                    }
142                                    finally {
143                                            MergeLayoutPrototypesThreadLocal.setInProgress(false);
144                                            WorkflowThreadLocal.setEnabled(workflowEnabled);
145                                    }
146    
147                                    if (!PropsValues.
148                                                    USER_GROUPS_COPY_LAYOUTS_TO_USER_PERSONAL_SITE &&
149                                            group.isUser() &&
150                                            (parentLayoutId ==
151                                                    LayoutConstants.DEFAULT_PARENT_LAYOUT_ID)) {
152    
153                                            Object returnValue = methodInvocation.proceed();
154    
155                                            return addUserGroupLayouts(
156                                                    group, layoutSet, (List<Layout>)returnValue);
157                                    }
158                            }
159                            catch (Exception e) {
160                                    _log.error(e, e);
161    
162                                    throw e;
163                            }
164                    }
165    
166                    return methodInvocation.proceed();
167            }
168    
169            protected List<Layout> addUserGroupLayouts(
170                            Group group, LayoutSet layoutSet, List<Layout> layouts)
171                    throws Exception {
172    
173                    layouts = ListUtil.copy(layouts);
174    
175                    List<UserGroup> userUserGroups =
176                            UserGroupLocalServiceUtil.getUserUserGroups(group.getClassPK());
177    
178                    for (UserGroup userGroup : userUserGroups) {
179                            Group userGroupGroup = userGroup.getGroup();
180    
181                            List<Layout> userGroupLayouts = LayoutLocalServiceUtil.getLayouts(
182                                    userGroupGroup.getGroupId(), layoutSet.isPrivateLayout());
183    
184                            for (Layout userGroupLayout : userGroupLayouts) {
185                                    Layout virtualLayout = new VirtualLayout(
186                                            userGroupLayout, group);
187    
188                                    layouts.add(virtualLayout);
189                            }
190                    }
191    
192                    return layouts;
193            }
194    
195            protected Map<String, String[]> getLayoutTemplatesParameters(
196                    boolean firstTime) {
197    
198                    Map<String, String[]> parameterMap =
199                            new LinkedHashMap<String, String[]>();
200    
201                    parameterMap.put(
202                            PortletDataHandlerKeys.CATEGORIES,
203                            new String[] {Boolean.TRUE.toString()});
204                    parameterMap.put(
205                            PortletDataHandlerKeys.DELETE_MISSING_LAYOUTS,
206                            new String[] {Boolean.FALSE.toString()});
207                    parameterMap.put(
208                            PortletDataHandlerKeys.DELETE_PORTLET_DATA,
209                            new String[] {Boolean.FALSE.toString()});
210                    parameterMap.put(
211                            PortletDataHandlerKeys.IGNORE_LAST_PUBLISH_DATE,
212                            new String[] {Boolean.TRUE.toString()});
213                    parameterMap.put(
214                            PortletDataHandlerKeys.LAYOUT_SET_PROTOTYPE_LINK_ENABLED,
215                            new String[] {Boolean.TRUE.toString()});
216                    parameterMap.put(
217                            PortletDataHandlerKeys.LAYOUTS_IMPORT_MODE,
218                            new String[] {
219                                    PortletDataHandlerKeys.
220                                            LAYOUTS_IMPORT_MODE_CREATED_FROM_PROTOTYPE
221                            });
222                    parameterMap.put(
223                            PortletDataHandlerKeys.PERMISSIONS,
224                            new String[] {Boolean.TRUE.toString()});
225                    parameterMap.put(
226                            PortletDataHandlerKeys.PORTLET_ARCHIVED_SETUPS,
227                            new String[] {Boolean.TRUE.toString()});
228                    parameterMap.put(
229                            PortletDataHandlerKeys.PORTLET_SETUP,
230                            new String[] {Boolean.TRUE.toString()});
231                    parameterMap.put(
232                            PortletDataHandlerKeys.PORTLET_SETUP_ALL,
233                            new String[] {Boolean.TRUE.toString()});
234                    parameterMap.put(
235                            PortletDataHandlerKeys.THEME,
236                            new String[] {Boolean.FALSE.toString()});
237                    parameterMap.put(
238                            PortletDataHandlerKeys.THEME_REFERENCE,
239                            new String[] {Boolean.TRUE.toString()});
240                    parameterMap.put(
241                            PortletDataHandlerKeys.UPDATE_LAST_PUBLISH_DATE,
242                            new String[] {Boolean.FALSE.toString()});
243                    parameterMap.put(
244                            PortletDataHandlerKeys.USER_ID_STRATEGY,
245                            new String[] {UserIdStrategy.CURRENT_USER_ID});
246    
247                    if (firstTime) {
248                            parameterMap.put(
249                                    PortletDataHandlerKeys.DATA_STRATEGY,
250                                    new String[] {PortletDataHandlerKeys.DATA_STRATEGY_MIRROR});
251                            parameterMap.put(
252                                    PortletDataHandlerKeys.PORTLET_DATA,
253                                    new String[] {Boolean.TRUE.toString()});
254                            parameterMap.put(
255                                    PortletDataHandlerKeys.PORTLET_DATA_ALL,
256                                    new String[] {Boolean.TRUE.toString()});
257                    }
258                    else {
259                            parameterMap.put(
260                                    PortletDataHandlerKeys.PORTLET_DATA,
261                                    new String[] {Boolean.FALSE.toString()});
262                            parameterMap.put(
263                                    PortletDataHandlerKeys.PORTLET_DATA_ALL,
264                                    new String[] {Boolean.FALSE.toString()});
265                    }
266    
267                    return parameterMap;
268            }
269    
270            protected void importLayoutSetPrototype(
271                            LayoutSetPrototype layoutSetPrototype, long groupId,
272                            boolean privateLayout, Map<String, String[]> parameterMap)
273                    throws PortalException, SystemException {
274    
275                    File file = null;
276    
277                    File cacheFile = new File(
278                            _TEMP_DIR.concat(layoutSetPrototype.getUuid()).concat(".lar"));
279    
280                    if (cacheFile.exists()) {
281                            Date modifiedDate = layoutSetPrototype.getModifiedDate();
282    
283                            if (cacheFile.lastModified() >= modifiedDate.getTime()) {
284                                    if (_log.isDebugEnabled()) {
285                                            _log.debug(
286                                                    "Using cached layout set prototype LAR file " +
287                                                            cacheFile.getAbsolutePath());
288                                    }
289    
290                                    file = cacheFile;
291                            }
292                    }
293    
294                    boolean newFile = false;
295    
296                    if (file == null) {
297                            Group layoutSetPrototypeGroup = layoutSetPrototype.getGroup();
298    
299                            file = LayoutLocalServiceUtil.exportLayoutsAsFile(
300                                    layoutSetPrototypeGroup.getGroupId(), true, null,
301                                    parameterMap, null, null);
302    
303                            newFile = true;
304                    }
305    
306                    long userId = UserLocalServiceUtil.getDefaultUserId(
307                            layoutSetPrototype.getCompanyId());
308    
309                    LayoutLocalServiceUtil.importLayouts(
310                            userId, groupId, privateLayout, parameterMap, file);
311    
312                    if (newFile) {
313                            try {
314                                    FileUtil.copyFile(file, cacheFile);
315    
316                                    if (_log.isDebugEnabled()) {
317                                            _log.debug(
318                                                    "Copied " + file.getAbsolutePath() + " to " +
319                                                            cacheFile.getAbsolutePath());
320                                    }
321                            }
322                            catch (Exception e) {
323                                    _log.error(
324                                            "Unable to copy file " + file.getAbsolutePath() + " to " +
325                                                    cacheFile.getAbsolutePath(),
326                                            e);
327                            }
328                    }
329            }
330    
331            protected void mergeLayoutProtypeLayout(Group group, Layout layout)
332                    throws Exception {
333    
334                    if (!layout.isLayoutPrototypeLinkActive() ||
335                            group.isLayoutPrototype() || group.isLayoutSetPrototype() ||
336                            group.hasStagingGroup()) {
337    
338                            return;
339                    }
340    
341                    UnicodeProperties typeSettingsProperties =
342                            layout.getTypeSettingsProperties();
343    
344                    long lastMergeTime = GetterUtil.getLong(
345                            typeSettingsProperties.getProperty("last-merge-time"));
346    
347                    LayoutPrototype layoutPrototype =
348                            LayoutPrototypeLocalServiceUtil.getLayoutPrototypeByUuid(
349                                    layout.getLayoutPrototypeUuid());
350    
351                    Layout layoutPrototypeLayout = layoutPrototype.getLayout();
352    
353                    Date modifiedDate = layoutPrototypeLayout.getModifiedDate();
354    
355                    if (lastMergeTime >= modifiedDate.getTime()) {
356                            return;
357                    }
358    
359                    UnicodeProperties prototypeTypeSettingsProperties =
360                            layoutPrototypeLayout.getTypeSettingsProperties();
361    
362                    int mergeFailCount = GetterUtil.getInteger(
363                            prototypeTypeSettingsProperties.getProperty("merge-fail-count"));
364    
365                    if (mergeFailCount >
366                            PropsValues.LAYOUT_PROTOTYPE_MERGE_FAIL_THRESHOLD) {
367    
368                            return;
369                    }
370    
371                    String owner = PortalUUIDUtil.generate();
372    
373                    try {
374                            Lock lock = LockLocalServiceUtil.lock(
375                                    LayoutLocalServiceVirtualLayoutsAdvice.class.getName(),
376                                    String.valueOf(layout.getPlid()), owner, false);
377    
378                            // Double deep check
379    
380                            if (!owner.equals(lock.getOwner())) {
381                                    Date createDate = lock.getCreateDate();
382    
383                                    if (((System.currentTimeMillis() - createDate.getTime()) >=
384                                            PropsValues.LAYOUT_PROTOTYPE_MERGE_LOCK_MAX_TIME)) {
385    
386                                            // Acquire lock if the lock is older than the lock max time
387    
388                                            lock = LockLocalServiceUtil.lock(
389                                                    LayoutLocalServiceVirtualLayoutsAdvice.class.getName(),
390                                                    String.valueOf(layout.getPlid()), lock.getOwner(),
391                                                    owner, false);
392    
393                                            // Check if acquiring the lock succeeded or if another
394                                            // process has the lock
395    
396                                            if (!owner.equals(lock.getOwner())) {
397                                                    return;
398                                            }
399                                    }
400                                    else {
401                                            return;
402                                    }
403                            }
404                    }
405                    catch (Exception e) {
406                            return;
407                    }
408    
409                    try {
410                            SitesUtil.applyLayoutPrototype(layoutPrototype, layout, true);
411                    }
412                    catch (Exception e) {
413                            _log.error(e, e);
414    
415                            prototypeTypeSettingsProperties.setProperty(
416                                    "merge-fail-count", String.valueOf(++mergeFailCount));
417    
418                            // Invoke updateImpl so that we do not trigger the listeners
419    
420                            LayoutUtil.updateImpl(layoutPrototypeLayout, false);
421                    }
422                    finally {
423                            LockLocalServiceUtil.unlock(
424                                    LayoutLocalServiceVirtualLayoutsAdvice.class.getName(),
425                                    String.valueOf(layout.getPlid()), owner, false);
426                    }
427            }
428    
429            protected void mergeLayoutSetProtypeLayouts(
430                            Group group, LayoutSet layoutSet)
431                    throws Exception {
432    
433                    if (!layoutSet.isLayoutSetPrototypeLinkActive() ||
434                            group.isLayoutPrototype() || group.isLayoutSetPrototype()) {
435    
436                            return;
437                    }
438    
439                    UnicodeProperties settingsProperties =
440                            layoutSet.getSettingsProperties();
441    
442                    long lastMergeTime = GetterUtil.getLong(
443                            settingsProperties.getProperty("last-merge-time"));
444    
445                    LayoutSetPrototype layoutSetPrototype =
446                            LayoutSetPrototypeLocalServiceUtil.getLayoutSetPrototypeByUuid(
447                                    layoutSet.getLayoutSetPrototypeUuid());
448    
449                    Date modifiedDate = layoutSetPrototype.getModifiedDate();
450    
451                    if (lastMergeTime >= modifiedDate.getTime()) {
452                            return;
453                    }
454    
455                    LayoutSet layoutSetPrototypeLayoutSet =
456                            layoutSetPrototype.getLayoutSet();
457    
458                    UnicodeProperties layoutSetPrototypeSettingsProperties =
459                            layoutSetPrototypeLayoutSet.getSettingsProperties();
460    
461                    int mergeFailCount = GetterUtil.getInteger(
462                            layoutSetPrototypeSettingsProperties.getProperty(
463                                    "merge-fail-count"));
464    
465                    if (mergeFailCount >
466                                    PropsValues.LAYOUT_SET_PROTOTYPE_MERGE_FAIL_THRESHOLD) {
467    
468                            return;
469                    }
470    
471                    String owner = PortalUUIDUtil.generate();
472    
473                    try {
474                            Lock lock = LockLocalServiceUtil.lock(
475                                    LayoutLocalServiceVirtualLayoutsAdvice.class.getName(),
476                                    String.valueOf(layoutSet.getLayoutSetId()), owner, false);
477    
478                            // Double deep check
479    
480                            if (!owner.equals(lock.getOwner())) {
481                                    Date createDate = lock.getCreateDate();
482    
483                                    if (((System.currentTimeMillis() - createDate.getTime()) >=
484                                                    PropsValues.LAYOUT_SET_PROTOTYPE_MERGE_LOCK_MAX_TIME)) {
485    
486                                            // Acquire lock if the lock is older than the lock max time
487    
488                                            lock = LockLocalServiceUtil.lock(
489                                                    LayoutLocalServiceVirtualLayoutsAdvice.class.getName(),
490                                                    String.valueOf(layoutSet.getLayoutSetId()),
491                                                    lock.getOwner(), owner, false);
492    
493                                            // Check if acquiring the lock succeeded or if another
494                                            // process has the lock
495    
496                                            if (!owner.equals(lock.getOwner())) {
497                                                    return;
498                                            }
499                                    }
500                                    else {
501                                            return;
502                                    }
503                            }
504                    }
505                    catch (Exception e) {
506                            return;
507                    }
508    
509                    try {
510                            Map<String, String[]> parameterMap = null;
511    
512                            if (lastMergeTime > 0) {
513                                    parameterMap = getLayoutTemplatesParameters(false);
514                            }
515                            else {
516                                    parameterMap = getLayoutTemplatesParameters(true);
517                            }
518    
519                            importLayoutSetPrototype(
520                                    layoutSetPrototype, layoutSet.getGroupId(),
521                                    layoutSet.isPrivateLayout(), parameterMap);
522    
523                            settingsProperties.setProperty(
524                                    "last-merge-time", String.valueOf(modifiedDate.getTime()));
525    
526                            LayoutSetLocalServiceUtil.updateLayoutSet(layoutSet, false);
527                    }
528                    catch (Exception e) {
529                            _log.error(e, e);
530    
531                            layoutSetPrototypeSettingsProperties.setProperty(
532                                    "merge-fail-count", String.valueOf(++mergeFailCount));
533    
534                            // Invoke updateImpl so that we do not trigger the listeners
535    
536                            LayoutSetUtil.updateImpl(layoutSetPrototypeLayoutSet, false);
537                    }
538                    finally {
539                            LockLocalServiceUtil.unlock(
540                                    LayoutLocalServiceVirtualLayoutsAdvice.class.getName(),
541                                    String.valueOf(layoutSet.getLayoutSetId()), owner, false);
542                    }
543            }
544    
545            private static final String _TEMP_DIR =
546                    SystemProperties.get(SystemProperties.TMP_DIR) +
547                            "/liferay/layout_set_prototype/";
548    
549            private static final Class<?>[] _TYPES_L = {Long.TYPE};
550    
551            private static final Class<?>[] _TYPES_L_B_L = {
552                    Long.TYPE, Boolean.TYPE, Long.TYPE
553            };
554    
555            private static final Class<?>[] _TYPES_L_B_L_B_I_I = {
556                    Long.TYPE, Boolean.TYPE, Long.TYPE, Boolean.TYPE, Integer.TYPE,
557                    Integer.TYPE
558            };
559    
560            private static Log _log = LogFactoryUtil.getLog(
561                    LayoutLocalServiceVirtualLayoutsAdvice.class);
562    
563    }