blob: 6cbe539b7ab28c03f1575d755670b4a1e5598334 [file] [log] [blame]
/*
* 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.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<Support> {
private boolean upToDate = false;
private OperationContainerImpl () {}
public static final Logger LOGGER = Logger.getLogger (OperationContainerImpl.class.getName ());
private final List<OperationInfo<Support>> operations = new CopyOnWriteArrayList<OperationInfo<Support>>();
private Throwable lastModified;
private final Collection<OperationInfo<Support>> affectedEagers = new HashSet<OperationInfo<Support>> ();
public static OperationContainerImpl<InstallSupport> createForInstall () {
return new OperationContainerImpl<InstallSupport> (OperationType.INSTALL);
}
public static OperationContainerImpl<InstallSupport> createForInternalUpdate () {
return new OperationContainerImpl<InstallSupport> (OperationType.INTERNAL_UPDATE);
}
public static OperationContainerImpl<InstallSupport> createForUpdate () {
return new OperationContainerImpl<InstallSupport> (OperationType.UPDATE);
}
public static OperationContainerImpl<OperationSupport> createForDirectInstall() {
OperationContainerImpl<OperationSupport> impl = new OperationContainerImpl<OperationSupport>(OperationType.INSTALL);
impl.delegate = OperationContainer.createForUpdate();
return impl;
}
public static OperationContainerImpl<OperationSupport> createForDirectUpdate() {
OperationContainerImpl<OperationSupport> impl = new OperationContainerImpl<OperationSupport>(OperationType.UPDATE);
impl.delegate = OperationContainer.createForUpdate();
return impl;
}
public static OperationContainerImpl<OperationSupport> createForUninstall () {
return new OperationContainerImpl<OperationSupport> (OperationType.UNINSTALL);
}
public static OperationContainerImpl<OperationSupport> createForDirectUninstall () {
return new OperationContainerImpl<OperationSupport> (OperationType.DIRECT_UNINSTALL);
}
public static OperationContainerImpl<OperationSupport> createForEnable () {
return new OperationContainerImpl<OperationSupport> (OperationType.ENABLE);
}
public static OperationContainerImpl<OperationSupport> createForDisable () {
return new OperationContainerImpl<OperationSupport> (OperationType.DISABLE);
}
public static OperationContainerImpl<OperationSupport> createForDirectDisable () {
return new OperationContainerImpl<OperationSupport> (OperationType.DIRECT_DISABLE);
}
public static OperationContainerImpl<OperationSupport> createForInstallNativeComponent () {
return new OperationContainerImpl<OperationSupport> (OperationType.CUSTOM_INSTALL);
}
public static OperationContainerImpl<OperationSupport> createForUninstallNativeComponent () {
return new OperationContainerImpl<OperationSupport> (OperationType.CUSTOM_UNINSTALL);
}
@SuppressWarnings({"unchecked"})
public OperationInfo<Support> add (UpdateUnit updateUnit, UpdateElement updateElement) throws IllegalArgumentException {
OperationInfo<Support> 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<Support> (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<Support> find (UpdateElement updateElement) {
OperationInfo<Support> toRemove = null;
for (OperationInfo<Support> info : listAll ()) {
if (info.getUpdateElement ().equals (updateElement)) {
toRemove = info;
break;
}
}
return toRemove;
}
private boolean contains (UpdateUnit unit, UpdateElement element) {
List<OperationInfo<Support>> infos = operations;
for (OperationInfo info : infos) {
if (info.getUpdateElement ().equals (element) ||
info.getUpdateUnit ().equals (unit)) {
return true;
}
}
return false;
}
private List<OperationInfo<Support>> listAll () {
return Collections.unmodifiableList(operations);
}
synchronized public List<OperationInfo<Support>> 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<UpdateElement> all = new HashSet<UpdateElement> (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 <ModuleInfo> infos = new ArrayList <ModuleInfo>();
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<UpdateElement> reqs = new HashSet<UpdateElement> ();
for (Dependency dep : mi.getDependencies ()) {
Collection<UpdateElement> requestedElements = Utilities.handleDependency (eagerEl, dep, Collections.singleton (mi), new HashSet<Dependency> (),
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<Support> 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<OperationInfo<Support>> listInvalid () {
List<OperationInfo<Support>> retval = new ArrayList<OperationInfo<Support>>();
List<OperationInfo<Support>> infos = listAll ();
for (OperationInfo<Support> 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<Support> {
private final UpdateElement updateElement;
private final UpdateUnit uUnit;
private Set<String> 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<UpdateElement> requiredElements;
public List<UpdateElement> getRequiredElements (){
if (upToDate && requiredElements != null) {
return requiredElements;
}
List<ModuleInfo> moduleInfos = new ArrayList<ModuleInfo>();
for (OperationContainer.OperationInfo oii : listAll ()) {
UpdateElementImpl impl = Trampoline.API.impl (oii.getUpdateElement ());
List<ModuleInfo> infos = impl.getModuleInfos ();
assert infos != null : "ModuleInfo for UpdateElement " + oii.getUpdateElement () + " found.";
moduleInfos.addAll (infos);
}
brokenDeps = new HashSet<String> ();
Set<UpdateElement> recommeded = new HashSet<UpdateElement>();
requiredElements = OperationValidator.getRequiredElements (type, getUpdateElement (), moduleInfos, brokenDeps, recommeded);
if (! brokenDeps.isEmpty() && ! recommeded.isEmpty()) {
brokenDeps = new HashSet<String> ();
requiredElements = OperationValidator.getRequiredElements (type, getUpdateElement (), moduleInfos, brokenDeps, recommeded);
}
return requiredElements;
}
public Set<String> getBrokenDependencies () {
if (! upToDate) {
brokenDeps = null;
}
if (brokenDeps != null) {
return brokenDeps;
}
List<ModuleInfo> moduleInfos = new ArrayList<ModuleInfo>();
for (OperationContainer.OperationInfo oii : listAll ()) {
UpdateElementImpl impl = Trampoline.API.impl (oii.getUpdateElement ());
Collection<ModuleInfo> infos = impl.getModuleInfos ();
assert infos != null : "ModuleInfo for UpdateElement " + oii.getUpdateElement () + " found.";
moduleInfos.addAll (infos);
}
return OperationValidator.getBrokenDependencies (type, getUpdateElement (), moduleInfos);
}
}
/** Creates a new instance of OperationContainer */
private OperationContainerImpl (OperationType type) {
this.type = type;
}
public OperationType getType () {
return type;
}
public static enum OperationType {
/** Install <code>UpdateElement</code> */
INSTALL,
/** Uninstall <code>UpdateElement</code> */
UNINSTALL,
/** Internally update installed <code>UpdateElement</code> without version increase */
INTERNAL_UPDATE,
/** Uninstall <code>UpdateElement</code> on-the-fly */
DIRECT_UNINSTALL,
/** Update installed <code>UpdateElement</code> to newer version. */
UPDATE,
/** Rollback installed <code>UpdateElement</code> to previous version. */
REVERT,
/** Enable <code>UpdateElement</code> */
ENABLE,
/** Disable <code>UpdateElement</code> */
DIRECT_DISABLE,
/** Disable <code>UpdateElement</code> on-the-fly */
DISABLE,
/** Install <code>UpdateElement</code> with custom installer. */
CUSTOM_INSTALL,
/** Uninstall <code>UpdateElement</code> with custom installer. */
CUSTOM_UNINSTALL
}
private OperationType type;
private OperationContainer delegate;
}