Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ When you select e.g. a page for inclusion into package, all the assets reference

### Project structure

The project consists of two modules:
The project is organized as a multi-module Maven build:

* *core module* runs in an AEM instance containing to respond to user commands and provide data processing service.
* *ui/application module* embeds into AEM administering interface and runs in browser to give to the user control over package creation and storage, as well as detailed feedback. This module is built upon the modern Coral 3 graphic interface and TouchUI-ready.
* `core` — the server-side OSGi bundle. It contains the Java code that implements BackPack services, request processing, package handling logic, and tests.
* `ui.apps` — the AEM application package. It contains the repository content installed under the application area, including the UI assets and browser-facing integration.
* `ui.apps.structure` — the repository structure package. It defines the AEM repository roots required by the application packages.
* `ui.config` — the AEM configuration package. It contains configuration content packaged separately from the application code.
* `all` — the aggregate container package. It embeds the `core`, `ui.apps`, and `ui.config` artifacts into a single package for deployment.

The two modules communicate via JSON-based REST protocol. Basically, a user command is sent to an endpoint, parsed and processed by a specially designed AEM service, then result and/or status message returned to user.

### Installation

Expand Down Expand Up @@ -57,10 +59,6 @@ There is also the specific "test" profile that helps to collect code quality sta

The project is in active development stage. Community contribution is heartily welcome.

### Known issues

The project uses `com.google.common.cache.Cache` bundled in `uber-jar-6.3.0` which is in "beta" status. Despite this class is out of beta stage in standalone Google Guava releases since *20.0*, we are currently sticking to *uber-jar* version to avoid inconsistency. We have tested this doesn't cause trouble in regular usage.

### Licensing

The project is licensed under [Apache License, Version 2.0](LICENSE). All runtime project dependencies are guaranteed to be compliant with the license. Dependencies such as Adobe's *uber-jar* are considered *provided* in the end-user environment and are not explicitly engaged. The end user is to comply with the regulations of the corresponding licenses.
The project is licensed under [Apache License, Version 2.0](LICENSE). All runtime project dependencies are guaranteed to be compliant with the license. Dependencies such as Adobe's *uber-jar* are considered *provided* in the end-user environment and are not explicitly engaged. The end user is to comply with the regulations of the corresponding licenses.
7 changes: 1 addition & 6 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@
</executions>
<configuration>
<instructions>
<Embed-Dependency>commons-lang3;scope=compile,guava;scope=compile</Embed-Dependency>
<Import-Package>sun.misc;resolution:=optional,*</Import-Package>
<Embed-Dependency>commons-lang3;scope=compile</Embed-Dependency>
</instructions>
</configuration>
</plugin>
Expand Down Expand Up @@ -174,10 +173,6 @@
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- Testing -->
<dependency>
<groupId>junit</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.exadel.etoolbox.backpack.core.servlets.model.PackageInfoModel;
import com.exadel.etoolbox.backpack.request.RequestAdapter;
import com.exadel.etoolbox.backpack.request.validator.ValidatorResponse;
import com.google.common.collect.ImmutableMap;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
Expand Down Expand Up @@ -110,7 +109,7 @@ private Resource createPackageEntry(String type, String title, Map<String, Objec
private void addSubsidiaries(List<Resource> subsidiaries, Set<String> subsidiaryPaths, String type) {
if (!subsidiaryPaths.isEmpty()) {
subsidiaries.addAll(subsidiaryPaths.stream()
.map(path -> createPackageEntry(type, path, ImmutableMap.of("upstream", path)))
.map(path -> createPackageEntry(type, path, Collections.singletonMap("upstream", path)))
.collect(Collectors.toList()));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import java.util.Collection;
import java.util.concurrent.ConcurrentMap;

/**
* Represents a service running in an AEM instance responsible for base operation with package
*/
Expand Down Expand Up @@ -83,7 +81,6 @@ public interface BasePackageService {
* @param jcrPackageDefinition {@code JcrPackageDefinition} Definition of the package to update
* @param userSession Current user {@code Session} as adapted from the acting {@code ResourceResolver}
* @param packageInfo {@code PackageInfo} object to store status information in
* @param paths {@code List} of {@code PathModel} will be stored in package metadata information and used in future package modifications
* @param filter {@code DefaultWorkspaceFilter} instance representing resource selection mechanism for the package
*/
void setPackageInfo(JcrPackageDefinition jcrPackageDefinition,
Expand Down Expand Up @@ -125,13 +122,11 @@ boolean isPackageExist(JcrPackageManager packageMgr,
String version) throws RepositoryException;

/**
* Gets current {@link PackageInfo} objects cache
* Gets current {@link PackageInfo} objects cache via a narrow cache-specific API
*
* @return {@code Cache<String, PackageInfo>} object
* @return {@link PackageInfoCache} object
*/
@SuppressWarnings("UnstableApiUsage")
// sticking to Guava Cache version bundled in uber-jar; still safe to use
ConcurrentMap<String, PackageInfo> getPackageCacheAsMap();
PackageInfoCache getPackageCache();

/**
* @param resourceResolver {@code ResourceResolver} used to collect assets details
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.exadel.etoolbox.backpack.core.services.pckg;

import com.exadel.etoolbox.backpack.core.dto.response.PackageInfo;

/**
* Narrow cache-facing API for storing and retrieving package build information.
*/
public interface PackageInfoCache {

PackageInfo get(String packagePath);

PackageInfo put(String packagePath, PackageInfo packageInfo);

PackageInfo remove(String packagePath);

void clear();
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
import com.exadel.etoolbox.backpack.core.dto.response.PackageInfo;
import com.exadel.etoolbox.backpack.core.dto.response.PackageStatus;
import com.exadel.etoolbox.backpack.core.services.pckg.BasePackageService;
import com.exadel.etoolbox.backpack.core.services.pckg.PackageInfoCache;
import com.exadel.etoolbox.backpack.core.services.util.constants.BackpackConstants;
import com.exadel.etoolbox.backpack.core.servlets.model.PackageModel;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.gson.Gson;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
Expand Down Expand Up @@ -53,7 +52,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand All @@ -78,10 +76,8 @@ public class BasePackageServiceImpl implements BasePackageService {
private SlingRepository slingRepository;


@SuppressWarnings("UnstableApiUsage") // sticking to Guava Cache version bundled in uber-jar; still safe to use
protected Cache<String, PackageInfo> packageInfos;

protected boolean enableStackTrace;
private PackageInfoCache packageCache;

/**
* Run upon this OSGi service activation to initialize cache storage of collected {@link PackageInfo} objects
Expand All @@ -92,10 +88,8 @@ public class BasePackageServiceImpl implements BasePackageService {
@SuppressWarnings("unused") // run internally by the OSGi mechanism
private void activate(Configuration config) {
enableStackTrace = config.enableStackTraceShowing();
packageInfos = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(config.buildInfoTTL(), TimeUnit.DAYS)
.build();
long cacheTtlMillis = Math.max(0L, TimeUnit.DAYS.toMillis(config.buildInfoTTL()));
packageCache = new PackageInfoCacheImpl(cacheTtlMillis);
}
Comment thread
liubou-masiuk marked this conversation as resolved.

@Override
Expand Down Expand Up @@ -297,11 +291,9 @@ public long getAssetSize(ResourceResolver resourceResolver, String path) {
/**
* {@inheritDoc}
*/
@SuppressWarnings("UnstableApiUsage")
// sticking to Guava Cache version bundled in uber-jar; still safe to use
@Override
public ConcurrentMap<String, PackageInfo> getPackageCacheAsMap() {
return packageInfos.asMap();
public PackageInfoCache getPackageCache() {
return packageCache;
}

@Override
Expand Down Expand Up @@ -371,6 +363,6 @@ private String getPackageId(final String packageGroupName, final String packageN
}

private void clearCache(String key) {
getPackageCacheAsMap().remove(key);
getPackageCache().remove(key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
import com.exadel.etoolbox.backpack.core.services.util.SessionService;
import com.exadel.etoolbox.backpack.core.services.util.constants.BackpackConstants;
import com.exadel.etoolbox.backpack.core.servlets.model.BuildPackageModel;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
Expand Down Expand Up @@ -57,7 +57,6 @@
*/
@Component(service = BuildPackageService.class)
public class BuildPackageServiceImpl implements BuildPackageService {
private static final String SERVICE_NAME = "backpack-service";

private static final Logger LOGGER = LoggerFactory.getLogger(BuildPackageServiceImpl.class);

Expand Down Expand Up @@ -197,7 +196,7 @@ public PackageInfo buildPackage(final ResourceResolver resourceResolver,
if (!PackageStatus.BUILD_IN_PROGRESS.equals(packageInfo.getPackageStatus()) && !PackageStatus.INSTALL_IN_PROGRESS.equals(packageInfo.getPackageStatus())) {
packageInfo.setPackageStatus(PackageStatus.BUILD_IN_PROGRESS);
packageInfo.clearLog();
basePackageService.getPackageCacheAsMap().put(requestInfo.getPackagePath(), packageInfo);
basePackageService.getPackageCache().put(requestInfo.getPackagePath(), packageInfo);
buildPackageAsync(resourceResolver.getUserID(), packageInfo, requestInfo.getReferencedResources());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public PackageInfo createPackage(ResourceResolver resourceResolver, PackageModel
createPackage(session, packageInfo, basePackageService.buildWorkspaceFilter(packageInfo.getPaths()));

if (PackageStatus.CREATED.equals(packageInfo.getPackageStatus())) {
basePackageService.getPackageCacheAsMap().put(packageInfo.getPackagePath(), packageInfo);
basePackageService.getPackageCache().put(packageInfo.getPackagePath(), packageInfo);
}

return packageInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ public PackageInfo editPackage(final ResourceResolver resourceResolver, final Pa
modifyPackage(session, modificationPackageModel.getPackagePath(), packageInfo, basePackageService.buildWorkspaceFilter(packageInfo.getPaths()));

if (PackageStatus.MODIFIED.equals(packageInfo.getPackageStatus())) {
basePackageService.getPackageCacheAsMap().remove(modificationPackageModel.getPackagePath());
basePackageService.getPackageCacheAsMap().put(packageInfo.getPackagePath(), packageInfo);
basePackageService.getPackageCache().remove(modificationPackageModel.getPackagePath());
basePackageService.getPackageCache().put(packageInfo.getPackagePath(), packageInfo);
}

return packageInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public PackageInfo installPackage(ResourceResolver resourceResolver, InstallPack
packageInfo.clearLog();
packageInfo.addLogMessage(START_INSTALL_MESSAGE + packageInfo.getPackagePath());
packageInfo.addLogMessage(LocalDateTime.now().format(BackpackConstants.DATE_TIME_FORMATTER));
basePackageService.getPackageCacheAsMap().put(installPackageModel.getPackagePath(), packageInfo);
basePackageService.getPackageCache().put(installPackageModel.getPackagePath(), packageInfo);
installPackageAsync(resourceResolver.getUserID(), installPackageModel, packageInfo);
}
return packageInfo;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.exadel.etoolbox.backpack.core.services.pckg.impl;

import com.exadel.etoolbox.backpack.core.dto.response.PackageInfo;
import com.exadel.etoolbox.backpack.core.services.pckg.PackageInfoCache;

import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

final class PackageInfoCacheImpl implements PackageInfoCache {

private static final int MAX_CACHE_SIZE = 100;

private final ConcurrentMap<String, CacheEntry> packageInfos = new ConcurrentHashMap<>();
private final long cacheTtlMillis;

PackageInfoCacheImpl(long cacheTtlMillis) {
this.cacheTtlMillis = cacheTtlMillis;
}

private static final class CacheEntry {
Comment thread
liubou-masiuk marked this conversation as resolved.
Outdated
private final PackageInfo value;
private final long expiresAt;

private CacheEntry(PackageInfo value, long expiresAt) {
this.value = value;
this.expiresAt = expiresAt;
}

private boolean isExpired(long now) {
return expiresAt <= now;
}
}

@Override
public PackageInfo get(String key) {
CacheEntry entry = packageInfos.get(key);
if (entry == null) {
return null;
}
return unwrapEntry(key, entry);
}

@Override
public PackageInfo put(String key, PackageInfo value) {
Objects.requireNonNull(value);
Comment thread
liubou-masiuk marked this conversation as resolved.
Outdated
synchronized (this) {
Comment thread
liubou-masiuk marked this conversation as resolved.
Outdated
evictExpiredEntries();
ensureCapacityFor(key);
CacheEntry previous = packageInfos.put(key, newCacheEntry(value));
return previous == null ? null : unwrapEntry(key, previous);
}
}

@Override
public PackageInfo remove(String key) {
CacheEntry removed = packageInfos.remove(key);
return removed == null ? null : unwrapEntry(key, removed);
}

@Override
public void clear() {
packageInfos.clear();
}

private CacheEntry newCacheEntry(PackageInfo value) {
long expiresAt;
if (cacheTtlMillis < 0) {
expiresAt = Long.MAX_VALUE;
} else if (cacheTtlMillis == 0) {
expiresAt = 0L;
Comment thread
liubou-masiuk marked this conversation as resolved.
} else {
expiresAt = System.currentTimeMillis() + cacheTtlMillis;
}
return new CacheEntry(value, expiresAt);
}

private PackageInfo unwrapEntry(String key, CacheEntry entry) {
long now = System.currentTimeMillis();
if (entry.isExpired(now)) {
if (key != null) {
packageInfos.remove(key, entry);
}
return null;
}
return entry.value;
}

private void evictExpiredEntries() {
long now = System.currentTimeMillis();
packageInfos.entrySet()
.removeIf(entry -> entry.getValue().isExpired(now));
}

private void ensureCapacityFor(String key) {
if (packageInfos.containsKey(key) || packageInfos.size() < MAX_CACHE_SIZE) {
return;
}
evictOneEntry();
}

private void evictOneEntry() {
Iterator<Map.Entry<String, CacheEntry>> iterator = packageInfos.entrySet().iterator();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we evict not any entry but the oldest one?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but this will introduce some complexity - we don't have something like concurrentLinkedHashMap ootb, so we'll have to track the incertion order manually. We'll have to add more synchronized blocks to basically all main methods. I am just not sure that this is justified, for this case

if (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
Comment thread
liubou-masiuk marked this conversation as resolved.
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public class PackageInfoServiceImpl implements PackageInfoService {

@Override
public PackageInfo getPackageInfo(final ResourceResolver resourceResolver, final String packagePath) {
PackageInfo packageInfo = basePackageService.getPackageCacheAsMap().get(packagePath);
PackageInfo packageInfo = basePackageService.getPackageCache().get(packagePath);
if (packageInfo != null) {
return packageInfo;
}
Expand All @@ -79,7 +79,7 @@ public PackageInfo getPackageInfo(final ResourceResolver resourceResolver, final
if (packageNode != null) {
jcrPackage = packMgr.open(packageNode);
getPackageInfo(packageInfo, jcrPackage, packageNode);
basePackageService.getPackageCacheAsMap().put(packagePath, packageInfo);
basePackageService.getPackageCache().put(packagePath, packageInfo);
}
}
} catch (RepositoryException e) {
Expand Down Expand Up @@ -234,7 +234,7 @@ private PackageModel getPackageModel(final JcrPackage jcrPackage) throws Reposit
}

/**
* Called by {@link PackageInfoService#getPackageInfo(ResourceResolver, PackageInfoModel)} to populate a preliminarily
* Called by {@link PackageInfoService#getPackageInfo(ResourceResolver, String)} to populate a preliminarily
* initialized {@link PackageInfo} object as it represents an <i>actual</i> storage item, with information on
* package specifics
*
Expand Down Expand Up @@ -285,7 +285,7 @@ private void getPackageInfo(final PackageInfo packageInfo, final JcrPackage jcrP
public PackageInfo getLatestPackageBuildInfo(final LatestPackageInfoModel latestPackageInfoModel) {
String packagePath = latestPackageInfoModel.getPackagePath();
String packageNotExistMsg = String.format(BackpackConstants.PACKAGE_DOES_NOT_EXIST_MESSAGE, packagePath);
PackageInfo completeBuildInfo = basePackageService.getPackageCacheAsMap().get(packagePath);
PackageInfo completeBuildInfo = basePackageService.getPackageCache().get(packagePath);
PackageInfo partialBuildInfo;

if (completeBuildInfo != null) {
Expand Down
Loading
Loading