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