/* * 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.apache.karaf.profile.impl; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.karaf.profile.LockHandle; import org.apache.karaf.profile.PlaceholderResolver; import org.apache.karaf.profile.Profile; import org.apache.karaf.profile.ProfileService; import static org.apache.karaf.profile.impl.Utils.assertFalse; import static org.apache.karaf.profile.impl.Utils.assertNotNull; import static org.apache.karaf.profile.impl.Utils.join; public class ProfileServiceImpl implements ProfileService { private static final long ACQUIRE_LOCK_TIMEOUT = 25 * 1000L; private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final List<PlaceholderResolver> resolvers = new CopyOnWriteArrayList<>(); private final Path profilesDirectory; private Map<String, Profile> cache; public ProfileServiceImpl(Path profilesDirectory) throws IOException { this.profilesDirectory = profilesDirectory; Files.createDirectories(profilesDirectory); } @Override public LockHandle acquireWriteLock() { return acquireLock(getLock().writeLock(), "Cannot obtain profile write lock in time"); } @Override public LockHandle acquireReadLock() { return acquireLock(getLock().readLock(), "Cannot obtain profile read lock in time"); } protected LockHandle acquireLock(final Lock lock, String message) { try { if (!lock.tryLock(ACQUIRE_LOCK_TIMEOUT, TimeUnit.MILLISECONDS)) { throw new IllegalStateException(message); } } catch (InterruptedException ex) { throw new IllegalStateException(message, ex); } return new LockHandle() { @Override public void close() { lock.unlock(); } }; } protected ReadWriteLock getLock() { return readWriteLock; } @Override public void registerResolver(PlaceholderResolver resolver) { resolvers.add(resolver); } @Override public void unregisterResolver(PlaceholderResolver resolver) { resolvers.remove(resolver); } @Override @SuppressWarnings("unused") public void createProfile(Profile profile) { assertNotNull(profile, "profile is null"); try (LockHandle lock = acquireWriteLock()) { String profileId = profile.getId(); assertFalse(hasProfile(profileId), "Profile already exists: " + profileId); createOrUpdateProfile(null, profile); } } @Override @SuppressWarnings("unused") public void updateProfile(Profile profile) { assertNotNull(profile, "profile is null"); try (LockHandle lock = acquireWriteLock()) { final String profileId = profile.getId(); final Profile lastProfile = getRequiredProfile(profileId); createOrUpdateProfile(lastProfile, profile); } } @Override @SuppressWarnings("unused") public boolean hasProfile(String profileId) { assertNotNull(profileId, "profileId is null"); try (LockHandle lock = acquireReadLock()) { Profile profile = getProfileFromCache(profileId); return profile != null; } } @Override @SuppressWarnings("unused") public Profile getProfile(String profileId) { assertNotNull(profileId, "profileId is null"); try (LockHandle lock = acquireReadLock()) { return getProfileFromCache(profileId); } } @Override @SuppressWarnings("unused") public Profile getRequiredProfile(String profileId) { assertNotNull(profileId, "profileId is null"); try (LockHandle lock = acquireReadLock()) { Profile profile = getProfileFromCache(profileId); assertNotNull(profile, "Profile does not exist: " + profileId); return profile; } } @Override @SuppressWarnings("unused") public Collection<String> getProfiles() { try (LockHandle lock = acquireReadLock()) { Collection<String> profiles = getProfilesFromCache(); return Collections.unmodifiableCollection(profiles); } } @Override @SuppressWarnings("unused") public void deleteProfile(String profileId) { assertNotNull(profileId, "profileId is null"); try (LockHandle lock = acquireWriteLock()) { final Profile lastProfile = getRequiredProfile(profileId); deleteProfileFromCache(lastProfile); } } @Override public Profile getOverlayProfile(Profile profile) { return Profiles.getOverlay(profile, loadCache()); } @Override public Profile getOverlayProfile(Profile profile, String environment) { return Profiles.getOverlay(profile, loadCache(), environment); } @Override public Profile getEffectiveProfile(Profile profile) { return Profiles.getEffective(profile, resolvers); } @Override public Profile getEffectiveProfile(Profile profile, boolean defaultsToEmptyString) { return Profiles.getEffective(profile, resolvers, defaultsToEmptyString); } protected void createOrUpdateProfile(Profile lastProfile, Profile profile) { if (lastProfile != null) { deleteProfileFromCache(lastProfile); } try { loadCache(); for (String parentId : profile.getParentIds()) { if (!cache.containsKey(parentId)) { throw new IllegalStateException("Parent profile " + parentId + " does not exist"); } } Profiles.writeProfile(profilesDirectory, profile); if (cache != null) { cache.put(profile.getId(), profile); } } catch (IOException e) { throw new IllegalStateException("Error writing profiles", e); } } protected Profile getProfileFromCache(String profileId) { return loadCache().get(profileId); } protected Collection<String> getProfilesFromCache() { return loadCache().keySet(); } protected void deleteProfileFromCache(Profile lastProfile) { loadCache(); List<String> children = new ArrayList<>(); for (Profile p : cache.values()) { if (p.getParentIds().contains(lastProfile.getId())) { children.add(p.getId()); } } if (!children.isEmpty()) { throw new IllegalStateException("Profile " + lastProfile.getId() + " is a parent of " + join(", ", children)); } try { Profiles.deleteProfile(profilesDirectory, lastProfile.getId()); cache.remove(lastProfile.getId()); } catch (IOException e) { cache = null; throw new IllegalStateException("Error deleting profiles", e); } } protected Map<String, Profile> loadCache() { if (cache == null) { try { cache = Profiles.loadProfiles(profilesDirectory); } catch (IOException e) { throw new IllegalStateException("Error reading profiles", e); } } return cache; } }