blob: 2f0a739c4fad32a1209ee08e28cf92c35a7a10b9 [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.
*/
/*
* Contributor(s): theanuradha@netbeans.org
*/
package org.netbeans.modules.maven.indexer.api;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.swing.event.ChangeListener;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Settings;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.modules.maven.embedder.EmbedderFactory;
import org.netbeans.modules.maven.embedder.MavenEmbedder;
import org.openide.util.ChangeSupport;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle.Messages;
import org.openide.util.NbPreferences;
import org.eclipse.aether.repository.MirrorSelector;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.util.repository.DefaultMirrorSelector;
/**
* List of Maven repositories of interest.
*/
public final class RepositoryPreferences {
private static final Logger LOG = Logger.getLogger(RepositoryPreferences.class.getName());
private static RepositoryPreferences instance;
private static final String KEY_DISPLAY_NAME = "name";//NOI18N
private static final String KEY_PATH = "path";//NOI18N
private static final String KEY_INDEX_URL = "index";//NOI18N
private static final String KEY_REPO_URL = "url";//NOI18N
/*index settings */
public static final String PROP_INDEX_FREQ = "indexUpdateFrequency"; //NOI18N
public static final String PROP_INDEX = "createIndex"; //NOI18N
public static final String PROP_LAST_INDEX_UPDATE = "lastIndexUpdate"; //NOI18N
public static final int FREQ_ONCE_WEEK = 0;
public static final int FREQ_ONCE_DAY = 1;
public static final int FREQ_STARTUP = 2;
public static final int FREQ_NEVER = 3;
private final Map<String,RepositoryInfo> infoCache = new HashMap<String,RepositoryInfo>();
private final Map<Object,List<RepositoryInfo>> transients = new LinkedHashMap<Object,List<RepositoryInfo>>();
private RepositoryInfo local;
private final RepositoryInfo central;
private final ChangeSupport cs = new ChangeSupport(this);
private RepositoryPreferences() {
try {
central = new RepositoryInfo(RepositorySystem.DEFAULT_REMOTE_REPO_ID, /* XXX pull display name from superpom? */RepositorySystem.DEFAULT_REMOTE_REPO_ID, null, RepositorySystem.DEFAULT_REMOTE_REPO_URL);
//this repository can be mirrored
central.setMirrorStrategy(RepositoryInfo.MirrorStrategy.ALL);
} catch (URISyntaxException x) {
throw new AssertionError(x);
}
}
private static Preferences getPreferences() {
return NbPreferences.root().node("org/netbeans/modules/maven/nexus/indexing"); //NOI18N
}
private static Preferences storage() {
return NbPreferences.root().node("org/netbeans/modules/maven/repositories");
}
public synchronized static RepositoryPreferences getInstance() {
if (instance == null) {
instance = new RepositoryPreferences();
}
return instance;
}
private static @CheckForNull RepositoryInfo createRepositoryInfo(Preferences p) throws URISyntaxException {
String id = p.name();
String name = p.get(KEY_DISPLAY_NAME, null);
if (name == null) {
return null;
}
String path = p.get(KEY_PATH, null);
String repourl = p.get(KEY_REPO_URL, null);
String indexurl = p.get(KEY_INDEX_URL, null);
RepositoryInfo repo = new RepositoryInfo(id, name, path, repourl, indexurl);
//repository infos from preferences cannot be wildcard mirrored.
repo.setMirrorStrategy(RepositoryInfo.MirrorStrategy.NON_WILDCARD);
return repo;
}
/** @since 2.2 */
@Messages("local=Local")
public @NonNull synchronized RepositoryInfo getLocalRepository() {
if (local == null) {
try {
//TODO do we care about changing the instance when localrepo location changes?
local = new RepositoryInfo(RepositorySystem.DEFAULT_LOCAL_REPO_ID, Bundle.local(), EmbedderFactory.getProjectEmbedder().getLocalRepositoryFile().getAbsolutePath(), null);
local.setMirrorStrategy(RepositoryInfo.MirrorStrategy.NONE);
} catch (URISyntaxException x) {
throw new AssertionError(x);
}
}
return local;
}
/**
* returns the RepositoryInfo object with the given id or a mirror repository info
* that mirrors the given id.
* @param id
* @return
*/
public @CheckForNull RepositoryInfo getRepositoryInfoById(String id) {
List<RepositoryInfo> infos = getRepositoryInfos();
//repository infos are now including mirrors
//first check if the repository itself in the list has the id
for (RepositoryInfo ri : infos) {
if (ri.getId().equals(id)) {
return ri;
}
}
//if not, then try checking the mirrored repos..
for (RepositoryInfo ri : infos) {
if (ri.isMirror()) {
for (RepositoryInfo rii : ri.getMirroredRepositories()) {
if (rii.getId().equals(id)) {
return ri;
}
}
}
}
return null;
}
public List<RepositoryInfo> getRepositoryInfos() {
List<RepositoryInfo> toRet = new ArrayList<RepositoryInfo>();
toRet.add(getLocalRepository());
Set<String> ids = new HashSet<String>();
ids.add(RepositorySystem.DEFAULT_LOCAL_REPO_ID);
Set<String> urls = new HashSet<String>();
synchronized (infoCache) {
Preferences storage = storage();
try {
Set<String> gone = new HashSet<String>(infoCache.keySet());
for (String c : storage.childrenNames()) {
RepositoryInfo ri = infoCache.get(c);
if (ri == null) {
Preferences child = storage.node(c);
try {
ri = createRepositoryInfo(child);
if (ri == null) {
continue;
}
infoCache.put(c, ri);
} catch (/*IllegalArgument,URISyntax*/Exception x) {
LOG.log(Level.INFO, c, x);
try {
child.removeNode();
} catch (BackingStoreException x2) {
LOG.log(Level.INFO, null, x2);
}
continue;
}
}
toRet.add(ri);
gone.remove(c);
ids.add(ri.getId());
urls.add(ri.getRepositoryUrl());
}
for (String g : gone) {
infoCache.remove(g);
}
} catch (BackingStoreException x) {
LOG.log(Level.INFO, null, x);
}
if (transients.isEmpty()) {
if (ids.add(central.getId()) && urls.add(central.getRepositoryUrl())) {
toRet.add(central);
}
} else {
for (List<RepositoryInfo> infos : transients.values()) {
for (RepositoryInfo info : infos) {
if (ids.add(info.getId()) && urls.add(info.getRepositoryUrl())) {
toRet.add(info);
}
}
}
}
}
MavenEmbedder embedder2 = EmbedderFactory.getOnlineEmbedder();
DefaultMirrorSelector selectorWithGroups = new DefaultMirrorSelector();
DefaultMirrorSelector selectorWithoutGroups = new DefaultMirrorSelector();
final Settings settings = embedder2.getSettings();
for (Mirror mirror : settings.getMirrors()) {
String mirrorOf = mirror.getMirrorOf();
selectorWithGroups.add(mirror.getId(), mirror.getUrl(), mirror.getLayout(), false, mirrorOf, mirror.getMirrorOfLayouts());
if (!mirrorOf.contains("*")) {
selectorWithoutGroups.add(mirror.getId(), mirror.getUrl(), mirror.getLayout(), false, mirrorOf, mirror.getMirrorOfLayouts());
}
}
List<RepositoryInfo> semiTreed = new ArrayList<RepositoryInfo>();
for (RepositoryInfo in: toRet) {
if (in.getMirrorStrategy() == RepositoryInfo.MirrorStrategy.ALL || in.getMirrorStrategy() == RepositoryInfo.MirrorStrategy.NON_WILDCARD) {
RepositoryInfo processed = getMirrorInfo(in, in.getMirrorStrategy() == RepositoryInfo.MirrorStrategy.ALL ? selectorWithGroups : selectorWithoutGroups, settings);
boolean isMirror = true;
if (processed == null) {
isMirror = false;
processed = in;
}
int index = semiTreed.indexOf(processed);
if (index > -1) {
processed = semiTreed.get(index);
} else {
semiTreed.add(processed);
}
if (isMirror) {
processed.addMirrorOfRepository(in);
}
} else {
semiTreed.add(in);
}
}
return semiTreed;
}
/**
* if the repository has a mirror, then create a repositoryinfo object for it..
*/
private RepositoryInfo getMirrorInfo(RepositoryInfo info, MirrorSelector selector, Settings settings) {
RemoteRepository original = new RemoteRepository.Builder(info.getId(), /* XXX do we even support any other layout?*/"default", info.getRepositoryUrl()).build();
RemoteRepository mirror = selector.getMirror(original);
if (mirror != null) {
try {
String name = mirror.getId();
//#213078 need to lookup name for mirror
for (Mirror m : settings.getMirrors()) {
if (m.getId() != null && m.getId().equals(mirror.getId())) {
name = m.getName();
break;
}
}
RepositoryInfo toret = new RepositoryInfo(mirror.getId(), name, null, mirror.getUrl());
toret.setMirrorStrategy(RepositoryInfo.MirrorStrategy.NONE);
return toret;
} catch (URISyntaxException ex) {
Exceptions.printStackTrace(ex);
}
}
return null;
}
public void addOrModifyRepositoryInfo(RepositoryInfo info) {
String id = info.getId();
synchronized (infoCache) {
infoCache.put(id, info);
Preferences p = storage().node(id);
p.put(KEY_DISPLAY_NAME, info.getName());
put(p, KEY_PATH, info.getRepositoryPath());
put(p, KEY_REPO_URL, info.getRepositoryUrl());
if (info.getRepositoryUrl() != null) {
put(p, KEY_INDEX_URL, info.getIndexUpdateUrl().equals(info.getRepositoryUrl() + RepositoryInfo.DEFAULT_INDEX_SUFFIX) ? null : info.getIndexUpdateUrl());
}
}
cs.fireChange();
}
private static void put(@NonNull Preferences p, @NonNull String key, @NullAllowed String value) {
if (value != null) {
p.put(key, value);
} else {
p.remove(key);
}
}
/**
* Checks whether a given repository is persisted.
* @param id the repository's ID
* @return true if it is persistent (custom), false if it is the local repository or was added transiently
* @since 2.1
*/
public boolean isPersistent(String id) {
return storage().node(id).get(KEY_DISPLAY_NAME, null) != null;
}
public void removeRepositoryInfo(RepositoryInfo info) {
synchronized (infoCache) {
String id = info.getId();
infoCache.remove(id);
try {
storage().node(id).removeNode();
} catch (BackingStoreException x) {
LOG.log(Level.INFO, null, x);
}
}
cs.fireChange();
}
public static void setIndexUpdateFrequency(int fr) {
getPreferences().putInt(PROP_INDEX_FREQ, fr);
}
public static int getIndexUpdateFrequency() {
return getPreferences().getInt(PROP_INDEX_FREQ, Boolean.getBoolean("netbeans.full.hack") ? FREQ_NEVER : FREQ_ONCE_WEEK);
}
/**
* @since 2.27
* @param bool
*/
public static void setIndexRepositories(boolean bool) {
getPreferences().putBoolean(PROP_INDEX, bool);
}
/**
* @since 2.27
* @return
*/
public static boolean isIndexRepositories() {
return getPreferences().getBoolean(PROP_INDEX, true);
}
public static Date getLastIndexUpdate(String repoId) {
if(repoId.contains("//")) {
repoId = repoId.replace("//", "_");
LOG.log(Level.FINE, "Getting last index update: Repo''s id contains consecutive slashes, replacing them with '_': {0}", repoId);
}
long old = getPreferences().getLong(PROP_LAST_INDEX_UPDATE + "." + repoId, 0); // compatibility
if (old != 0) { // upgrade it
getPreferences().remove(PROP_LAST_INDEX_UPDATE + "." + repoId);
storage().node(repoId).putLong(PROP_LAST_INDEX_UPDATE, old);
}
return new Date(storage().node(repoId).getLong(PROP_LAST_INDEX_UPDATE, 0));
}
public static void setLastIndexUpdate(String repoId,Date date) {
if(repoId.contains("//")) {
repoId = repoId.replace("//", "_");
LOG.log(Level.FINE, "Setting last index update: Repo''s id contains consecutive slashes, replacing them with '_': {0}", repoId);
}
getPreferences().remove(PROP_LAST_INDEX_UPDATE + "." + repoId);
storage().node(repoId).putLong(PROP_LAST_INDEX_UPDATE, date.getTime());
}
/**
* Register a transient repository, the effective url actually used depends on maven settings for mirrors.
* Its definition will not be persisted.
* Repositories whose ID or URL duplicate that of a persistent repository,
* or previously registered transient repository, will be ignored
* (unless and until that repository is removed).
* @param key an arbitrary key for use with {@link #removeTransientRepositories}
* @param id the repository ID
* @param displayName a display name (may just be {@code id})
* @param url the remote URL (prefer the canonical public URL to that of a mirror)
* @param strategy how is the url parameter processed by local maven mirror settings
* @throws URISyntaxException in case the URL is malformed
* @since 2.11
*/
public void addTransientRepository(Object key, String id, String displayName, String url, RepositoryInfo.MirrorStrategy strategy) throws URISyntaxException {
if(id.contains("//")) {
id = id.replace("//", "_");
LOG.log(Level.FINE, "Adding transient repository: Repo''s id contains consecutive slashes, replacing them with '_': {0}", id);
}
if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("file:")) {
//only register repositories we can safely handle.. #227322
return;
}
synchronized (infoCache) {
List<RepositoryInfo> infos = transients.get(key);
if (infos == null) {
infos = new ArrayList<RepositoryInfo>();
transients.put(key, infos);
}
RepositoryInfo info = new RepositoryInfo(id, displayName, null, url);
info.setMirrorStrategy(strategy);
infos.add(info);
}
cs.fireChange();
}
/**
* Remote all transient repositories associated with a given ID.
* @param key a key as with {@link #addTransientRepository}
* @since 2.1
*/
public void removeTransientRepositories(Object key) {
synchronized (infoCache) {
transients.remove(key);
}
cs.fireChange();
}
/**
* @since 2.1
*/
public void addChangeListener(ChangeListener l) {
cs.addChangeListener(l);
}
/**
* @since 2.1
*/
public void removeChangeListener(ChangeListener l) {
cs.removeChangeListener(l);
}
/**
* Produces a list of remote repositories.
* @see MavenEmbedder#resolve
* @see ProjectBuildingRequest#setRemoteRepositories
* @since 2.12
*/
public List<ArtifactRepository> remoteRepositories(MavenEmbedder embedder) {
List<ArtifactRepository> remotes = new ArrayList<ArtifactRepository>();
for (RepositoryInfo info : getRepositoryInfos()) {
// XXX should there be a String preferredId parameter to limit the remote repositories used in case we have a "reference" ID somehow?
if (!info.isLocal()) {
remotes.add(embedder.createRemoteRepository(info.getRepositoryUrl(), info.getId()));
}
// XXX do we care to handle mirrors specially?
}
return remotes;
}
}