Make CustomContentType queryable through CQL

Hi,

I’m working on a P2 plugin for Confluence Server and I want to create a CustomContentType which I can query using CQL, including permission checks. I have created the custom type in accordance to Bitbucket. Creating entities works just fine and whenever I run a generic CQL query for all content entities in a space (http://cyclobase:1990/confluence/rest/api/content/search?cql=space=DS) my PermissionDelegate.canView(User) is being invoked and returns true. The result however does not contain my content entities.

Here is the relevant code-base:

CustomContent
public class CustomContent {

    private static final long serialVersionUID = -2878773629921274872L;


    private final CustomContentEntityObject customContentEntityObject;

    private final List<Label> labels;


    public static boolean isCustomContent(CustomContentEntityObject customContentEntityObject) {
        return customContentEntityObject.getPluginModuleKey().equals(CustomContentEntityAdapter.PLUGIN_CONTENT_KEY);
    }


    CustomContent(@Nonnull final CustomContentEntityObject customContentEntityObject,
                   @Nullable final List<Label> labels) {

        if (!isCustomContent(customContentEntityObject)) {
            throw new IllegalArgumentException("Attempted to create a CustomContent from a CustomContentEntityObject which is no CustomContent!");
        }

        this.customContentEntityObject = customContentEntityObject;
        this.labels = labels;
    }


    public CustomContentEntityObject getEntity() {
        return customContentEntityObject;
    }

    public Long getPageId() {
        return customContentEntityObject.getProperties().getLongProperty("pageId", 0L);
    }

    public String getSpaceKey() {
        return customContentEntityObject.getSpaceKey();
    }

    public Space getSpace() {
        return customContentEntityObject.getSpace();
    }

    public String getTitle() {
        return customContentEntityObject.getTitle();
    }

    public String getBodyAsString() {
        return customContentEntityObject.getBodyAsString();
    }

    public List<Label> getLabels() {
        return labels;
    }
}
CustomContentBuilderImpl
public class CustomContentBuilderImpl implements CustomContentBuilder {

    final PageManager pageManager;

    final CustomContentManager customContentManager;


    private Long pageId;

    private String title;

    private String body;

    private List<Label> labels;


    CustomContentBuilderImpl(final PageManager pageManager,
                              final CustomContentManager customContentManager) {
        this.pageManager = pageManager;
        this.customContentManager = customContentManager;
    }


    public CustomContentBuilder setPageId(Long pageId) {
        this.pageId = pageId;
        return this;
    }

    public CustomContentBuilder setTitle(String title) {
        this.title = title;
        return this;
    }

    public CustomContentBuilder setBody(String body) {
        this.body = body;
        return this;
    }

    public CustomContentBuilder setLabels(List<Label> labels) {
        this.labels = labels;
        return this;
    }

    public CustomContent build() {

        if (pageId == null || title == null || body == null) {
            throw new RuntimeException("pageId, title and body must be set!");
        }

        AbstractPage abstractPage = pageManager.getAbstractPage(pageId);
        if (abstractPage == null) {
            throw new IllegalArgumentException("There exists no page with the id " + pageId + "!");
        }

        final CustomContentEntityObject customEntityObject = customContentManager.newPluginContentEntityObject(CustomContentEntityAdapter.PLUGIN_CONTENT_KEY);
        customEntityObject.setPluginModuleKey(CustomContentEntityAdapter.PLUGIN_CONTENT_KEY);

        customEntityObject.setTitle(title);
        customEntityObject.setCreationDate(new Date());
        customEntityObject.setBodyAsString(body);
        customEntityObject.setCreator(AuthenticatedUserThreadLocal.get());
        customEntityObject.setSpace(abstractPage.getSpace());

        customEntityObject.getProperties().setLongProperty("pageId", pageId);

        CustomContent customContent = new CustomContent(customEntityObject, labels);
        return customContent;
    }
}
CustomContentEntityAdapter
@Component
public class CustomContentEntityAdapter implements ContentEntityAdapter {

    private static final String BASE_URL = ""; // TODO

    public static final String PLUGIN_CONTENT_KEY = "de.cyclonit.confluence.customcontent:custom-content";


    @Override
    public Option<String> getUrlPath(CustomContentEntityObject customContentEntityObject) {
        if (customContentEntityObject.getId() != 0 && CustomContent.isCustomContent(customContentEntityObject)) {

            Long pageId = customContentEntityObject.getProperties().getLongProperty("pageId", 0L);
            String title = customContentEntityObject.getTitle();

            return some(BASE_URL + "/rest/custom/latest/get?pageId=" + pageId + "&title=" + title);
        } else {
            return none();
        }
    }

    @Override
    public Option<String> getDisplayTitle(CustomContentEntityObject customContentEntityObject) {
        return none();
    }

    @Override
    public Option<String> getNameForComparison(CustomContentEntityObject customContentEntityObject) {
        if (customContentEntityObject.getId() != 0 && CustomContent.isCustomContent(customContentEntityObject)) {
            return some(customContentEntityObject.getTitle());
        } else {
            return none();
        }
    }

    @Override
    public Option<String> getAttachmentsUrlPath(CustomContentEntityObject customContentEntityObject) {
        return none();
    }

    @Override
    public Option<String> getAttachmentUrlPath(CustomContentEntityObject customContentEntityObject, Attachment attachment) {
        return none();
    }

    @Override
    public BodyType getDefaultBodyType(CustomContentEntityObject customContentEntityObject) {
        return BodyType.RAW;
    }

    @Override
    public Option<String> getExcerpt(CustomContentEntityObject customContentEntityObject) {
        return none();
    }

    @Override
    public boolean isAllowedParent(CustomContentEntityObject customContentEntityObject, CustomContentEntityObject customContentEntityObject1) {
        return false;
    }

    @Override
    public boolean isAllowedContainer(ContentEntityObject contentEntityObject, ContentEntityObject contentEntityObject1) {
        return false;
    }

    @Override
    public boolean isIndexable(CustomContentEntityObject customContentEntityObject, boolean b) {
        return CustomContent.isCustomContent(customContentEntityObject);
    }

    @Override
    public boolean shouldConvertToContent(CustomContentEntityObject customContentEntityObject) {
        return false;
    }

    @Override
    public VersionChildOwnerPolicy getVersionChildPolicy(ContentType contentType) {
        return null;
    }
}
CustomContentManagerImpl
@Component
@Named("customContentManager")
public class CustomContentManagerImpl implements CustomContentManager {

    private static final SaveContext CUSTOM_SAVE_CONTEXT = (new DefaultSaveContext.Builder())
            .updateLastModifier(false)
            .suppressAutowatch(true)
            .suppressNotifications(false)
            .build();


    private final PageManager pageManager;

    private final CustomContentManager customContentManager;

    private final LabelManager labelManager;

    private final TransactionTemplate transactionTemplate;


    @Inject
    public CustomContentManagerImpl(@ComponentImport final PageManager pageManager,
                                     @ComponentImport final CustomContentManager customContentManager,
                                     @ComponentImport final LabelManager labelManager,
                                     @ComponentImport final TransactionTemplate transactionTemplate) {
        this.pageManager = pageManager;
        this.customContentManager = customContentManager;
        this.labelManager = labelManager;
        this.transactionTemplate = transactionTemplate;
    }


    public CustomContentBuilder builder() {
        return new CustomContentBuilderImpl(pageManager, customContentManager);
    }

    public CustomContent get(Long pageId, String customTitle) {
		// TODO: Implement
        return null;
    }

    public void create(final CustomContent customContent) {

        ContentEntityObject pageEntity = pageManager.getById(customContent.getPageId());
        if (pageEntity == null) {
            throw new IllegalArgumentException("There exists no page with the id " + customContent.getPageId() + "!");
        }

        transactionTemplate.execute(new TransactionCallback() {
                                        @Override
                                        public Void doInTransaction() {

                                            // create the CustomContent itself
                                            CustomContent oldExerpt = get(customContent.getPageId(), customContent.getTitle());
                                            if (oldExerpt != null) {
                                                throw new IllegalArgumentException("attempted to store duplicate CustomContent called " + customContent.getTitle() + " for page " + customContent.getPageId());
                                            }
                                            customContentManager.saveContentEntity(customContent.getEntity(), CUSTOM_SAVE_CONTEXT);

                                            // add labels
                                            if (customContent.getLabels() != null) {
                                                for (Label label : customContent.getLabels()) {
                                                    labelManager.addLabel(customContent.getEntity(), label);
                                                }
                                            }

                                            return null;
                                        }
                                    });
    }
}
CustomContentType
public class CustomContentType implements ContentType {

    private final CustomContentEntityAdapter customContentEntityAdapter;

    private final PermissionDelegate customPermissionDelegate;

    private final ContentUiSupport contentUiSupport;


    @Inject
    public CustomContentType(final CustomPermissionDelegate permissionDelegate,
                              @ComponentImport final WebResourceUrlProvider webResourceUrlProvider) {

        this.customContentEntityAdapter = new CustomContentEntityAdapter();
        this.customPermissionDelegate = permissionDelegate;
        this.contentUiSupport = new CustomUiSupport(webResourceUrlProvider); // TODO: Replace for CustomContent.
    }


    @Override
    public ContentEntityAdapter getContentAdapter() {
        return customContentEntityAdapter;
    }

    @Override
    public PermissionDelegate getPermissionDelegate() {
        return customPermissionDelegate;
    }

    @Override
    public ContentUiSupport getContentUiSupport() {
        return contentUiSupport;
    }
}
CustomPermissionDelegateImpl
@Component
@Named("customPermissionDelegate")
public class CustomPermissionDelegateImpl implements CustomPermissionDelegate {

    private final PermissionManager permissionManager;

    private final PageManager pageManager;

    @Autowired
    public CustomPermissionDelegateImpl(@ComponentImport final PermissionManager permissionManager,
                                         @ComponentImport final PageManager pageManager) {
        this.permissionManager = permissionManager;
        this.pageManager = pageManager;
    }


    public boolean hasCustomPermission(User user, Permission permission, Object obj) {

        if (obj instanceof CustomContentEntityObject && CustomContent.isCustomContent((CustomContentEntityObject) obj)) {
            CustomContent customContent = new CustomContent((CustomContentEntityObject) obj, null);
            return hasCustomPermission(user, permission, customContent);
        } else {
            return false;
        }
    }

    public boolean hasCustomPermission(User user, Permission permission, CustomContent customContent) {

        Long pageId = customContent.getPageId();
        AbstractPage abstractPage = pageManager.getAbstractPage(pageId);

        return permissionManager.hasPermission(user, permission, abstractPage);
    }


    @Override
    public boolean canView(User user, Object obj) {
        return hasCustomPermission(user, Permission.VIEW, obj);
    }

    @Override
    public boolean canView(User user) {
        return true; // TODO: Is this right?
    }

    @Override
    public boolean canEdit(User user, Object obj) {
        return hasCustomPermission(user, Permission.EDIT, obj);
    }

    @Override
    public boolean canSetPermissions(User user, Object obj) {
        return hasCustomPermission(user, Permission.EDIT, obj);
    }

    @Override
    public boolean canRemove(User user, Object obj) {
        return hasCustomPermission(user, Permission.REMOVE, obj);
    }

    @Override
    public boolean canExport(User user, Object obj) {
        return hasCustomPermission(user, Permission.EXPORT, obj);
    }

    @Override
    public boolean canAdminister(User user, Object obj) {
        return hasCustomPermission(user, Permission.ADMINISTER, obj);
    }

    @Override
    public boolean canCreate(User user, Object obj) {
        return hasCustomPermission(user, Permission.EDIT, obj);
    }

    @Override
    public boolean canCreateInTarget(User user, Class aClass) {
        return true;
    }
}

Kind regards,
Christopher

Hi @christ.klinge,

maybe this could be of help: How may i search a content property via REST CQL search?
Best regards
Alex