Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@
*/
package org.apache.causeway.applib;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import org.apache.causeway.applib.domain.DomainObjectList;
import org.apache.causeway.applib.mixins.dto.Dto_downloadXml;
import org.apache.causeway.applib.mixins.dto.Dto_downloadXsd;
import org.apache.causeway.applib.mixins.layout.Object_downloadLayout;
import org.apache.causeway.applib.mixins.layout.Object_patchLayout;
import org.apache.causeway.applib.mixins.metamodel.Object_downloadMetamodelXml;
import org.apache.causeway.applib.mixins.metamodel.Object_rebuildMetamodel;
import org.apache.causeway.applib.mixins.rest.Object_openRestApi;
Expand Down Expand Up @@ -57,6 +55,8 @@
import org.apache.causeway.applib.services.user.UserService;
import org.apache.causeway.applib.services.userui.UserMenu;
import org.apache.causeway.schema.CausewayModuleSchema;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
* @since 2.0 {@index}
Expand Down Expand Up @@ -111,6 +111,7 @@
Dto_downloadXsd.class,
Object_downloadColumnOrderTxtFilesAsZip.class,
Object_downloadLayout.class,
Object_patchLayout.class,
Object_downloadMetamodelXml.class,
Object_openRestApi.class,
Object_rebuildMetamodel.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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.causeway.applib.mixins.layout;

import java.util.Optional;

import javax.inject.Inject;

import org.apache.causeway.applib.annotation.Action;
import org.apache.causeway.applib.annotation.ActionLayout;
import org.apache.causeway.applib.annotation.DomainObject;
import org.apache.causeway.applib.annotation.Introspection;
import org.apache.causeway.applib.annotation.MemberSupport;
import org.apache.causeway.applib.annotation.Nature;
import org.apache.causeway.applib.annotation.ParameterLayout;
import org.apache.causeway.applib.annotation.Publishing;
import org.apache.causeway.applib.annotation.RestrictTo;
import org.apache.causeway.applib.annotation.SemanticsOf;
import org.apache.causeway.applib.layout.LayoutConstants;
import org.apache.causeway.applib.layout.resource.LayoutResource;
import org.apache.causeway.applib.services.grid.GridService;
import org.apache.causeway.applib.services.grid.GridService.LayoutKey;
import org.apache.causeway.applib.services.layout.LayoutService;
import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;

import lombok.RequiredArgsConstructor;

