How to create a Site for Alfresco using Java API

Some time ago, Site creation in Alfresco was driven by Share web application. So using a simple SiteService.createSite from repository Java API is not enough to provide access to the site from Share.

Below some useful snippets are listed.

Creating the Site

// Default site preset
String sitePreset = "site-dashboard";
SiteInfo siteInfo = siteService.createSite(sitePreset,
    "test-site", 
    "Test Site",
    "Test Site description", 
    SiteVisibility.PRIVATE);

Adding site members

Including at least a Manager is advisable.

siteService.setMembership("test-site", 
    "admin",
    SiteRole.SiteManager.toString());

Creating dashboard

This step is required to provide access from Share.

private void createDefaultDashboard(SiteInfo siteInfo) {
    
    NodeService nodeService = serviceRegistry.getNodeService();
    FileFolderService fileFolderService = serviceRegistry.getFileFolderService();
    ContentService contentService = serviceRegistry.getContentService();
    
    FileInfo surfConfig = fileFolderService.create(siteInfo.getNodeRef(), "surf-config", ContentModel.TYPE_FOLDER);
    Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
    properties.put(ContentModel.PROP_CASCADE_HIDDEN, Boolean.TRUE);
    properties.put(ContentModel.PROP_CASCADE_INDEX_CONTROL, Boolean.TRUE);
    nodeService.addAspect(surfConfig.getNodeRef(), ContentModel.ASPECT_HIDDEN, properties);
    // Hint from Bertrand Forest
    properties = new HashMap<QName, Serializable>();
    properties.put(ContentModel.PROP_IS_INDEXED, Boolean.FALSE);
    properties.put(ContentModel.PROP_IS_CONTENT_INDEXED, Boolean.FALSE);
    nodeService.addAspect(surfConfig.getNodeRef(), ContentModel.ASPECT_INDEX_CONTROL, properties);
    
    FileInfo pages = fileFolderService.create(surfConfig.getNodeRef(), "pages", ContentModel.TYPE_FOLDER);
    FileInfo site = fileFolderService.create(pages.getNodeRef(), "site", ContentModel.TYPE_FOLDER);
    FileInfo siteName = fileFolderService.create(site.getNodeRef(), siteInfo.getShortName(), ContentModel.TYPE_FOLDER);
    
    Map<QName, Serializable> props = new HashMap<QName, Serializable>(1);
    props.put(ContentModel.PROP_NAME, "dashboard.xml");  

    NodeRef node = nodeService.createNode(
                            siteName.getNodeRef(), 
                        ContentModel.ASSOC_CONTAINS, 
                        QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "dashboard.xml"),
                        ContentModel.TYPE_CONTENT, 
                        props).getChildRef();
                        
    ContentWriter writer = contentService.getWriter(node, ContentModel.PROP_CONTENT, true);
    writer.setMimetype(MimetypeMap.MIMETYPE_XML);
    writer.setEncoding("UTF-8");
    // TODO Create dashboard.xml file by using an external file resource instead of a hand-coded String
    writer.putContent("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 
            "<page>\n" + 
            "      <title>Collaboration Site Dashboard</title>\n" + 
            "      <title-id>page.siteDashboard.title</title-id>\n" + 
            "      <description>Collaboration site's dashboard page</description>\n" + 
            "      <description-id>page.siteDashboard.description</description-id>\n" + 
            "      <authentication>user</authentication>\n" + 
            "      <template-instance>dashboard-2-columns-wide-left</template-instance>\n" + 
            "      <properties>\n" + 
            "        <sitePages>[{\"pageId\": \"documentlibrary\"}]</sitePages>\n" + 
            "      <theme/><dashboardSitePage>true</dashboardSitePage></properties>\n" + 
            "    <page-type-id>generic</page-type-id></page>");
    
}

Creating Site containers

There are different containers that should be created (wiki, forum,…) but having a Document Library is required for many use cases.

NodeRef documentLibraryNodeRef =  siteService.createContainer(siteInfo.getShortName(), SiteService.DOCUMENT_LIBRARY, null, null);

Some operations are not described in Alfresco documentation, but they can be guessed inspecting internal structure of the repository.

Anuncios

Alfresco 5: using just only alfresco-global.properties

Deploying Alfresco artifacts in several environments requires different parameters in configuration files.

Some time ago I was working with Alfresco in a maven way: packaging properties together with the artifact by using profiles. This method requires several packagings for the same module: one for each environment.

Alfresco experienced developers, and alfresco documentation itself, are suggesting to include module properties just only in alfresco-global.properties for repo artifacts and in share-config-custom.xml for share artifacts.

However, this is one more file than required. Let me share with you how to use alfresco-global.properties for share modules with the following example.

Properties

Including properties values in alfresco-global.properties.

customHeaderModule.url=http://intranet.keensoft.local/alfresco/cabecera.html 
customHeaderModule.height=216px

Repo project

Exposing these properties as a REST service.

1. Describing the REST service
/src/main/amp/config/alfresco/extension/templates/webscripts/es/keensoft/custom-actions/headerParams.get.desc.xml

<webscript>
  <shortname>Get header params</shortname>
  <description>Get header params
  To test: curl -v -u "http://localhost:8080/alfresco/service/keensoft/header/params"
  </description>
  <family>keensoft</family>
  <url>/keensoft/header/params</url>
  <format default="json"/>
  <authentication>none</authentication>
  <transaction>none</transaction>
</webscript>

2. Declaring Spring bean for the Web Script
/src/main/amp/config/alfresco/module/custom-actions/context/services-context.xml

