/**
* Copyright (c) 2010, 2013 Darmstadt University of Technology.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Olav Lenz - initial API and implementation
*/
package org.eclipse.recommenders.coordinates.rcp;
import static com.google.common.base.Optional.absent;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.eclipse.recommenders.internal.coordinates.rcp.CoordinatesRcpModule.IDENTIFIED_PROJECT_COORDINATES;
import static org.eclipse.recommenders.internal.coordinates.rcp.l10n.LogMessages.*;
import static org.eclipse.recommenders.utils.Constants.REASON_NOT_IN_CACHE;
import static org.eclipse.recommenders.utils.Logs.log;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.io.FileUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.recommenders.coordinates.DependencyInfo;
import org.eclipse.recommenders.coordinates.IProjectCoordinateAdvisor;
import org.eclipse.recommenders.coordinates.IProjectCoordinateAdvisorService;
import org.eclipse.recommenders.coordinates.ProjectCoordinate;
import org.eclipse.recommenders.coordinates.ProjectCoordinateAdvisorService;
import org.eclipse.recommenders.coordinates.rcp.CoordinateEvents.AdvisorConfigurationChangedEvent;
import org.eclipse.recommenders.coordinates.rcp.CoordinateEvents.ProjectCoordinateChangeEvent;
import org.eclipse.recommenders.internal.coordinates.rcp.AdvisorDescriptor;
import org.eclipse.recommenders.internal.coordinates.rcp.AdvisorDescriptors;
import org.eclipse.recommenders.internal.coordinates.rcp.CoordinatesRcpPreferences;
import org.eclipse.recommenders.internal.coordinates.rcp.DependencyInfoJsonTypeAdapter;
import org.eclipse.recommenders.internal.coordinates.rcp.ProjectCoordinateJsonTypeAdapter;
import org.eclipse.recommenders.internal.coordinates.rcp.l10n.LogMessages;
import org.eclipse.recommenders.rcp.IRcpService;
import org.eclipse.recommenders.utils.Logs;
import org.eclipse.recommenders.utils.Result;
import org.eclipse.recommenders.utils.gson.OptionalJsonTypeAdapter;
import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.common.io.Files;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.AbstractIdleService;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
public class EclipseProjectCoordinateAdvisorService extends AbstractIdleService
implements IProjectCoordinateAdvisorService, IRcpService {
@SuppressWarnings("serial")
private static final Type CACHE_TYPE_TOKEN = new TypeToken<Map<DependencyInfo, Optional<ProjectCoordinate>>>() {
}.getType();
private final ProjectCoordinateAdvisorService delegate;
private final CoordinatesRcpPreferences prefs;
private final LoadingCache<DependencyInfo, Optional<ProjectCoordinate>> projectCoordinateCache;
private final File persistenceFile;
private final Gson cacheGson;
private Map<IProjectCoordinateAdvisor, AdvisorDescriptor> descriptors = Maps.newHashMap();
@Inject
public EclipseProjectCoordinateAdvisorService(@Named(IDENTIFIED_PROJECT_COORDINATES) File persistenceFile,
EventBus bus, CoordinatesRcpPreferences prefs) {
bus.register(this);
this.prefs = prefs;
this.delegate = new ProjectCoordinateAdvisorService();
this.persistenceFile = persistenceFile;
this.cacheGson = new GsonBuilder()
.registerTypeAdapter(ProjectCoordinate.class, new ProjectCoordinateJsonTypeAdapter())
.registerTypeAdapter(DependencyInfo.class, new DependencyInfoJsonTypeAdapter())
.registerTypeAdapter(Optional.class, new OptionalJsonTypeAdapter<ProjectCoordinate>())
.enableComplexMapKeySerialization().serializeNulls().create();
projectCoordinateCache = createCache();
}
private LoadingCache<DependencyInfo, Optional<ProjectCoordinate>> createCache() {
return CacheBuilder.newBuilder().expireAfterAccess(30, MINUTES)
.build(new CacheLoader<DependencyInfo, Optional<ProjectCoordinate>>() {
@Override
public Optional<ProjectCoordinate> load(DependencyInfo info) {
return delegate.suggest(info);
}
});
}
@Override
public ImmutableList<IProjectCoordinateAdvisor> getAdvisors() {
return delegate.getAdvisors();
}
@Override
public void addAdvisor(IProjectCoordinateAdvisor advisor) {
delegate.addAdvisor(advisor);
}
@Override
public void setAdvisors(List<IProjectCoordinateAdvisor> advisors) {
delegate.setAdvisors(advisors);
}
public AdvisorDescriptor getDescriptor(IProjectCoordinateAdvisor advisor) {
return descriptors.get(advisor);
}
/**
* Looks up the ProjectCoordinate and resolves if necessary. This method blocks until the service is started and may
* be long-running.
*/
@Override
public Optional<ProjectCoordinate> suggest(DependencyInfo dependencyInfo) {
try {
awaitRunning();
return projectCoordinateCache.get(dependencyInfo);
} catch (Exception e) {
log(ERROR_IN_ADVISOR_SERVICE_SUGGEST, e, dependencyInfo.toString());
return absent();
}
}
@Override
public Result<ProjectCoordinate> trySuggest(DependencyInfo dependencyInfo) {
Optional<ProjectCoordinate> pc = projectCoordinateCache.getIfPresent(dependencyInfo);
if (pc == null) {
return Result.absent(REASON_NOT_IN_CACHE);
} else if (pc.isPresent()) {
return Result.of(pc.get());
} else {
return Result.absent();
}
}
@PostConstruct
public void open() {
startAsync();
}
@Override
protected void startUp() {
configureAdvisorList(prefs.advisorConfiguration);
if (!persistenceFile.exists()) {
return;
}
Map<DependencyInfo, Optional<ProjectCoordinate>> deserializedCache;
try {
String json = Files.toString(persistenceFile, StandardCharsets.UTF_8);
deserializedCache = cacheGson.fromJson(json, CACHE_TYPE_TOKEN);
} catch (IOException | JsonParseException e) {
Logs.log(ERROR_FAILED_TO_READ_CACHED_COORDINATES, e, persistenceFile);
return;
}
if (deserializedCache == null) {
// Can happen in json == "".
Logs.log(ERROR_FAILED_TO_READ_CACHED_COORDINATES, persistenceFile);
return;
}
projectCoordinateCache.putAll(deserializedCache);
}
private void configureAdvisorList(String advisorConfiguration) {
setAdvisors(provideAdvisors(advisorConfiguration));
}
private List<IProjectCoordinateAdvisor> provideAdvisors(String advisorConfiguration) {
Map<IProjectCoordinateAdvisor, AdvisorDescriptor> newDescriptors = Maps.newHashMap();
List<AdvisorDescriptor> registeredAdvisors = AdvisorDescriptors.getRegisteredAdvisors();
List<AdvisorDescriptor> loadedDescriptors = AdvisorDescriptors.load(advisorConfiguration, registeredAdvisors);
List<IProjectCoordinateAdvisor> advisors = Lists.newArrayListWithCapacity(loadedDescriptors.size());
for (AdvisorDescriptor descriptor : loadedDescriptors) {
try {
if (descriptor.isEnabled()) {
IProjectCoordinateAdvisor advisor = descriptor.createAdvisor();
advisors.add(advisor);
newDescriptors.put(advisor, descriptor);
}
} catch (CoreException e) {
Logs.log(LogMessages.ERROR_FAILED_TO_CREATE_ADVISOR, e, descriptor.getId());
}
}
descriptors = newDescriptors;
return advisors;
}
@PreDestroy
public void close() {
stopAsync();
}
@Override
protected void shutDown() {
try {
String json = cacheGson.toJson(projectCoordinateCache.asMap(), CACHE_TYPE_TOKEN);
Files.write(json, persistenceFile, StandardCharsets.UTF_8);
} catch (IOException e) {
Logs.log(LogMessages.ERROR_FAILED_TO_WRITE_CACHED_COORDINATES, e, persistenceFile);
// Delete the file (if it exists at all) so not to leave it in a corrupt state.
FileUtils.deleteQuietly(persistenceFile);
}
}
@Subscribe
public void onEvent(ProjectCoordinateChangeEvent e) {
projectCoordinateCache.invalidate(e.dependencyInfo);
}
@Subscribe
public void onEvent(AdvisorConfigurationChangedEvent e) {
clearCache();
configureAdvisorList(prefs.advisorConfiguration);
}
public void clearCache() {
projectCoordinateCache.invalidateAll();
}
}