All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.autoupdate.services.OperationContainerImpl Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.autoupdate.services;

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.autoupdate.*;
import org.netbeans.api.autoupdate.OperationContainer.OperationInfo;
import org.openide.modules.Dependency;
import org.openide.modules.ModuleInfo;

/**
 *
 * @author Radek Matous, Jiri Rechtacek
 */
public final class OperationContainerImpl {
    private boolean upToDate = false;
    private File unpack200;
    private OperationContainerImpl () {}
    public static final Logger LOGGER = Logger.getLogger (OperationContainerImpl.class.getName ());    
    private final List> operations = new CopyOnWriteArrayList>();
    private Throwable lastModified;
    private final Collection> affectedEagers = new HashSet> ();
    public static OperationContainerImpl createForInstall () {
        return new OperationContainerImpl (OperationType.INSTALL);
    }
    public static OperationContainerImpl createForInternalUpdate () {
        return new OperationContainerImpl (OperationType.INTERNAL_UPDATE);
    }
    public static OperationContainerImpl createForUpdate () {
        return new OperationContainerImpl (OperationType.UPDATE);
    }
    public static OperationContainerImpl createForDirectInstall() {
        OperationContainerImpl impl = new OperationContainerImpl(OperationType.INSTALL);
        impl.delegate = OperationContainer.createForUpdate();
        return impl;
    }

    public static OperationContainerImpl createForDirectUpdate() {
        OperationContainerImpl impl = new OperationContainerImpl(OperationType.UPDATE);
        impl.delegate = OperationContainer.createForUpdate();
        return impl;
    }
    public static OperationContainerImpl createForUninstall () {
        return new OperationContainerImpl (OperationType.UNINSTALL);
    }
    public static OperationContainerImpl createForDirectUninstall () {
        return new OperationContainerImpl (OperationType.DIRECT_UNINSTALL);
    }
    public static OperationContainerImpl createForEnable () {
        return new OperationContainerImpl (OperationType.ENABLE);
    }
    public static OperationContainerImpl createForDisable () {
        return new OperationContainerImpl (OperationType.DISABLE);
    }
    public static OperationContainerImpl createForDirectDisable () {
        return new OperationContainerImpl (OperationType.DIRECT_DISABLE);
    }
    public static OperationContainerImpl createForInstallNativeComponent () {
        return new OperationContainerImpl (OperationType.CUSTOM_INSTALL);
    }
    public static OperationContainerImpl createForUninstallNativeComponent () {
        return new OperationContainerImpl (OperationType.CUSTOM_UNINSTALL);
    }
    @SuppressWarnings({"unchecked"})
    public OperationInfo add (UpdateUnit updateUnit, UpdateElement updateElement) throws IllegalArgumentException {
        OperationInfo retval = null;
        boolean isValid = isValid (updateUnit, updateElement);
        if (UpdateUnitFactory.getDefault().isScheduledForRestart (updateElement)) {
            LOGGER.log (Level.INFO, updateElement + " is scheduled for restart IDE.");
            throw new IllegalArgumentException (updateElement + " is scheduled for restart IDE.");
        }
        if (!isValid) {
            throw new IllegalArgumentException("Invalid " + updateUnit + " for operation " + type);
        }
        if (isValid) {
            switch (type) {
            case UNINSTALL :
            case DIRECT_UNINSTALL :
            case CUSTOM_UNINSTALL :
            case ENABLE :
            case DISABLE :
            case DIRECT_DISABLE :
                if (updateUnit.getInstalled () != updateElement) {
                    throw new IllegalArgumentException (updateUnit.getInstalled () +
                            " and " + updateElement + " must be same for operation " + type);
                }
                break;
            case INSTALL :
            case UPDATE :
            case CUSTOM_INSTALL:
                if (updateUnit.getInstalled () == updateElement) {
                    throw new IllegalArgumentException (updateUnit.getInstalled () +
                            " and " + updateElement + " cannot be same for operation " + type);
                }
                break;
            case INTERNAL_UPDATE:
                /*
                if (updateUnit.getInstalled () != updateElement) {
                    throw new IllegalArgumentException (updateUnit.getInstalled () +
                            " and " + updateElement + " must be same for operation " + type);
                }*/
                break;
            default:
                assert false : "Unknown type of operation " + type;
            }
        }
        synchronized(this) {
            if (!contains (updateUnit, updateElement)) {
                retval = Trampoline.API.createOperationInfo (new OperationInfoImpl (updateUnit, updateElement));
                assert retval != null : "Null support for " + updateUnit + " and " + updateElement;
                changeState (operations.add (retval));
                boolean asserts = false;
                assert asserts = true;
                if (asserts) {
                    lastModified = new Exception("Added operation: " + retval);
                }
            }
        }
        return retval;
    }
    public boolean remove (UpdateElement updateElement) {
        OperationInfo toRemove = find (updateElement);
        if (toRemove != null) {
            remove (toRemove);
        }
        return toRemove != null;
    }
    
