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