資料 リソース
Liferayは、コミュニティにてテクノロジーをより良く使うために役立つ豊富なリソースと知識を提供しています。
Asset Framework
The asset framework provides a set of functionalities that are common to several different content types. It was initially created to be able to add tags to blog entries, wiki pages, web content, etc without having to reimplement this same functionality over and over. Since then, it has grown to add more functionalities and it has been made possible to use the framework for custom applications even if they are implemented within a plugin.
The term asset is used as a generic way to refer to any type of content regardless of whether it's purely text, an external file, a URL, an image, an record in an online book library, etc. From now on, whenever the word asset is used, think of it as a generic way to refer to documents, blog entries, bookmarks, wiki pages, etc.
Here are the main functionalities that you will be able to reuse thanks to the asset framework:
Associate tags to custom content types (new tags will be created automatically when the author assigns them to the content).
Associate categories to custom content types (authors will only be allowed to select from predefined categories within several predefined vocabularies)
Manage tags from the control panel (including merging tags)
Manage categories from the control panel (including creating complex hierachies).
Keep track of the number of visualizations of an asset.
Publish your content using the Asset Publisher portlet. Asset Publisher is able to publish dynamic lists of assets or manually selected lists of assets. It is also able to show a summary view of an asset and offer a link to the full view if desired. Because of this it will save you time since for many use cases it will make it unnecessary to develop custom portlets for your custom content types.
If these functionalities seem useful for your case, then you might be wondering, what do I have to do to benefit from them?
The following subsections describe the steps involved in using the asset framework. The first one is mandatory and consists on letting the framework know whenever one of your custom content entries is added, updated or deleted. The second part is optional but can save a lot of time so most developers will probably make use of it. It consists on using a set of taglibs to provide widgets that allow authors to enter tags and categories as well as how to show the entered tags and categories along with the content. The rest of the sections are also optional but offer interesting functionalities such as how to allow your custom assets to be published through the Asset Publisher.
Adding, updating and deleting assets
Whenever one of your custom content is created you need to let the asset framework know. Don't worry, it is simple. You just need to invoke a method of the asset framework. When invoking this method you will also let the framework know about the tags and/or categories of the content that was just authored.
All the methods that you will need to invoke are part of the AssetEntryLocalService. In particular you should access these methods using either the static methods of AssetLocalServiceUtil or by using an instance of the AssetEntryLocalService injected by Spring. To make this section simpler we will be using the former, since it doesn't require any special setup in your application.
The method that you need to invoke when one of your custom content has been added or updated is the same and is called updateAsset. Here is the full signature:
AssetEntry updateEntry(
long userId, long groupId, String className, long classPK, String classUuid, long[] categoryIds,
String[] tagNames, boolean visible, Date startDate, Date endDate,
Date publishDate, Date expirationDate, String mimeType, String title,
String description, String summary, String url, int height, int width,
Integer priority, boolean sync)
throws PortalException, SystemException
Here is an example invocation to this method extracted out from the blogs portlets that comes bundled with Liferay:
assetEntryLocalService.updateEntry(
userId, entry.getGroupId(), BlogsEntry.class.getName(),
entry.getEntryId(), entry.getUuid(), assetCategoryIds,
assetTagNames, visible, null, null, entry.getDisplayDate(), null,
ContentTypes.TEXT_HTML, entry.getTitle(), null, summary, null, 0, 0,
null, false);
Here is a quick summary of the most important parameters of this method:
userId: is the identifier of the user who created the content
groupId: identifies the scope in which the content has been created. If your content does not support scopes, you can just pass 0 as the value.
className: identifies the type of asset. By convention we recommend that it is the name of the Java class that represents your content type, but you can actually use any String you want as long as you are sure that it is unique.
classPK: identifies the specific content being created among any other of the same type. It is usually the primary key of the table where the custom content is stored. The classUuid parameter can optionally be used to specify a secondary identifier that is guaranteed to be unique universally. Having this type of identifier is specially useful if your contents will be exported and imported across separate portals.
assetCategoryIds and assetTagNames: represent the categories and tags that have been selected by the author of the content. The asset framework will sotre them for you.
visible: specifies whether this content should be shown at all by Asset Publisher.
title, description and summary: are descriptive fields that will be used by the Asset Publisher when displaying entries of your content type.
publishDate and expirationDate: can be optionally specified to let Asset Publisher know that it should not show the content before a given publication date of after a given expiration date.
All other fields are optional and might not make sense in all cases. The sync parameter should always be false unless you are doing something very advanced (look at the code if you are really curious).
When one of your custom content is deleted you should also let the Asset Framework know, to clean up the information stored and also to make sure that the Asset Publisher doesn't show any information for a content that has been deleted. The signature of method to do this is:
void deleteEntry(
String className, long classPK)
throws PortalException, SystemException
Here is an example invocation extracted again from the blogs portlet:
assetEntryLocalService.deleteEntry(
BlogsEntry.class.getName(), entry.getEntryId());
Entering and displaying tags and categories
The previous section showed how you can let the asset framework know about the tags and categories that have been associated with a given asset, but how does a content author specify such tags and categories?
The answer is that you can choose any method that you prefer, but Liferay provides a set of JSP tags that you can use to make this task very easy. The following tags can be used within the form you have created to create your type of content to allow entering tags or selecting a predefined category:
<label>Tags</label>
<liferay-ui:asset-tags-selector
className="<%= entry.getClass().getName() %>"
classPK="<%= entry.getPrimaryKey() %>"
/>
<label>Categories</label>
<liferay-ui:asset-categories-selector
className="<%= entry.getClass().getName() %>"
classPK="<%= entry.getPrimaryKey() %>"
/>
These two taglibs will create appropriate form controls that allow the user to enter any tag (even if it doesn't exist) or search and select one of the existing categories.
Tip: If you are using Liferay's Allow Form taglibs, then creating a field to enter tags or categories is even simpler. You just need to use <aui:input name="tags" type="assetTags" /> and <aui:input name="categories" type="assetCategories" /> respectively.
Once the tags and categories have been entered you will want to show them somewhere along with the content of the asset, there are another pair of tags that you can use to do so:
<label>Tags</label>
<liferay-ui:asset-tags-summary
className="<%= entry.getClass().getName() %>"
classPK="<%= entry.getPrimaryKey() %>"
/>
<label>Categories</label>
<liferay-ui:asset-categories-summary
className="<%= entry.getClass().getName() %>"
classPK="<%= entry.getPrimaryKey() %>"
/>
In both tags you can also use an optional parameter called portletURL. When specifying this parameter each of the tags will be a link built with the provided URL and adding a "tag" parameter or a "categoryId" parameter. This is very useful in order to provide support for tags navigation and categories navigation within your portlet. But you will need to take care of implementing this functionality yourself in the code of the portlet by reading the values of those two parameters and using the AssetEntryService to query the database for entries based on the specified tag or category.
Publishing assets with Asset Publisher
One of the nice benefits of using the asset framework is the possibility of using the Asset Publisher portlet, which is part of the Liferay distribution, to publish lists of your custom asset types. These lists can be dynamic (for example based on the tags or categories that the asset has) or manually selected by an administrator.
In order to be able to display your assets the Asset Publisher needs to know how to access some metadata of them and also needs you to provide templates for the different type of views that it can display (abstract and full view).
You can provide all this information to the Asset Publisher through a pair of classes that implement the AssetRendererFactory interface and the AssetRenderer interface:
AssetRendererFactory: this is the class that knows how to retrieve specific assets from the persistent storage from the classPK (that is usually the primary key, but can be anything you have chosen when invoking the updateAsset method used to add or update the asset). This class should be able to grab the asset from a groupId (that identifies an scope of data) and a urlTitle (which is a title that can be used in friendly URLs to refer uniquele to the asset within a given scope). Finally, it can also provide a URL that the Asset Publisher can use when a user wants to add a new asset of your custom type. This URL should point to your own portlet. There are other less important methods, but you can avoid implementing them by extending from BaseAssetRendererFactory. Extending from this class, instead of implementing the interface directly will also make your code more robust if there are changes in the interface in future versions of Liferay, since the base implementation will provide custom implementations for them.
AssetRenderer: this is the class that provides metadata information about one specific asset and is also able to check for permissions to edit or view it for the current user. It is also reponsible for rendering the asset for the different templates (abstract, and full content), by forwarding to an specific JSP. It is also recommended that instead of implementing the interface directly, you extend from the BaseAssetRenderer class, that provides with nice defaults and more robustness for methods that could be added to the interface in the future.
Let's seen an example of these two classes. Again we will pick Liferay's Blogs portlet. Lets start with the implementation for the AssetRendererFactory:
public class BlogsEntryAssetRendererFactory extends BaseAssetRendererFactory {
public static final String CLASS_NAME = BlogsEntry.class.getName();
public static final String TYPE = "blog";
public AssetRenderer getAssetRenderer(long classPK, int type)
throws PortalException, SystemException {
BlogsEntry entry = BlogsEntryLocalServiceUtil.getEntry(classPK);
return new BlogsEntryAssetRenderer(entry);
}
public AssetRenderer getAssetRenderer(long groupId, String urlTitle)
throws PortalException, SystemException {
BlogsEntry entry = BlogsEntryServiceUtil.getEntry(
groupId, urlTitle);
return new BlogsEntryAssetRenderer(entry);
}
public String getClassName() {
return CLASS_NAME;
}
public String getType() {
return TYPE;
}
public PortletURL getURLAdd(
LiferayPortletRequest liferayPortletRequest,
LiferayPortletResponse liferayPortletResponse)
throws PortalException, SystemException {
HttpServletRequest request =
liferayPortletRequest.getHttpServletRequest();
ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
WebKeys.THEME_DISPLAY);
if (!BlogsPermission.contains(
themeDisplay.getPermissionChecker(),
themeDisplay.getScopeGroupId(), ActionKeys.ADD_ENTRY)) {
return null;
}
PortletURL portletURL = PortletURLFactoryUtil.create(
request, PortletKeys.BLOGS, getControlPanelPlid(themeDisplay),
PortletRequest.RENDER_PHASE);
portletURL.setParameter("struts_action", "/blogs/edit_entry");
return portletURL;
}
public boolean hasPermission(
PermissionChecker permissionChecker, long classPK, String actionId)
throws Exception {
return BlogsEntryPermission.contains(
permissionChecker, classPK, actionId);
}
protected String getIconPath(ThemeDisplay themeDisplay) {
return themeDisplay.getPathThemeImages() + "/blogs/blogs.png";
}
}
And here is the AssetRenderer implementation:
public class BlogsEntryAssetRenderer extends BaseAssetRenderer {
public BlogsEntryAssetRenderer(BlogsEntry entry) {
_entry = entry;
}
public long getClassPK() {
return _entry.getEntryId();
}
public String getDiscussionPath() {
if (PropsValues.BLOGS_ENTRY_COMMENTS_ENABLED) {
return "edit_entry_discussion";
}
else {
return null;
}
}
public long getGroupId() {
return _entry.getGroupId();
}
public String getSummary(Locale locale) {
return HtmlUtil.stripHtml(_entry.getContent());
}
public String getTitle(Locale locale) {
return _entry.getTitle();
}
public PortletURL getURLEdit(
LiferayPortletRequest liferayPortletRequest,
LiferayPortletResponse liferayPortletResponse) {
PortletURL portletURL = liferayPortletResponse.createRenderURL(
PortletKeys.BLOGS);
portletURL.setParameter("struts_action", "/blogs/edit_entry");
portletURL.setParameter(
"entryId", String.valueOf(_entry.getEntryId()));
return portletURL;
}
public String getUrlTitle() {
return _entry.getUrlTitle();
}
public String getURLViewInContext(
LiferayPortletRequest liferayPortletRequest,
LiferayPortletResponse liferayPortletResponse,
String noSuchEntryRedirect) {
ThemeDisplay themeDisplay =
(ThemeDisplay)liferayPortletRequest.getAttribute(
WebKeys.THEME_DISPLAY);
return themeDisplay.getPortalURL() + themeDisplay.getPathMain() +
"/blogs/find_entry?noSuchEntryRedirect=" +
HttpUtil.encodeURL(noSuchEntryRedirect) + "&entryId=" +
_entry.getEntryId();
}
public long getUserId() {
return _entry.getUserId();
}
public String getUuid() {
return _entry.getUuid();
}
public boolean hasEditPermission(PermissionChecker permissionChecker) {
return BlogsEntryPermission.contains(
permissionChecker, _entry, ActionKeys.UPDATE);
}
public boolean hasViewPermission(PermissionChecker permissionChecker) {
return BlogsEntryPermission.contains(
permissionChecker, _entry, ActionKeys.VIEW);
}
public boolean isPrintable() {
return true;
}
public String render(
RenderRequest renderRequest, RenderResponse renderResponse,
String template)
throws Exception {
if (template.equals(TEMPLATE_ABSTRACT) ||
template.equals(TEMPLATE_FULL_CONTENT)) {
renderRequest.setAttribute(WebKeys.BLOGS_ENTRY, _entry);
return "/html/portlet/blogs/asset/" + template + ".jsp";
}
else {
return null;
}
}
protected String getIconPath(ThemeDisplay themeDisplay) {
return themeDisplay.getPathThemeImages() + "/blogs/blogs.png";
}
private BlogsEntry _entry;
}
Note that in the render method, there is a forward to a JSP in the case of the abstract and the full content templates. The abstract is not mandatory and if it is not provided, the Asset Publisher will show the title and the summary obtained through the appropriate methods of the renderer. The full content template should always be provided. Here is how it looks like for blogs entries:
<%@ include file="/html/portlet/blogs/init.jsp" %>
<%
BlogsEntry entry = (BlogsEntry)request.getAttribute(WebKeys.BLOGS_ENTRY);
%>
<%= entry.getContent() %>
<liferay-ui:custom-attributes-available className="<%= BlogsEntry.class.getName() %>">
<liferay-ui:custom-attribute-list
className="<%= BlogsEntry.class.getName() %>"
classPK="<%= (entry != null) ? entry.getEntryId() : 0 %>"
editable="<%= false %>"
label="<%= true %>"
/>
</liferay-ui:custom-attributes-available>
That's about it. It wasn't that hard, right? Now you can start enjoying the benefits of the asset framework in your custom portlets.