/*
* 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 ro.nextreports.server.service;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.security.access.annotation.Secured;
import org.springframework.transaction.annotation.Transactional;
import ro.nextreports.server.StorageConstants;
import ro.nextreports.server.aop.Profile;
import ro.nextreports.server.audit.AuditEvent;
import ro.nextreports.server.audit.Auditor;
import ro.nextreports.server.dao.SecurityDao;
import ro.nextreports.server.dao.StorageDao;
import ro.nextreports.server.domain.Entity;
import ro.nextreports.server.domain.Folder;
import ro.nextreports.server.domain.Settings;
import ro.nextreports.server.domain.VersionInfo;
import ro.nextreports.server.exception.DuplicationException;
import ro.nextreports.server.exception.NotFoundException;
import ro.nextreports.server.exception.ReferenceException;
import ro.nextreports.server.search.SearchCondition;
import ro.nextreports.server.search.SearchConditionFactory;
import ro.nextreports.server.search.SearchEntry;
import ro.nextreports.server.util.PermissionUtil;
import ro.nextreports.server.util.SearchManager;
import ro.nextreports.server.util.ServerUtil;
/**
* @author Decebal Suiu
*/
public class DefaultStorageService implements StorageService {
private StorageDao storageDao;
private SecurityDao securityDao;
private SearchConditionFactory searchCoditionFactory;
private Auditor auditor;
private static final Logger LOG = LoggerFactory.getLogger(DefaultStorageService.class);
@Required
public void setStorageDao(StorageDao storageDao) {
this.storageDao = storageDao;
searchCoditionFactory = new SearchConditionFactory(storageDao);
}
@Required
public void setSecurityDao(SecurityDao securityDao) {
this.securityDao = securityDao;
}
@Required
public void setAuditor(Auditor auditor) {
this.auditor = auditor;
}
@Transactional(readOnly = true)
@Secured("AFTER_ACL_READ")
public Entity getEntity(String path) throws NotFoundException {
return storageDao.getEntity(path);
}
@Transactional(readOnly = true)
public Entity getEntityById(String id) throws NotFoundException {
return storageDao.getEntityById(id);
}
@Transactional(readOnly = true)
@Secured("AFTER_ACL_COLLECTION_READ")
@Profile
public Entity[] getEntityChildren(String path) throws NotFoundException {
return storageDao.getEntityChildren(path);
}
@Transactional(readOnly = true)
@Secured("AFTER_ACL_COLLECTION_READ")
public Entity[] getBaseEntityChildren(String path) throws NotFoundException {
return storageDao.getBaseEntityChildren(path);
}
@Transactional(readOnly = true)
@Secured("AFTER_ACL_COLLECTION_READ")
@Profile
public Entity[] getEntityChildrenById(String id) throws NotFoundException {
return storageDao.getEntityChildrenById(id);
}
// This method must be used only where there is no need for security (like
// users whichare seen only by administrators)
@Transactional(readOnly = true)
@Profile
public Entity[] getEntityChildrenById(String id, long firstResult, long maxResults) throws NotFoundException {
return storageDao.getEntityChildrenById(id, firstResult, maxResults);
}
@Transactional(readOnly = true)
@Secured("AFTER_ACL_COLLECTION_READ")
@Profile
public Entity[] getEntitiesByClassName(String path, String className) throws NotFoundException {
return storageDao.getEntitiesByClassName(path, className);
}
@Transactional(readOnly = true)
@Profile
public Entity[] getEntitiesByClassNameWithoutSecurity(String path, String className) throws NotFoundException {
return storageDao.getEntitiesByClassName(path, className);
}
@Transactional
public String addEntity(Entity entity) throws DuplicationException {
String id = storageDao.addEntity(entity);
auditPath("Add entity", entity.getPath());
if (entity.allowPermissions()) {
try {
securityDao.grantUser(entity.getPath(), ServerUtil.getUsername(), PermissionUtil.getFullPermissions(),
false);
} catch (NotFoundException e) {
// never happening
e.printStackTrace();
}
}
return id;
}
@Transactional
public String addEntity(Entity entity, boolean keepId) throws DuplicationException {
String id = storageDao.addEntity(entity, keepId);
auditPath("Add entity", entity.getPath());
if (entity.allowPermissions()) {
try {
securityDao.grantUser(entity.getPath(), ServerUtil.getUsername(), PermissionUtil.getFullPermissions(),
false);
} catch (NotFoundException e) {
// never happening
e.printStackTrace();
}
}
return id;
}
@Transactional
public void modifyEntity(Entity entity) {
storageDao.modifyEntity(entity);
auditPath("Modify entity", entity.getPath());
}
@Transactional
public void modifyEntity(Entity entity, String excludeChildrenName) {
storageDao.modifyEntity(entity, excludeChildrenName);
auditPath("Modify entity", entity.getPath());
}
@Transactional
public void modifyEntities(List<Entity> entities) {
for (Entity entity : entities) {
storageDao.modifyEntity(entity);
auditPath("Modify entity", entity.getPath());
}
}
@Transactional
// @Secured("ACL_DELETE")
public void removeEntity(String path) throws ReferenceException {
storageDao.removeEntity(path);
auditPath("Delete entity", path);
}
@Transactional
public void removeEntityById(String id) throws NotFoundException {
String path = storageDao.getEntityPath(id);
storageDao.removeEntityById(id);
auditPath("Delete entity", path);
}
@Transactional
public void removeEntitiesById(List<String> ids) throws NotFoundException {
for (String id : ids) {
String path = storageDao.getEntityPath(id);
storageDao.removeEntityById(id);
auditPath("Delete entity", path);
}
}
@Transactional
public List<String> getReferences(List<String> ids) {
List<String> result = new ArrayList<String>();
for (String id : ids) {
List<String> refs = storageDao.getReferences(id);
result.addAll(refs);
}
return result;
}
@Transactional
public void renameEntity(String path, String newName) throws NotFoundException, DuplicationException {
storageDao.renameEntity(path, newName);
auditRename(path, newName);
}
@Transactional
public void copyEntity(String sourcePath, String destPath) throws NotFoundException, DuplicationException {
storageDao.copyEntity(sourcePath, destPath);
auditCopyMove("Copy entity", sourcePath, destPath);
}
@Transactional
public void copyEntities(List<String> sourcePaths, String destPath) throws NotFoundException, DuplicationException {
for (String sourcePath : sourcePaths) {
storageDao.copyEntity(sourcePath, destPath);
auditCopyMove("Copy entity", sourcePath, destPath);
}
}
@Transactional
public void moveEntity(String sourcePath, String destPath) throws NotFoundException, DuplicationException {
storageDao.moveEntity(sourcePath, destPath);
auditCopyMove("Move entity", sourcePath, destPath);
}
@Transactional
public void moveEntities(List<String> sourcePaths, String destPath) throws NotFoundException, DuplicationException {
for (String sourcePath : sourcePaths) {
storageDao.moveEntity(sourcePath, destPath);
auditCopyMove("Move entity", sourcePath, destPath);
}
}
@Transactional
public boolean isEntityReferenced(String path) throws NotFoundException {
return storageDao.isEntityReferenced(path);
}
@Transactional
public boolean entityExists(String path) {
return storageDao.entityExists(path);
}
@Transactional(readOnly = true)
public VersionInfo[] getVersionInfos(String id) throws NotFoundException {
return storageDao.getVersionInfos(id);
}
@Transactional(readOnly = true)
public Entity getVersion(String id, String versionName) throws NotFoundException {
return storageDao.getVersion(id, versionName);
}
@Transactional
public void restoreVersion(String path, String versionName) throws NotFoundException {
storageDao.restoreVersion(path, versionName);
auditRestore(path, versionName);
}
@Transactional(readOnly = true)
@Secured("AFTER_ACL_COLLECTION_READ")
public Entity[] search(List<SearchEntry> searchEntries, String searchKey) {
SearchManager.addSearch(searchKey);
try {
List<Entity> entities = new ArrayList<Entity>();
if (searchEntries.size() > 0) {
String fromPath = searchEntries.get(0).getFromPath();
try {
search(entities, fromPath, searchEntries, searchKey);
} catch (NotFoundException e) {
// TODO
e.printStackTrace();
}
}
return entities.toArray(new Entity[entities.size()]);
} finally {
SearchManager.removeSearch(searchKey);
}
}
public void stopSearch(String searchKey) {
SearchManager.stopSearch(searchKey);
}
@Transactional
public String addOrModifyEntity(Entity entity) {
// return storageDao.addOrModifyEntity(entity); // no permissions
// support
String id = null;
String path = entity.getPath();
if (entityExists(path)) {
modifyEntity(entity);
id = entity.getId();
} else {
try {
id = addEntity(entity);
} catch (DuplicationException e) {
// never happening
}
}
return id;
}
@Transactional(readOnly = true)
public String getEntityPath(String id) throws NotFoundException {
return storageDao.getEntityPath(id);
}
// This method must be used only where there is no need for security (like
// users whichare seen only by administrators)
@Transactional(readOnly = true)
@Profile
public int countEntityChildrenById(String id) throws NotFoundException {
return storageDao.countEntityChildrenById(id);
}
private void search(List<Entity> entities, String fromPath, List<SearchEntry> searchEntries, String searchKey)
throws NotFoundException {
if (SearchManager.wasStopped(searchKey)) {
return;
}
Entity[] children = getBaseEntityChildren(fromPath);
if (children.length == 0) {
return;
}
for (Entity child : children) {
searchChild(entities, child.getPath(), searchEntries);
search(entities, child.getPath(), searchEntries, searchKey);
}
}
private void searchChild(List<Entity> entities, String fromPath, List<SearchEntry> searchEntries)
throws NotFoundException {
Entity entity = getEntity(fromPath);
boolean isTrue = true;
for (SearchEntry se : searchEntries) {
SearchCondition sc = searchCoditionFactory.getSearchCondition(se);
int result = sc.getStatus(this, entity);
// System.out.println(" @@@@ name=" + entity.getPath() + "
// getStatus="+result);
if ((result == SearchCondition.FALSE) || (result == SearchCondition.INVALID)) {
isTrue = false;
break;
}
}
if (isTrue) {
entities.add(entity);
}
}
// Audit methods
private void auditPath(String action, String path) {
AuditEvent auditEvent = new AuditEvent(action);
auditEvent.getContext().put("PATH", path);
auditor.logEvent(auditEvent);
}
private void auditCopyMove(String action, String sourcePath, String destPath) {
AuditEvent auditEvent = new AuditEvent(action);
auditEvent.getContext().put("SOURCE_PATH", sourcePath);
auditEvent.getContext().put("DEST_PATH", destPath);
auditor.logEvent(auditEvent);
}
private void auditRename(String path, String newName) {
AuditEvent auditEvent = new AuditEvent("Rename entity");
auditEvent.getContext().put("PATH", path);
auditEvent.getContext().put("NEW_NAME", newName);
auditor.logEvent(auditEvent);
}
private void auditRestore(String path, String versionName) {
AuditEvent auditEvent = new AuditEvent("Restore entity");
auditEvent.getContext().put("PATH", path);
auditEvent.getContext().put("VERSION_NAME", versionName);
auditor.logEvent(auditEvent);
}
public void clearEntityCache(String id) {
storageDao.getEntitiesCache().remove(id);
}
public void clearCache() {
storageDao.getEntitiesCache().clear();
}
@Transactional
public void setDefaultProperty(String path, String defaultValue) {
storageDao.setDefaultProperty(path, defaultValue);
}
@Transactional(readOnly = true)
public String getDefaultProperty(String path) throws NotFoundException {
return storageDao.getDefaultProperty(path);
}
@Transactional(readOnly = true)
public byte[] getLogoImage() {
return storageDao.getLogoImage();
}
@Transactional
public void personalizeSettings(String fileName, byte[] content, String theme, String language) {
storageDao.personalizeSettings(fileName, content, theme, language);
}
@Transactional
public void personalizeTheme(String theme) {
storageDao.personalizeTheme(theme);
}
// Settings are modified only by administrator
// We must not use @Secured("AFTER_ACL_COLLECTION_READ") because in some
// util classes like
// ConnectionUtil we do not have the Authentication object and we will get
// an exception:
// 'An Authentication object was not found in the SecurityContext'
@Transactional(readOnly = true)
public Settings getSettings() {
try {
return (Settings) getEntity(StorageConstants.SETTINGS_ROOT);
} catch (NotFoundException e) {
// should never happen
e.printStackTrace();
LOG.error("Could not read Settings node", e);
return new Settings();
}
}
@Transactional(readOnly = true)
public String getDashboardId(String widgetId) throws NotFoundException {
return storageDao.getDashboardId(widgetId);
}
@Transactional
public void createFolders(String path) throws DuplicationException {
if (path.startsWith(StorageConstants.PATH_SEPARATOR)) {
path = path.substring(1);
}
String[] nodes = path.split(StorageConstants.PATH_SEPARATOR);
path = "";
for (String node : nodes) {
path = path + StorageConstants.PATH_SEPARATOR + node;
if (!storageDao.nodeExists(path)) {
Folder entity = new Folder();
entity.setName(node);
entity.setPath(path);
addEntity(entity);
}
}
}
@Transactional
public void clearUserWidgetData(String widgetId) {
storageDao.clearUserWidgetData(widgetId);
}
@Transactional
public void shrinkDataFolder() {
storageDao.shrinkDataFolder();
}
}