From c78be6977a8573c9042e22222b7ff0d49a1e3bbb Mon Sep 17 00:00:00 2001 From: Namics OSS CI Date: Fri, 7 Jul 2023 09:56:53 +0000 Subject: [PATCH 1/4] Release 1.1.10: set develop to next development version 1.1.11-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 82f56dd..0d75f9d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.namics.oss.magnolia magnolia-appbuilder - 1.1.10 + 1.1.11-SNAPSHOT jar ${project.artifactId} From 24dec40bde7e4d6a51b4222396edf2a5f4158317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20H=C3=A4ssig?= Date: Wed, 13 Dec 2023 16:34:44 +0100 Subject: [PATCH 2/4] add space to Exception message --- .../oss/magnolia/appbuilder/action/add/CreateNodeAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/namics/oss/magnolia/appbuilder/action/add/CreateNodeAction.java b/src/main/java/com/namics/oss/magnolia/appbuilder/action/add/CreateNodeAction.java index a561d77..4bf3921 100644 --- a/src/main/java/com/namics/oss/magnolia/appbuilder/action/add/CreateNodeAction.java +++ b/src/main/java/com/namics/oss/magnolia/appbuilder/action/add/CreateNodeAction.java @@ -43,7 +43,7 @@ public void execute() { final String nodeName = nodeNameHelper.getUniqueName( parent, getForm().getPropertyValue(definition.getNodeNameProperty()).map(String::valueOf).orElseThrow(() -> - new ActionExecutionException("Failed to get node name property " + definition.getNodeNameProperty() + "from form") + new ActionExecutionException("Failed to get node name property " + definition.getNodeNameProperty() + " from form") ) ); From fd33aa85e96301b048da5de7955dd8a65116527b Mon Sep 17 00:00:00 2001 From: eschleb Date: Wed, 31 Jan 2024 16:35:11 +0100 Subject: [PATCH 3/4] Add legacy/ui5 app chooser to be able to open a legacy/UI5 chooser dialog to content from an UI6 app. --- README.md | 21 +- .../AppDefinitionMetaDataBuilder.java | 39 +++- .../oss/magnolia/appbuilder/AppExporter.java | 5 + .../LegacyAppDescriptorProvider.java | 203 ++++++++++++++++++ .../appbuilder/annotations/AppFactory.java | 10 +- .../appbuilder/annotations/ChooseDialog.java | 17 -- 6 files changed, 239 insertions(+), 56 deletions(-) create mode 100644 src/main/java/com/namics/oss/magnolia/appbuilder/LegacyAppDescriptorProvider.java delete mode 100644 src/main/java/com/namics/oss/magnolia/appbuilder/annotations/ChooseDialog.java diff --git a/README.md b/README.md index ac14a21..8f31bbf 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,6 @@ The position of the app in the group can be configured with the 'order' annotati The AppFactory requires at least one method marked with the `@SubApp` annotation this method must return a `info.magnolia.ui.api.app.SubAppDescriptor`. -#### ChooseDialog, optional (Target: Method) -A method marked with `@ChooseDialog` must return a `info.magnolia.ui.dialog.definition.ChooseDialogDefinition`. - #### AppPermissions, optional (Target: Method) A method marked with `@AppPermissions` must return a `info.magnolia.cms.security.operations.AccessDefinition`. @@ -122,7 +119,8 @@ import info.magnolia.ui.workbench.column.definition.ColumnDefinition; id = SampleApp.ID, name = SampleApp.NAME, label = SampleApp.NAME, - icon = MgnlIcon.TAG_2_APP + icon = MgnlIcon.TAG_2_APP, + generateLegacyChooserApp = true ) public class SampleApp { public static final String NAME = "SampleApp"; @@ -149,21 +147,6 @@ public class SampleApp { .width(160) }; - // optional - if not specified the chooser dialog will contain the same columns as the app (columnDefinitions) - @ChooseDialog - public ChooseDialogDefinition getChooseDialog() { - return new ChooseDialogBuilder().contentConnector( - new JcrContentConnectorBuilder() - .workspace("") - .defaultOrder("jcrName") - .rootPath("/") - .nodeTypes( - new NodeTypeBuilder() - .name("") - .icon(MgnlIcon.OPEN_NEW_WINDOW) - )); - } - @SubApp public SubAppDescriptor getBrowser() { return new BrowserAppBuilder<>() diff --git a/src/main/java/com/namics/oss/magnolia/appbuilder/AppDefinitionMetaDataBuilder.java b/src/main/java/com/namics/oss/magnolia/appbuilder/AppDefinitionMetaDataBuilder.java index a054ee3..541e3c5 100644 --- a/src/main/java/com/namics/oss/magnolia/appbuilder/AppDefinitionMetaDataBuilder.java +++ b/src/main/java/com/namics/oss/magnolia/appbuilder/AppDefinitionMetaDataBuilder.java @@ -7,25 +7,42 @@ import org.springframework.aop.support.AopUtils; public class AppDefinitionMetaDataBuilder extends DefinitionMetadataBuilder { - private final Object appFactory; + private final String name; public AppDefinitionMetaDataBuilder(final Object appFactory) { - this.appFactory = appFactory; + final AppFactory appFactoryAnnotation = AopUtils + .getTargetClass(appFactory) + .getAnnotation(AppFactory.class); + + this.name = generateName(appFactoryAnnotation); type(DefinitionTypes.APP); - location(appFactory.getClass().getName()); + location(generateLocation(appFactory)); + module(generateModule(appFactoryAnnotation)); + relativeLocation(generateRelativeLocation(appFactoryAnnotation)); + name(name); } @Override protected String buildReferenceId() { - final AppFactory appFactoryAnnotation = AopUtils - .getTargetClass(appFactory) - .getAnnotation(AppFactory.class); - final String id = appFactoryAnnotation.id(); - final String module = id.contains(":") ? StringUtils.substringBefore(id, ":") : null; - final String relativeLocation = id.contains(":") ? StringUtils.substringAfter(id, ":") : id; - final String name = relativeLocation.indexOf('/') != -1 ? StringUtils.substringAfterLast(relativeLocation, "/") : relativeLocation; - module(module).relativeLocation(relativeLocation).name(name); return name; } + protected String generateLocation(final Object appFactory) { + return appFactory.getClass().getName(); + } + + protected String generateModule(final AppFactory appFactoryAnnotation) { + final String id = appFactoryAnnotation.id(); + return id.contains(":") ? StringUtils.substringBefore(id, ":") : null; + } + + protected String generateRelativeLocation(final AppFactory appFactoryAnnotation) { + final String id = appFactoryAnnotation.id(); + return id.contains(":") ? StringUtils.substringAfter(id, ":") : id; + } + + protected String generateName(final AppFactory appFactoryAnnotation) { + final String relativeLocation = generateRelativeLocation(appFactoryAnnotation); + return relativeLocation.indexOf('/') != -1 ? StringUtils.substringAfterLast(relativeLocation, "/") : relativeLocation; + } } diff --git a/src/main/java/com/namics/oss/magnolia/appbuilder/AppExporter.java b/src/main/java/com/namics/oss/magnolia/appbuilder/AppExporter.java index faf876d..b1857fc 100644 --- a/src/main/java/com/namics/oss/magnolia/appbuilder/AppExporter.java +++ b/src/main/java/com/namics/oss/magnolia/appbuilder/AppExporter.java @@ -36,7 +36,12 @@ protected void onBeanDetection(final Object appFactory, final String beanName) { LOG.info("Detected app bean with name '{}'", beanName); // build app descriptor from detected factory bean final AppDescriptorProvider appDescriptorProvider = new AppDescriptorProvider(appFactory); + final LegacyAppDescriptorProvider legacyAppDescriptorProvider = new LegacyAppDescriptorProvider(appFactory); // register app descriptor + if(legacyAppDescriptorProvider.shouldRegister()) { + LOG.info("Registered legacy chooser app '{}'", legacyAppDescriptorProvider.getMetadata().getName()); + appDescriptorRegistry.register(legacyAppDescriptorProvider); + } appDescriptorRegistry.register(appDescriptorProvider); LOG.info("Registered app '{}'", appDescriptorProvider.getMetadata().getName()); } diff --git a/src/main/java/com/namics/oss/magnolia/appbuilder/LegacyAppDescriptorProvider.java b/src/main/java/com/namics/oss/magnolia/appbuilder/LegacyAppDescriptorProvider.java new file mode 100644 index 0000000..53213e0 --- /dev/null +++ b/src/main/java/com/namics/oss/magnolia/appbuilder/LegacyAppDescriptorProvider.java @@ -0,0 +1,203 @@ +package com.namics.oss.magnolia.appbuilder; + +import com.google.common.collect.ImmutableList; +import com.namics.oss.magnolia.appbuilder.annotations.AppFactory; +import info.magnolia.config.registry.DefinitionMetadata; +import info.magnolia.config.registry.Registry; +import info.magnolia.ui.api.app.AppDescriptor; +import info.magnolia.ui.api.app.SubAppDescriptor; +import info.magnolia.ui.contentapp.ConfiguredContentAppDescriptor; +import info.magnolia.ui.contentapp.ContentApp; +import info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor; +import info.magnolia.ui.contentapp.browser.ConfiguredBrowserSubAppDescriptor; +import info.magnolia.ui.contentapp.column.jcr.JcrTitleColumnDefinition; +import info.magnolia.ui.contentapp.configuration.BrowserDescriptor; +import info.magnolia.ui.contentapp.configuration.ContentViewDefinition; +import info.magnolia.ui.contentapp.configuration.TreeViewDefinition; +import info.magnolia.ui.contentapp.configuration.WorkbenchDefinition; +import info.magnolia.ui.contentapp.configuration.column.ColumnDefinition; +import info.magnolia.ui.datasource.jcr.JcrDatasourceDefinition; +import info.magnolia.ui.vaadin.integration.contentconnector.ConfiguredJcrContentConnectorDefinition; +import info.magnolia.ui.vaadin.integration.contentconnector.ConfiguredNodeTypeDefinition; +import info.magnolia.ui.vaadin.integration.contentconnector.JcrContentConnectorDefinition; +import info.magnolia.ui.vaadin.integration.jcr.ModelConstants; +import info.magnolia.ui.workbench.column.definition.PropertyColumnDefinition; +import info.magnolia.ui.workbench.definition.ConfiguredWorkbenchDefinition; +import info.magnolia.ui.workbench.tree.TreePresenterDefinition; +import org.springframework.aop.support.AopUtils; + +import java.util.*; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Provides legacy/UI5 appDescriptor to be able to open a legacy/UI5 chooser dialog to content from an UI6 app. + *
+ * See appName description warning + */ +public class LegacyAppDescriptorProvider extends AppDescriptorProvider { + public static final UnaryOperator CONVERT_APP_NAME = appName -> appName + "-legacyChooser"; + private final DefinitionMetadata metadata; + private final boolean shouldRegister; + + public LegacyAppDescriptorProvider(final Object appFactory) { + super(appFactory); + final AppFactory appFactoryAnnotation = AopUtils + .getTargetClass(appFactory) + .getAnnotation(AppFactory.class); + this.shouldRegister = appFactoryAnnotation.generateLegacyChooserApp(); + this.metadata = new LegacyAppDefinitionMetaDataBuilder(appFactory).build(); + } + + boolean shouldRegister() { + return shouldRegister; + } + + @Override + public AppDescriptor get() throws Registry.InvalidDefinitionException { + final AppDescriptor appDescriptor = super.get(); + final ConfiguredContentAppDescriptor legacyAppDescriptor = new ConfiguredContentAppDescriptor(); + legacyAppDescriptor.setName(CONVERT_APP_NAME.apply(appDescriptor.getName())); + legacyAppDescriptor.setAppClass(ContentApp.class); + legacyAppDescriptor.setEnabled(false); + legacyAppDescriptor.setSubApps(getBrowserSubApps(appDescriptor)); + return legacyAppDescriptor; + } + + @Override + public DefinitionMetadata getMetadata() { + return metadata; + } + + private Map getBrowserSubApps(final AppDescriptor appDescriptor) { + return appDescriptor.getSubApps().values().stream() + .filter(BrowserDescriptor.class::isInstance) + .map(BrowserDescriptor.class::cast) + .map(browserDescriptor -> convert(appDescriptor, browserDescriptor)) + .collect(Collectors.toMap(SubAppDescriptor::getName, Function.identity())); + } + + private BrowserSubAppDescriptor convert(final AppDescriptor appDescriptor, final BrowserDescriptor browserDescriptor) { + final ConfiguredBrowserSubAppDescriptor legacyBrowserDescriptor = new ConfiguredBrowserSubAppDescriptor(); + legacyBrowserDescriptor.setName(browserDescriptor.getName()); + legacyBrowserDescriptor.setIcon(browserDescriptor.getIcon()); + legacyBrowserDescriptor.setLabel(browserDescriptor.getLabel()); + legacyBrowserDescriptor.setContentConnector(convert( + (JcrDatasourceDefinition) browserDescriptor.getDatasource(), + getIcons(browserDescriptor.getWorkbench()) + )); + legacyBrowserDescriptor.setWorkbench(convert(appDescriptor, browserDescriptor, browserDescriptor.getWorkbench())); + return legacyBrowserDescriptor; + } + + private JcrContentConnectorDefinition convert(final JcrDatasourceDefinition jcrDatasourceDefinition, final Map icons) { + final ConfiguredJcrContentConnectorDefinition legacyJcrContentConnectorDefinition = new ConfiguredJcrContentConnectorDefinition(); + legacyJcrContentConnectorDefinition.setWorkspace(jcrDatasourceDefinition.getWorkspace()); + legacyJcrContentConnectorDefinition.setNodeTypes( + jcrDatasourceDefinition.getAllowedNodeTypes().stream().map(nodeType -> { + final ConfiguredNodeTypeDefinition nodeTypeDefinition = new ConfiguredNodeTypeDefinition(); + nodeTypeDefinition.setName(nodeType); + nodeTypeDefinition.setIcon(icons.get(nodeType)); + return nodeTypeDefinition; + }).collect(Collectors.toList()) + ); + return legacyJcrContentConnectorDefinition; + } + + private Map getIcons(final WorkbenchDefinition workbench) { + return streamTreeViewColumns(workbench) + .filter(JcrTitleColumnDefinition.class::isInstance) + .map(JcrTitleColumnDefinition.class::cast) + .map(JcrTitleColumnDefinition::getNodeTypeToIcon) + .findFirst() + .orElseGet(Collections::emptyMap); + } + + private Stream streamTreeViewColumns(final WorkbenchDefinition workbench) { + return ((List) workbench.getContentViews()) + .stream() + .filter(TreeViewDefinition.class::isInstance) + .map(TreeViewDefinition.class::cast) + .map(view -> (List) view.getColumns()) + .flatMap(Collection::stream); + } + + private info.magnolia.ui.workbench.definition.WorkbenchDefinition convert( + final AppDescriptor appDescriptor, + final BrowserDescriptor browserDescriptor, + final WorkbenchDefinition workbench + ) { + final ConfiguredWorkbenchDefinition legacyWorkbench = new ConfiguredWorkbenchDefinition(); + legacyWorkbench.setName(workbench.getName()); + legacyWorkbench.setDialogWorkbench(true); + legacyWorkbench.setContentViews(List.of(getContentView(appDescriptor, browserDescriptor, workbench))); + return legacyWorkbench; + } + + protected TreePresenterDefinition getContentView( + final AppDescriptor appDescriptor, + final BrowserDescriptor browserDescriptor, + final WorkbenchDefinition workbench + ) { + final PropertyColumnDefinition nameColumn = new PropertyColumnDefinition(); + nameColumn.setName("name"); + nameColumn.setPropertyName(ModelConstants.JCR_NAME); + + final TreePresenterDefinition treePresenter = new TreePresenterDefinition(); + final List simpleColumns = getSimpleColumns(appDescriptor, browserDescriptor, workbench); + final ImmutableList.Builder columns = ImmutableList.builder(); + if(simpleColumns.stream().noneMatch(column -> Objects.equals(column.getName(), nameColumn.getName()))) { + //add name column on first place if missing + columns.add(nameColumn); + } + columns.addAll(simpleColumns); + treePresenter.setColumns(columns.build()); + return treePresenter; + } + + private List getSimpleColumns( + final AppDescriptor appDescriptor, + final BrowserDescriptor browserDescriptor, + final WorkbenchDefinition workbench + ) { + return streamTreeViewColumns(workbench) + .filter(column -> column.getValueProvider() == null) + .map(column -> + convert(appDescriptor, browserDescriptor, column) + ) + .collect(Collectors.toList()); + } + + private PropertyColumnDefinition convert( + final AppDescriptor appDescriptor, + final BrowserDescriptor browserDescriptor, + final ColumnDefinition column + ) { + final PropertyColumnDefinition legacyColumn = new PropertyColumnDefinition(); + legacyColumn.setName(column.getName()); + legacyColumn.setWidth((int) column.getWidth()); + legacyColumn.setExpandRatio(column.getExpandRatio()); + legacyColumn.setLabel(appDescriptor.getName() + "." + browserDescriptor.getName() + ".views." + column.getName() + ".label"); + legacyColumn.setSortable(column.isSortable()); + legacyColumn.setPropertyName(column.getName()); + return legacyColumn; + } + + private static final class LegacyAppDefinitionMetaDataBuilder extends AppDefinitionMetaDataBuilder { + public LegacyAppDefinitionMetaDataBuilder(final Object appFactory) { + super(appFactory); + } + + @Override + protected String generateName(final AppFactory appFactoryAnnotation) { + return CONVERT_APP_NAME.apply(super.generateName(appFactoryAnnotation)); + } + + @Override + protected String generateLocation(final Object appFactory) { + return CONVERT_APP_NAME.apply(super.generateLocation(appFactory)); + } + } +} diff --git a/src/main/java/com/namics/oss/magnolia/appbuilder/annotations/AppFactory.java b/src/main/java/com/namics/oss/magnolia/appbuilder/annotations/AppFactory.java index e3cdb1e..5cc295e 100644 --- a/src/main/java/com/namics/oss/magnolia/appbuilder/annotations/AppFactory.java +++ b/src/main/java/com/namics/oss/magnolia/appbuilder/annotations/AppFactory.java @@ -23,13 +23,5 @@ boolean isEnabled() default true; - /** - * Leave empty to use admincentral/config.yaml - */ - String launcherGroup() default ""; - - /** - * Leave empty to use admincentral/config.yaml - */ - int order() default Integer.MAX_VALUE; + boolean generateLegacyChooserApp() default false; } diff --git a/src/main/java/com/namics/oss/magnolia/appbuilder/annotations/ChooseDialog.java b/src/main/java/com/namics/oss/magnolia/appbuilder/annotations/ChooseDialog.java deleted file mode 100644 index 20a2c65..0000000 --- a/src/main/java/com/namics/oss/magnolia/appbuilder/annotations/ChooseDialog.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.namics.oss.magnolia.appbuilder.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to mark methods which produce - * a {@link info.magnolia.ui.dialog.definition.ChooseDialogDefinition}. - *

- * - Used in classes annotated with {@link AppFactory}, optional. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD}) -public @interface ChooseDialog { -} From 5534aac259c695c13bf6791e505f203696d0b9eb Mon Sep 17 00:00:00 2001 From: eschleb Date: Thu, 1 Feb 2024 07:50:33 +0100 Subject: [PATCH 4/4] Implement PropertyNameColumn (different propertyName than name) --- .../PropertyNameColumnDefinition.java | 19 +++++++++++++++++ .../formatter/PropertyNameValueProvider.java | 21 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/main/java/com/namics/oss/magnolia/appbuilder/formatter/PropertyNameColumnDefinition.java create mode 100644 src/main/java/com/namics/oss/magnolia/appbuilder/formatter/PropertyNameValueProvider.java diff --git a/src/main/java/com/namics/oss/magnolia/appbuilder/formatter/PropertyNameColumnDefinition.java b/src/main/java/com/namics/oss/magnolia/appbuilder/formatter/PropertyNameColumnDefinition.java new file mode 100644 index 0000000..52c3279 --- /dev/null +++ b/src/main/java/com/namics/oss/magnolia/appbuilder/formatter/PropertyNameColumnDefinition.java @@ -0,0 +1,19 @@ +package com.namics.oss.magnolia.appbuilder.formatter; + +import info.magnolia.ui.contentapp.configuration.column.ConfiguredColumnDefinition; + +import javax.jcr.Item; + +public class PropertyNameColumnDefinition extends ConfiguredColumnDefinition { + private final String propertyName; + + public PropertyNameColumnDefinition(final String name, final String propertyName) { + this.propertyName = propertyName; + setName(name); + setValueProvider(PropertyNameValueProvider.class); + } + + public String getPropertyName() { + return propertyName; + } +} \ No newline at end of file diff --git a/src/main/java/com/namics/oss/magnolia/appbuilder/formatter/PropertyNameValueProvider.java b/src/main/java/com/namics/oss/magnolia/appbuilder/formatter/PropertyNameValueProvider.java new file mode 100644 index 0000000..ce2b013 --- /dev/null +++ b/src/main/java/com/namics/oss/magnolia/appbuilder/formatter/PropertyNameValueProvider.java @@ -0,0 +1,21 @@ +package com.namics.oss.magnolia.appbuilder.formatter; + +import info.magnolia.jcr.util.PropertyUtil; + +import javax.inject.Inject; +import javax.jcr.Node; +import java.util.Optional; + +public class PropertyNameValueProvider extends AbstractValueProvider { + private final PropertyNameColumnDefinition definition; + + @Inject + public PropertyNameValueProvider(final PropertyNameColumnDefinition definition) { + this.definition = definition; + } + + @Override + protected Optional getValue(final Node node) { + return Optional.ofNullable(PropertyUtil.getString(node, definition.getPropertyName())); + } +}