| /******************************************************************************* |
| * Copyright (c) 2020 Christian Pontesegger and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Christian Pontesegger - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.skills.service; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.stream.Collectors; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.e4.core.services.events.IEventBroker; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; |
| import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; |
| import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl; |
| import org.eclipse.skills.Activator; |
| import org.eclipse.skills.BrokerTools; |
| import org.eclipse.skills.Logger; |
| import org.eclipse.skills.model.IQuest; |
| import org.eclipse.skills.model.IReward; |
| import org.eclipse.skills.model.ISkill; |
| import org.eclipse.skills.model.ISkillsFactory; |
| import org.eclipse.skills.model.ISkillsPackage; |
| import org.eclipse.skills.model.ITask; |
| import org.eclipse.skills.model.IUser; |
| import org.eclipse.skills.model.IUserTask; |
| import org.eclipse.skills.service.storage.WorkspaceDataStorage; |
| import org.eclipse.ui.PlatformUI; |
| import org.osgi.service.event.Event; |
| import org.osgi.service.event.EventHandler; |
| |
| public class SkillService implements ISkillService, EventHandler { |
| |
| private static ISkillService fInstance = null; |
| |
| public synchronized static ISkillService getInstance() { |
| if (fInstance == null) |
| fInstance = new SkillService(); |
| |
| return fInstance; |
| } |
| |
| private static Collection<ITask> extractTasksRecursively(Collection<ITask> tasks) { |
| final Collection<ITask> result = new ArrayList<>(tasks); |
| |
| for (final ITask task : tasks) |
| result.addAll(extractTasksRecursively(task.getTasks())); |
| |
| return result; |
| } |
| |
| private static String capitalizeFirstLetter(String word) { |
| if ((word != null) && (!word.isEmpty())) |
| return word.substring(0, 1).toUpperCase() + word.substring(1); |
| |
| return word; |
| } |
| |
| private IUser fUser = null; |
| |
| private IDataStorage fStorage = null; |
| |
| private IQuestProvider fQuestProvider = null; |
| |
| private UINotificationService fNotificationService = null; |
| |
| /* package */ SkillService() { |
| } |
| |
| @Override |
| public void activateService() { |
| getUser(); |
| |
| registerForEvents(); |
| |
| startUserTasks(); |
| activateOpenTasks(); |
| |
| BrokerTools.post(ISkillService.EVENT_USER_UPDATE, getUser()); |
| } |
| |
| @Override |
| public void deactivateService() { |
| stopUserTasks(); |
| deactivateOpenTasks(); |
| |
| unregisterFromEvents(); |
| |
| BrokerTools.post(ISkillService.EVENT_USER_UPDATE, getUser()); |
| |
| // unload resources |
| fUser = null; |
| fStorage = null; |
| fQuestProvider = null; |
| fNotificationService = null; |
| } |
| |
| private void registerForEvents() { |
| final IEventBroker broker = BrokerTools.getBroker(); |
| if (broker != null) { |
| broker.subscribe(ISkillService.EVENT_BASE + "/*", this); |
| createNotificationService(); |
| |
| } else if (PlatformUI.isWorkbenchRunning()) { |
| // probably the broker service will startup later |
| final Job job = new Job("Register for Skill events") { |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| registerForEvents(); |
| return Status.OK_STATUS; |
| } |
| }; |
| |
| job.setSystem(true); |
| job.schedule(10 * 1000); |
| } |
| } |
| |
| private void unregisterFromEvents() { |
| BrokerTools.unsubscribe(this); |
| getNotificationService().dispose(); |
| } |
| |
| @Override |
| public void handleEvent(Event event) { |
| if (ISkillService.EVENT_TASK_COMPLETED.equals(event.getTopic())) { |
| final IUserTask userTask = (IUserTask) event.getProperty(IEventBroker.DATA); |
| |
| for (final IReward reward : userTask.getTask().getRewards()) |
| getUser().consume(reward); |
| } |
| |
| storeUser(); |
| } |
| |
| @Override |
| public IUser getUser() { |
| if (fUser == null) |
| loadUser(); |
| |
| return fUser; |
| } |
| |
| private void loadUser() { |
| if (getStorage().hasResource(USER_PROFILE)) { |
| try { |
| final InputStream userData = new ByteArrayInputStream(getStorage().loadResource(USER_PROFILE)); |
| |
| // initialize the model |
| ISkillsPackage.eINSTANCE.eClass(); |
| |
| final Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE; |
| final Map<String, Object> extensionsMap = reg.getExtensionToFactoryMap(); |
| extensionsMap.put("skills", new XMIResourceFactoryImpl()); |
| |
| final ResourceSet resourceSet = new ResourceSetImpl(); |
| final Resource resource = resourceSet.createResource(URI.createURI("user.skills")); |
| resource.load(userData, new HashMap<>()); |
| |
| fUser = (IUser) resource.getContents().get(0); |
| |
| } catch (final IOException e) { |
| // user profile is damaged |
| fUser = createUser(); |
| storeUser(); |
| } |
| |
| } else { |
| fUser = createUser(); |
| storeUser(); |
| } |
| } |
| |
| private IUser createUser() { |
| final IUser user = ISkillsFactory.eINSTANCE.createUser(); |
| |
| user.setName(capitalizeFirstLetter(System.getProperty("user.name"))); |
| |
| final ISkill experience = ISkillsFactory.eINSTANCE.createSkill(); |
| experience.setName("Experience"); |
| user.setExperience(experience); |
| |
| if (fUser != null) { |
| user.setName(fUser.getName()); |
| user.setImageLocation(fUser.getImageLocation()); |
| } |
| |
| return user; |
| } |
| |
| private void storeUser() { |
| final IUser user = getUser(); |
| |
| final Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE; |
| final Map<String, Object> extensionsMap = reg.getExtensionToFactoryMap(); |
| extensionsMap.put("skills", new XMIResourceFactoryImpl()); |
| |
| final Resource resource = new XMIResourceImpl(); |
| resource.getContents().add(user); |
| |
| try { |
| final ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| resource.save(out, extensionsMap); |
| getStorage().storeResource(USER_PROFILE, out.toByteArray()); |
| |
| } catch (final IOException e) { |
| Logger.error(Activator.PLUGIN_ID, "Could not store user profile", e); |
| } |
| } |
| |
| private void createNotificationService() { |
| fNotificationService = new UINotificationService(); |
| } |
| |
| private UINotificationService getNotificationService() { |
| if (fNotificationService == null) |
| createNotificationService(); |
| |
| return fNotificationService; |
| } |
| |
| private void startUserTasks() { |
| for (final IUserTask task : getUser().getUsertasks()) { |
| if (task.isStarted() && !task.isCompleted()) |
| task.activate(); |
| } |
| } |
| |
| private void stopUserTasks() { |
| for (final IUserTask task : getUser().getUsertasks()) { |
| if (!task.isCompleted()) |
| task.getTask().getGoal().deactivate(); |
| } |
| } |
| |
| private void activateOpenTasks() { |
| for (final ITask task : getOpenTasks()) { |
| task.getRequirement().activate(); |
| |
| } |
| } |
| |
| private void deactivateOpenTasks() { |
| for (final ITask task : getOpenTasks()) |
| task.getRequirement().deactivate(); |
| } |
| |
| @Override |
| public Collection<ITask> getOpenTasks() { |
| final Collection<ITask> openTasks = getAllAvailableTasks(); |
| final List<ITask> runningTasks = getUser().getUsertasks().stream().map(t -> t.getTask()).collect(Collectors.toList()); |
| |
| // TODO is there a better way to remove the running tasks? openTasks.removeAll() does not work |
| for (final ITask runningTask : runningTasks) { |
| for (final ITask openTask : openTasks) { |
| if (Objects.equals(runningTask, openTask)) { |
| openTasks.remove(openTask); |
| break; |
| } |
| } |
| } |
| |
| return openTasks; |
| } |
| |
| @Override |
| public void storeResource(String name, byte[] data) throws IOException { |
| getStorage().storeResource(name, data); |
| } |
| |
| @Override |
| public byte[] loadResource(String name) throws IOException { |
| return getStorage().loadResource(name); |
| } |
| |
| @Override |
| public boolean hasResource(String name) { |
| return getStorage().hasResource(name); |
| } |
| |
| @Override |
| public void resetProgress() { |
| fUser = createUser(); |
| |
| storeUser(); |
| } |
| |
| @Override |
| public void reportTaskReady(ITask task) { |
| final IUserTask userTask = getUser().addTask(task); |
| if (task.isAutoActivation()) |
| startTask(userTask); |
| } |
| |
| @Override |
| public void startTask(final IUserTask userTask) { |
| userTask.setStarted(new Date()); |
| |
| BrokerTools.post(ISkillService.EVENT_TASK_STARTED, userTask); |
| |
| userTask.activate(); |
| } |
| |
| @Override |
| public void setQuestProvider(IQuestProvider questProvider) { |
| fQuestProvider = questProvider; |
| } |
| |
| @Override |
| public IQuestProvider getQuestProvider() { |
| if (fQuestProvider != null) |
| return fQuestProvider; |
| |
| return new ExtensionPointQuestProvider(); |
| } |
| |
| @Override |
| public Collection<ITask> getAllAvailableTasks() { |
| final Collection<ITask> tasks = new HashSet<>(); |
| |
| for (final IQuest quest : getQuestProvider().getQuests()) |
| tasks.addAll(extractTasksRecursively(quest.getTasks())); |
| |
| return tasks; |
| } |
| |
| @Override |
| public void setStorage(IDataStorage storage) { |
| fStorage = storage; |
| } |
| |
| private IDataStorage getStorage() { |
| if (fStorage == null) |
| fStorage = new WorkspaceDataStorage(); |
| |
| return fStorage; |
| } |
| } |