    public boolean contains (UpdateElement updateElement) {
        return find (updateElement) != null;
    }
    
    private OperationInfo find (UpdateElement updateElement) {
        OperationInfo toRemove = null;
        for (OperationInfo info : listAll ()) {
            if (info.getUpdateElement ().equals (updateElement)) {
                toRemove = info;
                break;
            }
        }
        return toRemove;
    }
    
    private boolean contains (UpdateUnit unit, UpdateElement element) {
        List> infos = operations;
        for (OperationInfo info : infos) {
            if (info.getUpdateElement ().equals (element) ||
                    info.getUpdateUnit ().equals (unit)) {
                return true;
            }
        }
        return false;
    }
    
    private List> listAll () {
        return Collections.unmodifiableList(operations);
    }
    
    public synchronized List> listAllWithPossibleEager () {
        if (upToDate) {
            return listAll();
        }
            
        clearCache ();

        //if operations contains only first class modules - don`t search for eagers.
        boolean checkEagers = false;
        for (OperationInfo i : operations) {
            if(!Utilities.isFirstClassModule(i.getUpdateElement())) {
               checkEagers = true;
               break;
            }
        }
        // handle eager modules

        if ((type == OperationType.INSTALL || type == OperationType.UPDATE || type==OperationType.INTERNAL_UPDATE) && checkEagers) {
            Collection all = new HashSet (operations.size ());
            for (OperationInfo i : operations) {
                all.add(i.getUpdateElement());
            }
            for (OperationInfo i : operations) {
                all.addAll(i.getRequiredElements());
            }
            // TODO: fragment modules are somewhat eager: they need to enable with their hosting module. They are not handled now,
            // so unless they are also eager, they won't be autoincluded.
            for (UpdateElement eagerEl : UpdateManagerImpl.getInstance ().getAvailableEagers ()) {
                if(eagerEl.getUpdateUnit().isPending() || eagerEl.getUpdateUnit().getAvailableUpdates().isEmpty()) {
                    continue;
                }
                UpdateElementImpl impl = Trampoline.API.impl (eagerEl);
                List  infos = new ArrayList ();
                if(impl instanceof ModuleUpdateElementImpl) {
                    ModuleUpdateElementImpl eagerImpl = (ModuleUpdateElementImpl) impl;
                    infos.add(eagerImpl.getModuleInfo ());
                } else if (impl instanceof FeatureUpdateElementImpl) {
                    FeatureUpdateElementImpl eagerImpl = (FeatureUpdateElementImpl) impl;
                    infos.addAll(eagerImpl.getModuleInfos ());
                } else {
                    assert false : eagerEl + " must instanceof ModuleUpdateElementImpl or FeatureUpdateElementImpl";
                }

                for(ModuleInfo mi: infos) {
                    Set reqs = new HashSet ();
                    for (Dependency dep : mi.getDependencies ()) {
                        Collection requestedElements = Utilities.handleDependency (eagerEl, dep, Collections.singleton (mi), new HashSet (), 
                                type == OperationType.UPDATE || type == OperationType.INTERNAL_UPDATE);
                        if (requestedElements != null) {
                            for (UpdateElement req : requestedElements) {
                                reqs.add (req);
                            }
                        }
                    }
                    if ((! reqs.isEmpty() && all.containsAll(reqs) && ! all.contains (eagerEl)) ||
                            (reqs.isEmpty() && impl.getUpdateUnit().getInstalled()!=null && type == OperationType.UPDATE && operations.size() > 0)) {
                        // adds affectedEager into list of elements for the operation
                        OperationInfo i = null;
                        try {
                            if(impl instanceof ModuleUpdateElementImpl) {
                                i = add (eagerEl.getUpdateUnit (), eagerEl);
                            } else if (impl instanceof FeatureUpdateElementImpl) {
                                FeatureUpdateElementImpl eagerImpl = (FeatureUpdateElementImpl) impl;
                                for (UpdateElementImpl contained : eagerImpl.getContainedModuleElements()) {
                                    if (contained.isEager()) {
                                        i = add (contained.getUpdateUnit (), contained.getUpdateElement());
                                    }
                                }
                            }
                        } catch (IllegalArgumentException e) {
                            //investigate the reason of 172220, 171975, 169588
                            boolean firstCondition = (! reqs.isEmpty() && all.containsAll (reqs) && ! all.contains (eagerEl));
                            boolean secondCondition = reqs.isEmpty() && impl.getUpdateUnit().getInstalled()!=null && type == OperationType.UPDATE && operations.size() > 0;
                            StringBuilder sb = new StringBuilder();
                            sb.append("\nIAE while adding eager element to the ").append(type).append(" container\n");
                            sb.append("\nEager: ").append(eagerEl);
                            sb.append("\nFirst condition : ").append(firstCondition);
                            sb.append("\nSecond condition : ").append(secondCondition);
                            sb.append("\nInstalled: ").append(impl.getUpdateUnit().getInstalled());
                            sb.append("\nPending: ").append(impl.getUpdateUnit().isPending());
                            sb.append("\nreqs: ").append(reqs).append(" (total : ").append(reqs.size()).append(")");
                            sb.append("\nall: ").append(all).append(" (total : ").append(all.size()).append(")");                            
                            sb.append("\noperation: ").append(operations).append(" (total: ").append(operations.size());
                            sb.append("\neager available updates: ").append(eagerEl.getUpdateUnit().getAvailableUpdates());
                            sb.append("\nUpdateElements in operations:");
                            for (OperationInfo op : operations) {
                                sb.append("\n  ").append(op.getUpdateElement());
                            }
                            sb.append("\nUpdateElements in all:");
                            for (UpdateElement elem : all) {
                                sb.append("\n  ").append(elem);
                            }
                            sb.append("\n");
                            LOGGER.log(Level.INFO, sb.toString(), e);
                            throw e;
                        }
                        if (i != null) {
                            affectedEagers.add (i);
                        }
                    }
                }
            }
        }
        if (LOGGER.isLoggable (Level.FINE)) {
            LOGGER.log (Level.FINE, "== do listAllWithPossibleEager for " + type + " operation ==");
            for (OperationInfo info : operations) {
                LOGGER.log (Level.FINE, "--> " + info.getUpdateElement ());
            }
            if (affectedEagers != null) {
                LOGGER.log (Level.FINE, "   == includes affected eagers for " + type + " operation ==");
                for (OperationInfo eagerInfo : affectedEagers) {
                    LOGGER.log (Level.FINE, "   --> " + eagerInfo.getUpdateElement ());
                }
                LOGGER.log (Level.FINE, "   == done eagers. ==");
            }
            LOGGER.log (Level.FINE, "== done. ==");
        }
        upToDate = true;
        return listAll();
    }
    