<!-- Return params from alfresco-global.properties -->
<bean id="webscript.es.keensoft.custom-actions.headerParams.get" 
      class="es.keensoft.alfresco.action.webscript.HeaderParamsWebScript" parent="webscript">
	<property name="url" value="${customHeaderModule.url}" />
    <property name="height" value="${customHeaderModule.height}"/>
</bean>

3. Implementing logic for the service
/src/main/java/es/keensoft/alfresco/action/webscript/HeaderParamsWebScript.java

package es.keensoft.alfresco.action.webscript;

import java.io.IOException;

import org.json.simple.JSONObject;
import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.http.MediaType;

public class HeaderParamsWebScript  extends AbstractWebScript {
	
	private String url;
	private String height;
	
	@SuppressWarnings("unchecked")
	@Override
	public void execute(WebScriptRequest request, WebScriptResponse response) throws IOException {
		
		JSONObject obj = new JSONObject();
		obj.put("customHeaderModuleHeight", height);
		obj.put("customHeaderModuleUrl", url);
    	
    	String jsonString = obj.toString();
    	response.setContentEncoding("UTF-8");
    	response.setContentType(MediaType.APPLICATION_JSON.toString());
    	response.getWriter().write(jsonString);
	
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public void setHeight(String height) {
		this.height = height;
	}

}

Share project

Recovering alfresco-global.properties value by using Alfresco REST API.

1. Declaring Surf customization
/src/main/amp/config/alfresco/web-extension/site-data/extensions/custom-header.xml

<extension>
  <modules>
    <module>
      <id>Custom Header</id>
      <version>1.0</version>
      <auto-deploy>true</auto-deploy>
      <customizations>
          <customization>
              <targetPackageRoot>org.alfresco.share.header</targetPackageRoot>
              <sourcePackageRoot>es.keensoft.share.header</sourcePackageRoot>
          </customization>
      </customizations>
    </module>
  </modules>
</extension>

2. Recovering values from REST service
/src/main/amp/config/alfresco/web-extension/site-webscripts/es/keensoft/share/header/share-header.get.js

function main() {
	var headerParams = jsonConnection("/keensoft/header/params");
	model.url = headerParams.customHeaderModuleUrl;
	model.height = headerParams.customHeaderModuleHeight;
}

main();

function jsonConnection(url) {
	
	var connector = remote.connect("alfresco"),
		result = connector.get(url);

	if (result.status == 200) {		
		return eval('(' + result + ')')
	} else {
		return null;
	}
}

3. Extending view by using FTL
/src/main/amp/config/alfresco/web-extension/site-webscripts/es/keensoft/share/header/share-header.get.html.ftl

<@markup id="custom-header-resources" action="before" target="html">
	<iframe id="ifHeader" name="ifHeader" scrolling="auto" frameborder="0"
	    width="100%" 
	    height="${height}" 
	    src="${url}">
	</iframe>
</@markup>

Result

A new header is showed above original Alfresco header. Url and height parameters are recovered from alfresco-global.properties

alfresco-header-customized

Alfresco 5: Customizing facet filter for future dates

From Alfresco 5, faceted search is available in Share web app.

However, when using dates, only past facets are available (yesterday, last week…). When using future dates in customized metadata, some modifications are required.

Following instructions can be deployed as an standard Repository extension project  by using Alfresco SDK.

Overriding date facets Spring beans from Alfresco to add extra classification must be included in service-context.xml

<!-- Include your own customized properties from content model: futureDate -->
<bean id="facet.dateFacetFields" class="org.springframework.beans.factory.config.SetFactoryBean">
   <property name="sourceSet">
      <set>
         <value>@{http://www.alfresco.org/model/content/1.0}created</value>
         <value>@{http://www.alfresco.org/model/content/1.0}modified</value>
         <value>@{http://www.keensoft.es/model/document/1.0}futureDate</value>
      </set>
   </property>   
</bean>

<!-- Include different categories for future dates: next year, next six months... -->
<bean id="facet.dateBuckets" class="org.springframework.beans.factory.config.MapFactoryBean">
   <property name="sourceMap">
            <map>
               <entry key="[NOW/DAY+1DAY TO NOW/DAY+1YEAR]" value="Next year" />
               <entry key="[NOW/DAY+1DAY TO NOW/DAY+6MONTHS]" value="Next six months" />
               <entry key="[NOW/DAY+1DAY TO NOW/DAY+1MONTH]" value="Next month" />
               <entry key="[NOW/DAY+1DAY TO NOW/DAY+7DAYS]" value="Next week" />
               <entry key="[NOW/DAY-1DAY TO NOW/DAY+1DAY]" value="faceted-search.date.one-day.label" />
               <entry key="[NOW/DAY-7DAYS TO NOW/DAY+1DAY]" value="faceted-search.date.one-week.label" />
               <entry key="[NOW/DAY-1MONTH TO NOW/DAY+1DAY]" value="faceted-search.date.one-month.label" />
               <entry key="[NOW/DAY-6MONTHS TO NOW/DAY+1DAY]" value="faceted-search.date.six-months.label" />
               <entry key="[NOW/DAY-1YEAR TO NOW/DAY+1DAY]" value="faceted-search.date.one-year.label" />
            </map>
   </property>
</bean>

Once this module is deployed, future dates are classified in the future in faceted search Alfresco page.

alfresco-faceted-search-future

This functionality may be modified in the future. This article has only been tested with Alfresco CE 5.0.d