/**
* Allows uploading of layout resources, that overrule the default layout resource lookup.
*
* @since 4.0 {@index}
*/
@Action(
domainEvent = Object_patchLayout.ActionDomainEvent.class,
semantics = SemanticsOf.IDEMPOTENT,
commandPublishing = Publishing.DISABLED,
executionPublishing = Publishing.DISABLED,
restrictTo = RestrictTo.PROTOTYPING)
@ActionLayout(
cssClassFa = "solid file-arrow-up",
describedAs = "Uploads layout XML, to be stored in memory for this object type and current layout name. "
+ "It overrules the default layout resource lookup. "
+ "On application restart this information is lost.",
fieldSetId = LayoutConstants.FieldSetId.METADATA,
position = ActionLayout.Position.PANEL_DROPDOWN,
sequence = "700.1.1")
// framework provided domain objects and mixins should explicitly specify their introspection policy
@DomainObject(nature=Nature.MIXIN, mixinMethod = "act", introspection = Introspection.ANNOTATION_REQUIRED)
@RequiredArgsConstructor
public class Object_patchLayout {

public static class ActionDomainEvent
extends org.apache.causeway.applib.CausewayModuleApplib.ActionDomainEvent<Object_patchLayout> {}

@Inject LayoutService layoutService;
@Inject GridService gridService;

private final Object mixee;

@MemberSupport public Object act(
@ParameterLayout(multiLine = 20)
final String layoutXml) {
layoutKey()
.ifPresent(layoutKey->{
gridService.addPatchedLayout(
layoutKey,
new LayoutResource(layoutKey.resourceName(CommonMimeType.XML), CommonMimeType.XML, layoutXml));
});
return mixee;
}

@MemberSupport public boolean hideAct() {
return layoutKey().isEmpty();
}

// -- HELPER

private Optional<LayoutKey> layoutKey() {
return layoutService.layoutKey(mixee);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Optional;

import org.apache.causeway.applib.layout.grid.bootstrap.BSGrid;
import org.apache.causeway.applib.layout.resource.LayoutResource;
import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
import org.apache.causeway.commons.internal.base._Strings;
import org.springframework.lang.NonNull;
Expand Down Expand Up @@ -64,6 +65,18 @@ public LayoutKey(final Class<?> domainClass) {

public boolean isDefault() { return layoutIfAny==null; }
public boolean isVariant() { return layoutIfAny!=null; }

/**
* Suggested resource name e.g. for export. (not strictly binding)
*/
public String resourceName(CommonMimeType commonMimeType) {
return String.format("%s%s.%s",
domainClass.getSimpleName(),
isVariant()
? "-" + layoutIfAny()
: "",
commonMimeType.getProposedFileExtensions().getFirst().orElse("unknown"));
}
}

/**
Expand All @@ -79,6 +92,17 @@ public LayoutKey(final Class<?> domainClass) {
* <p> Acts as a no-op if not {@link #supportsReloading()}.
*/
void invalidate(Class<?> domainClass);

/**
* Allows to replace or prime layout caches with a custom layout (as provided by given {@link LayoutResource}).
* Useful for prototyping.
*
* <p>This patching is potentially in conflict with layout reloading.
* Patched layouts must stick around even when a layout reload attempt is made.
*
* @since 4.0
*/
void addPatchedLayout(LayoutKey layoutKey, LayoutResource layoutResource);

/**
* Returns a {@link BSGrid} for given {@link LayoutKey}.
Expand All @@ -93,6 +117,14 @@ public LayoutKey(final Class<?> domainClass) {
EnumSet<CommonMimeType> supportedFormats();
Optional<GridMarshaller> marshaller(CommonMimeType format);

/**
* Clears any pre-calculated BSGrid instances from the cache. Useful to start with a clean slate,
* after the MM was initialized.
*
* <p> The MM needs to be aware of all mixins, in order to reliably produce valid BSGrid instances.
* During MM initialization incomplete BSGrid instances might be created and cached,
* which are missing e.g. mixed-in Actions.
*/
void clearCache();

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,14 @@
*/
package org.apache.causeway.applib.util.schema;

import java.io.IOException;
import java.io.InputStream;

import org.apache.causeway.applib.value.Blob;
import org.apache.causeway.applib.value.NamedWithMimeType;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;

import org.apache.causeway.schema.cmd.v2.CommandDto;
import org.apache.causeway.schema.cmd.v2.MapDto;

import org.springframework.util.StreamUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class CommandDtoUtils_Test {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,14 @@
import java.io.InputStream;
import java.util.List;

import javax.xml.datatype.DatatypeConstants;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import org.springframework.util.StreamUtils;

import org.apache.causeway.commons.io.DataSource;
import org.apache.causeway.schema.cmd.v2.ActionDto;
import org.apache.causeway.schema.cmd.v2.CommandDto;
import org.apache.causeway.schema.cmd.v2.ParamDto;
import org.apache.causeway.schema.common.v2.ValueType;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.util.StreamUtils;

class CommandDtoUtils_fromYaml_Approval_Test {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.apache.causeway.commons.functional.Try;
import org.apache.causeway.commons.internal._Java17Ex;
import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel;
import org.apache.causeway.core.metamodel.facets.object.grid.GridFacet;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -110,6 +111,17 @@ public BSGrid load(final LayoutKey layoutKey) {
.valueAsNonNullElseFail(); // at least we should have a fallback, otherwise there is some serious issue
return grid;
}

@Override
public void addPatchedLayout(LayoutKey layoutKey, LayoutResource layoutResource) {
layoutLookup.addPatchedLayout(layoutKey, layoutResource);
// from 2nd level cache removes the grid that is associated with given layoutKey
cache.gridsByKey().map().remove(layoutKey);
// purge 1st level cache as well
context.specLoaderProvider().get().specForType(layoutKey.domainClass())
.ifPresent(spec->spec.lookupFacet(GridFacet.class)
.ifPresent(GridFacet::clearCache));
}

// -- HELPER

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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.causeway.core.metamodel.services.grid;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.causeway.applib.layout.resource.LayoutResource;
import org.apache.causeway.applib.services.grid.GridService.LayoutKey;
import org.springframework.lang.NonNull;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;

/**
* Just a wrapper around a concurrent map, that stores patched layout resources.
*/
@RequiredArgsConstructor @Getter @Accessors(fluent = true)
final class LayoutPatchesMap {

/**
* Can be uploaded via UI, for prototyping or troubleshooting.
* Those are meant to overrule the default lookup.
*/
final Map<LayoutKey, LayoutResource> layoutPatches;

public LayoutPatchesMap() {
this(new ConcurrentHashMap<>());
}

public Optional<LayoutResource> lookupPatchedLayoutResource(
@NonNull LayoutKey layoutKey) {
return Optional.ofNullable(layoutPatches.get(layoutKey));
}

public LayoutPatchesMap putPatchedLayoutResource(
@NonNull LayoutKey layoutKey,
@NonNull LayoutResource layoutResource) {
layoutPatches.put(layoutKey, layoutResource);
return this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,35 @@
final class LayoutResourceLookup {

private final Can<LayoutResourceLoader> layoutResourceLoaders;
/**
* In effect is used as a Set<LayoutKey>. (there is no concurrent hash set)
*/
/**
* In effect is used as a Set<LayoutKey>. (there is no concurrent hash set)
*/
private final Map<LayoutKey, LayoutKey> knownInvalidKeys;
/**
* Patched layouts can be uploaded via UI, for prototyping or troubleshooting.
* Those are meant to overrule the default lookup.
*/
private final LayoutPatchesMap layoutPatchesMap;

public LayoutResourceLookup(
final Can<LayoutResourceLoader> layoutResourceLoaders) {
this(layoutResourceLoaders, new ConcurrentHashMap<>());
this(layoutResourceLoaders, new ConcurrentHashMap<>(), new LayoutPatchesMap());
}

/**
* if known invalid returns {@link Optional#empty()}
*/
public Optional<LayoutResource> lookupLayoutResource(
Optional<LayoutResource> lookupLayoutResource(
final LayoutKey layoutKey,
final EnumSet<CommonMimeType> supportedFormats) {

if(isKnownInvalid(layoutKey))
return Optional.empty();

var patchedLayoutResourceOpt = layoutPatchesMap.lookupPatchedLayoutResource(layoutKey);
if(patchedLayoutResourceOpt.isPresent()) {
return patchedLayoutResourceOpt;
}

var layoutResourceOpt = MetaModelContext.instance()
.flatMap(mmc->mmc.specForType(layoutKey.domainClass()))
Expand All @@ -97,19 +107,28 @@ public Optional<LayoutResource> lookupLayoutResource(

return Optional.empty();
}

/**
* Adds given {@link LayoutResource} to the map of patched layouts.
* Patched layouts overrule the default lookup process.
*/
void addPatchedLayout(LayoutKey layoutKey, LayoutResource layoutResource) {
layoutPatchesMap.putPatchedLayoutResource(layoutKey, layoutResource);
unmarkInvalid(layoutKey.domainClass()); // re-evalute validity later on next load
}

boolean isKnownInvalid(final LayoutKey layoutKey) {
return knownInvalidKeys.get(layoutKey)!=null;
}

public void markInvalid(final LayoutKey layoutKey) {
void markInvalid(final LayoutKey layoutKey) {
knownInvalidKeys.put(layoutKey, layoutKey);
}

/**
* To support metamodel invalidation/rebuilding of spec.
*/
public void unmarkInvalid(final Class<?> domainClass) {
void unmarkInvalid(final Class<?> domainClass) {
knownInvalidKeys.entrySet().removeIf(entry->entry.getKey().domainClass().equals(domainClass));
}

Expand Down
Loading