    public List> listInvalid () {
        List> retval = new ArrayList>();
        List> infos = listAll ();
        for (OperationInfo oii: infos) {
            // find type of operation
            // differ primary element and required elements
            // primary use-case can be Install but could required update of other elements
            if (!isValid (oii.getUpdateUnit (), oii.getUpdateElement ())) {
                retval.add (oii);
            }
        }
        return retval;
    }
    
    public boolean isValid (UpdateUnit updateUnit, UpdateElement updateElement) {
        if (updateElement == null) {
            throw new IllegalArgumentException ("UpdateElement cannot be null for UpdateUnit " + updateUnit);
        } else if (updateUnit == null) {
            throw new IllegalArgumentException ("UpdateUnit cannot be null for UpdateElement " + updateElement);
        }
        boolean isValid;
        switch (type) {
        case INSTALL :
            isValid = OperationValidator.isValidOperation (type, updateUnit, updateElement);
            // at least first add must pass and respect type of operation
            if (! isValid && operations.size () > 0) {
                // try Update
                isValid = OperationValidator.isValidOperation (OperationType.UPDATE, updateUnit, updateElement);
            }
            break;
        case UPDATE :
            isValid = OperationValidator.isValidOperation (type, updateUnit, updateElement);
            // at least first add must pass and respect type of operation
            if (! isValid && operations.size () > 0) {
                // try Update
                isValid = OperationValidator.isValidOperation (OperationType.INSTALL, updateUnit, updateElement);
            }
            break;
        case INTERNAL_UPDATE:
            isValid = OperationValidator.isValidOperation (type, updateUnit, updateElement);
            // at least first add must pass and respect type of operation
            if (! isValid && operations.size () > 0) {
                // try Update
                isValid = OperationValidator.isValidOperation (OperationType.UPDATE, updateUnit, updateElement);
            }
            if (! isValid && operations.size () > 0) {
                // try Install
                isValid = OperationValidator.isValidOperation (OperationType.INSTALL, updateUnit, updateElement);
            }
            break;

        default:
            isValid = OperationValidator.isValidOperation (type, updateUnit, updateElement);
        }
        
        return isValid;
    }
    
