001/*
002 * (C) Copyright 2014-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Anahide Tchertchian
018 */
019package org.nuxeo.targetplatforms.core.service;
020
021import java.util.ArrayList;
022import java.util.Comparator;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Locale;
027import java.util.Map;
028
029import org.apache.commons.lang3.StringUtils;
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.joda.time.DateTime;
033import org.joda.time.DateTimeZone;
034import org.joda.time.format.DateTimeFormat;
035import org.joda.time.format.DateTimeFormatter;
036import org.nuxeo.ecm.core.api.DocumentModel;
037import org.nuxeo.ecm.directory.BaseSession;
038import org.nuxeo.ecm.directory.Session;
039import org.nuxeo.ecm.directory.api.DirectoryService;
040import org.nuxeo.runtime.api.Framework;
041import org.nuxeo.runtime.model.ComponentContext;
042import org.nuxeo.runtime.model.ComponentInstance;
043import org.nuxeo.runtime.model.DefaultComponent;
044import org.nuxeo.targetplatforms.api.TargetInfo;
045import org.nuxeo.targetplatforms.api.TargetPackage;
046import org.nuxeo.targetplatforms.api.TargetPackageInfo;
047import org.nuxeo.targetplatforms.api.TargetPlatform;
048import org.nuxeo.targetplatforms.api.TargetPlatformFilter;
049import org.nuxeo.targetplatforms.api.TargetPlatformInfo;
050import org.nuxeo.targetplatforms.api.TargetPlatformInstance;
051import org.nuxeo.targetplatforms.api.impl.TargetPackageImpl;
052import org.nuxeo.targetplatforms.api.impl.TargetPackageInfoImpl;
053import org.nuxeo.targetplatforms.api.impl.TargetPlatformFilterImpl;
054import org.nuxeo.targetplatforms.api.impl.TargetPlatformImpl;
055import org.nuxeo.targetplatforms.api.impl.TargetPlatformInfoImpl;
056import org.nuxeo.targetplatforms.api.impl.TargetPlatformInstanceImpl;
057import org.nuxeo.targetplatforms.api.service.TargetPlatformService;
058import org.nuxeo.targetplatforms.core.descriptors.ServiceConfigurationDescriptor;
059import org.nuxeo.targetplatforms.core.descriptors.TargetPackageDescriptor;
060import org.nuxeo.targetplatforms.core.descriptors.TargetPlatformDescriptor;
061
062/**
063 * {@link TargetPlatformService} implementation relying on runtime extension points.
064 *
065 * @since 5.7.1
066 */
067public class TargetPlatformServiceImpl extends DefaultComponent implements TargetPlatformService {
068
069    private static final Log log = LogFactory.getLog(TargetPlatformServiceImpl.class);
070
071    public static final String XP_CONF = "configuration";
072
073    public static final String XP_PLATFORMS = "platforms";
074
075    public static final String XP_PACKAGES = "packages";
076
077    protected static final DateTimeFormatter dateParser = DateTimeFormat.forPattern("yyyy/MM/dd")
078                                                                        .withLocale(Locale.ENGLISH)
079                                                                        .withZone(DateTimeZone.UTC);
080
081    protected ServiceConfigurationRegistry conf;
082
083    protected TargetPlatformRegistry platforms;
084
085    protected TargetPackageRegistry packages;
086
087    // Runtime component API
088
089    @Override
090    public void activate(ComponentContext context) {
091        platforms = new TargetPlatformRegistry();
092        packages = new TargetPackageRegistry();
093        conf = new ServiceConfigurationRegistry();
094    }
095
096    @Override
097    public void deactivate(ComponentContext context) {
098        platforms = null;
099        packages = null;
100        conf = null;
101    }
102
103    @Override
104    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
105        if (XP_PLATFORMS.equals(extensionPoint)) {
106            TargetPlatformDescriptor desc = (TargetPlatformDescriptor) contribution;
107            log.info(String.format("Register target platform '%s'", desc.getId()));
108            platforms.addContribution(desc);
109        } else if (XP_PACKAGES.equals(extensionPoint)) {
110            TargetPackageDescriptor desc = (TargetPackageDescriptor) contribution;
111            log.info(String.format("Register target package '%s'", desc.getId()));
112            packages.addContribution(desc);
113        } else if (XP_CONF.equals(extensionPoint)) {
114            ServiceConfigurationDescriptor desc = (ServiceConfigurationDescriptor) contribution;
115            log.info("Register TargetPlatformService configuration");
116            conf.addContribution(desc);
117        }
118    }
119
120    @Override
121    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
122        if (XP_PLATFORMS.equals(extensionPoint)) {
123            TargetPlatformDescriptor desc = (TargetPlatformDescriptor) contribution;
124            log.info(String.format("Unregister target platform '%s'", desc.getId()));
125            platforms.removeContribution(desc);
126        } else if (XP_PACKAGES.equals(extensionPoint)) {
127            TargetPackageDescriptor desc = (TargetPackageDescriptor) contribution;
128            log.info(String.format("Unregister target package '%s'", desc.getId()));
129            packages.removeContribution(desc);
130        } else if (XP_CONF.equals(extensionPoint)) {
131            ServiceConfigurationDescriptor desc = (ServiceConfigurationDescriptor) contribution;
132            log.info("Unregister TargetPlatformService configuration");
133            conf.removeContribution(desc);
134        }
135    }
136
137    // Service API
138
139    @Override
140    public TargetPlatform getDefaultTargetPlatform(TargetPlatformFilter filter) {
141        List<TargetPlatform> tps = getAvailableTargetPlatforms(filter);
142        if (tps.isEmpty()) {
143            return null;
144        }
145        TargetPlatform defaultTP = null;
146        for (TargetPlatform tp : tps) {
147            if (tp.isDefault()) {
148                if (!tp.isRestricted()) {
149                    // Return the first default and unrestricted target platform
150                    return tp;
151                }
152                // If the target platform is restricted, we keep it in case no
153                // unrestricted target platform is found
154                if (defaultTP == null) {
155                    defaultTP = tp;
156                }
157            }
158        }
159        return defaultTP;
160    }
161
162    @Override
163    public String getOverrideDirectory() {
164        String res = DirectoryUpdater.DEFAULT_DIR;
165        ServiceConfigurationDescriptor desc = conf.getConfiguration();
166        if (desc == null) {
167            return res;
168        }
169        String id = desc.getOverrideDirectory();
170        if (!StringUtils.isBlank(id)) {
171            res = id;
172        }
173        return res;
174    }
175
176    @Override
177    public TargetPlatform getTargetPlatform(String id) {
178        if (id == null) {
179            return null;
180        }
181        TargetPlatformDescriptor desc = platforms.getTargetPlatform(id);
182        return getTargetPlatform(desc);
183    }
184
185    protected TargetPlatform getTargetPlatform(TargetPlatformDescriptor desc) {
186        if (desc == null) {
187            return null;
188        }
189        String id = desc.getId();
190        TargetPlatformImpl tp = new TargetPlatformImpl(id, desc.getName(), desc.getVersion(), desc.getRefVersion(),
191                desc.getLabel());
192        tp.setDeprecated(desc.isDeprecated());
193        tp.setDescription(desc.getDescription());
194        tp.setDownloadLink(desc.getDownloadLink());
195        tp.setEnabled(desc.isEnabled());
196        tp.setEndOfAvailability(toDate(desc.getEndOfAvailability()));
197        tp.setFastTrack(desc.isFastTrack());
198        tp.setTrial(desc.isTrial());
199        tp.setDefault(desc.isDefault());
200        tp.setParent(getTargetPlatform(desc.getParent()));
201        tp.setRefVersion(desc.getRefVersion());
202        tp.setReleaseDate(toDate(desc.getReleaseDate()));
203        tp.setRestricted(desc.isRestricted());
204        tp.setStatus(desc.getStatus());
205        tp.setTestVersions(desc.getTestVersions());
206        tp.setTypes(desc.getTypes());
207        // resolve available packages
208        tp.setAvailablePackages(getTargetPackages(id));
209
210        // check if there's an override
211        DocumentModel entry = getDirectoryEntry(id);
212        if (entry != null) {
213            Long enabled = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.ENABLED_PROP);
214            if (enabled != null && enabled.intValue() >= 0) {
215                tp.setEnabled(enabled.intValue() != 0);
216            }
217            Long restricted = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.RESTRICTED_PROP);
218            if (restricted != null && restricted.intValue() >= 0) {
219                tp.setRestricted(restricted.intValue() != 0);
220            }
221            Long deprecated = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.DEPRECATED_PROP);
222            if (deprecated != null && deprecated.intValue() >= 0) {
223                tp.setDeprecated(deprecated.intValue() != 0);
224            }
225            Long trial = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.TRIAL_PROP);
226            if (trial != null && trial.intValue() >= 0) {
227                tp.setTrial(trial.intValue() != 0);
228            }
229            Long isDefault = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.DEFAULT_PROP);
230            if (isDefault != null && isDefault.intValue() >= 0) {
231                tp.setDefault(isDefault.intValue() != 0);
232            }
233            tp.setOverridden(true);
234        }
235
236        return tp;
237    }
238
239    /**
240     * Lookup all packages referencing this target platform.
241     */
242    protected Map<String, TargetPackage> getTargetPackages(String targetPlatform) {
243        Map<String, TargetPackage> tps = new HashMap<>();
244        List<TargetPackageDescriptor> pkgs = packages.getTargetPackages(targetPlatform);
245        if (pkgs != null) {
246            for (TargetPackageDescriptor pkg : pkgs) {
247                TargetPackage tp = getTargetPackage(pkg);
248                if (tp != null) {
249                    tps.put(tp.getId(), tp);
250                }
251            }
252        }
253        return tps;
254    }
255
256    protected Map<String, TargetPackageInfo> getTargetPackagesInfo(String targetPlatform) {
257        Map<String, TargetPackageInfo> tps = new HashMap<>();
258        List<TargetPackageDescriptor> pkgs = packages.getTargetPackages(targetPlatform);
259        if (pkgs != null) {
260            for (TargetPackageDescriptor pkg : pkgs) {
261                TargetPackageInfo tp = getTargetPackageInfo(pkg.getId());
262                if (tp != null) {
263                    tps.put(tp.getId(), tp);
264                }
265            }
266        }
267        return tps;
268    }
269
270    protected Date toDate(String date) {
271        if (StringUtils.isBlank(date)) {
272            return null;
273        }
274        DateTime dt = dateParser.parseDateTime(date);
275        return dt.toDate();
276    }
277
278    @Override
279    public TargetPlatformInfo getTargetPlatformInfo(String id) {
280        if (id == null) {
281            return null;
282        }
283        TargetPlatformDescriptor desc = platforms.getTargetPlatform(id);
284        return getTargetPlatformInfo(desc);
285    }
286
287    protected TargetPlatformInfo getTargetPlatformInfo(TargetPlatformDescriptor desc) {
288        if (desc == null) {
289            return null;
290        }
291        String id = desc.getId();
292        TargetPlatformInfoImpl tpi = new TargetPlatformInfoImpl(id, desc.getName(), desc.getVersion(),
293                desc.getRefVersion(), desc.getLabel());
294        tpi.setDescription(desc.getDescription());
295        tpi.setStatus(desc.getStatus());
296        tpi.setEnabled(desc.isEnabled());
297        tpi.setFastTrack(desc.isFastTrack());
298        tpi.setReleaseDate(toDate(desc.getReleaseDate()));
299        tpi.setRestricted(desc.isRestricted());
300        tpi.setEndOfAvailability(toDate(desc.getEndOfAvailability()));
301        tpi.setDownloadLink(desc.getDownloadLink());
302        tpi.setDeprecated(desc.isDeprecated());
303        tpi.setAvailablePackagesInfo(getTargetPackagesInfo(id));
304        tpi.setTypes(desc.getTypes());
305        tpi.setTrial(desc.isTrial());
306        tpi.setDefault(desc.isDefault());
307
308        DocumentModel entry = getDirectoryEntry(id);
309        if (entry != null) {
310            Long enabled = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.ENABLED_PROP);
311            if (enabled != null && enabled.intValue() >= 0) {
312                tpi.setEnabled(enabled.intValue() != 0);
313            }
314            Long restricted = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.RESTRICTED_PROP);
315            if (restricted != null && restricted.intValue() >= 0) {
316                tpi.setRestricted(restricted.intValue() != 0);
317            }
318            Long deprecated = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.DEPRECATED_PROP);
319            if (deprecated != null && deprecated.intValue() >= 0) {
320                tpi.setDeprecated(deprecated.intValue() != 0);
321            }
322            Long trial = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.TRIAL_PROP);
323            if (trial != null && trial.intValue() >= 0) {
324                tpi.setTrial(trial.intValue() != 0);
325            }
326            Long isDefault = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.DEFAULT_PROP);
327            if (isDefault != null && isDefault.intValue() >= 0) {
328                tpi.setDefault(isDefault.intValue() != 0);
329            }
330            tpi.setOverridden(true);
331        }
332
333        return tpi;
334    }
335
336    @Override
337    public TargetPackage getTargetPackage(String id) {
338        if (id == null) {
339            return null;
340        }
341        return getTargetPackage(packages.getTargetPackage(id));
342    }
343
344    @Override
345    public TargetPackageInfo getTargetPackageInfo(String id) {
346        if (id == null) {
347            return null;
348        }
349        TargetPackageDescriptor desc = packages.getTargetPackage(id);
350        TargetPackageInfoImpl tpi = new TargetPackageInfoImpl(desc.getId(), desc.getName(), desc.getVersion(),
351                desc.getRefVersion(), desc.getLabel());
352        tpi.setDescription(desc.getDescription());
353        tpi.setStatus(desc.getStatus());
354        tpi.setEnabled(desc.isEnabled());
355        tpi.setReleaseDate(toDate(desc.getReleaseDate()));
356        tpi.setRestricted(desc.isRestricted());
357        tpi.setEndOfAvailability(toDate(desc.getEndOfAvailability()));
358        tpi.setDownloadLink(desc.getDownloadLink());
359        tpi.setDeprecated(desc.isDeprecated());
360        tpi.setDependencies(desc.getDependencies());
361        return tpi;
362    }
363
364    protected TargetPackage getTargetPackage(TargetPackageDescriptor desc) {
365        if (desc == null) {
366            return null;
367        }
368        TargetPackageImpl tp = new TargetPackageImpl(desc.getId(), desc.getName(), desc.getVersion(),
369                desc.getRefVersion(), desc.getLabel());
370        tp.setDependencies(desc.getDependencies());
371        tp.setDeprecated(desc.isDeprecated());
372        tp.setDescription(desc.getDescription());
373        tp.setDownloadLink(desc.getDownloadLink());
374        tp.setEnabled(desc.isEnabled());
375        tp.setEndOfAvailability(toDate(desc.getEndOfAvailability()));
376        tp.setParent(getTargetPackage(desc.getParent()));
377        tp.setRefVersion(desc.getRefVersion());
378        tp.setReleaseDate(toDate(desc.getReleaseDate()));
379        tp.setRestricted(desc.isRestricted());
380        tp.setStatus(desc.getStatus());
381        tp.setTypes(desc.getTypes());
382        return tp;
383    }
384
385    @Override
386    public TargetPlatformInstance getTargetPlatformInstance(String id, List<String> packages) {
387        if (id == null) {
388            return null;
389        }
390
391        TargetPlatformInstanceImpl tpi = createTargetPlatformInstanceFromId(id);
392
393        if (packages != null) {
394            for (String pkg : packages) {
395                TargetPackage tpkg = getTargetPackage(pkg);
396                if (tpkg != null) {
397                    tpi.addEnabledPackage(tpkg);
398                } else {
399                    log.warn(String.format("Referenced target package '%s' not found.", pkg));
400                }
401            }
402        }
403
404        return tpi;
405    }
406
407    @Override
408    public List<TargetPlatform> getAvailableTargetPlatforms(TargetPlatformFilter filter) {
409        List<TargetPlatform> tps = new ArrayList<>();
410        for (TargetPlatformDescriptor desc : platforms.getTargetPlatforms()) {
411            TargetPlatform tp = getTargetPlatform(desc);
412            if (tp == null) {
413                continue;
414            }
415            if (filter != null && !filter.accepts(tp)) {
416                continue;
417            }
418            tps.add(tp);
419        }
420        // always sort for a deterministic result
421        tps.sort(Comparator.comparing(TargetInfo::getId));
422        return tps;
423    }
424
425    @Override
426    public List<TargetPlatformInfo> getAvailableTargetPlatformsInfo(TargetPlatformFilter filter) {
427        List<TargetPlatformInfo> tps = new ArrayList<>();
428        for (TargetPlatformDescriptor desc : platforms.getTargetPlatforms()) {
429            TargetPlatformInfo tp = getTargetPlatformInfo(desc);
430            if (tp == null) {
431                continue;
432            }
433            if (filter != null && !filter.accepts(tp)) {
434                continue;
435            }
436            tps.add(tp);
437        }
438        tps.sort(Comparator.comparing(TargetInfo::getId));
439        return tps;
440    }
441
442    @Override
443    public void deprecateTargetPlatform(boolean deprecate, final String id) {
444        Integer val = deprecate ? Integer.valueOf(1) : Integer.valueOf(0);
445        updateOrCreateEntry(id, DirectoryUpdater.DEPRECATED_PROP, val);
446    }
447
448    @Override
449    public void enableTargetPlatform(boolean enable, final String id) {
450        Integer val = enable ? Integer.valueOf(1) : Integer.valueOf(0);
451        updateOrCreateEntry(id, DirectoryUpdater.ENABLED_PROP, val);
452    }
453
454    @Override
455    public void restrictTargetPlatform(boolean restrict, final String id) {
456        Integer val = restrict ? Integer.valueOf(1) : Integer.valueOf(0);
457        updateOrCreateEntry(id, DirectoryUpdater.RESTRICTED_PROP, val);
458    }
459
460    @Override
461    public void setTrialTargetPlatform(boolean trial, final String id) {
462        Integer val = trial ? Integer.valueOf(1) : Integer.valueOf(0);
463        updateOrCreateEntry(id, DirectoryUpdater.TRIAL_PROP, val);
464    }
465
466    @Override
467    public void setDefaultTargetPlatform(boolean isDefault, final String id) {
468        Integer val = isDefault ? Integer.valueOf(1) : Integer.valueOf(0);
469        updateOrCreateEntry(id, DirectoryUpdater.DEFAULT_PROP, val);
470    }
471
472    @Override
473    public void restoreTargetPlatform(final String id) {
474        new DirectoryUpdater(getOverrideDirectory()) {
475            @Override
476            public void run(DirectoryService service, Session session) {
477                session.deleteEntry(id);
478            }
479        }.run();
480    }
481
482    @Override
483    public void restoreAllTargetPlatforms() {
484        new DirectoryUpdater(getOverrideDirectory()) {
485            @Override
486            public void run(DirectoryService service, Session session) {
487                for (DocumentModel entry : session.getEntries()) {
488                    session.deleteEntry(entry.getId());
489                }
490            }
491        }.run();
492    }
493
494    protected void updateOrCreateEntry(final String id, final String prop, final Integer value) {
495        new DirectoryUpdater(getOverrideDirectory()) {
496            @Override
497            public void run(DirectoryService service, Session session) {
498                DocumentModel doc = session.getEntry(id);
499                if (doc != null) {
500                    doc.setProperty(DirectoryUpdater.SCHEMA, prop, value);
501                    session.updateEntry(doc);
502                } else {
503                    DocumentModel entry = BaseSession.createEntryModel(null, DirectoryUpdater.SCHEMA, null, null);
504                    entry.setProperty(DirectoryUpdater.SCHEMA, prop, value);
505                    entry.setProperty(DirectoryUpdater.SCHEMA, "id", id);
506                    session.createEntry(entry);
507                }
508            }
509        }.run();
510    }
511
512    protected DocumentModel getDirectoryEntry(String id) {
513        Session dirSession = null;
514        try {
515            // check if entry already exists
516            DirectoryService dirService = Framework.getService(DirectoryService.class);
517            String dirName = getOverrideDirectory();
518            dirSession = dirService.open(dirName);
519            return dirSession.getEntry(id);
520        } finally {
521            if (dirSession != null) {
522                dirSession.close();
523            }
524        }
525    }
526
527    @Override
528    public TargetPlatformInstance getDefaultTargetPlatformInstance(boolean restricted) {
529        TargetPlatformInstance tpi = null;
530        TargetPlatformFilterImpl filter = new TargetPlatformFilterImpl();
531        filter.setFilterRestricted(restricted);
532        TargetPlatform defaultTP = getDefaultTargetPlatform(filter);
533        if (defaultTP != null) {
534            tpi = createTargetPlatformInstanceFromId(defaultTP.getId());
535        }
536
537        return tpi;
538    }
539
540    /**
541     * Create a TargetPlatformInstance given an id.
542     *
543     * @since 5.9.3-NXP-15602
544     */
545    protected TargetPlatformInstanceImpl createTargetPlatformInstanceFromId(String id) {
546        TargetPlatformDescriptor desc = platforms.getTargetPlatform(id);
547        if (desc == null) {
548            return null;
549        }
550        TargetPlatformInstanceImpl tpi = new TargetPlatformInstanceImpl(id, desc.getName(), desc.getVersion(),
551                desc.getRefVersion(), desc.getLabel());
552        tpi.setDeprecated(desc.isDeprecated());
553        tpi.setDescription(desc.getDescription());
554        tpi.setDownloadLink(desc.getDownloadLink());
555        tpi.setEnabled(desc.isEnabled());
556        tpi.setEndOfAvailability(toDate(desc.getEndOfAvailability()));
557        tpi.setFastTrack(desc.isFastTrack());
558        tpi.setParent(getTargetPlatform(desc.getParent()));
559        tpi.setRefVersion(desc.getRefVersion());
560        tpi.setReleaseDate(toDate(desc.getReleaseDate()));
561        tpi.setRestricted(desc.isRestricted());
562        tpi.setStatus(desc.getStatus());
563        tpi.setTypes(desc.getTypes());
564
565        DocumentModel entry = getDirectoryEntry(id);
566        if (entry != null) {
567            Long enabled = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.ENABLED_PROP);
568            if (enabled != null && enabled.intValue() >= 0) {
569                tpi.setEnabled(enabled.intValue() != 0);
570            }
571            Long restricted = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.RESTRICTED_PROP);
572            if (restricted != null && restricted.intValue() >= 0) {
573                tpi.setRestricted(restricted.intValue() != 0);
574            }
575            Long deprecated = (Long) entry.getProperty(DirectoryUpdater.SCHEMA, DirectoryUpdater.DEPRECATED_PROP);
576            if (deprecated != null && deprecated.intValue() >= 0) {
577                tpi.setDeprecated(deprecated.intValue() != 0);
578            }
579            tpi.setOverridden(true);
580        }
581
582        return tpi;
583    }
584}