001    /**
002     * Copyright (c) 2000-2013 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.verify;
016    
017    import com.liferay.portal.kernel.dao.db.DB;
018    import com.liferay.portal.kernel.dao.db.DBFactoryUtil;
019    import com.liferay.portal.kernel.dao.jdbc.DataAccess;
020    import com.liferay.portal.kernel.dao.orm.ActionableDynamicQuery;
021    import com.liferay.portal.kernel.exception.PortalException;
022    import com.liferay.portal.kernel.exception.SystemException;
023    import com.liferay.portal.kernel.log.Log;
024    import com.liferay.portal.kernel.log.LogFactoryUtil;
025    import com.liferay.portal.kernel.util.CharPool;
026    import com.liferay.portal.kernel.util.FriendlyURLNormalizerUtil;
027    import com.liferay.portal.kernel.util.GetterUtil;
028    import com.liferay.portal.kernel.util.HtmlUtil;
029    import com.liferay.portal.kernel.util.HttpUtil;
030    import com.liferay.portal.kernel.util.StringBundler;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.kernel.util.StringUtil;
033    import com.liferay.portal.kernel.util.Validator;
034    import com.liferay.portal.kernel.workflow.WorkflowConstants;
035    import com.liferay.portal.kernel.xml.Document;
036    import com.liferay.portal.kernel.xml.Element;
037    import com.liferay.portal.kernel.xml.Node;
038    import com.liferay.portal.kernel.xml.SAXReaderUtil;
039    import com.liferay.portal.service.ResourceLocalServiceUtil;
040    import com.liferay.portal.util.PortalInstances;
041    import com.liferay.portlet.PortletPreferencesFactoryUtil;
042    import com.liferay.portlet.asset.model.AssetEntry;
043    import com.liferay.portlet.asset.service.AssetEntryLocalServiceUtil;
044    import com.liferay.portlet.documentlibrary.model.DLFileEntry;
045    import com.liferay.portlet.documentlibrary.service.DLFileEntryLocalServiceUtil;
046    import com.liferay.portlet.dynamicdatamapping.NoSuchStructureException;
047    import com.liferay.portlet.dynamicdatamapping.util.DDMFieldsCounter;
048    import com.liferay.portlet.journal.model.JournalArticle;
049    import com.liferay.portlet.journal.model.JournalArticleConstants;
050    import com.liferay.portlet.journal.model.JournalArticleImage;
051    import com.liferay.portlet.journal.model.JournalContentSearch;
052    import com.liferay.portlet.journal.model.JournalFolder;
053    import com.liferay.portlet.journal.service.JournalArticleImageLocalServiceUtil;
054    import com.liferay.portlet.journal.service.JournalArticleLocalServiceUtil;
055    import com.liferay.portlet.journal.service.JournalContentSearchLocalServiceUtil;
056    import com.liferay.portlet.journal.service.JournalFolderLocalServiceUtil;
057    import com.liferay.portlet.journal.service.persistence.JournalArticleActionableDynamicQuery;
058    
059    import java.sql.Connection;
060    import java.sql.PreparedStatement;
061    import java.sql.ResultSet;
062    
063    import java.util.List;
064    import java.util.regex.Pattern;
065    
066    import javax.portlet.PortletPreferences;
067    
068    /**
069     * @author Alexander Chow
070     * @author Shinn Lok
071     */
072    public class VerifyJournal extends VerifyProcess {
073    
074            public static final long DEFAULT_GROUP_ID = 14;
075    
076            public static final int NUM_OF_ARTICLES = 5;
077    
078            @Override
079            protected void doVerify() throws Exception {
080                    verifyContent();
081                    verifyDynamicElements();
082                    updateFolderAssets();
083                    verifyOracleNewLine();
084                    verifyPermissionsAndAssets();
085                    verifySearch();
086                    verifyTree();
087                    verifyURLTitle();
088            }
089    
090            protected void updateDynamicElements(List<Element> dynamicElements)
091                    throws PortalException, SystemException {
092    
093                    DDMFieldsCounter ddmFieldsCounter = new DDMFieldsCounter();
094    
095                    for (Element dynamicElement : dynamicElements) {
096                            updateDynamicElements(dynamicElement.elements("dynamic-element"));
097    
098                            String name = dynamicElement.attributeValue("name");
099    
100                            int index = ddmFieldsCounter.get(name);
101    
102                            dynamicElement.addAttribute("index", String.valueOf(index));
103    
104                            String type = dynamicElement.attributeValue("type");
105    
106                            if (type.equals("image")) {
107                                    updateImageElement(dynamicElement, name, index);
108                            }
109    
110                            ddmFieldsCounter.incrementKey(name);
111                    }
112            }
113    
114            protected void updateDocumentLibraryElements(Element element)
115                    throws Exception {
116    
117                    Element dynamicContentElement = element.element("dynamic-content");
118    
119                    String path = dynamicContentElement.getStringValue();
120    
121                    String[] pathArray = StringUtil.split(path, CharPool.SLASH);
122    
123                    if (pathArray.length != 5) {
124                            return;
125                    }
126    
127                    long groupId = GetterUtil.getLong(pathArray[2]);
128                    long folderId = GetterUtil.getLong(pathArray[3]);
129                    String title = HttpUtil.decodeURL(HtmlUtil.escape(pathArray[4]));
130    
131                    if (title.contains(StringPool.SLASH)) {
132                            title = StringUtil.replace(
133                                    title, StringPool.SLASH, StringPool.BLANK);
134    
135                            StringBundler sb = new StringBundler(9);
136    
137                            for (int i = 0; i < 4; i++) {
138                                    sb.append(pathArray[i]);
139                                    sb.append(StringPool.SLASH);
140                            }
141    
142                            sb.append(title);
143    
144                            path = sb.toString();
145                    }
146    
147                    DLFileEntry dlFileEntry = DLFileEntryLocalServiceUtil.fetchFileEntry(
148                            groupId, folderId, title);
149    
150                    if (dlFileEntry == null) {
151                            return;
152                    }
153    
154                    Node node = dynamicContentElement.node(0);
155    
156                    node.setText(path + StringPool.SLASH + dlFileEntry.getUuid());
157            }
158    
159            protected void updateElement(long groupId, Element element)
160                    throws Exception {
161    
162                    List<Element> dynamicElementElements = element.elements(
163                            "dynamic-element");
164    
165                    for (Element dynamicElementElement : dynamicElementElements) {
166                            updateElement(groupId, dynamicElementElement);
167                    }
168    
169                    String type = element.attributeValue("type");
170    
171                    if (type.equals("document_library")) {
172                            updateDocumentLibraryElements(element);
173                    }
174                    else if (type.equals("link_to_layout")) {
175                            updateLinkToLayoutElements(groupId, element);
176                    }
177            }
178    
179            protected void updateFolderAssets() throws Exception {
180                    List<JournalFolder> folders =
181                            JournalFolderLocalServiceUtil.getNoAssetFolders();
182    
183                    if (_log.isDebugEnabled()) {
184                            _log.debug(
185                                    "Processing " + folders.size() + " folders with no asset");
186                    }
187    
188                    for (JournalFolder folder : folders) {
189                            try {
190                                    JournalFolderLocalServiceUtil.updateAsset(
191                                            folder.getUserId(), folder, null, null, null);
192                            }
193                            catch (Exception e) {
194                                    if (_log.isWarnEnabled()) {
195                                            _log.warn(
196                                                    "Unable to update asset for folder " +
197                                                            folder.getFolderId() + ": " + e.getMessage());
198                                    }
199                            }
200                    }
201    
202                    if (_log.isDebugEnabled()) {
203                            _log.debug("Assets verified for folders");
204                    }
205            }
206    
207            protected void updateImageElement(Element element, String name, int index)
208                    throws SystemException {
209    
210                    Element dynamicContentElement = element.element("dynamic-content");
211    
212                    long articleImageId = GetterUtil.getLong(
213                            dynamicContentElement.attributeValue("id"));
214    
215                    JournalArticleImage articleImage =
216                            JournalArticleImageLocalServiceUtil.fetchJournalArticleImage(
217                                    articleImageId);
218    
219                    if (articleImage == null) {
220                            return;
221                    }
222    
223                    articleImage.setElName(name + StringPool.UNDERLINE + index);
224    
225                    JournalArticleImageLocalServiceUtil.updateJournalArticleImage(
226                            articleImage);
227            }
228    
229            protected void updateLinkToLayoutElements(long groupId, Element element) {
230                    Element dynamicContentElement = element.element("dynamic-content");
231    
232                    Node node = dynamicContentElement.node(0);
233    
234                    String text = node.getText();
235    
236                    if (!text.isEmpty() && !text.endsWith(StringPool.AT + groupId)) {
237                            node.setText(
238                                    dynamicContentElement.getStringValue() + StringPool.AT +
239                                            groupId);
240                    }
241            }
242    
243            protected void updateURLTitle(
244                            long groupId, String articleId, String urlTitle)
245                    throws Exception {
246    
247                    String normalizedURLTitle = FriendlyURLNormalizerUtil.normalize(
248                            urlTitle, _friendlyURLPattern);
249    
250                    if (urlTitle.equals(normalizedURLTitle)) {
251                            return;
252                    }
253    
254                    normalizedURLTitle = JournalArticleLocalServiceUtil.getUniqueUrlTitle(
255                            groupId, articleId, normalizedURLTitle);
256    
257                    Connection con = null;
258                    PreparedStatement ps = null;
259    
260                    try {
261                            con = DataAccess.getUpgradeOptimizedConnection();
262    
263                            ps = con.prepareStatement(
264                                    "update JournalArticle set urlTitle = ? where urlTitle = ?");
265    
266                            ps.setString(1, normalizedURLTitle);
267                            ps.setString(2, urlTitle);
268    
269                            ps.executeUpdate();
270                    }
271                    finally {
272                            DataAccess.cleanUp(con, ps);
273                    }
274            }
275    
276            protected void verifyContent() throws Exception {
277                    Connection con = null;
278                    PreparedStatement ps = null;
279                    ResultSet rs = null;
280    
281                    try {
282                            con = DataAccess.getUpgradeOptimizedConnection();
283    
284                            ps = con.prepareStatement(
285                                    "select id_ from JournalArticle where (content like " +
286                                            "'%document_library%' or content like '%link_to_layout%')" +
287                                                    " and structureId != ''");
288    
289                            rs = ps.executeQuery();
290    
291                            while (rs.next()) {
292                                    long id = rs.getLong("id_");
293    
294                                    JournalArticle article =
295                                            JournalArticleLocalServiceUtil.getArticle(id);
296    
297                                    Document document = SAXReaderUtil.read(article.getContent());
298    
299                                    Element rootElement = document.getRootElement();
300    
301                                    for (Element element : rootElement.elements()) {
302                                            updateElement(article.getGroupId(), element);
303                                    }
304    
305                                    article.setContent(document.asXML());
306    
307                                    JournalArticleLocalServiceUtil.updateJournalArticle(article);
308                            }
309                    }
310                    finally {
311                            DataAccess.cleanUp(con, ps, rs);
312                    }
313            }
314    
315            protected void verifyContentSearch(long groupId, String portletId)
316                    throws Exception {
317    
318                    Connection con = null;
319                    PreparedStatement ps = null;
320                    ResultSet rs = null;
321    
322                    try {
323                            con = DataAccess.getUpgradeOptimizedConnection();
324    
325                            ps = con.prepareStatement(
326                                    "select preferences from PortletPreferences inner join " +
327                                            "Layout on PortletPreferences.plid = Layout.plid where " +
328                                                    "groupId = ? and portletId = ?");
329    
330                            ps.setLong(1, groupId);
331                            ps.setString(2, portletId);
332    
333                            rs = ps.executeQuery();
334    
335                            while (rs.next()) {
336                                    String xml = rs.getString("preferences");
337    
338                                    PortletPreferences portletPreferences =
339                                            PortletPreferencesFactoryUtil.fromDefaultXML(xml);
340    
341                                    String articleId = portletPreferences.getValue(
342                                            "articleId", null);
343    
344                                    List<JournalContentSearch> contentSearches =
345                                            JournalContentSearchLocalServiceUtil.
346                                                    getArticleContentSearches(groupId, articleId);
347    
348                                    if (contentSearches.isEmpty()) {
349                                            continue;
350                                    }
351    
352                                    JournalContentSearch contentSearch = contentSearches.get(0);
353    
354                                    JournalContentSearchLocalServiceUtil.updateContentSearch(
355                                            contentSearch.getGroupId(), contentSearch.isPrivateLayout(),
356                                            contentSearch.getLayoutId(), contentSearch.getPortletId(),
357                                            articleId, true);
358                            }
359                    }
360                    finally {
361                            DataAccess.cleanUp(con, ps, rs);
362                    }
363            }
364    
365            protected void verifyDynamicElements()
366                    throws PortalException, SystemException {
367    
368                    ActionableDynamicQuery actionableDynamicQuery =
369                            new JournalArticleActionableDynamicQuery() {
370    
371                                    @Override
372                                    public void performAction(Object object) {
373                                            JournalArticle article = (JournalArticle)object;
374    
375                                            try {
376                                                    verifyDynamicElements(article);
377                                            }
378                                            catch (Exception e) {
379                                                    _log.error(
380                                                            "Unable to update content for article " +
381                                                                    article.getId(),
382                                                            e);
383                                            }
384                                    }
385    
386                    };
387    
388                    actionableDynamicQuery.performActions();
389    
390                    if (_log.isDebugEnabled()) {
391                            _log.debug("Dynamic elements verified for articles");
392                    }
393            }
394    
395            protected void verifyDynamicElements(JournalArticle article)
396                    throws Exception {
397    
398                    Document document = SAXReaderUtil.read(article.getContent());
399    
400                    Element rootElement = document.getRootElement();
401    
402                    updateDynamicElements(rootElement.elements("dynamic-element"));
403    
404                    article.setContent(document.asXML());
405    
406                    JournalArticleLocalServiceUtil.updateJournalArticle(article);
407            }
408    
409            protected void verifyOracleNewLine() throws Exception {
410                    DB db = DBFactoryUtil.getDB();
411    
412                    String dbType = db.getType();
413    
414                    if (!dbType.equals(DB.TYPE_ORACLE)) {
415                            return;
416                    }
417    
418                    // This is a workaround for a limitation in Oracle sqlldr's inability
419                    // insert new line characters for long varchar columns. See
420                    // http://forums.liferay.com/index.php?showtopic=2761&hl=oracle for more
421                    // information. Check several articles because some articles may not
422                    // have new lines.
423    
424                    boolean checkNewLine = false;
425    
426                    List<JournalArticle> articles =
427                            JournalArticleLocalServiceUtil.getArticles(
428                                    DEFAULT_GROUP_ID, 0, NUM_OF_ARTICLES);
429    
430                    for (JournalArticle article : articles) {
431                            String content = article.getContent();
432    
433                            if ((content != null) && content.contains("\\n")) {
434                                    articles = JournalArticleLocalServiceUtil.getArticles(
435                                            DEFAULT_GROUP_ID);
436    
437                                    for (int j = 0; j < articles.size(); j++) {
438                                            article = articles.get(j);
439    
440                                            JournalArticleLocalServiceUtil.checkNewLine(
441                                                    article.getGroupId(), article.getArticleId(),
442                                                    article.getVersion());
443                                    }
444    
445                                    checkNewLine = true;
446    
447                                    break;
448                            }
449                    }
450    
451                    // Only process this once
452    
453                    if (!checkNewLine) {
454                            if (_log.isInfoEnabled()) {
455                                    _log.info("Do not fix oracle new line");
456                            }
457    
458                            return;
459                    }
460                    else {
461                            if (_log.isInfoEnabled()) {
462                                    _log.info("Fix oracle new line");
463                            }
464                    }
465            }
466    
467            protected void verifyPermissionsAndAssets() throws Exception {
468                    ActionableDynamicQuery actionableDynamicQuery =
469                            new JournalArticleActionableDynamicQuery() {
470    
471                            @Override
472                            protected void performAction(Object object)
473                                    throws PortalException, SystemException {
474    
475                                    JournalArticle article = (JournalArticle)object;
476    
477                                    long groupId = article.getGroupId();
478                                    String articleId = article.getArticleId();
479                                    double version = article.getVersion();
480                                    String structureId = article.getStructureId();
481    
482                                    if (article.getResourcePrimKey() <= 0) {
483                                            article =
484                                                    JournalArticleLocalServiceUtil.
485                                                            checkArticleResourcePrimKey(
486                                                                    groupId, articleId, version);
487                                    }
488    
489                                    ResourceLocalServiceUtil.addResources(
490                                            article.getCompanyId(), 0, 0,
491                                            JournalArticle.class.getName(),
492                                            article.getResourcePrimKey(), false, false, false);
493    
494                                    AssetEntry assetEntry = AssetEntryLocalServiceUtil.fetchEntry(
495                                            JournalArticle.class.getName(),
496                                            article.getResourcePrimKey());
497    
498                                    if (assetEntry == null) {
499                                            try {
500                                                    JournalArticleLocalServiceUtil.updateAsset(
501                                                            article.getUserId(), article, null, null, null);
502                                            }
503                                            catch (Exception e) {
504                                                    if (_log.isWarnEnabled()) {
505                                                            _log.warn(
506                                                                    "Unable to update asset for article " +
507                                                                            article.getId() + ": " + e.getMessage());
508                                                    }
509                                            }
510                                    }
511                                    else if ((article.getStatus() ==
512                                                            WorkflowConstants.STATUS_DRAFT) &&
513                                                     (article.getVersion() ==
514                                                            JournalArticleConstants.VERSION_DEFAULT)) {
515    
516                                            AssetEntryLocalServiceUtil.updateEntry(
517                                                    assetEntry.getClassName(), assetEntry.getClassPK(),
518                                                    null, assetEntry.isVisible());
519                                    }
520    
521                                    if (Validator.isNotNull(structureId)) {
522                                            /*JournalStructure structure =
523                                                    JournalStructureLocalServiceUtil.getStructure(
524                                                            groupId, structureId);
525    
526                                            newContent = JournalUtil.removeOldContent(
527                                                    newContent, structure.getXsd());*/
528                                    }
529    
530                                    try {
531                                            JournalArticleLocalServiceUtil.checkStructure(
532                                                    groupId, articleId, version);
533                                    }
534                                    catch (NoSuchStructureException nsse) {
535                                            if (_log.isWarnEnabled()) {
536                                                    _log.warn(
537                                                            "Removing reference to missing structure for " +
538                                                                    "article " + article.getId());
539                                            }
540    
541                                            article.setStructureId(StringPool.BLANK);
542                                            article.setTemplateId(StringPool.BLANK);
543    
544                                            JournalArticleLocalServiceUtil.updateJournalArticle(
545                                                    article);
546                                    }
547                            }
548    
549                    };
550    
551                    actionableDynamicQuery.performActions();
552    
553                    if (_log.isDebugEnabled()) {
554                            _log.debug("Permissions and assets verified for articles");
555                    }
556            }
557    
558            protected void verifySearch() throws Exception {
559                    Connection con = null;
560                    PreparedStatement ps = null;
561                    ResultSet rs = null;
562    
563                    try {
564                            con = DataAccess.getUpgradeOptimizedConnection();
565    
566                            ps = con.prepareStatement(
567                                    "select groupId, portletId from JournalContentSearch group " +
568                                            "by groupId, portletId having count(groupId) > 1 and " +
569                                                    "count(portletId) > 1");
570    
571                            rs = ps.executeQuery();
572    
573                            while (rs.next()) {
574                                    long groupId = rs.getLong("groupId");
575                                    String portletId = rs.getString("portletId");
576    
577                                    verifyContentSearch(groupId, portletId);
578                            }
579                    }
580                    finally {
581                            DataAccess.cleanUp(con, ps, rs);
582                    }
583            }
584    
585            protected void verifyTree() throws Exception {
586                    long[] companyIds = PortalInstances.getCompanyIdsBySQL();
587    
588                    for (long companyId : companyIds) {
589                            JournalFolderLocalServiceUtil.rebuildTree(companyId);
590                    }
591            }
592    
593            protected void verifyURLTitle() throws Exception {
594                    Connection con = null;
595                    PreparedStatement ps = null;
596                    ResultSet rs = null;
597    
598                    try {
599                            con = DataAccess.getUpgradeOptimizedConnection();
600    
601                            ps = con.prepareStatement(
602                                    "select distinct groupId, articleId, urlTitle from " +
603                                            "JournalArticle");
604    
605                            rs = ps.executeQuery();
606    
607                            while (rs.next()) {
608                                    long groupId = rs.getLong("groupId");
609                                    String articleId = rs.getString("articleId");
610                                    String urlTitle = GetterUtil.getString(
611                                            rs.getString("urlTitle"));
612    
613                                    updateURLTitle(groupId, articleId, urlTitle);
614                            }
615                    }
616                    finally {
617                            DataAccess.cleanUp(con, ps, rs);
618                    }
619            }
620    
621            private static Log _log = LogFactoryUtil.getLog(VerifyJournal.class);
622    
623            private static Pattern _friendlyURLPattern = Pattern.compile("[^a-z0-9_-]");
624    
625    }