    public synchronized void remove (OperationInfo op) {
        synchronized(this) {
            changeState (operations.remove (op));
            changeState (operations.removeAll (affectedEagers));
            affectedEagers.clear ();
            boolean asserts = false;
            assert asserts = true;
            if (asserts) {
                lastModified = new Exception("Removed " + op); // NOI18N
            }
        }
    }
    public synchronized void removeAll () {
        synchronized(this) {
            changeState (true);
            operations.clear ();
            affectedEagers.clear ();
            boolean asserts = false;
            assert asserts = true;
            if (asserts) {
                lastModified = new Exception("Removed all"); // NOI18N
            }
        }
    }

    @Override
    public String toString() {
        StringWriter sb = new StringWriter();
        PrintWriter pw = new PrintWriter(sb);
        pw.print(super.toString());
        if (lastModified != null) {
            pw.println();
            lastModified.printStackTrace(pw);
        }
        pw.flush();
        return sb.toString();
    }
    
    private void clearCache () {
        OperationValidator.clearMaps ();
    }
    
    private void changeState (boolean changed) {
        if (changed) {
            clearCache ();
        }
        upToDate = upToDate && ! changed;
    }
    
    public class OperationInfoImpl {
        private final UpdateElement updateElement;
        private final UpdateUnit uUnit;
        private Set brokenDeps = null;
        private OperationInfoImpl (UpdateUnit uUnit, UpdateElement updateElement) {
            this.updateElement = updateElement;
            this.uUnit = uUnit;
        }
        public UpdateElement/*or null*/ getUpdateElement () {
            return updateElement;
        }
        public UpdateUnit/*or null*/ getUpdateUnit () {
            return uUnit;
        }
        private List requiredElements;
        public List getRequiredElements (){
            if (upToDate && requiredElements != null) {
                return requiredElements;
            }
            List moduleInfos = new ArrayList();
            for (OperationContainer.OperationInfo oii : listAll ()) {
                UpdateElementImpl impl = Trampoline.API.impl (oii.getUpdateElement ());
                List infos = impl.getModuleInfos ();
                assert infos != null : "ModuleInfo for UpdateElement " + oii.getUpdateElement () + " found.";
                moduleInfos.addAll (infos);
            }
            brokenDeps = new HashSet ();
            Set recommeded = new HashSet();
            requiredElements = OperationValidator.getRequiredElements (type, getUpdateElement (), moduleInfos, brokenDeps, recommeded);
            if (! brokenDeps.isEmpty() && ! recommeded.isEmpty()) {
                brokenDeps = new HashSet ();
                requiredElements = OperationValidator.getRequiredElements (type, getUpdateElement (), moduleInfos, brokenDeps, recommeded);
            }
            return requiredElements;
        }
        
