1   /**
2    * Copyright (c) 2000-2009 Liferay, Inc. All rights reserved.
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20   * SOFTWARE.
21   */
22  
23  package com.liferay.portal.plugin;
24  
25  import com.liferay.portal.SystemException;
26  import com.liferay.portal.kernel.log.Log;
27  import com.liferay.portal.kernel.log.LogFactoryUtil;
28  import com.liferay.portal.kernel.plugin.License;
29  import com.liferay.portal.kernel.plugin.PluginPackage;
30  import com.liferay.portal.kernel.plugin.RemotePluginPackageRepository;
31  import com.liferay.portal.kernel.plugin.Screenshot;
32  import com.liferay.portal.kernel.plugin.Version;
33  import com.liferay.portal.kernel.search.BooleanClauseOccur;
34  import com.liferay.portal.kernel.search.BooleanQuery;
35  import com.liferay.portal.kernel.search.BooleanQueryFactoryUtil;
36  import com.liferay.portal.kernel.search.Field;
37  import com.liferay.portal.kernel.search.Hits;
38  import com.liferay.portal.kernel.search.Query;
39  import com.liferay.portal.kernel.search.SearchEngineUtil;
40  import com.liferay.portal.kernel.search.TermQueryFactoryUtil;
41  import com.liferay.portal.kernel.util.ArrayUtil;
42  import com.liferay.portal.kernel.util.DateFormatFactoryUtil;
43  import com.liferay.portal.kernel.util.GetterUtil;
44  import com.liferay.portal.kernel.util.HtmlUtil;
45  import com.liferay.portal.kernel.util.Http;
46  import com.liferay.portal.kernel.util.HttpUtil;
47  import com.liferay.portal.kernel.util.PropsKeys;
48  import com.liferay.portal.kernel.util.ReleaseInfo;
49  import com.liferay.portal.kernel.util.StringPool;
50  import com.liferay.portal.kernel.util.StringUtil;
51  import com.liferay.portal.kernel.util.Time;
52  import com.liferay.portal.kernel.util.Validator;
53  import com.liferay.portal.kernel.xml.Attribute;
54  import com.liferay.portal.kernel.xml.Document;
55  import com.liferay.portal.kernel.xml.DocumentException;
56  import com.liferay.portal.kernel.xml.Element;
57  import com.liferay.portal.kernel.xml.SAXReaderUtil;
58  import com.liferay.portal.model.CompanyConstants;
59  import com.liferay.portal.model.Plugin;
60  import com.liferay.portal.util.HttpImpl;
61  import com.liferay.portal.util.PrefsPropsUtil;
62  import com.liferay.portal.util.PropsValues;
63  
64  import java.io.IOException;
65  
66  import java.net.MalformedURLException;
67  
68  import java.text.DateFormat;
69  
70  import java.util.ArrayList;
71  import java.util.Arrays;
72  import java.util.Collection;
73  import java.util.Date;
74  import java.util.HashMap;
75  import java.util.Iterator;
76  import java.util.List;
77  import java.util.Locale;
78  import java.util.Map;
79  import java.util.Properties;
80  import java.util.Set;
81  import java.util.TreeSet;
82  
83  import javax.servlet.http.HttpServletResponse;
84  
85  import org.apache.commons.httpclient.HostConfiguration;
86  import org.apache.commons.httpclient.HttpClient;
87  import org.apache.commons.httpclient.methods.GetMethod;
88  import org.apache.commons.lang.time.StopWatch;
89  
90  /**
91   * <a href="PluginPackageUtil.java.html"><b><i>View Source</i></b></a>
92   *
93   * @author Jorge Ferrer
94   * @author Brian Wing Shun Chan
95   * @author Sandeep Soni
96   */
97  public class PluginPackageUtil {
98  
99      public static final String REPOSITORY_XML_FILENAME_EXTENSION =
100         "xml";
101 
102     public static final String REPOSITORY_XML_FILENAME_PREFIX =
103         "liferay-plugin-repository";
104 
105     public static void endPluginPackageInstallation(String preliminaryContext) {
106         _instance._endPluginPackageInstallation(preliminaryContext);
107     }
108 
109     public static List<PluginPackage> getAllAvailablePluginPackages()
110         throws PluginPackageException {
111 
112         return _instance._getAllAvailablePluginPackages();
113     }
114 
115     public static Collection<String> getAvailableTags() {
116         return _instance._getAvailableTags();
117     }
118 
119     public static List<PluginPackage> getInstalledPluginPackages() {
120         return _instance._getInstalledPluginPackages();
121     }
122 
123     public static Date getLastUpdateDate() {
124         return _instance._getLastUpdateDate();
125     }
126 
127     public static PluginPackage getLatestAvailablePluginPackage(
128             String groupId, String artifactId)
129         throws SystemException {
130 
131         return _instance._getLatestAvailablePluginPackage(groupId, artifactId);
132     }
133 
134     public static PluginPackage getLatestInstalledPluginPackage(
135         String groupId, String artifactId) {
136 
137         return _instance._getLatestInstalledPluginPackage(groupId, artifactId);
138     }
139 
140     public static PluginPackage getPluginPackageByModuleId(
141             String moduleId, String repositoryURL)
142         throws PluginPackageException {
143 
144         return _instance._getPluginPackageByModuleId(moduleId, repositoryURL);
145     }
146 
147     public static PluginPackage getPluginPackageByURL(String url)
148         throws PluginPackageException {
149 
150         return _instance._getPluginPackageByURL(url);
151     }
152 
153     public static RemotePluginPackageRepository getRepository(
154             String repositoryURL)
155         throws PluginPackageException {
156 
157         return _instance._getRepository(repositoryURL);
158     }
159 
160     public static String[] getRepositoryURLs() throws PluginPackageException {
161         return _instance._getRepositoryURLs();
162     }
163 
164     public static String[] getSupportedTypes() {
165         return _instance._getSupportedTypes();
166     }
167 
168     public static boolean isCurrentVersionSupported(List<String> versions) {
169         return _instance._isCurrentVersionSupported(versions);
170     }
171 
172     public static boolean isIgnored(PluginPackage pluginPackage)
173         throws SystemException {
174 
175         return _instance._isIgnored(pluginPackage);
176     }
177 
178     public static boolean isInstallationInProcess(String context) {
179         return _instance._isInstallationInProcess(context);
180     }
181 
182     public static boolean isTrusted(String repositoryURL)
183         throws PluginPackageException {
184 
185         return _instance._isTrusted(repositoryURL);
186     }
187 
188     public static boolean isUpdateAvailable() throws SystemException {
189         return _instance._isUpdateAvailable();
190     }
191 
192     public static PluginPackage readPluginPackageProperties(
193         String displayName, Properties props) {
194 
195         return _instance._readPluginPackageProperties(displayName, props);
196     }
197 
198     public static PluginPackage readPluginPackageXml(Element pluginPackageEl) {
199         return _instance._readPluginPackageXml(pluginPackageEl);
200     }
201 
202     public static PluginPackage readPluginPackageXml(String xml)
203         throws DocumentException {
204 
205         return _instance._readPluginPackageXml(xml);
206     }
207 
208     public static void refreshUpdatesAvailableCache() {
209         _instance._refreshUpdatesAvailableCache();
210     }
211 
212     public static void registerInstalledPluginPackage(
213         PluginPackage pluginPackage) {
214 
215         _instance._registerInstalledPluginPackage(pluginPackage);
216     }
217 
218     public static void registerPluginPackageInstallation(
219         String preliminaryContext) {
220 
221         _instance._registerPluginPackageInstallation(preliminaryContext);
222     }
223 
224     public static void reIndex() throws SystemException {
225         _instance._reIndex();
226     }
227 
228     public static RepositoryReport reloadRepositories() throws SystemException {
229         return _instance._reloadRepositories();
230     }
231 
232     public static Hits search(
233             String keywords, String type, String tag, String license,
234             String repositoryURL, String status, int start, int end)
235         throws SystemException {
236 
237         return _instance._search(
238             keywords, type, tag, license, repositoryURL, status, start, end);
239     }
240 
241     public static void unregisterInstalledPluginPackage(
242         PluginPackage pluginPackage) {
243 
244         _instance._unregisterInstalledPluginPackage(pluginPackage);
245     }
246 
247     public static void updateInstallingPluginPackage(
248         String preliminaryContext, PluginPackage pluginPackage) {
249 
250         _instance._updateInstallingPluginPackage(
251             preliminaryContext, pluginPackage);
252     }
253 
254     private PluginPackageUtil() {
255         _installedPluginPackages = new LocalPluginPackageRepository();
256         _repositoryCache = new HashMap<String, RemotePluginPackageRepository>();
257         _availableTagsCache = new TreeSet<String>();
258     }
259 
260     private void _checkRepositories(String repositoryURL)
261         throws PluginPackageException {
262 
263         String[] repositoryURLs = null;
264 
265         if (Validator.isNotNull(repositoryURL)) {
266             repositoryURLs = new String[] {repositoryURL};
267         }
268         else {
269             repositoryURLs = _getRepositoryURLs();
270         }
271 
272         for (int i = 0; i < repositoryURLs.length; i++) {
273             _getRepository(repositoryURLs[i]);
274         }
275     }
276 
277     private void _endPluginPackageInstallation(String preliminaryContext) {
278         _installedPluginPackages.unregisterPluginPackageInstallation(
279             preliminaryContext);
280     }
281 
282     private PluginPackage _findLatestVersion(
283         List<PluginPackage> pluginPackages) {
284 
285         PluginPackage latestPluginPackage = null;
286 
287         for (PluginPackage pluginPackage : pluginPackages) {
288             if ((latestPluginPackage == null) ||
289                 (pluginPackage.isLaterVersionThan(latestPluginPackage))) {
290 
291                 latestPluginPackage = pluginPackage;
292             }
293         }
294 
295         return latestPluginPackage;
296     }
297 
298     private List<PluginPackage> _getAllAvailablePluginPackages()
299         throws PluginPackageException {
300 
301         List<PluginPackage> pluginPackages = new ArrayList<PluginPackage>();
302 
303         String[] repositoryURLs = _getRepositoryURLs();
304 
305         for (int i = 0; i < repositoryURLs.length; i++) {
306             try {
307                 RemotePluginPackageRepository repository =
308                     _getRepository(repositoryURLs[i]);
309 
310                 pluginPackages.addAll(repository.getPluginPackages());
311             }
312             catch (PluginPackageException ppe) {
313                 String message = ppe.getMessage();
314 
315                 if (message.startsWith("Unable to communicate")) {
316                     if (_log.isWarnEnabled()) {
317                         _log.warn(message);
318                     }
319                 }
320                 else {
321                     _log.error(message);
322                 }
323             }
324         }
325 
326         return pluginPackages;
327     }
328 
329     private List<PluginPackage> _getAvailablePluginPackages(
330             String groupId, String artifactId)
331         throws PluginPackageException {
332 
333         List<PluginPackage> pluginPackages = new ArrayList<PluginPackage>();
334 
335         String[] repositoryURLs = _getRepositoryURLs();
336 
337         for (int i = 0; i < repositoryURLs.length; i++) {
338             RemotePluginPackageRepository repository =
339                 _getRepository(repositoryURLs[i]);
340 
341             List<PluginPackage> curPluginPackages =
342                 repository.findPluginsByGroupIdAndArtifactId(
343                     groupId, artifactId);
344 
345             if (curPluginPackages != null) {
346                 pluginPackages.addAll(curPluginPackages);
347             }
348         }
349 
350         return pluginPackages;
351     }
352 
353     private Collection<String> _getAvailableTags() {
354         return _availableTagsCache;
355     }
356 
357     private List<PluginPackage> _getInstalledPluginPackages() {
358         return _installedPluginPackages.getSortedPluginPackages();
359     }
360 
361     private Date _getLastUpdateDate() {
362         return _lastUpdateDate;
363     }
364 
365     private PluginPackage _getLatestAvailablePluginPackage(
366             String groupId, String artifactId)
367         throws SystemException {
368 
369         List<PluginPackage> pluginPackages = _getAvailablePluginPackages(
370             groupId, artifactId);
371 
372         return _findLatestVersion(pluginPackages);
373     }
374 
375     private PluginPackage _getLatestInstalledPluginPackage(
376         String groupId, String artifactId) {
377 
378         return _installedPluginPackages.getLatestPluginPackage(
379             groupId, artifactId);
380     }
381 
382     private PluginPackage _getPluginPackageByModuleId(
383             String moduleId, String repositoryURL)
384         throws PluginPackageException {
385 
386         RemotePluginPackageRepository repository = _getRepository(
387             repositoryURL);
388 
389         return repository.findPluginPackageByModuleId(moduleId);
390     }
391 
392     private PluginPackage _getPluginPackageByURL(String url)
393         throws PluginPackageException {
394 
395         String[] repositoryURLs = _getRepositoryURLs();
396 
397         for (int i = 0; i < repositoryURLs.length; i++) {
398             String repositoryURL = repositoryURLs[i];
399 
400             try {
401                 RemotePluginPackageRepository repository =
402                     _getRepository(repositoryURL);
403 
404                 return repository.findPluginByArtifactURL(url);
405             }
406             catch (PluginPackageException pe) {
407                 _log.error("Unable to load repository " + repositoryURL, pe);
408             }
409         }
410 
411         return null;
412     }
413 
414     private RemotePluginPackageRepository _getRepository(
415             String repositoryURL)
416         throws PluginPackageException {
417 
418         RemotePluginPackageRepository repository = _repositoryCache.get(
419             repositoryURL);
420 
421         if (repository != null) {
422             return repository;
423         }
424 
425         return _loadRepository(repositoryURL);
426     }
427 
428     private String[] _getRepositoryURLs() throws PluginPackageException {
429         try {
430             String[] trusted = PrefsPropsUtil.getStringArray(
431                 PropsKeys.PLUGIN_REPOSITORIES_TRUSTED, StringPool.NEW_LINE,
432                 PropsValues.PLUGIN_REPOSITORIES_TRUSTED);
433             String[] untrusted = PrefsPropsUtil.getStringArray(
434                 PropsKeys.PLUGIN_REPOSITORIES_UNTRUSTED, StringPool.NEW_LINE,
435                 PropsValues.PLUGIN_REPOSITORIES_UNTRUSTED);
436 
437             return ArrayUtil.append(trusted, untrusted);
438         }
439         catch (Exception e) {
440             throw new PluginPackageException(
441                 "Unable to read repository list", e);
442         }
443     }
444 
445     private String[] _getStatusAndInstalledVersion(
446         PluginPackage pluginPackage) {
447 
448         PluginPackage installedPluginPackage =
449             _installedPluginPackages.getLatestPluginPackage(
450                 pluginPackage.getGroupId(), pluginPackage.getArtifactId());
451 
452         String status = null;
453         String installedVersion = null;
454 
455         if (installedPluginPackage == null) {
456             status = PluginPackageImpl.STATUS_NOT_INSTALLED;
457         }
458         else {
459             installedVersion = installedPluginPackage.getVersion();
460 
461             if (installedPluginPackage.isLaterVersionThan(pluginPackage)) {
462                 status = PluginPackageImpl.STATUS_NEWER_VERSION_INSTALLED;
463             }
464             else if (installedPluginPackage.isPreviousVersionThan(
465                         pluginPackage)) {
466 
467                 status = PluginPackageImpl.STATUS_OLDER_VERSION_INSTALLED;
468             }
469             else {
470                 status = PluginPackageImpl.STATUS_SAME_VERSION_INSTALLED;
471             }
472         }
473 
474         return new String[] {status, installedVersion};
475     }
476 
477     private String[] _getSupportedTypes() {
478         return PropsValues.PLUGIN_TYPES;
479     }
480 
481     private void _indexPluginPackage(PluginPackage pluginPackage) {
482         String[] statusAndInstalledVersion =
483             _getStatusAndInstalledVersion(pluginPackage);
484 
485         String status = statusAndInstalledVersion[0];
486         String installedVersion = statusAndInstalledVersion[1];
487 
488         try {
489             PluginPackageIndexer.updatePluginPackage(
490                 pluginPackage.getModuleId(), pluginPackage.getName(),
491                 pluginPackage.getVersion(), pluginPackage.getModifiedDate(),
492                 pluginPackage.getAuthor(), pluginPackage.getTypes(),
493                 pluginPackage.getTags(), pluginPackage.getLicenses(),
494                 pluginPackage.getLiferayVersions(),
495                 pluginPackage.getShortDescription(),
496                 pluginPackage.getLongDescription(),
497                 pluginPackage.getChangeLog(), pluginPackage.getPageURL(),
498                 pluginPackage.getRepositoryURL(), status, installedVersion);
499         }
500         catch (Exception e) {
501             _log.error("Error reindexing " + pluginPackage.getModuleId(), e);
502         }
503     }
504 
505     private boolean _isCurrentVersionSupported(List<String> versions) {
506         Version currentVersion = Version.getInstance(ReleaseInfo.getVersion());
507 
508         for (String version : versions) {
509             Version supportedVersion = Version.getInstance(version);
510 
511             if (supportedVersion.includes(currentVersion)) {
512                 return true;
513             }
514         }
515 
516         return false;
517     }
518 
519     private boolean _isIgnored(PluginPackage pluginPackage)
520         throws SystemException {
521 
522         String packageId = pluginPackage.getPackageId();
523 
524         String[] pluginPackagesIgnored = PrefsPropsUtil.getStringArray(
525             PropsKeys.PLUGIN_NOTIFICATIONS_PACKAGES_IGNORED,
526             StringPool.NEW_LINE,
527             PropsValues.PLUGIN_NOTIFICATIONS_PACKAGES_IGNORED);
528 
529         for (int i = 0; i < pluginPackagesIgnored.length; i++) {
530             String curPluginPackagesIgnored = pluginPackagesIgnored[i];
531 
532             if (curPluginPackagesIgnored.endsWith(StringPool.STAR)) {
533                 String prefix = curPluginPackagesIgnored.substring(
534                     0, curPluginPackagesIgnored.length() - 2);
535 
536                 if (packageId.startsWith(prefix)) {
537                     return true;
538                 }
539             }
540             else {
541                 if (packageId.equals(curPluginPackagesIgnored)) {
542                     return true;
543                 }
544             }
545         }
546 
547         return false;
548     }
549 
550     private boolean _isInstallationInProcess(String context) {
551         if (_installedPluginPackages.getInstallingPluginPackage(
552                 context) != null) {
553 
554             return true;
555         }
556         else {
557             return false;
558         }
559     }
560 
561     private boolean _isTrusted(String repositoryURL)
562         throws PluginPackageException {
563 
564         try {
565             String[] trusted = PrefsPropsUtil.getStringArray(
566                 PropsKeys.PLUGIN_REPOSITORIES_TRUSTED, StringPool.NEW_LINE,
567                 PropsValues.PLUGIN_REPOSITORIES_TRUSTED);
568 
569             if (ArrayUtil.contains(trusted, repositoryURL)) {
570                 return true;
571             }
572             else {
573                 return false;
574             }
575         }
576         catch (Exception e) {
577             throw new PluginPackageException(
578                 "Unable to read repository list", e);
579         }
580     }
581 
582     private boolean _isUpdateAvailable() throws SystemException {
583         if (!PrefsPropsUtil.getBoolean(
584                 PropsKeys.PLUGIN_NOTIFICATIONS_ENABLED,
585                 PropsValues.PLUGIN_NOTIFICATIONS_ENABLED)) {
586 
587             return false;
588         }
589 
590         if (_updateAvailable != null) {
591             return _updateAvailable.booleanValue();
592         }
593         else if (!_settingUpdateAvailable) {
594             _settingUpdateAvailable = true;
595 
596             Thread indexerThread = new Thread(
597                 new UpdateAvailableRunner(), PluginPackageUtil.class.getName());
598 
599             indexerThread.setPriority(Thread.MIN_PRIORITY);
600 
601             indexerThread.start();
602         }
603 
604         return false;
605     }
606 
607     private RemotePluginPackageRepository _loadRepository(String repositoryURL)
608         throws PluginPackageException {
609 
610         RemotePluginPackageRepository repository = null;
611 
612         StringBuilder sb = new StringBuilder();
613 
614         if (!repositoryURL.startsWith(Http.HTTP_WITH_SLASH) &&
615             !repositoryURL.startsWith(Http.HTTPS_WITH_SLASH)) {
616 
617             sb.append(Http.HTTP_WITH_SLASH);
618         }
619 
620         sb.append(repositoryURL);
621         sb.append(StringPool.SLASH);
622         sb.append(REPOSITORY_XML_FILENAME_PREFIX);
623         sb.append(StringPool.DASH);
624         sb.append(ReleaseInfo.getVersion());
625         sb.append(StringPool.PERIOD);
626         sb.append(REPOSITORY_XML_FILENAME_EXTENSION);
627 
628         String pluginsXmlURL = sb.toString();
629 
630         try {
631             HttpImpl httpImpl = (HttpImpl)HttpUtil.getHttp();
632 
633             HostConfiguration hostConfig = httpImpl.getHostConfig(
634                 pluginsXmlURL);
635 
636             HttpClient client = httpImpl.getClient(hostConfig);
637 
638             GetMethod getFileMethod = new GetMethod(pluginsXmlURL);
639 
640             byte[] bytes = null;
641 
642             try {
643                 int responseCode = client.executeMethod(
644                     hostConfig, getFileMethod);
645 
646                 if (responseCode != HttpServletResponse.SC_OK) {
647                     if (_log.isDebugEnabled()) {
648                         _log.debug(
649                             "A repository for version " +
650                                 ReleaseInfo.getVersion() + " was not found. " +
651                                     "Checking general repository");
652                     }
653 
654                     sb = new StringBuilder();
655 
656                     sb.append(repositoryURL);
657                     sb.append(StringPool.SLASH);
658                     sb.append(REPOSITORY_XML_FILENAME_PREFIX);
659                     sb.append(StringPool.PERIOD);
660                     sb.append(REPOSITORY_XML_FILENAME_EXTENSION);
661 
662                     pluginsXmlURL = sb.toString();
663 
664                     getFileMethod.releaseConnection();
665 
666                     getFileMethod = new GetMethod(pluginsXmlURL);
667 
668                     responseCode = client.executeMethod(
669                         hostConfig, getFileMethod);
670 
671                     if (responseCode != HttpServletResponse.SC_OK) {
672                         throw new PluginPackageException(
673                             "Unable to download file " + pluginsXmlURL +
674                                 " because of response code " + responseCode);
675                     }
676                 }
677 
678                 bytes = getFileMethod.getResponseBody();
679             }
680             finally {
681                 getFileMethod.releaseConnection();
682             }
683 
684             if ((bytes != null) && (bytes.length > 0)) {
685                 repository = _parseRepositoryXml(
686                     new String(bytes), repositoryURL);
687 
688                 _repositoryCache.put(repositoryURL, repository);
689                 _availableTagsCache.addAll(repository.getTags());
690                 _lastUpdateDate = new Date();
691                 _updateAvailable = null;
692 
693                 return repository;
694             }
695             else {
696                 _lastUpdateDate = new Date();
697 
698                 throw new PluginPackageException("Download returned 0 bytes");
699             }
700         }
701         catch (MalformedURLException mue) {
702             _repositoryCache.remove(repositoryURL);
703 
704             throw new PluginPackageException(
705                 "Invalid URL " + pluginsXmlURL, mue);
706         }
707         catch (IOException ioe) {
708             _repositoryCache.remove(repositoryURL);
709 
710             throw new PluginPackageException(
711                 "Unable to communicate with repository " + repositoryURL, ioe);
712         }
713         catch (DocumentException de) {
714             _repositoryCache.remove(repositoryURL);
715 
716             throw new PluginPackageException(
717                 "Unable to parse plugin list for repository " + repositoryURL,
718                 de);
719         }
720     }
721 
722     private RemotePluginPackageRepository _parseRepositoryXml(
723             String xml, String repositoryURL)
724         throws DocumentException {
725 
726         List<String> supportedPluginTypes = Arrays.asList(getSupportedTypes());
727 
728         if (_log.isDebugEnabled()) {
729             _log.debug(
730                 "Loading plugin repository " + repositoryURL + ":\n" + xml);
731         }
732 
733         RemotePluginPackageRepository pluginPackageRepository =
734             new RemotePluginPackageRepository(repositoryURL);
735 
736         if (xml == null) {
737             return pluginPackageRepository;
738         }
739 
740         Document doc = SAXReaderUtil.read(xml);
741 
742         Element root = doc.getRootElement();
743 
744         Properties settings = _readProperties(
745             root.element("settings"), "setting");
746 
747         pluginPackageRepository.setSettings(settings);
748 
749         Iterator<Element> itr1 = root.elements("plugin-package").iterator();
750 
751         while (itr1.hasNext()) {
752             Element pluginPackageEl = itr1.next();
753 
754             PluginPackage pluginPackage = _readPluginPackageXml(
755                 pluginPackageEl);
756 
757             if (!_isCurrentVersionSupported(
758                     pluginPackage.getLiferayVersions())) {
759 
760                 continue;
761             }
762 
763             Iterator<String> itr2 = pluginPackage.getTypes().iterator();
764 
765             boolean containsSupportedTypes = false;
766 
767             while (itr2.hasNext()) {
768                 String type = itr2.next();
769 
770                 if (supportedPluginTypes.contains(type)) {
771                     containsSupportedTypes = true;
772 
773                     break;
774                 }
775             }
776 
777             if (!containsSupportedTypes) {
778                 continue;
779             }
780 
781             pluginPackage.setRepository(pluginPackageRepository);
782 
783             pluginPackageRepository.addPluginPackage(pluginPackage);
784 
785             _indexPluginPackage(pluginPackage);
786         }
787 
788         return pluginPackageRepository;
789     }
790 
791     private Date _readDate(String text) {
792         if (Validator.isNotNull(text)) {
793             DateFormat dateFormat = DateFormatFactoryUtil.getSimpleDateFormat(
794                 Time.RFC822_FORMAT, Locale.US);
795 
796             try {
797                 return dateFormat.parse(text);
798             }
799             catch (Exception e) {
800                 if (_log.isWarnEnabled()) {
801                     _log.warn("Unable to parse date " + text);
802                 }
803             }
804         }
805 
806         return new Date();
807     }
808 
809     private String _readHtml(String text) {
810         return GetterUtil.getString(text);
811     }
812 
813     private List<License> _readLicenseList(Element parentEL, String name) {
814         List<License> licenses = new ArrayList<License>();
815 
816         Iterator<Element> itr = parentEL.elements(name).iterator();
817 
818         while (itr.hasNext()) {
819             Element licenseEl = itr.next();
820 
821             License license = new License();
822 
823             license.setName(licenseEl.getText());
824 
825             Attribute osiApproved = licenseEl.attribute("osi-approved");
826 
827             if (osiApproved != null) {
828                 license.setOsiApproved(
829                     GetterUtil.getBoolean(osiApproved.getText()));
830             }
831 
832             Attribute url = licenseEl.attribute("url");
833 
834             if (url != null) {
835                 license.setUrl(url.getText());
836             }
837 
838             licenses.add(license);
839         }
840 
841         return licenses;
842     }
843     private List<String> _readList(Element parentEl, String name) {
844         List<String> result = new ArrayList<String>();
845 
846         if (parentEl != null) {
847             Iterator<Element> itr = parentEl.elements(name).iterator();
848 
849             while (itr.hasNext()) {
850                 Element el = itr.next();
851 
852                 String text = el.getText().trim().toLowerCase();
853 
854                 result.add(text);
855             }
856         }
857 
858         return result;
859     }
860     private PluginPackage _readPluginPackageProperties(
861         String displayName, Properties properties) {
862 
863         int pos = displayName.indexOf("-portlet");
864 
865         String pluginType = Plugin.TYPE_PORTLET;
866 
867         if (pos == -1) {
868             pos = displayName.indexOf("-ext");
869 
870             pluginType = Plugin.TYPE_EXT;
871         }
872 
873         if (pos == -1) {
874             pos = displayName.indexOf("-hook");
875 
876             pluginType = Plugin.TYPE_HOOK;
877         }
878 
879         if (pos == -1) {
880             pos = displayName.indexOf("-layouttpl");
881 
882             pluginType = Plugin.TYPE_LAYOUT_TEMPLATE;
883         }
884 
885         if (pos == -1) {
886             pos = displayName.indexOf("-theme");
887 
888             pluginType = Plugin.TYPE_THEME;
889         }
890 
891         if (pos == -1) {
892             pos = displayName.indexOf("-web");
893 
894             pluginType = Plugin.TYPE_WEB;
895         }
896 
897         if (pos == -1) {
898             return null;
899         }
900 
901         String displayPrefix = displayName.substring(0, pos);
902 
903         String moduleGroupId = GetterUtil.getString(
904             properties.getProperty("module-group-id"));
905         String moduleArtifactId = displayPrefix + "-" + pluginType;
906 
907         String moduleVersion = null;
908 
909         int moduleVersionPos = pos + pluginType.length() + 2;
910 
911         if (displayName.length() > moduleVersionPos) {
912             moduleVersion = displayName.substring(moduleVersionPos);
913         }
914         else {
915             moduleVersion = ReleaseInfo.getVersion();
916         }
917 
918         String moduleId =
919             moduleGroupId + "/" + moduleArtifactId + "/" + moduleVersion +
920                 "/war";
921 
922         String pluginName = GetterUtil.getString(
923             properties.getProperty("name"));
924 
925         String deploymentContext = GetterUtil.getString(
926             properties.getProperty("recommended-deployment-context"),
927             moduleArtifactId);
928 
929         String author = GetterUtil.getString(properties.getProperty("author"));
930 
931         List<String> types = new ArrayList<String>();
932 
933         types.add(pluginType);
934 
935         List<License> licenses = new ArrayList<License>();
936 
937         String[] licensesArray = StringUtil.split(
938             properties.getProperty("licenses"));
939 
940         for (int i = 0; i < licensesArray.length; i++) {
941             License license = new License();
942 
943             license.setName(licensesArray[i].trim());
944             license.setOsiApproved(true);
945 
946             licenses.add(license);
947         }
948 
949         List<String> liferayVersions = new ArrayList<String>();
950 
951         String[] liferayVersionsArray = StringUtil.split(
952             properties.getProperty("liferay-versions"));
953 
954         for (String liferayVersion : liferayVersionsArray) {
955             liferayVersions.add(liferayVersion.trim());
956         }
957 
958         if (liferayVersions.size() == 0) {
959             liferayVersions.add(ReleaseInfo.getVersion() + "+");
960         }
961 
962         List<String> tags = new ArrayList<String>();
963 
964         String[] tagsArray = StringUtil.split(properties.getProperty("tags"));
965 
966         for (String tag : tagsArray) {
967             tags.add(tag.trim());
968         }
969 
970         String shortDescription = GetterUtil.getString(
971             properties.getProperty("short-description"));
972         String longDescription = GetterUtil.getString(
973             properties.getProperty("long-description"));
974         String changeLog = GetterUtil.getString(
975             properties.getProperty("change-log"));
976         String pageURL = GetterUtil.getString(
977             properties.getProperty("page-url"));
978         String downloadURL = GetterUtil.getString(
979             properties.getProperty("download-url"));
980 
981         PluginPackage pluginPackage = new PluginPackageImpl(moduleId);
982 
983         pluginPackage.setName(pluginName);
984         pluginPackage.setRecommendedDeploymentContext(deploymentContext);
985         //pluginPackage.setModifiedDate(null);
986         pluginPackage.setAuthor(author);
987         pluginPackage.setTypes(types);
988         pluginPackage.setLicenses(licenses);
989         pluginPackage.setLiferayVersions(liferayVersions);
990         pluginPackage.setTags(tags);
991         pluginPackage.setShortDescription(shortDescription);
992         pluginPackage.setLongDescription(longDescription);
993         pluginPackage.setChangeLog(changeLog);
994         //pluginPackage.setScreenshots(null);
995         pluginPackage.setPageURL(pageURL);
996         pluginPackage.setDownloadURL(downloadURL);
997         //pluginPackage.setDeploymentSettings(null);
998 
999         return pluginPackage;
1000    }
1001    private PluginPackage _readPluginPackageXml(Element pluginPackageEl) {
1002        String name = pluginPackageEl.elementText("name");
1003
1004        if (_log.isDebugEnabled()) {
1005            _log.debug("Reading pluginPackage definition " + name);
1006        }
1007
1008        PluginPackage pluginPackage = new PluginPackageImpl(
1009            GetterUtil.getString(pluginPackageEl.elementText("module-id")));
1010
1011        List<String> liferayVersions = _readList(
1012            pluginPackageEl.element("liferay-versions"), "liferay-version");
1013
1014        List<String> types = _readList(
1015            pluginPackageEl.element("types"), "type");
1016
1017        pluginPackage.setName(_readText(name));
1018        pluginPackage.setRecommendedDeploymentContext(
1019            _readText(
1020                pluginPackageEl.elementText("recommended-deployment-context")));
1021        pluginPackage.setModifiedDate(
1022            _readDate(pluginPackageEl.elementText("modified-date")));
1023        pluginPackage.setAuthor(
1024            _readText(pluginPackageEl.elementText("author")));
1025        pluginPackage.setTypes(types);
1026        pluginPackage.setLicenses(
1027            _readLicenseList(
1028                pluginPackageEl.element("licenses"), "license"));
1029        pluginPackage.setLiferayVersions(liferayVersions);
1030        pluginPackage.setTags(
1031            _readList(pluginPackageEl.element("tags"), "tag"));
1032        pluginPackage.setShortDescription(
1033            _readText(pluginPackageEl.elementText("short-description")));
1034        pluginPackage.setLongDescription(
1035            _readHtml(pluginPackageEl.elementText("long-description")));
1036        pluginPackage.setChangeLog(
1037            _readHtml(pluginPackageEl.elementText("change-log")));
1038        pluginPackage.setScreenshots(
1039            _readScreenshots(pluginPackageEl.element("screenshots")));
1040        pluginPackage.setPageURL(
1041            _readText(pluginPackageEl.elementText("page-url")));
1042        pluginPackage.setDownloadURL(
1043            _readText(pluginPackageEl.elementText("download-url")));
1044        pluginPackage.setDeploymentSettings(
1045            _readProperties(
1046                pluginPackageEl.element("deployment-settings"), "setting"));
1047
1048        return pluginPackage;
1049    }
1050    private PluginPackage _readPluginPackageXml(String xml)
1051        throws DocumentException {
1052
1053        Document doc = SAXReaderUtil.read(xml);
1054
1055        Element root = doc.getRootElement();
1056
1057        return _readPluginPackageXml(root);
1058    }
1059    private Properties _readProperties(Element parentEl, String name) {
1060        Properties result = new Properties();
1061
1062        if (parentEl != null) {
1063            Iterator<Element> itr = parentEl.elements(name).iterator();
1064
1065            while (itr.hasNext()) {
1066                Element el = itr.next();
1067
1068                result.setProperty(
1069                    el.attribute("name").getValue(),
1070                    el.attribute("value").getValue());
1071            }
1072        }
1073
1074        return result;
1075    }
1076
1077    private List<Screenshot> _readScreenshots(Element parentEl) {
1078        List<Screenshot> screenshots = new ArrayList<Screenshot>();
1079
1080        if (parentEl != null) {
1081            Iterator<Element> itr = parentEl.elements("screenshot").iterator();
1082
1083            while (itr.hasNext()) {
1084                Element screenshotEl = itr.next();
1085
1086                Screenshot screenshot = new Screenshot();
1087
1088                screenshot.setThumbnailURL(
1089                    screenshotEl.element("thumbnail-url").getText());
1090                screenshot.setLargeImageURL(
1091                    screenshotEl.element("large-image-url").getText());
1092
1093                screenshots.add(screenshot);
1094            }
1095        }
1096
1097        return screenshots;
1098    }
1099
1100    private String _readText(String text) {
1101        return HtmlUtil.extractText(GetterUtil.getString(text));
1102    }
1103
1104    private void _refreshUpdatesAvailableCache() {
1105        _updateAvailable = null;
1106    }
1107
1108    private void _registerInstalledPluginPackage(
1109        PluginPackage pluginPackage) {
1110
1111        _installedPluginPackages.addPluginPackage(pluginPackage);
1112
1113        _updateAvailable = null;
1114
1115        _indexPluginPackage(pluginPackage);
1116    }
1117
1118    private void _registerPluginPackageInstallation(
1119        String preliminaryContext) {
1120
1121        _installedPluginPackages.registerPluginPackageInstallation(
1122            preliminaryContext);
1123    }
1124
1125    private void _reIndex() throws SystemException {
1126        if (SearchEngineUtil.isIndexReadOnly()) {
1127            return;
1128        }
1129
1130        try {
1131            PluginPackageIndexer.cleanIndex();
1132
1133            for (PluginPackage pluginPackage :
1134                    _getAllAvailablePluginPackages()) {
1135
1136                String[] statusAndInstalledVersion =
1137                    _getStatusAndInstalledVersion(pluginPackage);
1138
1139                String status = statusAndInstalledVersion[0];
1140                String installedVersion = statusAndInstalledVersion[1];
1141
1142                com.liferay.portal.kernel.search.Document doc =
1143                    PluginPackageIndexer.getPluginPackageDocument(
1144                        pluginPackage.getModuleId(), pluginPackage.getName(),
1145                        pluginPackage.getVersion(),
1146                        pluginPackage.getModifiedDate(),
1147                        pluginPackage.getAuthor(), pluginPackage.getTypes(),
1148                        pluginPackage.getTags(), pluginPackage.getLicenses(),
1149                        pluginPackage.getLiferayVersions(),
1150                        pluginPackage.getShortDescription(),
1151                        pluginPackage.getLongDescription(),
1152                        pluginPackage.getChangeLog(),
1153                        pluginPackage.getPageURL(),
1154                        pluginPackage.getRepositoryURL(), status,
1155                    installedVersion);
1156
1157                SearchEngineUtil.addDocument(CompanyConstants.SYSTEM, doc);
1158            }
1159        }
1160        catch (SystemException se) {
1161            throw se;
1162        }
1163        catch (Exception e) {
1164            throw new SystemException(e);
1165        }
1166    }
1167
1168    private RepositoryReport _reloadRepositories() throws SystemException {
1169        if (_log.isInfoEnabled()) {
1170            _log.info("Reloading repositories");
1171        }
1172
1173        RepositoryReport repositoryReport = new RepositoryReport();
1174
1175        String[] repositoryURLs = _getRepositoryURLs();
1176
1177        for (int i = 0; i < repositoryURLs.length; i++) {
1178            String repositoryURL = repositoryURLs[i];
1179
1180            try {
1181                _loadRepository(repositoryURL);
1182
1183                repositoryReport.addSuccess(repositoryURL);
1184            }
1185            catch (PluginPackageException pe) {
1186                repositoryReport.addError(repositoryURL, pe);
1187
1188                _log.error(
1189                    "Unable to load repository " + repositoryURL + " " +
1190                        pe.toString());
1191            }
1192
1193        }
1194
1195        _reIndex();
1196
1197        return repositoryReport;
1198    }
1199
1200    private Hits _search(
1201            String keywords, String type, String tag, String license,
1202            String repositoryURL, String status, int start, int end)
1203        throws SystemException {
1204
1205        _checkRepositories(repositoryURL);
1206
1207        try {
1208            BooleanQuery contextQuery = BooleanQueryFactoryUtil.create();
1209
1210            contextQuery.addRequiredTerm(
1211                Field.PORTLET_ID, PluginPackageIndexer.PORTLET_ID);
1212
1213            BooleanQuery fullQuery = BooleanQueryFactoryUtil.create();
1214
1215            fullQuery.add(contextQuery, BooleanClauseOccur.MUST);
1216
1217            if (Validator.isNotNull(keywords)) {
1218                BooleanQuery searchQuery = BooleanQueryFactoryUtil.create();
1219
1220                searchQuery.addTerm(Field.TITLE, keywords);
1221                searchQuery.addTerm(Field.CONTENT, keywords);
1222
1223                fullQuery.add(searchQuery, BooleanClauseOccur.MUST);
1224            }
1225
1226            if (Validator.isNotNull(type)) {
1227                BooleanQuery searchQuery = BooleanQueryFactoryUtil.create();
1228
1229                searchQuery.addExactTerm("type", type);
1230
1231                fullQuery.add(searchQuery, BooleanClauseOccur.MUST);
1232            }
1233
1234            if (Validator.isNotNull(tag)) {
1235                BooleanQuery searchQuery = BooleanQueryFactoryUtil.create();
1236
1237                searchQuery.addExactTerm("tag", tag);
1238
1239                fullQuery.add(searchQuery, BooleanClauseOccur.MUST);
1240            }
1241
1242            if (Validator.isNotNull(repositoryURL)) {
1243                BooleanQuery searchQuery = BooleanQueryFactoryUtil.create();
1244
1245                Query query = TermQueryFactoryUtil.create(
1246                    "repositoryURL", repositoryURL);
1247
1248                searchQuery.add(query, BooleanClauseOccur.SHOULD);
1249
1250                fullQuery.add(searchQuery, BooleanClauseOccur.MUST);
1251            }
1252
1253            if (Validator.isNotNull(license)) {
1254                BooleanQuery searchQuery = BooleanQueryFactoryUtil.create();
1255
1256                searchQuery.addExactTerm("license", license);
1257
1258                fullQuery.add(searchQuery, BooleanClauseOccur.MUST);
1259            }
1260
1261            if (Validator.isNotNull(status) && !status.equals("all")) {
1262                BooleanQuery searchQuery = BooleanQueryFactoryUtil.create();
1263
1264                if (status.equals(PluginPackageImpl.
1265                        STATUS_NOT_INSTALLED_OR_OLDER_VERSION_INSTALLED)) {
1266
1267                    searchQuery.addExactTerm(
1268                        "status", PluginPackageImpl.STATUS_NOT_INSTALLED);
1269                    searchQuery.addExactTerm(
1270                        "status",
1271                        PluginPackageImpl.STATUS_OLDER_VERSION_INSTALLED);
1272                }
1273                else {
1274                    searchQuery.addExactTerm("status", status);
1275                }
1276
1277                fullQuery.add(searchQuery, BooleanClauseOccur.MUST);
1278            }
1279
1280            return SearchEngineUtil.search(
1281                CompanyConstants.SYSTEM, fullQuery, start, end);
1282        }
1283        catch (Exception e) {
1284            throw new SystemException(e);
1285        }
1286    }
1287
1288    private void _unregisterInstalledPluginPackage(
1289        PluginPackage pluginPackage) {
1290
1291        _installedPluginPackages.removePluginPackage(pluginPackage);
1292
1293        try {
1294            List<PluginPackage> pluginPackages = _getAvailablePluginPackages(
1295                pluginPackage.getGroupId(), pluginPackage.getArtifactId());
1296
1297            for (PluginPackage availablePackage : pluginPackages) {
1298                _indexPluginPackage(availablePackage);
1299            }
1300        }
1301        catch (PluginPackageException ppe) {
1302            if (_log.isWarnEnabled()) {
1303                _log.warn(
1304                    "Unable to reindex unistalled package " +
1305                        pluginPackage.getContext() + ": " + ppe.getMessage());
1306            }
1307        }
1308    }
1309
1310    private void _updateInstallingPluginPackage(
1311        String preliminaryContext, PluginPackage pluginPackage) {
1312
1313        _installedPluginPackages.unregisterPluginPackageInstallation(
1314            preliminaryContext);
1315        _installedPluginPackages.registerPluginPackageInstallation(
1316            pluginPackage);
1317    }
1318
1319    private static Log _log = LogFactoryUtil.getLog(PluginPackageUtil.class);
1320
1321    private static PluginPackageUtil _instance = new PluginPackageUtil();
1322
1323    private Set<String> _availableTagsCache;
1324    private LocalPluginPackageRepository _installedPluginPackages;
1325    private Date _lastUpdateDate;
1326    private Map<String, RemotePluginPackageRepository> _repositoryCache;
1327    private boolean _settingUpdateAvailable;
1328    private Boolean _updateAvailable;
1329
1330    private class UpdateAvailableRunner implements Runnable {
1331
1332        public void run() {
1333            try {
1334                setUpdateAvailable();
1335            }
1336            catch (Exception e) {
1337                if (_log.isWarnEnabled()) {
1338                    _log.warn(e.getMessage());
1339                }
1340            }
1341        }
1342
1343        protected void setUpdateAvailable() throws Exception {
1344            StopWatch stopWatch = null;
1345
1346            if (_log.isInfoEnabled()) {
1347                _log.info("Checking for available updates");
1348
1349                stopWatch = new StopWatch();
1350
1351                stopWatch.start();
1352            }
1353
1354            for (PluginPackage pluginPackage :
1355                    _installedPluginPackages.getPluginPackages()) {
1356
1357                PluginPackage availablePluginPackage = null;
1358
1359                if (_isIgnored(pluginPackage)) {
1360                    continue;
1361                }
1362
1363                availablePluginPackage =
1364                    PluginPackageUtil.getLatestAvailablePluginPackage(
1365                        pluginPackage.getGroupId(),
1366                        pluginPackage.getArtifactId());
1367
1368                if (availablePluginPackage == null) {
1369                    continue;
1370                }
1371
1372                Version availablePluginPackageVersion = Version.getInstance(
1373                    availablePluginPackage.getVersion());
1374
1375                if (availablePluginPackageVersion.isLaterVersionThan(
1376                        pluginPackage.getVersion())) {
1377
1378                    _updateAvailable = Boolean.TRUE;
1379
1380                    break;
1381                }
1382            }
1383
1384            if (_updateAvailable == null) {
1385                _updateAvailable = Boolean.FALSE;
1386            }
1387
1388            _settingUpdateAvailable = false;
1389
1390            if (_log.isInfoEnabled()) {
1391                _log.info(
1392                    "Finished checking for available updates in " +
1393                        stopWatch.getTime() + " ms");
1394            }
1395        }
1396    }
1397
1398}