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

 

Customizing Alfresco for demo purposes

Sometimes users require not a product but a solution to their problem. If Alfresco Share itself is showed, they can fly away from the main target because of the richness of the interface. In this case we are focusing our presentation in Alfresco search capabilities, so any other elements should be cut off.

Login screen

Replacing Alfresco logo and changing Login button color can help to avoid product identification at first sight.

alfresco-login-customized

Below required files are detailed, based on an Alfresco SDK AMP Share project.

/src/main/amp/config/alfresco/web-extension/site-data/extensions/hide-elements.xml

<extension>
	<modules>
		<module>
			<id>Hide Elements Module</id>
			<customizations>
				<customization>
					<targetPackageRoot>org.alfresco.components</targetPackageRoot>
					<sourcePackageRoot>es.keensoft.components</sourcePackageRoot>
				</customization>
			</customizations>
			<auto-deploy>true</auto-deploy>
		</module>
	</modules>
</extension>

/src/main/amp/config/alfresco/web-extension/site-webscripts/es/keensoft/components/head/resources.get.html.ftl

<@markup id="custom-login-resources" action="after" target="resources">

   <link rel="stylesheet" type="text/css"
    href="${url.context}/res/login/customizations/components/head/resources.css" >
   </link>

</@markup>

/src/main/resources/META-INF/login/customizations/components/head/resources.css

.theme-company-logo {
    background: url("images/keensoft.png") no-repeat scroll 0 0 rgba(0, 0, 0, 0) !important;
    height: 141px !important;
    width: 304px !important;
}

.form-fields.login .form-field > span.yui-button {
	border-color: #2598d0 !important;
}
.form-fields.login .form-field > span.yui-button > .first-child {
	border-color: #2598d0 !important;
	background-color: #2598d0 !important;
}

/src/main/resources/META-INF/login/customizations/components/head/images/keensoft.png

keensoft

Header and footer

Alfresco Share header and footer should be hidden to avoid unwanted options.

Modify /src/main/amp/config/alfresco/web-extension/site-data/extensions/hide-elements.xml in order to include header and footer overriding.

<extension>
	<modules>
		<module>
			<id>Hide Elements Module</id>
			<components>
				<component>
					<scope>global</scope>
					<region-id>footer</region-id>
					<source-id>global</source-id>
					<sub-components>
						<sub-component id="default">
							<url>/keensoft/components/hide-footer</url>
						</sub-component>
					</sub-components>
				</component>
				<component>
					<scope>global</scope>
					<region-id>header</region-id>
					<source-id>global</source-id>
					<sub-components>
						<sub-component id="default">
							<url>/keensoft/components/hide-header</url>
						</sub-component>
					</sub-components>
				</component>
			</components>
			<customizations>
				<customization>
					<targetPackageRoot>org.alfresco.components</targetPackageRoot>
					<sourcePackageRoot>es.keensoft.components</sourcePackageRoot>
				</customization>
			</customizations>
			<auto-deploy>true</auto-deploy>
		</module>
	</modules>
</extension>

/src/main/amp/config/alfresco/web-extension/site-webscripts/es/keensoft/components/hide-footer.get.desc.xml

<webscript>
    <shortname>Hide Footer</shortname>
    <url>/keensoft/components/hide-footer</url>
    <family>keensoft</family>
</webscript>

/src/main/amp/config/alfresco/web-extension/site-webscripts/es/keensoft/components/hide-footer.get.head.ftl

<@link rel="stylesheet" type="text/css" href="${page.url.context}/res/resources/hide.css" />

/src/main/amp/config/alfresco/web-extension/site-webscripts/es/keensoft/components/hide-footer.get.html.ftl

Empty

/src/main/amp/config/alfresco/web-extension/site-webscripts/es/keensoft/components/hide-header.get.desc.xml

<webscript>
    <shortname>Hide Header</shortname>
    <url>/keensoft/components/hide-header</url>
    <family>keensoft</family>
</webscript>

/src/main/amp/config/alfresco/web-extension/site-webscripts/es/keensoft/components/hide-header.get.head.ftl

<@link rel="stylesheet" type="text/css" href="${page.url.context}/res/resources/hide.css" />

/src/main/amp/config/alfresco/web-extension/site-webscripts/es/keensoft/components/hide-header.get.html.ftl

Empty

/src/main/resources/META-INF/resources/hide.css

.sticky-push, .sticky-footer {
	display: none !important;
}

#bd {
	padding-bottom: 0px !important;	
}

.alfresco-header-Header {
	display: none !important;
}

alfresco-hide-header-footer

By now header and footer has been hidden from every page except for aikau pages. Aikau pages are served as a whole component, so we need to perform an override on faceted-search page.

/src/main/amp/config/alfresco/site-webscripts/org/alfresco/share/pages/faceted-search/faceted-search.get.html.ftl

<@processJsonModel group="share"/>
<@link rel="stylesheet" type="text/css" href="${page.url.context}/res/resources/hide.css" />

Navigation

Finally we are focusing our demo on faceted search, so we want the user login directly to that page. Including a web filter for an initial redirection should do the trick.

/src/main/java/es/keensoft/share/filter/SearchFilter.java

package es.keensoft.share.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.extensions.surf.site.AuthenticationUtil;

// Unordered filter
@WebFilter(urlPatterns={"/page/*"})
public class SearchFilter implements Filter {
	
	public static String SESSION_ATTRIBUTE_KEY_REDIRECTED = "_alf_REDIRECTED";
	
	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) 
            throws IOException, ServletException {
		
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;
                HttpSession session = request.getSession();
        
		String userId = AuthenticationUtil.getUserId(request);
        
		if (checkAgreement(request, userId)) {
        	    session.setAttribute(SESSION_ATTRIBUTE_KEY_REDIRECTED, Boolean.TRUE);
		    response.sendRedirect(request.getContextPath() + "/page/dp/ws/faceted-search");
		} else {
    		    chain.doFilter(req, resp);
		}

	}
	
	private boolean checkAgreement(HttpServletRequest request, String userId) {
		
                HttpSession session = request.getSession();
        
		boolean userLoggedIn = AuthenticationUtil.getUserId(request) != null;
		
		boolean previouslyRedirected = 
				session.getAttribute(SESSION_ATTRIBUTE_KEY_REDIRECTED) != null && 
				(Boolean) session.getAttribute(SESSION_ATTRIBUTE_KEY_REDIRECTED);
		
		return (userLoggedIn && !previouslyRedirected);

	}
	
	@Override
	public void init(FilterConfig config) throws ServletException {}

	@Override
	public void destroy() {}
}

In order to use a @WebFilter annotation, you need Tomcat 7 or higher and including a dependency in your pom.xml

<dependencies>
    <!-- @WebFilter available since servlet 3.0 (Tomcat 7) -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.0.1</version>
        <scope>provided</scope>
    </dependency>
    ...
</dependencies>

Conclusion

Now we have the demo entering directly to Alfresco faceted search and we can explain it deep without any distraction.

alfresco-hide-faceted-search