        public Set getBrokenDependencies () {
            if (! upToDate) {
                brokenDeps = null;
            }
            if (brokenDeps != null) {
                return brokenDeps;
            }
            // shortcut, because OperationValidator.getRequiredElements is NOT consistent with
            // OperationValidator.getBrokenDependencies: while getRequiredElements ignores breakages of 
            // feature contents, getBrokenDependencies wille verify them in full, so feature's optional (platform-specific) module
            // will report the whole feature as broken on activation or install.
            if (requiredElements != null) {
                getRequiredElements();
                if (brokenDeps != null) {
                    return brokenDeps;
                }
            }
            List moduleInfos = new ArrayList();
            Set brokenContents = new HashSet<>();
            for (OperationContainer.OperationInfo oii : listAll ()) {
                UpdateElementImpl impl = Trampoline.API.impl (oii.getUpdateElement ());
                Collection infos = impl.getModuleInfos ();
                assert infos != null : "ModuleInfo for UpdateElement " + oii.getUpdateElement () + " found.";
                moduleInfos.addAll (infos);
                if (impl.getType() == UpdateManager.TYPE.FEATURE) {
                    // add unknown tokens / codenames as broken content.
                    brokenContents.addAll(((FeatureUpdateElementImpl)impl).getMissingElements());
                }
            }
            brokenContents.addAll(OperationValidator.getBrokenDependencies (type, getUpdateElement (), moduleInfos));
            return brokenContents;
        }
        
        /**
         * Reports missing parts of the feature.
         * @return missing/unknown directly contained codenames
         */
        public Set getMissingParts() {
            Set brokenContents = new HashSet<>();
            for (OperationContainer.OperationInfo oii : listAll ()) {
                UpdateElementImpl impl = Trampoline.API.impl (oii.getUpdateElement ());
                if (impl instanceof FeatureUpdateElementImpl) {
                    brokenContents.addAll(((FeatureUpdateElementImpl)impl).getMissingElements());
                }
            }
            return brokenContents;
        }
    }
    
    /** Creates a new instance of OperationContainer */
    private OperationContainerImpl (OperationType type) {
        this.type = type;
    }
        
    public OperationType getType () {
        return type;
    }
    
    public static enum OperationType {
        /** Install UpdateElement */
        INSTALL,
        /** Uninstall UpdateElement */
        UNINSTALL,
        /** Internally update installed UpdateElement without version increase */
        INTERNAL_UPDATE,
        /** Uninstall UpdateElement on-the-fly */
        DIRECT_UNINSTALL,
        /** Update installed UpdateElement to newer version. */
        UPDATE,
        /** Rollback installed UpdateElement to previous version. */
        REVERT,
        /** Enable UpdateElement */
        ENABLE,
        /** Disable UpdateElement */
        DIRECT_DISABLE,
        /** Disable UpdateElement on-the-fly */
        DISABLE,
        /** Install UpdateElement with custom installer. */
        CUSTOM_INSTALL,
        /** Uninstall UpdateElement with custom installer. */
        CUSTOM_UNINSTALL
    }
    private OperationType type;
    private OperationContainer delegate;

    /**
     * @return the unpack200 executable or {@code null}
     */
    public final File getUnpack200() {
        NO_PACK: if (unpack200 == null) {
            final String jreHome = System.getProperty("java.home"); // NOI18N
            if (jreHome == null) {
                break NO_PACK;
            }
            File javaHome = new File(jreHome);
            File pack200ux = new File(new File(javaHome, "bin"), "unpack200"); // NOI18N
            if (pack200ux.canExecute()) {
                return pack200ux;
            }
            File pack200exe = new File(new File(javaHome, "bin"), "unpack200.exe"); // NOI18N
            if (pack200exe.canExecute()) {
                return pack200exe;
            }
        }
        return unpack200;
    }

    /**
     * @param pack200 the pack200 to set
     */
    public final void setUnpack200(File pack200) {
        this.unpack200 = pack200;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy