mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
REPO-4329 Configure Custom Renditions (#537)
Break done of changes: - Removed rendition definition spring beans and created 0100-baseRenditions.json. Changes are picked up periodically by the running system. - Refactored TransformServiceRegistryImpl to extract ConfigFileFinder to reads json from resources, files or directories. - Refactored TransformServiceRegistryImpl to extract ConfigScheduler to periodically reads config data. - Used ConfigFileFinder and ConfigScheduler in RenditionDefinition2Impl to read rendition defs. - Removed the need for a current 'Data' parameter to be passed to register methods when adding transforms or renditions. - Changes in test classes of AbstractRenditionIntegrationTests to force the config to be read once before each test, as tests were sometimes not getting the correct config due to scheduling.
This commit is contained in:
7
pom.xml
7
pom.xml
@@ -64,6 +64,7 @@
|
||||
<dependency.activemq.version>5.15.9</dependency.activemq.version>
|
||||
<dependency.pdfbox.version>2.0.16</dependency.pdfbox.version>
|
||||
<dependency.cxf.version>3.3.2</dependency.cxf.version>
|
||||
<dependency.jackson.version>2.9.9</dependency.jackson.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -545,6 +546,12 @@
|
||||
<version>1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${dependency.jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Surf -->
|
||||
<dependency>
|
||||
<groupId>org.alfresco.surf</groupId>
|
||||
|
@@ -62,7 +62,7 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl
|
||||
private static final String URL = ".url";
|
||||
static final String STRICT_MIMETYPE_CHECK_WHITELIST_MIMETYPES = "transformer.strict.mimetype.check.whitelist.mimetypes";
|
||||
|
||||
class LocalData extends TransformServiceRegistryImpl.Data
|
||||
public class LocalData extends TransformServiceRegistryImpl.Data
|
||||
{
|
||||
private Map<String, LocalTransform> localTransforms = new HashMap<>();
|
||||
}
|
||||
@@ -130,35 +130,40 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Data readConfig() throws IOException
|
||||
public boolean readConfig() throws IOException
|
||||
{
|
||||
Data data = createData();
|
||||
CombinedConfig combinedConfig = new CombinedConfig(getLog());
|
||||
List<String> urls = getTEngineUrls();
|
||||
boolean successReadingRemoteConfig = combinedConfig.addRemoteConfig(urls, "T-Engine");
|
||||
setSuccessReadingRemoteConfig(data, successReadingRemoteConfig);
|
||||
combinedConfig.addLocalConfig("alfresco/transformers");
|
||||
if (!pipelineConfigDir.isEmpty())
|
||||
boolean successReadingConfig = combinedConfig.addRemoteConfig(urls, "T-Engine");
|
||||
successReadingConfig &= combinedConfig.addLocalConfig("alfresco/transforms");
|
||||
if (pipelineConfigDir != null && !pipelineConfigDir.isBlank())
|
||||
{
|
||||
combinedConfig.addLocalConfig(pipelineConfigDir);
|
||||
successReadingConfig &= combinedConfig.addLocalConfig(pipelineConfigDir);
|
||||
}
|
||||
combinedConfig.register(data, this);
|
||||
return data;
|
||||
combinedConfig.register(this);
|
||||
return successReadingConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Data createData()
|
||||
public synchronized LocalData getData()
|
||||
{
|
||||
return (LocalData)super.getData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data createData()
|
||||
{
|
||||
return new LocalData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void register(Data data, Transformer transformer, String baseUrl, String readFrom)
|
||||
protected void register(Transformer transformer, String baseUrl, String readFrom)
|
||||
{
|
||||
try
|
||||
{
|
||||
String name = transformer.getTransformerName();
|
||||
Map<String, LocalTransform> localTransforms = ((LocalData)data).localTransforms;
|
||||
LocalData data = getData();
|
||||
Map<String, LocalTransform> localTransforms = data.localTransforms;
|
||||
if (name == null || localTransforms.get(name) != null)
|
||||
{
|
||||
throw new IllegalArgumentException("Local transformer names must exist and be unique (" + name + ")."+
|
||||
@@ -238,7 +243,7 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl
|
||||
}
|
||||
}
|
||||
localTransforms.put(name, localTransform);
|
||||
super.register(data, transformer, baseUrl, readFrom);
|
||||
super.register(transformer, baseUrl, readFrom);
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
@@ -396,7 +401,7 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl
|
||||
if (getFirstTime())
|
||||
{
|
||||
setFirstTime(false);
|
||||
transformerDebug.debug("Local transforms "+getCounts()+" are " + (enabled ? "enabled" : "disabled"));
|
||||
transformerDebug.debug("Local transforms "+getData()+" are " + (enabled ? "enabled" : "disabled"));
|
||||
}
|
||||
|
||||
return enabled
|
||||
@@ -419,7 +424,8 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl
|
||||
String sourceMimetype, String targetMimetype, long sourceSizeInBytes)
|
||||
{
|
||||
String name = getTransformerName(sourceMimetype, sourceSizeInBytes, targetMimetype, actualOptions, renditionName);
|
||||
Map<String, LocalTransform> localTransforms = ((LocalData)data).localTransforms;
|
||||
LocalData data = getData();
|
||||
Map<String, LocalTransform> localTransforms = data.localTransforms;
|
||||
return localTransforms.get(name);
|
||||
}
|
||||
}
|
||||
|
@@ -1717,7 +1717,13 @@ public class TransformerDebug implements ApplicationContextAware
|
||||
finally
|
||||
{
|
||||
setStringBuilder(null);
|
||||
try
|
||||
{
|
||||
renditionDefinitionRegistry2.unregister(testRenditionName);
|
||||
}
|
||||
catch (IllegalArgumentException ignore)
|
||||
{
|
||||
}
|
||||
deleteSourceNode(sourceNodeRef);
|
||||
}
|
||||
return sb.toString();
|
||||
|
@@ -65,6 +65,11 @@ public class LegacyTransformServiceRegistry extends AbstractTransformServiceRegi
|
||||
firstTime = true;
|
||||
}
|
||||
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setTransformerDebug(TransformerDebug transformerDebug)
|
||||
{
|
||||
this.transformerDebug = transformerDebug;
|
||||
|
@@ -25,9 +25,19 @@
|
||||
*/
|
||||
package org.alfresco.repo.rendition2;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.alfresco.transform.client.model.config.TransformServiceRegistry;
|
||||
import org.alfresco.util.ConfigFileFinder;
|
||||
import org.alfresco.util.ConfigScheduler;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.quartz.CronExpression;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -39,17 +49,204 @@ import java.util.Set;
|
||||
*
|
||||
* @author adavis
|
||||
*/
|
||||
public class RenditionDefinitionRegistry2Impl implements RenditionDefinitionRegistry2
|
||||
public class RenditionDefinitionRegistry2Impl implements RenditionDefinitionRegistry2, InitializingBean
|
||||
{
|
||||
private TransformServiceRegistry transformServiceRegistry;
|
||||
private static final Log log = LogFactory.getLog(RenditionDefinitionRegistry2Impl.class);
|
||||
|
||||
private final Map<String, RenditionDefinition2> renditionDefinitions = new HashMap();
|
||||
private final Map<String, Set<Pair<String, Long>>> renditionsFor = new HashMap<>();
|
||||
static class Data
|
||||
{
|
||||
Map<String, RenditionDefinition2> renditionDefinitions = new HashMap();
|
||||
Map<String, Set<Pair<String, Long>>> renditionsFor = new HashMap<>();
|
||||
private int count = 0;
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "("+count+")";
|
||||
}
|
||||
}
|
||||
|
||||
static class RenditionDef
|
||||
{
|
||||
private String renditionName;
|
||||
private String targetMediaType;
|
||||
private Set<RenditionOpt> options;
|
||||
|
||||
public void setRenditionName(String renditionName)
|
||||
{
|
||||
this.renditionName = renditionName;
|
||||
}
|
||||
|
||||
public void setTargetMediaType(String targetMediaType)
|
||||
{
|
||||
this.targetMediaType = targetMediaType;
|
||||
}
|
||||
|
||||
public void setOptions(Set<RenditionOpt> options)
|
||||
{
|
||||
this.options = options;
|
||||
}
|
||||
}
|
||||
|
||||
static class RenditionOpt
|
||||
{
|
||||
private String name;
|
||||
private String value;
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private TransformServiceRegistry transformServiceRegistry;
|
||||
private String renditionConfigDir;
|
||||
private String timeoutDefault;
|
||||
private ObjectMapper jsonObjectMapper;
|
||||
private CronExpression cronExpression;
|
||||
private CronExpression initialAndOnErrorCronExpression;
|
||||
|
||||
private ConfigScheduler<Data> configScheduler = new ConfigScheduler(this)
|
||||
{
|
||||
@Override
|
||||
public boolean readConfig() throws IOException
|
||||
{
|
||||
return RenditionDefinitionRegistry2Impl.this.readConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createData()
|
||||
{
|
||||
return RenditionDefinitionRegistry2Impl.this.createData();
|
||||
}
|
||||
};
|
||||
private ConfigFileFinder configFileFinder;
|
||||
|
||||
public void setTransformServiceRegistry(TransformServiceRegistry transformServiceRegistry)
|
||||
{
|
||||
this.transformServiceRegistry = transformServiceRegistry;
|
||||
renditionsFor.clear();
|
||||
}
|
||||
|
||||
public void setRenditionConfigDir(String renditionConfigDir)
|
||||
{
|
||||
this.renditionConfigDir = renditionConfigDir;
|
||||
}
|
||||
|
||||
public void setTimeoutDefault(String timeoutDefault)
|
||||
{
|
||||
this.timeoutDefault = timeoutDefault;
|
||||
}
|
||||
|
||||
public void setJsonObjectMapper(ObjectMapper jsonObjectMapper)
|
||||
{
|
||||
this.jsonObjectMapper = jsonObjectMapper;
|
||||
}
|
||||
|
||||
public CronExpression getCronExpression()
|
||||
{
|
||||
return cronExpression;
|
||||
}
|
||||
|
||||
public void setCronExpression(CronExpression cronExpression)
|
||||
{
|
||||
this.cronExpression = cronExpression;
|
||||
}
|
||||
|
||||
public CronExpression getInitialAndOnErrorCronExpression()
|
||||
{
|
||||
return initialAndOnErrorCronExpression;
|
||||
}
|
||||
|
||||
public void setInitialAndOnErrorCronExpression(CronExpression initialAndOnErrorCronExpression)
|
||||
{
|
||||
this.initialAndOnErrorCronExpression = initialAndOnErrorCronExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception
|
||||
{
|
||||
PropertyCheck.mandatory(this, "transformServiceRegistry", transformServiceRegistry);
|
||||
PropertyCheck.mandatory(this, "renditionConfigDir", renditionConfigDir);
|
||||
PropertyCheck.mandatory(this, "timeoutDefault", timeoutDefault);
|
||||
PropertyCheck.mandatory(this, "jsonObjectMapper", jsonObjectMapper);
|
||||
if (cronExpression != null)
|
||||
{
|
||||
PropertyCheck.mandatory(this, "initialAndOnErrorCronExpression", initialAndOnErrorCronExpression);
|
||||
}
|
||||
configFileFinder = new ConfigFileFinder(jsonObjectMapper)
|
||||
{
|
||||
@Override
|
||||
protected void readJson(JsonNode jsonNode, String readFromMessage, String baseUrl) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
JsonNode renditions = jsonNode.get("renditions");
|
||||
if (renditions != null && renditions.isArray())
|
||||
{
|
||||
for (JsonNode rendition : renditions)
|
||||
{
|
||||
RenditionDef def = jsonObjectMapper.convertValue(rendition, RenditionDef.class);
|
||||
Map<String, String> map = new HashMap<>();
|
||||
if (def.options != null)
|
||||
{
|
||||
def.options.forEach(o -> map.put(o.name, o.value));
|
||||
}
|
||||
if (!map.containsKey(RenditionDefinition2.TIMEOUT))
|
||||
{
|
||||
map.put(RenditionDefinition2.TIMEOUT, timeoutDefault);
|
||||
}
|
||||
new RenditionDefinition2Impl(def.renditionName, def.targetMediaType, map,
|
||||
RenditionDefinitionRegistry2Impl.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
log.error("Error reading "+readFromMessage+" "+e.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
configScheduler.run(true, log, cronExpression, initialAndOnErrorCronExpression);
|
||||
}
|
||||
|
||||
public Data createData()
|
||||
{
|
||||
return new Data();
|
||||
}
|
||||
|
||||
public synchronized Data getData()
|
||||
{
|
||||
return configScheduler.getData();
|
||||
}
|
||||
|
||||
public boolean readConfig() throws IOException
|
||||
{
|
||||
boolean successReadingConfig = configFileFinder.readFiles("alfresco/renditions", log);
|
||||
if (renditionConfigDir != null && !renditionConfigDir.isBlank())
|
||||
{
|
||||
successReadingConfig &= configFileFinder.readFiles(renditionConfigDir, log);
|
||||
}
|
||||
return successReadingConfig;
|
||||
}
|
||||
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,12 +268,14 @@ public class RenditionDefinitionRegistry2Impl implements RenditionDefinitionRegi
|
||||
{
|
||||
throw new IllegalArgumentException("RenditionDefinition "+renditionName+" was already registered.");
|
||||
}
|
||||
renditionDefinitions.put(renditionName, renditionDefinition);
|
||||
Data data = getData();
|
||||
data.renditionDefinitions.put(renditionName, renditionDefinition);
|
||||
}
|
||||
|
||||
public void unregister(String renditionName)
|
||||
{
|
||||
if (renditionDefinitions.remove(renditionName) == null)
|
||||
Data data = getData();
|
||||
if (data.renditionDefinitions.remove(renditionName) == null)
|
||||
{
|
||||
throw new IllegalArgumentException("RenditionDefinition "+renditionName+" was not registered.");
|
||||
}
|
||||
@@ -85,20 +284,21 @@ public class RenditionDefinitionRegistry2Impl implements RenditionDefinitionRegi
|
||||
@Override
|
||||
public Set<String> getRenditionNames()
|
||||
{
|
||||
return renditionDefinitions.keySet();
|
||||
return getData().renditionDefinitions.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRenditionNamesFrom(String sourceMimetype, long size)
|
||||
{
|
||||
Set<Pair<String, Long>> renditionNamesWithMaxSize;
|
||||
synchronized (renditionsFor)
|
||||
Data data = getData();
|
||||
synchronized (data.renditionsFor)
|
||||
{
|
||||
renditionNamesWithMaxSize = renditionsFor.get(sourceMimetype);
|
||||
renditionNamesWithMaxSize = data.renditionsFor.get(sourceMimetype);
|
||||
if (renditionNamesWithMaxSize == null)
|
||||
{
|
||||
renditionNamesWithMaxSize = getRenditionNamesWithMaxSize(sourceMimetype);
|
||||
renditionsFor.put(sourceMimetype, renditionNamesWithMaxSize);
|
||||
data.renditionsFor.put(sourceMimetype, renditionNamesWithMaxSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +325,8 @@ public class RenditionDefinitionRegistry2Impl implements RenditionDefinitionRegi
|
||||
private Set<Pair<String,Long>> getRenditionNamesWithMaxSize(String sourceMimetype)
|
||||
{
|
||||
Set<Pair<String,Long>> renditions = new HashSet();
|
||||
for (Map.Entry<String, RenditionDefinition2> entry : renditionDefinitions.entrySet())
|
||||
Data data = getData();
|
||||
for (Map.Entry<String, RenditionDefinition2> entry : data.renditionDefinitions.entrySet())
|
||||
{
|
||||
RenditionDefinition2 renditionDefinition2 = entry.getValue();
|
||||
String targetMimetype = renditionDefinition2.getTargetMimetype();
|
||||
@@ -145,6 +346,7 @@ public class RenditionDefinitionRegistry2Impl implements RenditionDefinitionRegi
|
||||
@Override
|
||||
public RenditionDefinition2 getRenditionDefinition(String renditionName)
|
||||
{
|
||||
return renditionDefinitions.get(renditionName);
|
||||
Data data = getData();
|
||||
return data.renditionDefinitions.get(renditionName);
|
||||
}
|
||||
}
|
||||
|
@@ -25,12 +25,12 @@
|
||||
*/
|
||||
package org.alfresco.transform.client.model.config;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.util.ConfigFileFinder;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.StatusLine;
|
||||
@@ -40,26 +40,15 @@ import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* This class recreates the json format used in ACS 6.1 where we just had an array of transformers and each
|
||||
@@ -84,6 +73,7 @@ public class CombinedConfig
|
||||
private Map<String, ArrayNode> allTransformOptions = new HashMap<>();
|
||||
private List<TransformNodeAndItsOrigin> allTransforms = new ArrayList<>();
|
||||
private ObjectMapper jsonObjectMapper = new ObjectMapper();
|
||||
private ConfigFileFinder configFileFinder;
|
||||
|
||||
static class TransformNodeAndItsOrigin
|
||||
{
|
||||
@@ -116,26 +106,62 @@ public class CombinedConfig
|
||||
public CombinedConfig(Log log)
|
||||
{
|
||||
this.log = log;
|
||||
|
||||
configFileFinder = new ConfigFileFinder(jsonObjectMapper)
|
||||
{
|
||||
@Override
|
||||
protected void readJson(JsonNode jsonNode, String readFromMessage, String baseUrl) throws IOException
|
||||
{
|
||||
JsonNode transformOptions = jsonNode.get(TRANSFORM_OPTIONS);
|
||||
if (transformOptions != null && transformOptions.isObject())
|
||||
{
|
||||
Iterator<Map.Entry<String, JsonNode>> iterator = transformOptions.fields();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
Map.Entry<String, JsonNode> entry = iterator.next();
|
||||
|
||||
JsonNode options = entry.getValue();
|
||||
if (options.isArray())
|
||||
{
|
||||
String optionsName = entry.getKey();
|
||||
allTransformOptions.put(optionsName, (ArrayNode)options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode transformers = jsonNode.get(TRANSFORMERS);
|
||||
if (transformers != null && transformers.isArray())
|
||||
{
|
||||
for (JsonNode transformer : transformers)
|
||||
{
|
||||
if (transformer.isObject())
|
||||
{
|
||||
allTransforms.add(new TransformNodeAndItsOrigin((ObjectNode)transformer, baseUrl, readFromMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean addRemoteConfig(List<String> urls, String remoteType)
|
||||
{
|
||||
boolean successReadingRemoteConfig = true;
|
||||
boolean successReadingConfig = true;
|
||||
for (String url : urls)
|
||||
{
|
||||
if (!addRemoteConfig(url, remoteType))
|
||||
{
|
||||
successReadingRemoteConfig = false;
|
||||
successReadingConfig = false;
|
||||
}
|
||||
}
|
||||
return successReadingRemoteConfig;
|
||||
return successReadingConfig;
|
||||
}
|
||||
|
||||
private boolean addRemoteConfig(String baseUrl, String remoteType)
|
||||
{
|
||||
String url = baseUrl + TRANSFORM_CONFIG;
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
boolean successReadingRemoteConfig = true;
|
||||
boolean successReadingConfig = true;
|
||||
try
|
||||
{
|
||||
try (CloseableHttpClient httpclient = HttpClients.createDefault())
|
||||
@@ -160,10 +186,10 @@ public class CombinedConfig
|
||||
try (StringReader reader = new StringReader(content))
|
||||
{
|
||||
int transformCount = allTransforms.size();
|
||||
addJsonSource(reader, baseUrl, remoteType+" on "+baseUrl);
|
||||
configFileFinder.readFile(reader, remoteType+" on "+baseUrl, "json", baseUrl, log);
|
||||
if (transformCount == allTransforms.size())
|
||||
{
|
||||
successReadingRemoteConfig = false;
|
||||
successReadingConfig = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,9 +227,9 @@ public class CombinedConfig
|
||||
catch (AlfrescoRuntimeException e)
|
||||
{
|
||||
log.error(e.getMessage());
|
||||
successReadingRemoteConfig = false;
|
||||
successReadingConfig = false;
|
||||
}
|
||||
return successReadingRemoteConfig;
|
||||
return successReadingConfig;
|
||||
}
|
||||
|
||||
// Tests mock the return values
|
||||
@@ -235,117 +261,15 @@ public class CombinedConfig
|
||||
return message;
|
||||
}
|
||||
|
||||
public void addLocalConfig(String path) throws IOException
|
||||
public boolean addLocalConfig(String path) throws IOException
|
||||
{
|
||||
boolean somethingRead = false;
|
||||
final File jarFile = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
|
||||
if (jarFile.isFile())
|
||||
{
|
||||
JarFile jar = new JarFile(jarFile);
|
||||
Enumeration<JarEntry> entries = jar.entries(); // gives ALL entries in jar
|
||||
String prefix = path + "/";
|
||||
List<String> names = new ArrayList<>();
|
||||
while (entries.hasMoreElements())
|
||||
{
|
||||
final String name = entries.nextElement().getName();
|
||||
if (name.startsWith(prefix) && name.length() > prefix.length())
|
||||
{
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
Collections.sort(names);
|
||||
for (String name : names)
|
||||
{
|
||||
somethingRead = true;
|
||||
addJsonSource(new InputStreamReader(getResourceAsStream(name)), null,
|
||||
name+" from jar "+jarFile.getName());
|
||||
return configFileFinder.readFiles(path, log);
|
||||
}
|
||||
|
||||
jar.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
URL url = getClass().getClassLoader().getResource(path);
|
||||
if (url != null)
|
||||
{
|
||||
File root = new File(url.getPath());
|
||||
String rootPath = root.getPath();
|
||||
if (root.isDirectory())
|
||||
{
|
||||
File[] files = root.listFiles();
|
||||
Arrays.sort(files, (file1, file2) -> file1.getName().compareTo(file2.getName()));
|
||||
for (File file: files)
|
||||
{
|
||||
somethingRead = true;
|
||||
addJsonSource(new FileReader(file), null,"File " + file.getPath());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
somethingRead = true;
|
||||
addJsonSource(new FileReader(root), null, "File " + rootPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!somethingRead)
|
||||
{
|
||||
log.warn("No config read from "+path);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream getResourceAsStream(String resource)
|
||||
{
|
||||
final InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
|
||||
return in == null ? getClass().getResourceAsStream(resource) : in;
|
||||
}
|
||||
|
||||
private void addJsonSource(Reader reader, String baseUrl, String readFrom) throws IOException
|
||||
{
|
||||
JsonNode jsonNode = jsonObjectMapper.readValue(reader, new TypeReference<JsonNode>() {});
|
||||
if (log.isTraceEnabled())
|
||||
{
|
||||
log.trace(readFrom+" config is: "+jsonNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.debug(readFrom+" config read");
|
||||
}
|
||||
|
||||
JsonNode transformOptions = jsonNode.get(TRANSFORM_OPTIONS);
|
||||
if (transformOptions != null && transformOptions.isObject())
|
||||
{
|
||||
Iterator<Map.Entry<String, JsonNode>> iterator = transformOptions.fields();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
Map.Entry<String, JsonNode> entry = iterator.next();
|
||||
|
||||
JsonNode options = entry.getValue();
|
||||
if (options.isArray())
|
||||
{
|
||||
String optionsName = entry.getKey();
|
||||
allTransformOptions.put(optionsName, (ArrayNode)options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode transformers = jsonNode.get(TRANSFORMERS);
|
||||
if (transformers != null && transformers.isArray())
|
||||
{
|
||||
for (JsonNode transformer : transformers)
|
||||
{
|
||||
if (transformer.isObject())
|
||||
{
|
||||
allTransforms.add(new TransformNodeAndItsOrigin((ObjectNode)transformer, baseUrl, readFrom));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void register(TransformServiceRegistryImpl.Data data, TransformServiceRegistryImpl registry) throws IOException
|
||||
public void register(TransformServiceRegistryImpl registry) throws IOException
|
||||
{
|
||||
List<TransformAndItsOrigin> transformers = getTransforms();
|
||||
transformers.forEach(t->registry.register(data, t.transform, t.baseUrl, t.readFrom));
|
||||
transformers.forEach(t->registry.register(t.transform, t.baseUrl, t.readFrom));
|
||||
}
|
||||
|
||||
public List<TransformAndItsOrigin> getTransforms() throws IOException
|
||||
|
@@ -27,21 +27,11 @@ package org.alfresco.transform.client.model.config;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.alfresco.util.ConfigScheduler;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.quartz.CronExpression;
|
||||
import org.quartz.CronScheduleBuilder;
|
||||
import org.quartz.CronTrigger;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobBuilder;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.TriggerBuilder;
|
||||
import org.quartz.impl.StdSchedulerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -68,7 +58,12 @@ public abstract class TransformServiceRegistryImpl implements TransformServiceRe
|
||||
private int transformerCount = 0;
|
||||
private int transformCount = 0;
|
||||
boolean firstTime = true;
|
||||
boolean successReadingRemoteConfig = true;
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "("+transformerCount+":"+transformCount+")";
|
||||
}
|
||||
}
|
||||
|
||||
static class SupportedTransform
|
||||
@@ -90,40 +85,31 @@ public abstract class TransformServiceRegistryImpl implements TransformServiceRe
|
||||
}
|
||||
}
|
||||
|
||||
public static class TransformServiceRegistryJob implements Job
|
||||
{
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException
|
||||
{
|
||||
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
|
||||
TransformServiceRegistryImpl registry = (TransformServiceRegistryImpl)dataMap.get("registry");
|
||||
registry.readConfigAndReplace();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean enabled = true;
|
||||
protected Data data;
|
||||
private ObjectMapper jsonObjectMapper;
|
||||
private Scheduler scheduler;
|
||||
private CronExpression cronExpression;
|
||||
private CronExpression initialAndOnErrorCronExpression;
|
||||
private boolean normalCronSchedule;
|
||||
|
||||
private ConfigScheduler<Data> configScheduler = new ConfigScheduler(this)
|
||||
{
|
||||
@Override
|
||||
public boolean readConfig() throws IOException
|
||||
{
|
||||
return TransformServiceRegistryImpl.this.readConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createData()
|
||||
{
|
||||
return TransformServiceRegistryImpl.this.createData();
|
||||
}
|
||||
};
|
||||
|
||||
public void setJsonObjectMapper(ObjectMapper jsonObjectMapper)
|
||||
{
|
||||
this.jsonObjectMapper = jsonObjectMapper;
|
||||
}
|
||||
|
||||
public synchronized Scheduler getScheduler()
|
||||
{
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public synchronized void setScheduler(Scheduler scheduler)
|
||||
{
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
public CronExpression getCronExpression()
|
||||
{
|
||||
return cronExpression;
|
||||
@@ -147,126 +133,31 @@ public abstract class TransformServiceRegistryImpl implements TransformServiceRe
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception
|
||||
{
|
||||
if (jsonObjectMapper == null)
|
||||
PropertyCheck.mandatory(this, "jsonObjectMapper", jsonObjectMapper);
|
||||
if (cronExpression != null)
|
||||
{
|
||||
throw new IllegalStateException("jsonObjectMapper has not been set");
|
||||
}
|
||||
PropertyCheck.mandatory(this, "cronExpression", cronExpression);
|
||||
PropertyCheck.mandatory(this, "initialAndOnErrorCronExpression", initialAndOnErrorCronExpression);
|
||||
|
||||
setData(null);
|
||||
if (enabled)
|
||||
{
|
||||
schedule();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void schedule()
|
||||
{
|
||||
// Don't do an initial readConfigAndReplace() as the first scheduled read can be done almost instantly and
|
||||
// there is little point doing two in the space of a few seconds. If however the scheduler is already running
|
||||
// we do need to run it (this is only from test cases).
|
||||
if (scheduler == null)
|
||||
{
|
||||
StdSchedulerFactory sf = new StdSchedulerFactory();
|
||||
String jobName = getClass().getName()+"Job";
|
||||
try
|
||||
{
|
||||
scheduler = sf.getScheduler();
|
||||
|
||||
JobDetail job = JobBuilder.newJob()
|
||||
.withIdentity(jobName)
|
||||
.ofType(TransformServiceRegistryJob.class)
|
||||
.build();
|
||||
job.getJobDataMap().put("registry", this);
|
||||
CronExpression cronExpression = normalCronSchedule ? this.cronExpression : initialAndOnErrorCronExpression;
|
||||
CronTrigger trigger = TriggerBuilder.newTrigger()
|
||||
.withIdentity(jobName+"Trigger", Scheduler.DEFAULT_GROUP)
|
||||
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
|
||||
.build();
|
||||
scheduler.startDelayed(0);
|
||||
scheduler.scheduleJob(job, trigger);
|
||||
}
|
||||
catch (SchedulerException e)
|
||||
{
|
||||
getLog().error("Failed to start "+jobName+" "+e.getMessage());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
readConfigAndReplace();
|
||||
}
|
||||
}
|
||||
|
||||
protected void readConfigAndReplace()
|
||||
{
|
||||
boolean successReadingRemoteConfig = true;
|
||||
Log log = getLog();
|
||||
log.debug("Config read started");
|
||||
try
|
||||
{
|
||||
Data data = readConfig();
|
||||
successReadingRemoteConfig = data.successReadingRemoteConfig;
|
||||
setData(data);
|
||||
log.debug("Config read finished "+getCounts());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
successReadingRemoteConfig = false;
|
||||
log.error("Config read failed. "+e.getMessage(), e);
|
||||
configScheduler.run(enabled, log, cronExpression, initialAndOnErrorCronExpression);
|
||||
}
|
||||
|
||||
// Switch schedule sequence if we were on the normal schedule and we now have problems or if
|
||||
// we are on the initial/error schedule and there were no errors.
|
||||
if (normalCronSchedule && !successReadingRemoteConfig ||
|
||||
!normalCronSchedule && successReadingRemoteConfig)
|
||||
{
|
||||
normalCronSchedule = !normalCronSchedule;
|
||||
if (scheduler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
CronExpression cronExpression = normalCronSchedule ? this.cronExpression : initialAndOnErrorCronExpression;
|
||||
scheduler.clear();
|
||||
scheduler = null;
|
||||
schedule();
|
||||
}
|
||||
catch (SchedulerException e)
|
||||
{
|
||||
getLog().error("Problem stopping scheduler for transformer configuration "+e.getMessage());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("Switch schedule "+normalCronSchedule+" WITHOUT new schedule");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Data readConfig() throws IOException;
|
||||
|
||||
private synchronized void setData(Data data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
protected synchronized Data getData()
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
data = createData();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
protected Data createData()
|
||||
public Data createData()
|
||||
{
|
||||
return new Data();
|
||||
}
|
||||
|
||||
protected void setSuccessReadingRemoteConfig(Data data, boolean successReadingRemoteConfig)
|
||||
public synchronized Data getData()
|
||||
{
|
||||
data.successReadingRemoteConfig = successReadingRemoteConfig;
|
||||
return configScheduler.getData();
|
||||
}
|
||||
|
||||
public abstract boolean readConfig() throws IOException;
|
||||
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled)
|
||||
@@ -287,14 +178,15 @@ public abstract class TransformServiceRegistryImpl implements TransformServiceRe
|
||||
|
||||
protected abstract Log getLog();
|
||||
|
||||
public void register(Data data, Reader reader, String readFrom) throws IOException
|
||||
public void register(Reader reader, String readFrom) throws IOException
|
||||
{
|
||||
List<Transformer> transformers = jsonObjectMapper.readValue(reader, new TypeReference<List<Transformer>>(){});
|
||||
transformers.forEach(t -> register(data, t, null, readFrom));
|
||||
transformers.forEach(t -> register(t, null, readFrom));
|
||||
}
|
||||
|
||||
protected void register(Data data, Transformer transformer, String baseUrl, String readFrom)
|
||||
protected void register(Transformer transformer, String baseUrl, String readFrom)
|
||||
{
|
||||
Data data = getData();
|
||||
data.transformerCount++;
|
||||
transformer.getSupportedSourceAndTargetList().forEach(
|
||||
e -> data.transformers.computeIfAbsent(e.getSourceMediaType(),
|
||||
@@ -304,11 +196,6 @@ public abstract class TransformServiceRegistryImpl implements TransformServiceRe
|
||||
transformer.getTransformOptions(), e.getMaxSourceSizeBytes(), e.getPriority())));
|
||||
}
|
||||
|
||||
protected String getCounts()
|
||||
{
|
||||
return "("+getData().transformerCount+":"+getData().transformCount+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(String sourceMimetype, long sourceSizeInBytes, String targetMimetype,
|
||||
Map<String, String> actualOptions, String renditionName)
|
||||
@@ -366,7 +253,6 @@ public abstract class TransformServiceRegistryImpl implements TransformServiceRe
|
||||
renditionName = null;
|
||||
}
|
||||
|
||||
|
||||
Data data = getData();
|
||||
List<SupportedTransform> transformListBySize = renditionName == null ? null
|
||||
: data.cachedSupportedTransformList.computeIfAbsent(renditionName, k -> new ConcurrentHashMap<>()).get(sourceMimetype);
|
||||
|
164
src/main/java/org/alfresco/util/ConfigFileFinder.java
Normal file
164
src/main/java/org/alfresco/util/ConfigFileFinder.java
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2019 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.util;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* Used to find configuration files as resources from the jar file or from some external location. The path supplied
|
||||
* to {@link #readFiles(String, Log)} may be a directory name. Normally used by ConfigScheduler.
|
||||
*
|
||||
* @author adavis
|
||||
*/
|
||||
public abstract class ConfigFileFinder
|
||||
{
|
||||
private final ObjectMapper jsonObjectMapper;
|
||||
|
||||
public ConfigFileFinder(ObjectMapper jsonObjectMapper)
|
||||
{
|
||||
this.jsonObjectMapper = jsonObjectMapper;
|
||||
}
|
||||
|
||||
public boolean readFiles(String path, Log log)
|
||||
{
|
||||
boolean successReadingConfig = true;
|
||||
try
|
||||
{
|
||||
boolean somethingRead = false;
|
||||
final File jarFile = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
|
||||
if (jarFile.isFile())
|
||||
{
|
||||
Enumeration<JarEntry> entries = new JarFile(jarFile).entries(); // gives ALL entries in jar
|
||||
String prefix = path + "/";
|
||||
List<String> names = new ArrayList<>();
|
||||
while (entries.hasMoreElements())
|
||||
{
|
||||
final String name = entries.nextElement().getName();
|
||||
if (name.startsWith(prefix) && name.length() > prefix.length())
|
||||
{
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
Collections.sort(names);
|
||||
for (String name : names)
|
||||
{
|
||||
somethingRead = true;
|
||||
successReadingConfig &= readFile(new InputStreamReader(getResourceAsStream(name)),"resource", name, null, log);
|
||||
}
|
||||
|
||||
new JarFile(jarFile).close();
|
||||
}
|
||||
else
|
||||
{
|
||||
URL url = getClass().getClassLoader().getResource(path);
|
||||
if (url != null)
|
||||
{
|
||||
File root = new File(url.getPath());
|
||||
String rootPath = root.getPath();
|
||||
if (root.isDirectory())
|
||||
{
|
||||
File[] files = root.listFiles();
|
||||
Arrays.sort(files, (file1, file2) -> file1.getName().compareTo(file2.getName()));
|
||||
for (File file: files)
|
||||
{
|
||||
somethingRead = true;
|
||||
successReadingConfig &= readFile(new FileReader(file), "file", file.getPath(), null, log);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
somethingRead = true;
|
||||
successReadingConfig = readFile(new FileReader(root), "file", rootPath, null, log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!somethingRead)
|
||||
{
|
||||
log.warn("No config read from "+path);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
log.error("Error reading from "+path);
|
||||
successReadingConfig = false;
|
||||
}
|
||||
return successReadingConfig;
|
||||
}
|
||||
|
||||
private InputStream getResourceAsStream(String resource)
|
||||
{
|
||||
final InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
|
||||
return in == null ? getClass().getResourceAsStream(resource) : in;
|
||||
}
|
||||
|
||||
public boolean readFile(Reader reader, String readFrom, String path, String baseUrl, Log log)
|
||||
{
|
||||
// At the moment it is assumed the file is Json, but that does not need to be the case.
|
||||
// We have the path including extension.
|
||||
boolean successReadingConfig = true;
|
||||
try
|
||||
{
|
||||
JsonNode jsonNode = jsonObjectMapper.readValue(reader, new TypeReference<JsonNode>() {});
|
||||
String readFromMessage = readFrom + ' ' + path;
|
||||
if (log.isTraceEnabled())
|
||||
{
|
||||
log.trace(readFromMessage + " config is: " + jsonNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.debug(readFromMessage + " config read");
|
||||
}
|
||||
readJson(jsonNode, readFromMessage, baseUrl);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("Error reading "+path);
|
||||
successReadingConfig = false;
|
||||
}
|
||||
return successReadingConfig;
|
||||
}
|
||||
|
||||
protected abstract void readJson(JsonNode jsonNode, String readFromMessage, String baseUrl) throws IOException;
|
||||
}
|
223
src/main/java/org/alfresco/util/ConfigScheduler.java
Normal file
223
src/main/java/org/alfresco/util/ConfigScheduler.java
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2019 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.util;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.quartz.CronExpression;
|
||||
import org.quartz.CronScheduleBuilder;
|
||||
import org.quartz.CronTrigger;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobBuilder;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.TriggerBuilder;
|
||||
import org.quartz.impl.StdSchedulerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Used to schedule reading of config. The config is assumed to change from time to time.
|
||||
* Initially or on error the reading frequency is high but slower once no problems are reported.
|
||||
* If the normal cron schedule is not set or is in the past, the config is read only once when
|
||||
* {@link #run(boolean, Log, CronExpression, CronExpression)} is called.
|
||||
*
|
||||
* @author adavis
|
||||
*/
|
||||
public abstract class ConfigScheduler<Data>
|
||||
{
|
||||
public static class ConfigSchedulerJob implements Job
|
||||
{
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException
|
||||
{
|
||||
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
|
||||
ConfigScheduler configScheduler = (ConfigScheduler)dataMap.get(CONFIG_SCHEDULER);
|
||||
boolean successReadingConfig = configScheduler.readConfigAndReplace();
|
||||
configScheduler.changeScheduleOnStateChange(successReadingConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public static final String CONFIG_SCHEDULER = "configScheduler";
|
||||
|
||||
private static final Log defaultLog = LogFactory.getLog(ConfigScheduler.class);
|
||||
private static StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
|
||||
|
||||
private final String jobName;
|
||||
private Log log;
|
||||
private CronExpression cronExpression;
|
||||
private CronExpression initialAndOnErrorCronExpression;
|
||||
|
||||
private Scheduler scheduler;
|
||||
private boolean normalCronSchedule;
|
||||
|
||||
protected Data data;
|
||||
private ThreadLocal<Data> threadData = ThreadLocal.withInitial(() -> data);
|
||||
|
||||
public ConfigScheduler(Object client)
|
||||
{
|
||||
jobName = client.getClass().getName()+"Job";
|
||||
}
|
||||
|
||||
public abstract boolean readConfig() throws IOException;
|
||||
|
||||
public abstract Data createData();
|
||||
|
||||
public synchronized Data getData()
|
||||
{
|
||||
Data data = threadData.get();
|
||||
if (data == null)
|
||||
{
|
||||
data = createData();
|
||||
setData(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private synchronized void setData(Data data)
|
||||
{
|
||||
this.data = data;
|
||||
threadData.set(data);
|
||||
}
|
||||
|
||||
private synchronized void clearData()
|
||||
{
|
||||
this.data = null; // as run() should only be called multiple times in testing, it is okay to discard the
|
||||
// previous data, as there should be no other Threads trying to read it, unless they are
|
||||
// left over from previous tests.
|
||||
threadData.remove(); // we need to pick up the initial value next time (whatever the data value is at that point)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should only be called once in production on startup generally from Spring afterPropertiesSet methods.
|
||||
* In testing it is allowed to call this method multiple times, but in that case it is recommended to pass in a
|
||||
* null cronExpression (or a cronExpression such as a date in the past) so the scheduler is not started. If this is
|
||||
* done, the config is still read, but before the method returns.
|
||||
*/
|
||||
public void run(boolean enabled, Log log, CronExpression cronExpression, CronExpression initialAndOnErrorCronExpression)
|
||||
{
|
||||
clearPreviousSchedule();
|
||||
clearData();
|
||||
if (enabled)
|
||||
{
|
||||
this.log = log == null ? ConfigScheduler.defaultLog : log;
|
||||
Date now = new Date();
|
||||
if (cronExpression != null &&
|
||||
initialAndOnErrorCronExpression != null &&
|
||||
cronExpression.getNextValidTimeAfter(now) != null &&
|
||||
initialAndOnErrorCronExpression.getNextValidTimeAfter(now) != null)
|
||||
{
|
||||
this.cronExpression = cronExpression;
|
||||
this.initialAndOnErrorCronExpression = initialAndOnErrorCronExpression;
|
||||
schedule();
|
||||
}
|
||||
else
|
||||
{
|
||||
readConfigAndReplace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void schedule()
|
||||
{
|
||||
try
|
||||
{
|
||||
scheduler = schedulerFactory.getScheduler();
|
||||
|
||||
JobDetail job = JobBuilder.newJob()
|
||||
.withIdentity(jobName)
|
||||
.ofType(ConfigSchedulerJob.class)
|
||||
.build();
|
||||
job.getJobDataMap().put(CONFIG_SCHEDULER, this);
|
||||
CronExpression cronExpression = normalCronSchedule ? this.cronExpression : initialAndOnErrorCronExpression;
|
||||
CronTrigger trigger = TriggerBuilder.newTrigger()
|
||||
.withIdentity(jobName+"Trigger", Scheduler.DEFAULT_GROUP)
|
||||
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
|
||||
.build();
|
||||
scheduler.startDelayed(0);
|
||||
scheduler.scheduleJob(job, trigger);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("Error scheduling "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void clearPreviousSchedule()
|
||||
{
|
||||
if (scheduler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
scheduler.clear();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("Error clearing previous schedule " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readConfigAndReplace()
|
||||
{
|
||||
boolean successReadingConfig;
|
||||
log.debug("Config read started");
|
||||
Data data = getData();
|
||||
try
|
||||
{
|
||||
Data newData = createData();
|
||||
threadData.set(newData);
|
||||
successReadingConfig = readConfig();
|
||||
data = newData;
|
||||
log.debug("Config read finished "+data+
|
||||
(successReadingConfig ? "" : ". Config replaced but there were problems"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
successReadingConfig = false;
|
||||
log.error("Config read failed. "+e.getMessage(), e);
|
||||
}
|
||||
setData(data);
|
||||
return successReadingConfig;
|
||||
}
|
||||
|
||||
private void changeScheduleOnStateChange(boolean successReadingConfig)
|
||||
{
|
||||
// Switch schedule sequence if we were on the normal schedule and we now have problems or if
|
||||
// we are on the initial/error schedule and there were no errors.
|
||||
if ( normalCronSchedule && !successReadingConfig ||
|
||||
!normalCronSchedule && successReadingConfig)
|
||||
{
|
||||
normalCronSchedule = !normalCronSchedule;
|
||||
clearPreviousSchedule();
|
||||
schedule();
|
||||
}
|
||||
}
|
||||
}
|
@@ -25,16 +25,16 @@
|
||||
*/
|
||||
package org.alfresco.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.web.context.support.ServletContextResourcePatternResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Can be used in Spring configuration to search for all resources matching an array of patterns.
|
||||
*
|
||||
|
@@ -147,9 +147,16 @@
|
||||
|
||||
<!-- Rendition Definition Registry 2 -->
|
||||
<bean id="renditionDefinitionRegistry2" class="org.alfresco.repo.rendition2.RenditionDefinitionRegistry2Impl">
|
||||
<property name="jsonObjectMapper" ref="renditionDefinitionRegistry2JsonObjectMapper" />
|
||||
<property name="transformServiceRegistry" ref="transformServiceRegistry" />
|
||||
<property name="renditionConfigDir" value="${rendition.config.dir}" />
|
||||
<property name="timeoutDefault" value="${system.thumbnail.definition.default.timeoutMs}" />
|
||||
<property name="cronExpression" value="${rendition.config.cronExpression}"></property>
|
||||
<property name="initialAndOnErrorCronExpression" value="${rendition.config.initialAndOnError.cronExpression}"></property>
|
||||
</bean>
|
||||
|
||||
<bean id="renditionDefinitionRegistry2JsonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
|
||||
|
||||
<!-- Process raw content update events -->
|
||||
<bean id="renditionEventProcessor" class="org.alfresco.repo.rendition2.RenditionEventProcessor">
|
||||
<property name="renditionService2" ref="renditionService2" />
|
||||
@@ -157,108 +164,6 @@
|
||||
<property name="transactionService" ref="transactionService" />
|
||||
</bean>
|
||||
|
||||
<!-- The definitions -->
|
||||
|
||||
<bean id="renditionDefinition2Medium" class="org.alfresco.repo.rendition2.RenditionDefinition2Impl">
|
||||
<constructor-arg name="renditionName" value="medium"/>
|
||||
<constructor-arg name="targetMimetype" value="image/jpeg"/>
|
||||
<constructor-arg name="transformOptions">
|
||||
<map>
|
||||
<entry key="resizeWidth" value="100"/>
|
||||
<entry key="resizeHeight" value="100"/>
|
||||
<entry key="maintainAspectRatio" value="true"/>
|
||||
<entry key="thumbnail" value="true"/>
|
||||
<entry key="timeout" value="${system.thumbnail.definition.default.timeoutMs}" />
|
||||
</map>
|
||||
</constructor-arg>
|
||||
<constructor-arg name="registry" ref="renditionDefinitionRegistry2"/>
|
||||
</bean>
|
||||
|
||||
<bean id="renditionDefinition2DocLib" class="org.alfresco.repo.rendition2.RenditionDefinition2Impl">
|
||||
<constructor-arg name="renditionName" value="doclib"/>
|
||||
<constructor-arg name="targetMimetype" value="image/png"/>
|
||||
<constructor-arg name="transformOptions">
|
||||
<map>
|
||||
<entry key="resizeWidth" value="100"/>
|
||||
<entry key="resizeHeight" value="100"/>
|
||||
<entry key="allowEnlargement" value="false" />
|
||||
<entry key="maintainAspectRatio" value="true"/>
|
||||
<entry key="thumbnail" value="true"/>
|
||||
<entry key="timeout" value="${system.thumbnail.definition.default.timeoutMs}" />
|
||||
</map>
|
||||
</constructor-arg>
|
||||
<constructor-arg name="registry" ref="renditionDefinitionRegistry2"/>
|
||||
</bean>
|
||||
|
||||
<bean id="renditionDefinition2Imgpreview" class="org.alfresco.repo.rendition2.RenditionDefinition2Impl">
|
||||
<constructor-arg name="renditionName" value="imgpreview"/>
|
||||
<constructor-arg name="targetMimetype" value="image/jpeg"/>
|
||||
<constructor-arg name="transformOptions">
|
||||
<map>
|
||||
<entry key="resizeWidth" value="960"/>
|
||||
<entry key="resizeHeight" value="960"/>
|
||||
<entry key="allowEnlargement" value="false" />
|
||||
<entry key="maintainAspectRatio" value="true"/>
|
||||
<entry key="thumbnail" value="true"/>
|
||||
<entry key="timeout" value="${system.thumbnail.definition.default.timeoutMs}" />
|
||||
</map>
|
||||
</constructor-arg>
|
||||
<constructor-arg name="registry" ref="renditionDefinitionRegistry2"/>
|
||||
</bean>
|
||||
|
||||
<bean id="renditionDefinition2Avatar" class="org.alfresco.repo.rendition2.RenditionDefinition2Impl">
|
||||
<constructor-arg name="renditionName" value="avatar"/>
|
||||
<constructor-arg name="targetMimetype" value="image/png"/>
|
||||
<constructor-arg name="transformOptions">
|
||||
<map>
|
||||
<entry key="resizeWidth" value="64"/>
|
||||
<entry key="resizeHeight" value="64"/>
|
||||
<entry key="allowEnlargement" value="false" />
|
||||
<entry key="maintainAspectRatio" value="true"/>
|
||||
<entry key="thumbnail" value="true"/>
|
||||
<entry key="timeout" value="${system.thumbnail.definition.default.timeoutMs}" />
|
||||
</map>
|
||||
</constructor-arg>
|
||||
<constructor-arg name="registry" ref="renditionDefinitionRegistry2"/>
|
||||
</bean>
|
||||
|
||||
<bean id="renditionDefinition2Avatar32" class="org.alfresco.repo.rendition2.RenditionDefinition2Impl">
|
||||
<constructor-arg name="renditionName" value="avatar32"/>
|
||||
<constructor-arg name="targetMimetype" value="image/png"/>
|
||||
<constructor-arg name="transformOptions">
|
||||
<map>
|
||||
<entry key="resizeWidth" value="32"/>
|
||||
<entry key="resizeHeight" value="32"/>
|
||||
<entry key="allowEnlargement" value="false" />
|
||||
<entry key="maintainAspectRatio" value="true"/>
|
||||
<entry key="thumbnail" value="true"/>
|
||||
<entry key="timeout" value="${system.thumbnail.definition.default.timeoutMs}" />
|
||||
</map>
|
||||
</constructor-arg>
|
||||
<constructor-arg name="registry" ref="renditionDefinitionRegistry2"/>
|
||||
</bean>
|
||||
|
||||
<bean id="renditionDefinition2Webpreview" class="org.alfresco.repo.rendition2.RenditionDefinition2Impl">
|
||||
<constructor-arg name="renditionName" value="webpreview"/>
|
||||
<constructor-arg name="targetMimetype" value="application/x-shockwave-flash"/>
|
||||
<constructor-arg name="transformOptions">
|
||||
<map>
|
||||
<entry key="flashVersion" value="9"/>
|
||||
<entry key="timeout" value="${system.thumbnail.definition.default.timeoutMs}" />
|
||||
</map>
|
||||
</constructor-arg>
|
||||
<constructor-arg name="registry" ref="renditionDefinitionRegistry2"/>
|
||||
</bean>
|
||||
|
||||
<bean id="renditionDefinition2Pdf" class="org.alfresco.repo.rendition2.RenditionDefinition2Impl">
|
||||
<constructor-arg name="renditionName" value="pdf"/>
|
||||
<constructor-arg name="targetMimetype" value="application/pdf"/>
|
||||
<constructor-arg name="transformOptions">
|
||||
<map>
|
||||
<entry key="timeout" value="${system.thumbnail.definition.default.timeoutMs}" />
|
||||
</map>
|
||||
</constructor-arg>
|
||||
<constructor-arg name="registry" ref="renditionDefinitionRegistry2"/>
|
||||
</bean>
|
||||
<!-- The definitions are read from json files (possibly added to a volume via k8 ConfigMaps) -->
|
||||
|
||||
</beans>
|
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"renditions": [
|
||||
{
|
||||
"renditionName": "medium",
|
||||
"targetMediaType": "image/jpeg",
|
||||
"options": [
|
||||
{"name": "resizeWidth", "value": 100},
|
||||
{"name": "resizeHeight", "value": 100},
|
||||
{"name": "maintainAspectRatio", "value": true},
|
||||
{"name": "thumbnail", "value": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"renditionName": "doclib",
|
||||
"targetMediaType": "image/png",
|
||||
"options": [
|
||||
{"name": "resizeWidth", "value": 100},
|
||||
{"name": "resizeHeight", "value": 100},
|
||||
{"name": "allowEnlargement", "value": false},
|
||||
{"name": "maintainAspectRatio", "value": true},
|
||||
{"name": "thumbnail", "value": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"renditionName": "imgpreview",
|
||||
"targetMediaType": "image/jpeg",
|
||||
"options": [
|
||||
{"name": "resizeWidth", "value": 960},
|
||||
{"name": "resizeHeight", "value": 960},
|
||||
{"name": "allowEnlargement", "value": false},
|
||||
{"name": "maintainAspectRatio", "value": true},
|
||||
{"name": "thumbnail", "value": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"renditionName": "avatar",
|
||||
"targetMediaType": "image/png",
|
||||
"options": [
|
||||
{"name": "resizeWidth", "value": 64},
|
||||
{"name": "resizeHeight", "value": 64},
|
||||
{"name": "allowEnlargement", "value": false},
|
||||
{"name": "maintainAspectRatio", "value": true},
|
||||
{"name": "thumbnail", "value": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"renditionName": "avatar32",
|
||||
"targetMediaType": "image/png",
|
||||
"options": [
|
||||
{"name": "resizeWidth", "value": 32},
|
||||
{"name": "resizeHeight", "value": 32},
|
||||
{"name": "allowEnlargement", "value": false},
|
||||
{"name": "maintainAspectRatio", "value": true},
|
||||
{"name": "thumbnail", "value": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"renditionName": "webpreview",
|
||||
"targetMediaType": "application/x-shockwave-flash",
|
||||
"options": [
|
||||
{"name": "flashVersion", "value": 9}
|
||||
]
|
||||
},
|
||||
{
|
||||
"renditionName": "pdf",
|
||||
"targetMediaType": "application/pdf"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1079,6 +1079,16 @@ people.search.honor.hint.useCQ=true
|
||||
# Delays cron jobs after bootstrap to allow server to fully come up before jobs start
|
||||
system.cronJob.startDelayMilliseconds=60000
|
||||
|
||||
# Schedule for reading rendition config definitions dynamically. Initially checks every 10 seconds and then switches to
|
||||
# every 10 minutes after the configuration is read successfully. If there is a error later reading the config, the
|
||||
# checks return to every 10 seconds.
|
||||
rendition.config.cronExpression=0 0/10 * * * ?
|
||||
rendition.config.initialAndOnError.cronExpression=0/10 * * * * ?
|
||||
|
||||
# Optional property to specify an external file or directory that will be read for rendition definitions from YAML
|
||||
# files (possibly added to a volume via k8 ConfigMaps).
|
||||
rendition.config.dir=
|
||||
|
||||
# Optional property to specify an external file or directory that will be read for transformer json config.
|
||||
local.transform.pipeline.config.dir=
|
||||
|
||||
|
@@ -53,6 +53,7 @@ import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.quartz.CronExpression;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
@@ -119,6 +120,9 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
|
||||
protected static final String ADMIN = "admin";
|
||||
protected static final String DOC_LIB = "doclib";
|
||||
|
||||
private CronExpression origLocalTransCron;
|
||||
private CronExpression origRenditionCron;
|
||||
|
||||
@BeforeClass
|
||||
public static void before()
|
||||
{
|
||||
@@ -192,16 +196,25 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
|
||||
legacyTransformServiceRegistry.setEnabled(Boolean.parseBoolean(System.getProperty("legacy.transform.service.enabled")));
|
||||
legacyTransformServiceRegistry.afterPropertiesSet();
|
||||
|
||||
origLocalTransCron = localTransformServiceRegistry.getCronExpression();
|
||||
localTransformServiceRegistry.setCronExpression(null);
|
||||
localTransformServiceRegistry.setEnabled(Boolean.parseBoolean(System.getProperty("local.transform.service.enabled")));
|
||||
localTransformServiceRegistry.afterPropertiesSet();
|
||||
|
||||
origRenditionCron = renditionDefinitionRegistry2.getCronExpression();
|
||||
renditionDefinitionRegistry2.setCronExpression(null);
|
||||
renditionDefinitionRegistry2.setTransformServiceRegistry(transformServiceRegistry);
|
||||
renditionDefinitionRegistry2.afterPropertiesSet();
|
||||
|
||||
thumbnailRegistry.setTransformServiceRegistry(transformServiceRegistry);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp()
|
||||
{
|
||||
localTransformServiceRegistry.setCronExpression(origLocalTransCron);
|
||||
renditionDefinitionRegistry2.setCronExpression(origRenditionCron);
|
||||
|
||||
AuthenticationUtil.clearCurrentSecurityContext();
|
||||
}
|
||||
|
||||
|
@@ -130,7 +130,7 @@ public abstract class AbstractRenditionTest extends AbstractRenditionIntegration
|
||||
List<ThumbnailDefinition> thumbnailDefinitions = thumbnailRegistry.getThumbnailDefinitions(sourceMimetype, -1);
|
||||
Set<String> thumbnailNames = getThumbnailNames(thumbnailDefinitions);
|
||||
assertEquals("There should be the same renditions ("+renditionNames+") as deprecated thumbnails ("+thumbnailNames+")",
|
||||
renditionNames, thumbnailNames);
|
||||
thumbnailNames, renditionNames);
|
||||
|
||||
renditionCount += renditionNames.size();
|
||||
for (String renditionName : renditionNames)
|
||||
|
@@ -59,8 +59,16 @@ public class LegacyTransformServiceRegistryIntegrationTest extends LocalTransfor
|
||||
AbstractRenditionIntegrationTest.after();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setEnabled(boolean enabled)
|
||||
{
|
||||
legacyTransformServiceRegistry.setEnabled(enabled);
|
||||
legacyTransformServiceRegistry.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabled()
|
||||
{
|
||||
return legacyTransformServiceRegistry.isEnabled();
|
||||
}
|
||||
}
|
||||
|
@@ -27,11 +27,14 @@ package org.alfresco.repo.rendition2;
|
||||
|
||||
import org.alfresco.repo.content.transform.LocalTransformServiceRegistry;
|
||||
import org.alfresco.transform.client.model.config.TransformServiceRegistry;
|
||||
import org.alfresco.transform.client.model.config.TransformServiceRegistryImpl;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.quartz.CronExpression;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -45,6 +48,8 @@ import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_PDF;
|
||||
*/
|
||||
public class LocalTransformServiceRegistryIntegrationTest extends AbstractRenditionIntegrationTest
|
||||
{
|
||||
private static final String RENDITION_NAME = "pdf";
|
||||
|
||||
@Autowired
|
||||
private LocalTransformServiceRegistry localTransformServiceRegistry;
|
||||
|
||||
@@ -65,6 +70,7 @@ public class LocalTransformServiceRegistryIntegrationTest extends AbstractRendit
|
||||
AbstractRenditionIntegrationTest.after();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
@@ -75,11 +81,16 @@ public class LocalTransformServiceRegistryIntegrationTest extends AbstractRendit
|
||||
options = definition2.getTransformOptions();
|
||||
}
|
||||
|
||||
protected void setEnabled(boolean enabled)
|
||||
protected void setEnabled(boolean enabled) throws Exception
|
||||
{
|
||||
localTransformServiceRegistry.setEnabled(enabled);
|
||||
localTransformServiceRegistry.afterPropertiesSet();
|
||||
}
|
||||
|
||||
protected boolean isEnabled()
|
||||
{
|
||||
return localTransformServiceRegistry.isEnabled();
|
||||
}
|
||||
private static final String RENDITION_NAME = "pdf";
|
||||
|
||||
@Test
|
||||
public void testIsSupported()
|
||||
@@ -121,18 +132,22 @@ public class LocalTransformServiceRegistryIntegrationTest extends AbstractRendit
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnabledDisabled()
|
||||
public void testEnabledDisabled() throws Exception
|
||||
{
|
||||
Assert.assertTrue(transformServiceRegistry.isSupported(MIMETYPE_OPENXML_WORDPROCESSING, 1234, MIMETYPE_PDF, options, RENDITION_NAME));
|
||||
boolean origEnabled = isEnabled(); // should be true
|
||||
try
|
||||
{
|
||||
Assert.assertTrue(transformServiceRegistry.isSupported(MIMETYPE_OPENXML_WORDPROCESSING, 1234, MIMETYPE_PDF, options, RENDITION_NAME));
|
||||
|
||||
setEnabled(false);
|
||||
Assert.assertFalse(transformServiceRegistry.isSupported(MIMETYPE_OPENXML_WORDPROCESSING, 1234, MIMETYPE_PDF, options, RENDITION_NAME));
|
||||
|
||||
setEnabled(true);
|
||||
Assert.assertTrue(transformServiceRegistry.isSupported(MIMETYPE_OPENXML_WORDPROCESSING, 1234, MIMETYPE_PDF, options, RENDITION_NAME));
|
||||
}
|
||||
finally
|
||||
{
|
||||
setEnabled(true);
|
||||
}
|
||||
Assert.assertTrue(transformServiceRegistry.isSupported(MIMETYPE_OPENXML_WORDPROCESSING, 1234, MIMETYPE_PDF, options, RENDITION_NAME));
|
||||
setEnabled(origEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
package org.alfresco.repo.rendition2;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.policy.BehaviourFilter;
|
||||
import org.alfresco.repo.policy.PolicyComponent;
|
||||
@@ -38,16 +39,22 @@ import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.rule.RuleService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.transform.client.model.config.TransformServiceRegistryImpl;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.quartz.CronExpression;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
@@ -68,6 +75,8 @@ import static org.mockito.Mockito.when;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RenditionService2Test
|
||||
{
|
||||
private static final ObjectMapper JSON_OBJECT_MAPPER = new ObjectMapper();;
|
||||
|
||||
private RenditionService2Impl renditionService2;
|
||||
private RenditionDefinitionRegistry2Impl renditionDefinitionRegistry2;
|
||||
|
||||
@@ -80,9 +89,10 @@ public class RenditionService2Test
|
||||
@Mock private PolicyComponent policyComponent;
|
||||
@Mock private BehaviourFilter behaviourFilter;
|
||||
@Mock private RuleService ruleService;
|
||||
@Mock private TransformServiceRegistryImpl transformServiceRegistry;
|
||||
|
||||
private NodeRef nodeRef = new NodeRef("workspace://spacesStore/test-id");
|
||||
private static final String IMGPREVIEW = "imgpreview";
|
||||
private static final String TEST_RENDITION = "testRendition";
|
||||
private static final String JPEG = "image/jpeg";
|
||||
private String contentUrl = "test-content-url";
|
||||
|
||||
@@ -91,11 +101,11 @@ public class RenditionService2Test
|
||||
{
|
||||
renditionService2 = new RenditionService2Impl();
|
||||
renditionDefinitionRegistry2 = new RenditionDefinitionRegistry2Impl();
|
||||
|
||||
Map<String, String> options = new HashMap<>();
|
||||
options.put("width", "960");
|
||||
options.put("height", "1024");
|
||||
new RenditionDefinition2Impl(IMGPREVIEW, JPEG, options, renditionDefinitionRegistry2);
|
||||
renditionDefinitionRegistry2.setTransformServiceRegistry(transformServiceRegistry);
|
||||
renditionDefinitionRegistry2.setRenditionConfigDir("");
|
||||
renditionDefinitionRegistry2.setTimeoutDefault("120000");
|
||||
renditionDefinitionRegistry2.setJsonObjectMapper(JSON_OBJECT_MAPPER);
|
||||
renditionDefinitionRegistry2.setCronExpression(null); // just read it once
|
||||
|
||||
when(nodeService.exists(nodeRef)).thenReturn(true);
|
||||
when(nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT)).thenReturn(contentData);
|
||||
@@ -115,7 +125,14 @@ public class RenditionService2Test
|
||||
renditionService2.setEnabled(true);
|
||||
renditionService2.setThumbnailsEnabled(true);
|
||||
renditionService2.setRenditionRequestSheduler(new RenditionRequestSchedulerMock());
|
||||
|
||||
renditionDefinitionRegistry2.afterPropertiesSet();
|
||||
renditionService2.afterPropertiesSet();
|
||||
|
||||
Map<String, String> options = new HashMap<>();
|
||||
options.put("width", "960");
|
||||
options.put("height", "1024");
|
||||
new RenditionDefinition2Impl(TEST_RENDITION, JPEG, options, renditionDefinitionRegistry2);
|
||||
}
|
||||
|
||||
private class RenditionRequestSchedulerMock extends PostTxnCallbackScheduler
|
||||
@@ -138,20 +155,20 @@ public class RenditionService2Test
|
||||
public void disabled()
|
||||
{
|
||||
renditionService2.setEnabled(false);
|
||||
renditionService2.render(nodeRef, IMGPREVIEW);
|
||||
renditionService2.render(nodeRef, TEST_RENDITION);
|
||||
}
|
||||
|
||||
@Test(expected = RenditionService2Exception.class)
|
||||
public void thumbnailsDisabled()
|
||||
{
|
||||
renditionService2.setThumbnailsEnabled(false);
|
||||
renditionService2.render(nodeRef, IMGPREVIEW);
|
||||
renditionService2.render(nodeRef, TEST_RENDITION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useLocalTransform()
|
||||
{
|
||||
renditionService2.render(nodeRef, IMGPREVIEW);
|
||||
renditionService2.render(nodeRef, TEST_RENDITION);
|
||||
verify(transformClient, times(1)).transform(any(), any(), anyString(), anyInt());
|
||||
}
|
||||
|
||||
@@ -159,14 +176,14 @@ public class RenditionService2Test
|
||||
public void noTransform()
|
||||
{
|
||||
doThrow(UnsupportedOperationException.class).when(transformClient).checkSupported(any(), any(), any(), anyLong(), any());
|
||||
renditionService2.render(nodeRef, IMGPREVIEW);
|
||||
renditionService2.render(nodeRef, TEST_RENDITION);
|
||||
}
|
||||
|
||||
@Test(expected = RenditionService2PreventedException.class)
|
||||
public void checkSourceNodeForPreventionClass()
|
||||
{
|
||||
when(renditionPreventionRegistry.isContentClassRegistered((QName)any())).thenReturn(true);
|
||||
renditionService2.render(nodeRef, IMGPREVIEW);
|
||||
renditionService2.render(nodeRef, TEST_RENDITION);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@@ -174,4 +191,16 @@ public class RenditionService2Test
|
||||
{
|
||||
renditionService2.render(nodeRef, "doesNotExist");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void definitionExists() throws IOException
|
||||
{
|
||||
renditionDefinitionRegistry2.readConfig();
|
||||
|
||||
Set<String> renditionNames = renditionDefinitionRegistry2.getRenditionNames();
|
||||
for (String name: new String[] {"medium", "doclib", "imgpreview", "avatar", "avatar32", "webpreview", "pdf"})
|
||||
{
|
||||
assertTrue("Expected rendition "+name, renditionNames.contains(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -61,18 +61,18 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg
|
||||
{
|
||||
private class TestLocalTransformServiceRegistry extends LocalTransformServiceRegistry
|
||||
{
|
||||
private boolean lastReadSucceed = false;
|
||||
private boolean mockSuccessReadingRemoteConfig = true;
|
||||
private boolean mockSuccessReadingConfig = true;
|
||||
LocalData dummyData = new LocalData();
|
||||
|
||||
public synchronized boolean getMockSuccessReadingRemoteConfig()
|
||||
public synchronized boolean getMockSuccessReadingConfig()
|
||||
{
|
||||
return mockSuccessReadingRemoteConfig;
|
||||
return mockSuccessReadingConfig;
|
||||
}
|
||||
|
||||
public synchronized void setMockSuccessReadingRemoteConfig(boolean mockSuccessReadingRemoteConfig)
|
||||
public synchronized void setMockSuccessReadingConfig(boolean mockSuccessReadingConfig)
|
||||
{
|
||||
System.out.println("\n"+getMs()+": set next mock read to "+(mockSuccessReadingRemoteConfig ? "success" : "failure"));
|
||||
this.mockSuccessReadingRemoteConfig = mockSuccessReadingRemoteConfig;
|
||||
System.out.println("\n"+getMs()+": set next mock read to "+(mockSuccessReadingConfig ? "success" : "failure"));
|
||||
this.mockSuccessReadingConfig = mockSuccessReadingConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,31 +84,36 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransformServiceRegistryImpl.Data readConfig() throws IOException
|
||||
public boolean readConfig() throws IOException
|
||||
{
|
||||
readConfigCount++;
|
||||
data = createData();
|
||||
boolean mockSuccessReadingRemoteConfig = getMockSuccessReadingRemoteConfig();
|
||||
lastReadSucceed = mockSuccessReadingRemoteConfig;
|
||||
setSuccessReadingRemoteConfig(data, mockSuccessReadingRemoteConfig);
|
||||
dummyData = new LocalData();
|
||||
boolean mockSuccessReadingRemoteConfig = getMockSuccessReadingConfig();
|
||||
System.out.println(getMs() + "readConfig() success="+mockSuccessReadingRemoteConfig+" reads="+readConfigCount);
|
||||
return data;
|
||||
return mockSuccessReadingRemoteConfig;
|
||||
}
|
||||
|
||||
public Data assertDataChanged(Data data, String msg)
|
||||
@Override
|
||||
public synchronized LocalData getData()
|
||||
{
|
||||
return dummyData;
|
||||
}
|
||||
|
||||
public Data assertDataChanged(Data prevData, String msg)
|
||||
{
|
||||
// If the data changes, there has been a read
|
||||
System.out.println(getMs()+msg);
|
||||
assertNotEquals("The configuration data should have changed: "+msg, this.data, data);
|
||||
return this.data;
|
||||
Data data = getData();
|
||||
assertNotEquals("The configuration data should have changed: "+msg, data, prevData);
|
||||
return data;
|
||||
}
|
||||
|
||||
public Data assertDataUnchanged(Data data, String msg)
|
||||
{
|
||||
// If the data changes, there has been a read
|
||||
System.out.println(getMs()+msg);
|
||||
assertEquals("The configuration data should be the same: "+msg, this.data, data);
|
||||
return this.data;
|
||||
assertEquals("The configuration data should be the same: "+msg, getData(), data);
|
||||
return getData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +139,6 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg
|
||||
private Map<String, List<String>> libreofficeSupportedTransformation;
|
||||
private Map<String, List<String>> officeToImageViaPdfSupportedTransformation;
|
||||
|
||||
private TransformServiceRegistryImpl.Data data;
|
||||
private int readConfigCount;
|
||||
private long startMs;
|
||||
|
||||
@@ -155,8 +159,8 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg
|
||||
registry.setTransformerDebug(transformerDebug);
|
||||
registry.setMimetypeService(mimetypeMap);
|
||||
registry.setPipelineConfigDir("");
|
||||
registry.setCronExpression(new CronExpression("* * * * * ? 2099")); // not for a long time.
|
||||
registry.setInitialAndOnErrorCronExpression(new CronExpression("* * * * * ? 2099")); // not for a long time.
|
||||
registry.setCronExpression(null); // just read it once
|
||||
registry.afterPropertiesSet();
|
||||
return registry;
|
||||
}
|
||||
|
||||
@@ -416,22 +420,12 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg
|
||||
{
|
||||
CronExpression origCronExpression = registry.getCronExpression();
|
||||
CronExpression origInitialAndOnErrorCronExpression = registry.getInitialAndOnErrorCronExpression();
|
||||
|
||||
String origPipelineConfigDir = registry.getPipelineConfigDir();
|
||||
Scheduler origScheduler = registry.getScheduler();
|
||||
|
||||
if (origScheduler != null)
|
||||
{
|
||||
origScheduler.clear();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TransformServiceRegistryImpl.Data prevData;
|
||||
data = null;
|
||||
readConfigCount = 0;
|
||||
|
||||
registry.setScheduler(null);
|
||||
registry.setInitialAndOnErrorCronExpression(new CronExpression(("0/2 * * ? * * *"))); // every 2 seconds rather than 10 seconds
|
||||
registry.setCronExpression(new CronExpression(("0/4 * * ? * * *"))); // every 4 seconds rather than 10 mins
|
||||
|
||||
@@ -439,8 +433,9 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg
|
||||
// It avoids having to work out schedule offsets and extra quick runs that can otherwise take place.
|
||||
Thread.sleep(4000-System.currentTimeMillis()%4000);
|
||||
startMs = System.currentTimeMillis();
|
||||
registry.setMockSuccessReadingRemoteConfig(false);
|
||||
registry.setMockSuccessReadingConfig(false);
|
||||
registry.afterPropertiesSet();
|
||||
TransformServiceRegistryImpl.Data data = registry.getData();
|
||||
|
||||
Thread.sleep(1000); // 1 seconds
|
||||
data = registry.assertDataChanged(data, "There should have been a read after a few milliseconds that fails");
|
||||
@@ -456,7 +451,7 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg
|
||||
|
||||
// Should switch to normal 4s schedule after the next read, so the read at 12 seconds will be on that schedule.
|
||||
// It is always possible that another quick one gets scheduled almost straight away after the next read.
|
||||
registry.setMockSuccessReadingRemoteConfig(true);
|
||||
registry.setMockSuccessReadingConfig(true);
|
||||
Thread.sleep(2000); // 9 seconds
|
||||
data = registry.assertDataChanged(data, "There should have been a read after 8 seconds that succeeds");
|
||||
|
||||
@@ -467,7 +462,7 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg
|
||||
data = registry.assertDataChanged(data, "There should have been a read after 12 seconds that succeeds");
|
||||
|
||||
// Should switch back to initial/error schedule after failure
|
||||
registry.setMockSuccessReadingRemoteConfig(false);
|
||||
registry.setMockSuccessReadingConfig(false);
|
||||
Thread.sleep(4000); // 17 seconds
|
||||
data = registry.assertDataChanged(data, "There should have been a read after 16 seconds that fails");
|
||||
|
||||
@@ -476,15 +471,10 @@ public class LocalTransformServiceRegistryConfigTest extends TransformServiceReg
|
||||
}
|
||||
finally
|
||||
{
|
||||
registry.setMockSuccessReadingRemoteConfig(true);
|
||||
|
||||
// Reset scheduler properties just in case another tests needs them in future.
|
||||
// We don't start the scheduler with registry.afterPropertiesSet() as this is
|
||||
// really just mocked up version of the registry.
|
||||
registry.setMockSuccessReadingConfig(true);
|
||||
registry.setCronExpression(origCronExpression);
|
||||
registry.setInitialAndOnErrorCronExpression(origInitialAndOnErrorCronExpression);
|
||||
registry.setPipelineConfigDir(origPipelineConfigDir);
|
||||
registry.setScheduler(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,7 @@ import org.apache.log4j.LogManager;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.quartz.CronExpression;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@@ -93,9 +94,9 @@ public class TransformServiceRegistryConfigTest
|
||||
TransformServiceRegistryImpl registry = new TransformServiceRegistryImpl()
|
||||
{
|
||||
@Override
|
||||
protected Data readConfig() throws IOException
|
||||
public boolean readConfig() throws IOException
|
||||
{
|
||||
return createData(); // Empty config
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,6 +106,8 @@ public class TransformServiceRegistryConfigTest
|
||||
}
|
||||
};
|
||||
registry.setJsonObjectMapper(JSON_OBJECT_MAPPER);
|
||||
registry.setCronExpression(null); // just read once
|
||||
registry.afterPropertiesSet();
|
||||
return registry;
|
||||
}
|
||||
|
||||
@@ -182,7 +185,7 @@ public class TransformServiceRegistryConfigTest
|
||||
new SupportedSourceAndTarget(XLS, TXT, 1024000)));
|
||||
|
||||
registry = buildTransformServiceRegistryImpl();
|
||||
registry.register(registry.getData(), transformer, getBaseUrl(transformer), getClass().getName());
|
||||
registry.register(transformer, getBaseUrl(transformer), getClass().getName());
|
||||
|
||||
assertTrue(registry.isSupported(XLS, 1024, TXT, Collections.emptyMap(), null));
|
||||
assertTrue(registry.isSupported(XLS, 1024000, TXT, null, null));
|
||||
@@ -223,7 +226,7 @@ public class TransformServiceRegistryConfigTest
|
||||
registry = buildTransformServiceRegistryImpl();
|
||||
for (Transformer transformer : transformers)
|
||||
{
|
||||
registry.register(registry.getData(), transformer, getBaseUrl(transformer), getClass().getName());
|
||||
registry.register(transformer, getBaseUrl(transformer), getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +260,7 @@ public class TransformServiceRegistryConfigTest
|
||||
{
|
||||
CombinedConfig combinedConfig = new CombinedConfig(log);
|
||||
combinedConfig.addLocalConfig(path);
|
||||
combinedConfig.register(registry.getData(), registry);
|
||||
combinedConfig.register(registry);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -364,7 +367,7 @@ public class TransformServiceRegistryConfigTest
|
||||
|
||||
try (Reader reader = new BufferedReader(new FileReader(tempFile)))
|
||||
{
|
||||
registry.register(registry.getData(), reader, getClass().getName());
|
||||
registry.register(reader, getClass().getName());
|
||||
// Check the count of transforms supported
|
||||
assertEquals("The number of UNIQUE source to target mimetypes transforms has changed. Config change?",
|
||||
42, countSupportedTransforms(true));
|
||||
@@ -671,7 +674,7 @@ public class TransformServiceRegistryConfigTest
|
||||
new SupportedSourceAndTarget(DOC, GIF, 102400),
|
||||
new SupportedSourceAndTarget(MSG, GIF, -1)));
|
||||
|
||||
registry.register(registry.getData(), transformer, getBaseUrl(transformer), getClass().getName());
|
||||
registry.register(transformer, getBaseUrl(transformer), getClass().getName());
|
||||
|
||||
assertSupported(DOC, 1024, GIF, null, "doclib", "");
|
||||
assertSupported(MSG, 1024, GIF, null, "doclib", "");
|
||||
|
Reference in New Issue
Block a user