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