CCM NG/ccm-cms: AssetFolderBrowser

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4657 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2017-04-03 15:54:12 +00:00
parent 25661ae6ed
commit d8d15fc2fb
8 changed files with 923 additions and 99 deletions

View File

@ -36,6 +36,9 @@
<Logger name="com.arsdigita.ui.admin.usersgroupsroles.UsersTable" <Logger name="com.arsdigita.ui.admin.usersgroupsroles.UsersTable"
level="debug"> level="debug">
</Logger> </Logger>
<Logger name="com.arsdigita.cms.ui.assets.AssetPane"
level="debug">
</Logger>
<Logger name="com.arsdigita.ui.login.UserLoginForm" <Logger name="com.arsdigita.ui.login.UserLoginForm"
level="debug"> level="debug">
</Logger> </Logger>

View File

@ -18,7 +18,6 @@
*/ */
package com.arsdigita.cms.ui.assets; package com.arsdigita.cms.ui.assets;
import com.arsdigita.cms.ui.folder.FolderBrowser;
import com.arsdigita.kernel.KernelConfig; import com.arsdigita.kernel.KernelConfig;
import org.libreccm.categorization.Category; import org.libreccm.categorization.Category;
@ -29,13 +28,10 @@ import org.librecms.CmsConstants;
import org.librecms.assets.AssetTypeInfo; import org.librecms.assets.AssetTypeInfo;
import org.librecms.assets.AssetTypesManager; import org.librecms.assets.AssetTypesManager;
import org.librecms.contentsection.Asset; import org.librecms.contentsection.Asset;
import org.librecms.contentsection.ContentType;
import org.librecms.contentsection.Folder; import org.librecms.contentsection.Folder;
import org.librecms.contenttypes.ContentTypeInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
@ -53,10 +49,18 @@ import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path; import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import org.libreccm.core.CcmObject; import org.libreccm.core.CcmObject;
import org.librecms.contentsection.AssetManager;
import org.librecms.contentsection.AssetRepository; import org.librecms.contentsection.AssetRepository;
import org.librecms.contentsection.FolderManager;
import org.librecms.contentsection.FolderRepository; import org.librecms.contentsection.FolderRepository;
import java.util.Collections;
import java.util.Optional;
import static org.librecms.CmsConstants.*;
/** /**
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
@ -79,9 +83,15 @@ public class AssetFolderBrowserController {
@Inject @Inject
private FolderRepository folderRepo; private FolderRepository folderRepo;
@Inject
private FolderManager folderManager;
@Inject @Inject
private AssetRepository assetRepo; private AssetRepository assetRepo;
@Inject
private AssetManager assetManager;
@Inject @Inject
private GlobalizationHelper globalizationHelper; private GlobalizationHelper globalizationHelper;
@ -107,6 +117,7 @@ public class AssetFolderBrowserController {
final int firstResult, final int firstResult,
final int maxResults) { final int maxResults) {
final List<Folder> subFolders = findSubFolders(folder, final List<Folder> subFolders = findSubFolders(folder,
"%",
orderBy, orderBy,
orderDirection, orderDirection,
firstResult, firstResult,
@ -123,6 +134,7 @@ public class AssetFolderBrowserController {
final int firstAsset = firstResult - subFolders.size(); final int firstAsset = firstResult - subFolders.size();
final List<Asset> assets = findAssetsInFolder(folder, final List<Asset> assets = findAssetsInFolder(folder,
"%",
orderBy, orderBy,
orderDirection, orderDirection,
firstAsset, firstAsset,
@ -143,7 +155,15 @@ public class AssetFolderBrowserController {
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected long countObjects(final Folder folder) { protected long countObjects(final Folder folder) {
return countObjects(folder, "%");
}
@Transactional(Transactional.TxType.REQUIRED)
protected long countObjects(final Folder folder, final String filterTerm) {
Objects.requireNonNull(folder); Objects.requireNonNull(folder);
Objects.requireNonNull(filterTerm);
final CriteriaBuilder builder = entityManager.getCriteriaBuilder(); final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class); CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
@ -153,12 +173,14 @@ public class AssetFolderBrowserController {
final List<Folder> subFolders = findSubFolders( final List<Folder> subFolders = findSubFolders(
folder, folder,
filterTerm,
AssetFolderBrowser.SORT_KEY_NAME, AssetFolderBrowser.SORT_KEY_NAME,
AssetFolderBrowser.SORT_ACTION_UP, AssetFolderBrowser.SORT_ACTION_UP,
-1, -1,
-1); -1);
final List<Asset> assets = findAssetsInFolder( final List<Asset> assets = findAssetsInFolder(
folder, folder,
filterTerm,
AssetFolderBrowser.SORT_KEY_NAME, AssetFolderBrowser.SORT_KEY_NAME,
AssetFolderBrowser.SORT_ACTION_UP, AssetFolderBrowser.SORT_ACTION_UP,
-1, -1,
@ -166,7 +188,7 @@ public class AssetFolderBrowserController {
if (subFolders.isEmpty() && assets.isEmpty()) { if (subFolders.isEmpty() && assets.isEmpty()) {
return 0; return 0;
} else if(subFolders.isEmpty() && !assets.isEmpty()) { } else if (subFolders.isEmpty() && !assets.isEmpty()) {
criteriaQuery = criteriaQuery.where(from.in(assets)); criteriaQuery = criteriaQuery.where(from.in(assets));
} else if (!subFolders.isEmpty() && assets.isEmpty()) { } else if (!subFolders.isEmpty() && assets.isEmpty()) {
criteriaQuery = criteriaQuery.where(from.in(subFolders)); criteriaQuery = criteriaQuery.where(from.in(subFolders));
@ -180,6 +202,218 @@ public class AssetFolderBrowserController {
} }
@Transactional(Transactional.TxType.REQUIRED)
protected void copyObjects(final Folder targetFolder,
final String[] objectIds) {
Objects.requireNonNull(targetFolder);
Objects.requireNonNull(objectIds);
for (final String objectId : objectIds) {
if (objectId.startsWith(FOLDER_BROWSER_KEY_PREFIX_FOLDER)) {
copyFolder(targetFolder,
Long.parseLong(objectId.substring(
FOLDER_BROWSER_KEY_PREFIX_FOLDER.length())));
} else if (objectId.startsWith(FOLDER_BROWSER_KEY_PREFIX_ASSET)) {
copyAsset(targetFolder,
Long.parseLong(objectId.substring(
FOLDER_BROWSER_KEY_PREFIX_ITEM.length())));
} else {
throw new IllegalArgumentException(String.format(
"ID '%s' does not start with '%s' or '%s'.",
objectId,
FOLDER_BROWSER_KEY_PREFIX_FOLDER,
FOLDER_BROWSER_KEY_PREFIX_ASSET));
}
}
}
private void copyFolder(final Folder targetFolder,
final long folderId) {
Objects.requireNonNull(targetFolder);
final Folder folder = folderRepo.findById(folderId)
.orElseThrow(() -> new IllegalArgumentException(String.format(
"No folder with ID %d in the database. "
+ "Where did that ID come from?",
folderId)));
folderManager.copyFolder(folder, targetFolder);
}
private void copyAsset(final Folder targetFolder,
final long assetId) {
Objects.requireNonNull(targetFolder);
final Asset asset = assetRepo
.findById(assetId)
.orElseThrow(() -> new IllegalArgumentException(String.format(
"No asset ith ID %d in the database. Where did that ID come from?",
assetId)));
assetManager.copy(asset, targetFolder);
}
@Transactional(Transactional.TxType.REQUIRED)
public void moveObjects(final Folder targetFolder,
final String[] objectIds) {
Objects.requireNonNull(targetFolder);
Objects.requireNonNull(objectIds);
for (final String objectId : objectIds) {
if (objectId.startsWith(FOLDER_BROWSER_KEY_PREFIX_FOLDER)) {
moveFolder(targetFolder,
Long.parseLong(objectId.substring(
FOLDER_BROWSER_KEY_PREFIX_FOLDER.length())));
} else if (objectId.startsWith(FOLDER_BROWSER_KEY_PREFIX_ASSET)) {
moveAsset(targetFolder,
Long.parseLong(objectId.substring(
FOLDER_BROWSER_KEY_PREFIX_ASSET.length())));
} else {
throw new IllegalArgumentException(String.format(
"ID '%s' does not start with '%s' or '%s'.",
objectId,
FOLDER_BROWSER_KEY_PREFIX_FOLDER,
FOLDER_BROWSER_KEY_PREFIX_ASSET));
}
}
}
private void moveFolder(final Folder targetFolder, final long folderId) {
Objects.requireNonNull(targetFolder);
final Folder folder = folderRepo.findById(folderId)
.orElseThrow(() -> new IllegalArgumentException(String.format(
"No folder with ID %d in the database. "
+ "Where did that ID come from?",
folderId)));
folderManager.moveFolder(folder, targetFolder);
}
private void moveAsset(final Folder targetFolder, final long assetId) {
Objects.requireNonNull(targetFolder);
final Asset asset = assetRepo
.findById(assetId)
.orElseThrow(() -> new IllegalArgumentException(String.format(
"No asset with ID %d in the database. Where did that ID come from?",
assetId)));
assetManager.move(asset, targetFolder);
}
@Transactional(Transactional.TxType.REQUIRED)
protected List<String> createInvalidTargetsList(final List<String> sources) {
Objects.requireNonNull(sources);
final List<String> sourceFolderIds = sources
.stream()
.filter(source -> source.startsWith(
FOLDER_BROWSER_KEY_PREFIX_FOLDER))
.collect(Collectors.toList());
final List<String> parentFolderIds = sourceFolderIds
.stream()
.map(sourceFolderId -> findParentFolderId(sourceFolderId))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
final List<List<String>> subFolderIds = sourceFolderIds
.stream()
.map(sourceFolderId -> findSubFolderIds(sourceFolderId))
.collect(Collectors.toList());
final List<String> invalidTargetIds = new ArrayList<>();
invalidTargetIds.addAll(sourceFolderIds);
invalidTargetIds.addAll(parentFolderIds);
for (final List<String> subFolderIdList : subFolderIds) {
invalidTargetIds.addAll(subFolderIdList);
}
return invalidTargetIds;
}
private Optional<String> findParentFolderId(final String folderId) {
Objects.requireNonNull(folderId);
if (!folderId.startsWith(FOLDER_BROWSER_KEY_PREFIX_FOLDER)) {
throw new IllegalArgumentException(String.format(
"Provided string '%s' is not an ID of a folder.",
folderId));
}
final long objectId = Long.parseLong(folderId.substring(
FOLDER_BROWSER_KEY_PREFIX_FOLDER.length()));
final Folder folder = folderRepo.findById(objectId)
.orElseThrow(() -> new IllegalArgumentException(String.format(
"No folder with ID %d found in database. "
+ "Where did that ID come form?",
objectId)));
final Optional<Folder> parentFolder = folderManager.getParentFolder(
folder);
if (parentFolder.isPresent()) {
return Optional.empty();
} else {
return Optional.ofNullable(String.format(
"%s%d",
FOLDER_BROWSER_KEY_PREFIX_FOLDER,
parentFolder.get().getObjectId()));
}
}
private List<String> findSubFolderIds(final String folderId) {
Objects.requireNonNull(folderId);
if (!folderId.startsWith(FOLDER_BROWSER_KEY_PREFIX_FOLDER)) {
throw new IllegalArgumentException(String.format(
"Provided string '%s' is not the ID of a folder.",
folderId));
}
final long objectId = Long.parseLong(folderId.substring(
FOLDER_BROWSER_KEY_PREFIX_FOLDER.length()));
final Folder folder = folderRepo.findById(objectId)
.orElseThrow(() -> new IllegalArgumentException(String.format(
"No folder with ID %d found in database. "
+ "Where did that ID come form?",
objectId)));
return findSubFolders(folder)
.stream()
.map(subFolder -> String.format("%s%d",
FOLDER_BROWSER_KEY_PREFIX_FOLDER,
subFolder.getObjectId()))
.collect(Collectors.toList());
}
private List<Folder> findSubFolders(final Folder folder) {
Objects.requireNonNull(folder);
if (folder.getSubFolders() == null
|| folder.getSubFolders().isEmpty()) {
return Collections.emptyList();
}
final List<Folder> subFolders = new ArrayList<>();
for (final Folder subFolder : folder.getSubFolders()) {
subFolders.add(subFolder);
subFolders.addAll(findSubFolders(subFolder));
}
return subFolders;
}
/** /**
* Called by the {@link AssetFolderBrowser} to delete an object. * Called by the {@link AssetFolderBrowser} to delete an object.
* *
@ -252,10 +486,13 @@ public class AssetFolderBrowserController {
row.setFolder(false); row.setFolder(false);
row.setDeletable(!assetManager.isAssetInUse(asset));
return row; return row;
} }
private List<Folder> findSubFolders(final Folder folder, private List<Folder> findSubFolders(final Folder folder,
final String filterTerm,
final String orderBy, final String orderBy,
final String orderDirection, final String orderDirection,
final int firstResult, final int firstResult,
@ -279,8 +516,12 @@ public class AssetFolderBrowserController {
final TypedQuery<Folder> query = entityManager final TypedQuery<Folder> query = entityManager
.createQuery( .createQuery(
criteria.where( criteria.where(
builder. builder.and(
equal(from.get("parentCategory"), folder) builder.equal(from.get("parentCategory"),
folder),
builder.like(builder.lower(from.get("name")),
filterTerm)
)
) )
.orderBy(order) .orderBy(order)
); );
@ -297,6 +538,7 @@ public class AssetFolderBrowserController {
} }
private List<Asset> findAssetsInFolder(final Folder folder, private List<Asset> findAssetsInFolder(final Folder folder,
final String filterTerm,
final String orderBy, final String orderBy,
final String orderDirection, final String orderDirection,
final int firstResult, final int firstResult,
@ -339,7 +581,10 @@ public class AssetFolderBrowserController {
builder.equal(join.get( builder.equal(join.get(
"category"), folder), "category"), folder),
builder.equal(join.get("type"), builder.equal(join.get("type"),
CmsConstants.CATEGORIZATION_TYPE_FOLDER) CmsConstants.CATEGORIZATION_TYPE_FOLDER),
builder.like(builder.lower(fromAsset.get(
"displayName")),
filterTerm)
) )
) )
.orderBy(order) .orderBy(order)

View File

@ -19,27 +19,35 @@
package com.arsdigita.cms.ui.assets; package com.arsdigita.cms.ui.assets;
import com.arsdigita.bebop.ActionLink; import com.arsdigita.bebop.ActionLink;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.Component; import com.arsdigita.bebop.Component;
import com.arsdigita.bebop.ControlLink;
import com.arsdigita.bebop.Form; import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.FormData;
import com.arsdigita.bebop.FormProcessException; import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.Label; import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.Page; import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState; import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.Paginator; import com.arsdigita.bebop.Paginator;
import com.arsdigita.bebop.RequestLocal;
import com.arsdigita.bebop.Resettable; import com.arsdigita.bebop.Resettable;
import com.arsdigita.bebop.SaveCancelSection;
import com.arsdigita.bebop.SegmentedPanel; import com.arsdigita.bebop.SegmentedPanel;
import com.arsdigita.bebop.SimpleContainer; import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.bebop.SingleSelectionModel; import com.arsdigita.bebop.SingleSelectionModel;
import com.arsdigita.bebop.Table; import com.arsdigita.bebop.Table;
import com.arsdigita.bebop.Text; import com.arsdigita.bebop.Text;
import com.arsdigita.bebop.Tree;
import com.arsdigita.bebop.event.ActionEvent; import com.arsdigita.bebop.event.ActionEvent;
import com.arsdigita.bebop.event.ActionListener; import com.arsdigita.bebop.event.ActionListener;
import com.arsdigita.bebop.event.FormProcessListener; import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent; import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.event.FormSubmissionListener; import com.arsdigita.bebop.event.FormSubmissionListener;
import com.arsdigita.bebop.event.FormValidationListener;
import com.arsdigita.bebop.event.PrintEvent; import com.arsdigita.bebop.event.PrintEvent;
import com.arsdigita.bebop.event.PrintListener; import com.arsdigita.bebop.event.PrintListener;
import com.arsdigita.bebop.form.CheckboxGroup; import com.arsdigita.bebop.form.CheckboxGroup;
import com.arsdigita.bebop.form.FormErrorDisplay;
import com.arsdigita.bebop.form.Option; import com.arsdigita.bebop.form.Option;
import com.arsdigita.bebop.form.SingleSelect; import com.arsdigita.bebop.form.SingleSelect;
import com.arsdigita.bebop.form.Submit; import com.arsdigita.bebop.form.Submit;
@ -47,6 +55,7 @@ import com.arsdigita.bebop.parameters.ArrayParameter;
import com.arsdigita.bebop.parameters.StringParameter; import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.bebop.table.TableCellRenderer; import com.arsdigita.bebop.table.TableCellRenderer;
import com.arsdigita.bebop.table.TableColumn; import com.arsdigita.bebop.table.TableColumn;
import com.arsdigita.bebop.tree.TreeCellRenderer;
import com.arsdigita.cms.CMS; import com.arsdigita.cms.CMS;
import com.arsdigita.cms.ui.BaseTree; import com.arsdigita.cms.ui.BaseTree;
import com.arsdigita.cms.ui.folder.FolderCreateForm; import com.arsdigita.cms.ui.folder.FolderCreateForm;
@ -59,6 +68,8 @@ import com.arsdigita.globalization.GlobalizedMessage;
import com.arsdigita.toolbox.ui.ActionGroup; import com.arsdigita.toolbox.ui.ActionGroup;
import com.arsdigita.toolbox.ui.LayoutPanel; import com.arsdigita.toolbox.ui.LayoutPanel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.categorization.Category; import org.libreccm.categorization.Category;
import org.libreccm.cdi.utils.CdiUtil; import org.libreccm.cdi.utils.CdiUtil;
import org.librecms.CmsConstants; import org.librecms.CmsConstants;
@ -68,8 +79,21 @@ import org.librecms.contentsection.Folder;
import java.util.List; import java.util.List;
import org.arsdigita.cms.CMSConfig; import org.arsdigita.cms.CMSConfig;
import org.libreccm.categorization.CategoryManager;
import org.libreccm.core.CcmObject;
import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.security.PermissionChecker;
import org.librecms.contentsection.Asset;
import org.librecms.contentsection.AssetManager;
import org.librecms.contentsection.AssetRepository;
import org.librecms.contentsection.FolderManager;
import org.librecms.contentsection.FolderRepository;
import org.librecms.contentsection.privileges.ItemPrivileges;
import javax.swing.CellRendererPane; import java.util.Arrays;
import java.util.Objects;
import static org.librecms.CmsConstants.*;
/** /**
* *
@ -77,6 +101,8 @@ import javax.swing.CellRendererPane;
*/ */
public class AssetPane extends LayoutPanel implements Resettable { public class AssetPane extends LayoutPanel implements Resettable {
private static final Logger LOGGER = LogManager.getLogger(AssetPane.class);
public static final String SET_FOLDER = "set_folder"; public static final String SET_FOLDER = "set_folder";
private static final String SOURCES_PARAM = "sources"; private static final String SOURCES_PARAM = "sources";
private static final String ACTION_PARAM = "action"; private static final String ACTION_PARAM = "action";
@ -89,10 +115,14 @@ public class AssetPane extends LayoutPanel implements Resettable {
private final FolderRequestLocal folderRequestLocal; private final FolderRequestLocal folderRequestLocal;
private final ArrayParameter sourcesParameter = new ArrayParameter( private final ArrayParameter sourcesParameter = new ArrayParameter(
new StringParameter(SOURCES_PARAM)); new StringParameter(SOURCES_PARAM));
private final StringParameter actionParameter = new StringParameter(
ACTION_PARAM);
private AssetFolderBrowser folderBrowser; private AssetFolderBrowser folderBrowser;
private Form browserForm;
private SingleSelect actionSelect; private SingleSelect actionSelect;
private Submit actionSubmit; private Submit actionSubmit;
private TargetSelector targetSelector;
private SegmentedPanel.Segment browseSegment; private SegmentedPanel.Segment browseSegment;
private SegmentedPanel.Segment currentFolderSegment; private SegmentedPanel.Segment currentFolderSegment;
@ -145,7 +175,7 @@ public class AssetPane extends LayoutPanel implements Resettable {
final SegmentedPanel panel = new SegmentedPanel(); final SegmentedPanel panel = new SegmentedPanel();
browseSegment = panel.addSegment(); browseSegment = panel.addSegment();
final Form browserForm = new Form("assetFolderBrowser", browserForm = new Form("assetFolderBrowser",
new SimpleContainer()); new SimpleContainer());
browserForm.setMethod(Form.GET); browserForm.setMethod(Form.GET);
folderBrowser = new AssetFolderBrowser(folderSelectionModel); folderBrowser = new AssetFolderBrowser(folderSelectionModel);
@ -186,7 +216,7 @@ public class AssetPane extends LayoutPanel implements Resettable {
new GlobalizedMessage( new GlobalizedMessage(
"cms.ui.folder.edit_selection", "cms.ui.folder.edit_selection",
CmsConstants.CMS_FOLDER_BUNDLE))); CmsConstants.CMS_FOLDER_BUNDLE)));
actionSelect = new SingleSelect(ACTION_PARAM); actionSelect = new SingleSelect(actionParameter);
actionSelect.addOption( actionSelect.addOption(
new Option(COPY, new Option(COPY,
new Label(new GlobalizedMessage( new Label(new GlobalizedMessage(
@ -203,8 +233,68 @@ public class AssetPane extends LayoutPanel implements Resettable {
new GlobalizedMessage("cms.ui.folder.go", new GlobalizedMessage("cms.ui.folder.go",
CmsConstants.CMS_FOLDER_BUNDLE)); CmsConstants.CMS_FOLDER_BUNDLE));
actionFormContainer.add(actionSubmit); actionFormContainer.add(actionSubmit);
browserForm.addProcessListener(new FormProcessListener() {
@Override
public void process(final FormSectionEvent event)
throws FormProcessException {
final PageState state = event.getPageState();
moveCopyMode(state);
}
});
browserForm.add(actionFormContainer); browserForm.add(actionFormContainer);
targetSelector = new TargetSelector();
targetSelector.addProcessListener(new FormProcessListener() {
@Override
public void process(final FormSectionEvent event)
throws FormProcessException {
final PageState state = event.getPageState();
browseMode(state);
targetSelector.setVisible(state, false);
final Folder folder = targetSelector.getTarget(state);
final String[] objectIds = getSources(state);
if (isCopy(state)) {
copyObjects(folder, objectIds);
} else if (isMove(state)) {
moveObjects(folder, objectIds);
}
reset(state);
}
});
targetSelector.addValidationListener(
new TargetSelectorValidationListener());
targetSelector.addSubmissionListener(new FormSubmissionListener() {
@Override
public void submitted(final FormSectionEvent event)
throws FormProcessException {
final PageState state = event.getPageState();
if (targetSelector.isCancelled(state)) {
reset(state);
browseMode(state);
throw new FormProcessException(new GlobalizedMessage(
"cms.ui.folder.cancelled",
CmsConstants.CMS_FOLDER_BUNDLE));
}
}
});
browseSegment.add(targetSelector);
// browseSegment.add(paginator); // browseSegment.add(paginator);
// browseSegment.add(folderBrowser); // browseSegment.add(folderBrowser);
browseSegment.add(browserForm); browseSegment.add(browserForm);
@ -352,22 +442,44 @@ public class AssetPane extends LayoutPanel implements Resettable {
} }
protected void browseMode(final PageState state) { protected void browseMode(final PageState state) {
tree.setVisible(state, true);
browseSegment.setVisible(state, true); browseSegment.setVisible(state, true);
folderBrowser.setVisible(state, true);
browserForm.setVisible(state, true);
targetSelector.setVisible(state, false);
actionsSegment.setVisible(state, true); actionsSegment.setVisible(state, true);
newFolderSegment.setVisible(state, false); newFolderSegment.setVisible(state, false);
editFolderSegment.setVisible(state, false); editFolderSegment.setVisible(state, false);
} }
protected void moveCopyMode(final PageState state) {
tree.setVisible(state, false);
browseSegment.setVisible(state, true);
folderBrowser.setVisible(state, false);
browserForm.setVisible(state, false);
targetSelector.setVisible(state, true);
actionsSegment.setVisible(state, false);
newFolderSegment.setVisible(state, false);
editFolderSegment.setVisible(state, false);
targetSelector.expose(state);
}
protected void newFolderMode(final PageState state) { protected void newFolderMode(final PageState state) {
tree.setVisible(state, false);
browseSegment.setVisible(state, false); browseSegment.setVisible(state, false);
folderBrowser.setVisible(state, false);
browserForm.setVisible(state, false);
targetSelector.setVisible(state, false);
actionsSegment.setVisible(state, false); actionsSegment.setVisible(state, false);
newFolderSegment.setVisible(state, true); newFolderSegment.setVisible(state, true);
editFolderSegment.setVisible(state, false); editFolderSegment.setVisible(state, false);
} }
protected void editFolderMode(final PageState state) { protected void editFolderMode(final PageState state) {
tree.setVisible(state, false);
browseSegment.setVisible(state, false); browseSegment.setVisible(state, false);
targetSelector.setVisible(state, false);
actionsSegment.setVisible(state, false); actionsSegment.setVisible(state, false);
newFolderSegment.setVisible(state, false); newFolderSegment.setVisible(state, false);
editFolderSegment.setVisible(state, true); editFolderSegment.setVisible(state, true);
@ -381,10 +493,17 @@ public class AssetPane extends LayoutPanel implements Resettable {
page.addActionListener(new TreeListener()); page.addActionListener(new TreeListener());
page.addActionListener(new FolderListener()); page.addActionListener(new FolderListener());
page.setVisibleDefault(tree, true);
page.setVisibleDefault(browseSegment, true); page.setVisibleDefault(browseSegment, true);
page.setVisibleDefault(folderBrowser, true);
page.setVisibleDefault(browserForm, true);
page.setVisibleDefault(targetSelector, false);
page.setVisibleDefault(actionsSegment, true); page.setVisibleDefault(actionsSegment, true);
page.setVisibleDefault(newFolderSegment, false); page.setVisibleDefault(newFolderSegment, false);
page.setVisibleDefault(editFolderSegment, false); page.setVisibleDefault(editFolderSegment, false);
page.addComponentStateParam(this, actionParameter);
page.addComponentStateParam(this, sourcesParameter);
} }
@Override @Override
@ -394,6 +513,50 @@ public class AssetPane extends LayoutPanel implements Resettable {
folderBrowser.getPaginator().reset(state); folderBrowser.getPaginator().reset(state);
state.setValue(actionParameter, null);
state.setValue(sourcesParameter, null);
}
private String[] getSources(final PageState state) {
final String[] result = (String[]) state.getValue(sourcesParameter);
if (result == null) {
return new String[0];
} else {
return result;
}
}
protected final boolean isMove(final PageState state) {
return MOVE.equals(getAction(state));
}
protected final boolean isCopy(final PageState state) {
return COPY.equals(getAction(state));
}
private String getAction(final PageState state) {
return (String) state.getValue(actionParameter);
}
protected void moveObjects(final Folder target, final String[] objectIds) {
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final AssetFolderBrowserController controller = cdiUtil.findBean(
AssetFolderBrowserController.class);
controller.moveObjects(target, objectIds);
}
protected void copyObjects(final Folder target, final String[] objectIds) {
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final AssetFolderBrowserController controller = cdiUtil.findBean(
AssetFolderBrowserController.class);
controller.copyObjects(target, objectIds);
} }
private final class FolderListener implements ActionListener { private final class FolderListener implements ActionListener {
@ -452,4 +615,385 @@ public class AssetPane extends LayoutPanel implements Resettable {
} }
private class TargetSelector extends Form implements Resettable {
private final FolderSelectionModel targetFolderModel;
private final AssetFolderTree folderTree;
private final Submit cancelButton;
public TargetSelector() {
super("targetSelector", new BoxPanel());
setMethod(GET);
targetFolderModel = new FolderSelectionModel("target") {
@Override
protected Long getRootFolderID(final PageState state) {
final ContentSection section = CMS
.getContext()
.getContentSection();
return section.getRootAssetsFolder().getObjectId();
}
};
folderTree = new AssetFolderTree(targetFolderModel);
folderTree.setCellRenderer(new FolderTreeCellRenderer());
final Label label = new Label(new PrintListener() {
@Override
public void prepare(final PrintEvent event) {
final PageState state = event.getPageState();
final Label label = (Label) event.getTarget();
final int numberOfItems = getSources(state).length;
final Category folder = (Category) folderSelectionModel
.getSelectedObject(state);
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final CategoryManager categoryManager = cdiUtil
.findBean(CategoryManager.class);
if (isMove(state)) {
label.setLabel(new GlobalizedMessage(
"cms.ui.folder.move",
CmsConstants.CMS_FOLDER_BUNDLE,
new Object[]{numberOfItems,
categoryManager.getCategoryPath(folder)}));
} else if (isCopy(state)) {
label.setLabel(new GlobalizedMessage(
"cms.ui.folder.copy",
CMS_BUNDLE,
new Object[]{numberOfItems,
categoryManager.getCategoryPath(
folder)}));
}
}
});
label.setOutputEscaping(false);
add(label);
add(folderTree);
add(new FormErrorDisplay(this));
final SaveCancelSection saveCancelSection = new SaveCancelSection();
cancelButton = saveCancelSection.getCancelButton();
add(saveCancelSection);
}
@Override
public void register(final Page page) {
super.register(page);
page.addComponentStateParam(this, targetFolderModel
.getStateParameter());
}
public void expose(final PageState state) {
final Folder folder = folderSelectionModel.getSelectedObject(state);
targetFolderModel.clearSelection(state);
if (folder != null) {
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final FolderManager folderManager = cdiUtil.findBean(
FolderManager.class);
if (!folderManager.getParentFolder(folder).isPresent()) {
folderTree.expand(Long.toString(folder.getObjectId()),
state);
} else {
final List<Folder> parents = folderManager
.getParentFolders(folder);
parents
.stream()
.map(parent -> Long.toString(parent.getObjectId()))
.forEach(folderId -> folderTree.expand(folderId, state));
}
}
}
@Override
public void reset(final PageState state) {
folderTree.clearSelection(state);
state.setValue(folderTree.getSelectionModel().getStateParameter(),
null);
}
public Folder getTarget(final PageState state) {
return targetFolderModel.getSelectedObject(state);
}
public boolean isCancelled(final PageState state) {
return cancelButton.isSelected(state);
}
}
private class FolderTreeCellRenderer implements TreeCellRenderer {
private final RequestLocal invalidFoldersRequestLocal
= new RequestLocal();
/**
* Render the folders appropriately. The selected folder is a bold
* label. Invalid folders are plain labels. Unselected, valid folders
* are control links. Invalid folders are: the parent folder of the
* sources, any of the sources, and any subfolders of the sources.
*/
@Override
@SuppressWarnings("unchecked")
public Component getComponent(final Tree tree,
final PageState state,
final Object value,
final boolean isSelected,
final boolean isExpanded,
final boolean isLeaf,
final Object key) {
// Get the list of invalid folders once per request.
final List<String> invalidFolders;
if (invalidFoldersRequestLocal.get(state) == null) {
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final AssetFolderBrowserController controller = cdiUtil
.findBean(AssetFolderBrowserController.class);
invalidFolders = controller.createInvalidTargetsList(
Arrays.asList(getSources(state)));
invalidFoldersRequestLocal.set(state, invalidFolders);
} else {
invalidFolders = (List<String>) invalidFoldersRequestLocal
.get(state);
}
final Label label = new Label(value.toString());
if (invalidFolders.contains(String.format(
FOLDER_BROWSER_KEY_PREFIX_FOLDER + "%s", key))) {
return label;
}
// Bold if selected
if (isSelected) {
label.setFontWeight(Label.BOLD);
return label;
}
return new ControlLink(label);
}
}
private class TargetSelectorValidationListener
implements FormValidationListener {
@Override
public void validate(final FormSectionEvent event)
throws FormProcessException {
final PageState state = event.getPageState();
if (getSources(state).length <= 0) {
throw new IllegalStateException("No source items specified");
}
final Folder target = targetSelector.getTarget(state);
final FormData data = event.getFormData();
if (target == null) {
data.addError(new GlobalizedMessage(
"cms.ui.folder.need_select_target_folder",
CmsConstants.CMS_FOLDER_BUNDLE));
//If the target is null, we can skip the rest of the checks
return;
}
if (target.equals(folderSelectionModel.getSelectedObject(state))) {
data.addError(new GlobalizedMessage(
"cms.ui.folder.not_within_same_folder",
CmsConstants.CMS_FOLDER_BUNDLE));
}
// check create item permission
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final PermissionChecker permissionChecker = cdiUtil.findBean(
PermissionChecker.class);
if (!permissionChecker.isPermitted(
ItemPrivileges.CREATE_NEW, target)) {
data.addError("cms.ui.folder.no_permission_for_item",
CmsConstants.CMS_FOLDER_BUNDLE);
}
for (String source : getSources(state)) {
validateObject(source, target, state, data);
}
}
private void validateObject(final String objectId,
final Folder target,
final PageState state,
final FormData data) {
Objects.requireNonNull(objectId, "objectId can't be null.");
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final FolderRepository folderRepo = cdiUtil
.findBean(FolderRepository.class);
final AssetRepository assetRepo = cdiUtil
.findBean(AssetRepository.class);
final AssetManager assetManager = cdiUtil
.findBean(AssetManager.class);
final AssetFolderBrowserController controller = cdiUtil
.findBean(AssetFolderBrowserController.class);
final FolderManager folderManager = cdiUtil
.findBean(FolderManager.class);
final PermissionChecker permissionChecker = cdiUtil.findBean(
PermissionChecker.class);
final CcmObject object;
final String name;
if (objectId.startsWith(FOLDER_BROWSER_KEY_PREFIX_FOLDER)) {
final long folderId = Long.parseLong(objectId.substring(
FOLDER_BROWSER_KEY_PREFIX_FOLDER.length()));
final Folder folder = folderRepo.findById(folderId).orElseThrow(
() -> new IllegalArgumentException(String.format(
"No folder with id %d in database.", folderId)));
name = folder.getName();
//Check if folder or subfolder contains in use assets
if (isMove(state)) {
final FolderManager.FolderIsMovable movable = folderManager
.folderIsMovable(folder, target);
switch (movable) {
case DIFFERENT_SECTIONS:
addErrorMessage(data,
"cms.ui.folder.different_sections",
name);
break;
case HAS_IN_USE_ASSETS:
addErrorMessage(data,
"cms.ui.folder.has_in_use_assets",
name);
break;
case DIFFERENT_TYPES:
addErrorMessage(data,
"cms.ui.folder.different_folder_types",
name);
break;
case IS_ROOT_FOLDER:
addErrorMessage(data,
"cms.ui.folder.is_root_folder",
name);
break;
case SAME_FOLDER:
addErrorMessage(data,
"cms.ui.folder.same_folder",
name);
break;
case YES:
//Nothing
break;
default:
throw new UnexpectedErrorException(String.format(
"Unknown state '%s' for '%s'.",
movable,
FolderManager.FolderIsMovable.class.getName()));
}
}
object = folder;
} else if (objectId.startsWith(FOLDER_BROWSER_KEY_PREFIX_ASSET)) {
final long assetId = Long.parseLong(objectId.substring(
FOLDER_BROWSER_KEY_PREFIX_ASSET.length()));
final Asset asset = assetRepo
.findById(assetId)
.orElseThrow(() -> new IllegalArgumentException(
String.format(
"No asset with id %d in the database.",
assetId)));
name = asset.getDisplayName();
if (isMove(state) && assetManager.isAssetInUse(asset)) {
addErrorMessage(data, "cms.ui.folder.item_is_live", name);
}
object = asset;
} else {
throw new IllegalArgumentException(String.format(
"Provided objectId '%s' does not start with '%s' "
+ "or '%s'.",
objectId,
FOLDER_BROWSER_KEY_PREFIX_FOLDER,
FOLDER_BROWSER_KEY_PREFIX_ASSET));
}
final long count = controller.countObjects(target, name);
if (count > 0) {
// there is an item or subfolder in the target folder that already has this name
addErrorMessage(data, "cms.ui.folder.item_already_exists",
name);
}
if (!(permissionChecker.isPermitted(
ItemPrivileges.DELETE, object))
&& isMove(state)) {
addErrorMessage(data,
"cms.ui.folder.no_permission_for_item",
object.getDisplayName());
}
}
}
private void addErrorMessage(final FormData data,
final String message,
final String itemName) {
data.addError(new GlobalizedMessage(message,
CmsConstants.CMS_FOLDER_BUNDLE,
new Object[]{itemName}));
}
private class AssetFolderTree extends Tree {
public AssetFolderTree(final FolderSelectionModel folderSelectionModel) {
super(new FolderTreeModelBuilder() {
@Override
protected Folder getRootFolder(final PageState state) {
final ContentSection section = CMS
.getContext()
.getContentSection();
return section.getRootAssetsFolder();
}
});
setSelectionModel(selectionModel);
}
@Override
public void setSelectedKey(final PageState state, final Object key) {
if (key instanceof String) {
final Long keyAsLong;
if (((String) key).startsWith(
FOLDER_BROWSER_KEY_PREFIX_FOLDER)) {
keyAsLong = Long.parseLong(((String) key).substring(
FOLDER_BROWSER_KEY_PREFIX_FOLDER.length()));
} else {
keyAsLong = Long.parseLong((String) key);
}
super.setSelectedKey(state, keyAsLong);
} else if (key instanceof Long) {
super.setSelectedKey(state, key);
} else {
//We know that a FolderSelectionModel only takes keys of type Long.
//Therefore we try to cast here
final Long keyAsLong = (Long) key;
super.setSelectedKey(state, keyAsLong);
}
}
}
} }

View File

@ -573,7 +573,7 @@ public class FolderBrowserController {
CmsConstants.CATEGORIZATION_TYPE_FOLDER), CmsConstants.CATEGORIZATION_TYPE_FOLDER),
builder.equal(fromItem.get("version"), builder.equal(fromItem.get("version"),
ContentItemVersion.DRAFT), ContentItemVersion.DRAFT),
builder.like(fromItem.get("displayName"), builder.like(builder.lower(fromItem.get("displayName")),
filterTerm) filterTerm)
) )
) )
@ -780,7 +780,8 @@ public class FolderBrowserController {
Objects.requireNonNull(targetFolder); Objects.requireNonNull(targetFolder);
final ContentItem item = itemRepo.findById(itemId) final ContentItem item = itemRepo
.findById(itemId)
.orElseThrow(() -> new IllegalArgumentException(String.format( .orElseThrow(() -> new IllegalArgumentException(String.format(
"No content item with ID %d in the database. " "No content item with ID %d in the database. "
+ "Where did that ID come from?", + "Where did that ID come from?",
@ -834,7 +835,8 @@ public class FolderBrowserController {
Objects.requireNonNull(targetFolder); Objects.requireNonNull(targetFolder);
final ContentItem item = itemRepo.findById(itemId) final ContentItem item = itemRepo
.findById(itemId)
.orElseThrow(() -> new IllegalArgumentException(String.format( .orElseThrow(() -> new IllegalArgumentException(String.format(
"No content item with ID %d in the database. " "No content item with ID %d in the database. "
+ "Where did that ID come from?", + "Where did that ID come from?",

View File

@ -414,8 +414,8 @@ public class FolderManipulator extends SimpleContainer implements
} }
@Override @Override
public void process(final FormSectionEvent event) throws public void process(final FormSectionEvent event)
FormProcessException { throws FormProcessException {
final PageState state = event.getPageState(); final PageState state = event.getPageState();
@ -484,16 +484,16 @@ public class FolderManipulator extends SimpleContainer implements
} }
private class TargetSelectorValidationListener implements private class TargetSelectorValidationListener
FormValidationListener { implements FormValidationListener {
public TargetSelectorValidationListener() { public TargetSelectorValidationListener() {
//Nothing //Nothing
} }
@Override @Override
public void validate(final FormSectionEvent event) throws public void validate(final FormSectionEvent event)
FormProcessException { throws FormProcessException {
final PageState state = event.getPageState(); final PageState state = event.getPageState();
@ -519,7 +519,6 @@ public class FolderManipulator extends SimpleContainer implements
// check create item permission // check create item permission
final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final Shiro shiro = cdiUtil.findBean(Shiro.class);
final PermissionChecker permissionChecker = cdiUtil.findBean( final PermissionChecker permissionChecker = cdiUtil.findBean(
PermissionChecker.class); PermissionChecker.class);
if (!permissionChecker.isPermitted( if (!permissionChecker.isPermitted(
@ -614,8 +613,10 @@ public class FolderManipulator extends SimpleContainer implements
} else if (objectId.startsWith(FOLDER_BROWSER_KEY_PREFIX_ITEM)) { } else if (objectId.startsWith(FOLDER_BROWSER_KEY_PREFIX_ITEM)) {
final long itemId = Long.parseLong(objectId.substring( final long itemId = Long.parseLong(objectId.substring(
FOLDER_BROWSER_KEY_PREFIX_ITEM.length())); FOLDER_BROWSER_KEY_PREFIX_ITEM.length()));
final ContentItem item = itemRepo.findById(itemId).orElseThrow( final ContentItem item = itemRepo
() -> new IllegalArgumentException(String.format( .findById(itemId)
.orElseThrow(() -> new IllegalArgumentException(
String.format(
"No content item with id %d in database.", "No content item with id %d in database.",
itemId))); itemId)));
@ -742,8 +743,8 @@ public class FolderManipulator extends SimpleContainer implements
final PageState state = event.getPageState(); final PageState state = event.getPageState();
final Label label = (Label) event.getTarget(); final Label label = (Label) event.getTarget();
final int numberOfItems = getSources(state).length; final int numberOfItems = getSources(state).length;
final Category folder = (Category) sourceFolderModel. final Category folder = (Category) sourceFolderModel
getSelectedObject(state); .getSelectedObject(state);
final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final CategoryManager categoryManager = cdiUtil. final CategoryManager categoryManager = cdiUtil.
findBean(CategoryManager.class); findBean(CategoryManager.class);
@ -795,9 +796,10 @@ public class FolderManipulator extends SimpleContainer implements
folderTree.expand(Long.toString(folder.getObjectId()), folderTree.expand(Long.toString(folder.getObjectId()),
state); state);
} else { } else {
final List<Folder> parents = folderManager.getParentFolders( final List<Folder> parents = folderManager
folder); .getParentFolders(folder);
parents.stream() parents
.stream()
.map(parent -> Long.toString(parent.getObjectId())) .map(parent -> Long.toString(parent.getObjectId()))
.forEach(folderId -> folderTree.expand(folderId, state)); .forEach(folderId -> folderTree.expand(folderId, state));
} }

View File

@ -60,7 +60,7 @@ public class FolderTree extends Tree {
} else if (key instanceof Long) { } else if (key instanceof Long) {
super.setSelectedKey(state, key); super.setSelectedKey(state, key);
} else { } else {
//We now that a FolderSelectionModel only takes keys of type Long. //We know that a FolderSelectionModel only takes keys of type Long.
//Therefore we try to cast here //Therefore we try to cast here
final Long keyAsLong = (Long) key; final Long keyAsLong = (Long) key;
super.setSelectedKey(state, keyAsLong); super.setSelectedKey(state, keyAsLong);

View File

@ -55,6 +55,7 @@ public class CmsConstants {
public static final String CATEGORIZATION_TYPE_FOLDER = "folder"; public static final String CATEGORIZATION_TYPE_FOLDER = "folder";
public static final String FOLDER_BROWSER_KEY_PREFIX_FOLDER = "folder-"; public static final String FOLDER_BROWSER_KEY_PREFIX_FOLDER = "folder-";
public static final String FOLDER_BROWSER_KEY_PREFIX_ASSET = "asset-";
public static final String FOLDER_BROWSER_KEY_PREFIX_ITEM = "item-"; public static final String FOLDER_BROWSER_KEY_PREFIX_ITEM = "item-";
/** /**

View File

@ -66,6 +66,9 @@ public class FolderManager {
@Inject @Inject
private ContentItemManager itemManager; private ContentItemManager itemManager;
@Inject
private AssetManager assetManager;
/** /**
* An enum describing if a folder can be deleted or not and why. * An enum describing if a folder can be deleted or not and why.
* *
@ -118,7 +121,11 @@ public class FolderManager {
/** /**
* The folder to move contains live items. * The folder to move contains live items.
*/ */
HAS_LIVE_ITEMS HAS_LIVE_ITEMS,
/**
* The folder to contains assets which are in use.
*/
HAS_IN_USE_ASSETS
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
@ -368,6 +375,10 @@ public class FolderManager {
return FolderIsMovable.HAS_LIVE_ITEMS; return FolderIsMovable.HAS_LIVE_ITEMS;
} }
if (usedAssetsInFolder(movingFolder)) {
return FolderIsMovable.HAS_IN_USE_ASSETS;
}
return FolderIsMovable.YES; return FolderIsMovable.YES;
} }
@ -434,6 +445,22 @@ public class FolderManager {
return liveItemsInFolder || liveItemsInSubFolders; return liveItemsInFolder || liveItemsInSubFolders;
} }
private boolean usedAssetsInFolder(final Folder folder) {
final boolean usedAssetsInFolder = folder.getObjects()
.stream()
.map(categorization -> categorization.getCategorizedObject())
.filter(object -> object instanceof Asset)
.map(asset -> (Asset) asset)
.anyMatch(asset -> assetManager.isAssetInUse(asset));
final boolean usedAssetsInSubFolders = folder.getSubFolders()
.stream()
.anyMatch(subFolder -> usedAssetsInFolder(folder));
return usedAssetsInFolder || usedAssetsInSubFolders;
}
/** /**
* Returns the path of folder. * Returns the path of folder.
* *