/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.client.enduser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.apache.syncope.client.enduser.pages.HomePage;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.monitor.FileAlterationListener;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.client.enduser.annotations.Resource;
import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
import org.apache.syncope.client.enduser.init.EnduserInitializer;
import org.apache.syncope.client.enduser.model.CustomAttributesInfo;
import org.apache.syncope.client.enduser.resources.CaptchaResource;
import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.wicket.Page;
import org.apache.wicket.Session;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.resource.AbstractResource;
import org.apache.wicket.request.resource.IResource;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.util.lang.Args;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SyncopeEnduserApplication extends WebApplication implements Serializable {
private static final long serialVersionUID = -6445919351044845120L;
private static final Logger LOG = LoggerFactory.getLogger(SyncopeEnduserApplication.class);
private static final String ENDUSER_PROPERTIES = "enduser.properties";
private static final String CUSTOM_FORM_FILE = "customForm.json";
public static SyncopeEnduserApplication get() {
return (SyncopeEnduserApplication) WebApplication.get();
}
private String version;
private String site;
private String license;
private String domain;
private String adminUser;
private String anonymousUser;
private String anonymousKey;
private boolean captchaEnabled;
private boolean xsrfEnabled;
private SyncopeClientFactoryBean clientFactory;
private Map<String, CustomAttributesInfo> customForm;
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
protected void init() {
super.init();
// read enduser.properties
Properties props = new Properties();
try (InputStream is = getClass().getResourceAsStream("/" + ENDUSER_PROPERTIES)) {
props.load(is);
File enduserDir = new File(props.getProperty("enduser.directory"));
if (enduserDir.exists() && enduserDir.canRead() && enduserDir.isDirectory()) {
File enduserDirProps = FileUtils.getFile(enduserDir, ENDUSER_PROPERTIES);
if (enduserDirProps.exists() && enduserDirProps.canRead() && enduserDirProps.isFile()) {
props.clear();
props.load(FileUtils.openInputStream(enduserDirProps));
}
}
} catch (Exception e) {
throw new WicketRuntimeException("Could not read " + ENDUSER_PROPERTIES, e);
}
version = props.getProperty("version");
Args.notNull(version, "<version>");
site = props.getProperty("site");
Args.notNull(site, "<site>");
license = props.getProperty("license");
Args.notNull(license, "<license>");
domain = props.getProperty("domain", SyncopeConstants.MASTER_DOMAIN);
adminUser = props.getProperty("adminUser");
Args.notNull(adminUser, "<adminUser>");
anonymousUser = props.getProperty("anonymousUser");
Args.notNull(anonymousUser, "<anonymousUser>");
anonymousKey = props.getProperty("anonymousKey");
Args.notNull(anonymousKey, "<anonymousKey>");
captchaEnabled = Boolean.parseBoolean(props.getProperty("captcha"));
Args.notNull(captchaEnabled, "<captcha>");
xsrfEnabled = Boolean.parseBoolean(props.getProperty("xsrf"));
Args.notNull(xsrfEnabled, "<xsrf>");
String scheme = props.getProperty("scheme");
Args.notNull(scheme, "<scheme>");
String host = props.getProperty("host");
Args.notNull(host, "<host>");
String port = props.getProperty("port");
Args.notNull(port, "<port>");
String rootPath = props.getProperty("rootPath");
Args.notNull(rootPath, "<rootPath>");
String useGZIPCompression = props.getProperty("useGZIPCompression");
Args.notNull(useGZIPCompression, "<useGZIPCompression>");
clientFactory = new SyncopeClientFactoryBean().
setAddress(scheme + "://" + host + ":" + port + "/" + rootPath).
setContentType(SyncopeClientFactoryBean.ContentType.JSON).
setUseCompression(BooleanUtils.toBoolean(useGZIPCompression));
// read customForm.json
try (InputStream is = getClass().getResourceAsStream("/" + CUSTOM_FORM_FILE)) {
customForm = MAPPER.readValue(is,
new TypeReference<HashMap<String, CustomAttributesInfo>>() {
});
File enduserDir = new File(props.getProperty("enduser.directory"));
boolean existsEnduserDir = enduserDir.exists() && enduserDir.canRead() && enduserDir.isDirectory();
if (existsEnduserDir) {
File customFormFile = FileUtils.getFile(enduserDir, CUSTOM_FORM_FILE);
if (customFormFile.exists() && customFormFile.canRead() && customFormFile.isFile()) {
customForm = MAPPER.readValue(FileUtils.openInputStream(customFormFile),
new TypeReference<HashMap<String, CustomAttributesInfo>>() {
});
}
}
FileAlterationObserver observer = existsEnduserDir
? new FileAlterationObserver(enduserDir, new FileFilter() {
@Override
public boolean accept(final File pathname) {
return StringUtils.contains(pathname.getPath(), CUSTOM_FORM_FILE);
}
})
: new FileAlterationObserver(getClass().getResource("/" + CUSTOM_FORM_FILE).getFile(),
new FileFilter() {
@Override
public boolean accept(final File pathname) {
return StringUtils.contains(pathname.getPath(), CUSTOM_FORM_FILE);
}
});
FileAlterationMonitor monitor = new FileAlterationMonitor(5000);
FileAlterationListener listener = new FileAlterationListenerAdaptor() {
@Override
public void onFileChange(final File file) {
try {
LOG.trace("{} has changed. Reloading form customization configuration.", CUSTOM_FORM_FILE);
customForm = MAPPER.readValue(FileUtils.openInputStream(file),
new TypeReference<HashMap<String, CustomAttributesInfo>>() {
});
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
@Override
public void onFileCreate(final File file) {
try {
LOG.trace("{} has been created. Loading form customization configuration.", CUSTOM_FORM_FILE);
customForm = MAPPER.readValue(FileUtils.openInputStream(file),
new TypeReference<HashMap<String, CustomAttributesInfo>>() {
});
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
@Override
public void onFileDelete(final File file) {
LOG.trace("{} has been deleted. Resetting form customization configuration.", CUSTOM_FORM_FILE);
customForm = null;
}
};
observer.addListener(listener);
monitor.addObserver(observer);
monitor.start();
} catch (Exception e) {
throw new WicketRuntimeException("Could not read " + CUSTOM_FORM_FILE, e);
}
// mount resources
ClassPathScanImplementationLookup classPathScanImplementationLookup =
(ClassPathScanImplementationLookup) getServletContext().
getAttribute(EnduserInitializer.CLASSPATH_LOOKUP);
for (final Class<? extends AbstractResource> resource : classPathScanImplementationLookup.getResources()) {
Resource annotation = resource.getAnnotation(Resource.class);
if (annotation == null) {
LOG.debug("No @Resource annotation found on {}, ignoring", resource.getName());
} else {
try {
final AbstractResource instance = resource.newInstance();
mountResource(annotation.path(), new ResourceReference(annotation.key()) {
private static final long serialVersionUID = -128426276529456602L;
@Override
public IResource getResource() {
return instance;
}
});
} catch (Exception e) {
LOG.error("Could not instantiate {}", resource.getName(), e);
}
}
}
//mount captcha resource only if captcha is enabled
if (captchaEnabled) {
mountResource("/api/captcha", new ResourceReference("captcha") {
private static final long serialVersionUID = -128426276529456602L;
@Override
public IResource getResource() {
return new CaptchaResource();
}
});
}
}
@Override
public Class<? extends Page> getHomePage() {
return HomePage.class;
}
@Override
public Session newSession(final Request request, final Response response) {
return new SyncopeEnduserSession(request);
}
public String getVersion() {
return version;
}
public String getSite() {
return site;
}
public String getLicense() {
return license;
}
public String getDomain() {
return domain;
}
public String getAdminUser() {
return adminUser;
}
public String getAnonymousUser() {
return anonymousUser;
}
public String getAnonymousKey() {
return anonymousKey;
}
public SyncopeClientFactoryBean getClientFactory() {
return clientFactory;
}
public boolean isCaptchaEnabled() {
return captchaEnabled;
}
public boolean isXsrfEnabled() {
return xsrfEnabled;
}
public Map<String, CustomAttributesInfo> getCustomForm() {
return customForm;
}
public void setCustomForm(final Map<String, CustomAttributesInfo> customForm) {
this.customForm.clear();
this.customForm.putAll(customForm);
}
}