cancel
Showing results for 
Search instead for 
Did you mean: 

Custom Constraint

kbonnet
Champ in-the-making
Champ in-the-making
I'm a total newbie on the area of java. Nevertheless i wanted to try to create a custom constraint which implements databaselookups for a pick list. I found an example here on the forum.

I was able to get the java code compiled with javac against the Alfresco SDK. The result was a compiled .class file. I was not sure where to put it, so i put it in a new "classes" directory in my extensions root.

Next i created a test model, again based on the example i found, and i set the "type" parameter to my package name in the java code, extended with the name of the public class in the java code. In my case "org.contezza.customConstraints.ListOfValuesQueryConstraint".

Of course it did something wrong. When i try to deploy the model (it's a dynamic model in the data dictionary/models folder) i get the error message in the logging "Constraint type 'org.contezza.customConstraints.ListOfValuesQueryConstraint' on constraint 'test:CodeLabel' is not a well-known type or a valid Constraint implementation".

Because i was able to compile the java code against the SDK without error, i think this error has to do with the place i put the class file in. Can anyone give me a push into the right direction?

My environment is:
- Ubuntu 7.04
- Tomcat 5.5
- JDK 1.5
- MySQL 5

My (example) java code is as follows:


package org.contezza.customConstraints;

import java.util.ArrayList;
import java.util.List;
import java.sql.*;
//import org.alfresco.i18n.I18NUtil;
import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint;
import org.alfresco.web.bean.generator.BaseComponentGenerator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.Serializable;
import javax.faces.model.SelectItem;

public class ListOfValuesQueryConstraint extends ListOfValuesConstraint implements Serializable {

   private static Log logger = LogFactory.getLog(BaseComponentGenerator.class);

   private static final long serialVersionUID=1;

   private List<String> allowedLabels;

   public void setAllowedValues(List allowedValues) {}
   public void setCaseSensitive(boolean caseSensitive) {}

   public void initialize() {
       super.setCaseSensitive(false);
       this.loadDB();
    }

   public List<String> getAllowedLabels() {
      return this.allowedLabels;
   }

   public void setAllowedLabels(List<String> allowedLabels) {
      this.allowedLabels=allowedLabels;
   }

    public List<SelectItem> getSelectItemList() {
      List<SelectItem> result = new ArrayList<SelectItem>(this.getAllowedValues().size());
      for(int i=0;i<this.getAllowedValues().size();i++) {
         result.add(new SelectItem((Object)this.getAllowedValues().get(i),this.allowedLabels.get(i)));
      }
      return result;
   }

    protected void loadDB() {

       String driverName = "org.gjt.mm.mysql.Driver";
        String serverName = "localhost";
        String mydatabase = "alfresco";
        String username = "alfresco";
        String password = "alfresco";

        List<String> av = new ArrayList<String>();
        List<String> al=new ArrayList<String>();


        try {
           Connection connection = null;
            Class.forName(driverName);
            String url = "jdbc:mysql://" + serverName +  "/" + mydatabase;
            connection = DriverManager.getConnection(url, username, password);
            Statement stmt = connection.createStatement();
            ResultSet rs = stmt.executeQuery("select code,label from codelabel");
            while (rs.next()) {
                av.add(rs.getString("code"));
                al.add(rs.getString("label"));
            }
        }
        catch (Exception e) {}

      super.setAllowedValues(av);
      this.setAllowedLabels(al);
   }
}

My example model is as follows:

<?xml version="1.0" encoding="UTF-8"?>

<model name="test:test" xmlns="http://www.alfresco.org/model/dictionary/1.0">
   <description></description>
   <author>Contezza InformatieManagement</author>
   <version>0.1</version>
   <imports>
      <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" />
      <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm" />
   </imports>
   <namespaces>
      <namespace uri="http://www.contezza.nl/model/test/1.0" prefix="test" />
   </namespaces>


<constraints>
   <constraint name="test:CodeLabel" type="org.contezza.customConstraints.ListOfValuesQueryConstraint">
      <parameter name="allowedValues">
         <list>
         </list>
      </parameter>
   <parameter name="caseSensitive"><value>true</value></parameter>
</constraint>
   </constraints>
  
   <types>
      <type name="test:codecontent">
         <title>Contenu étendu</title>
         <parent>cm:content</parent>
         <properties>
            <property name="test:codestr">
               <title>Code str</title>
               <type>d:text</type>
               <constraints>
                  <constraint ref="test:CodeLabel" />
               </constraints>
            </property>
         </properties>
      </type>
   </types>
</model>
27 REPLIES 27

acis
Champ in-the-making
Champ in-the-making
Hi,

I recognize my custom constraint here  Smiley Very Happy

You should have posted in the same topic for further readers.

After compilation you should place your .class file in
<TOMCAT>/webapps/alfresco/WEB-INF/classes/org/contezza/customConstraints/

Don't forget to change connection parameters to the database and to create a table codelabel with 2 fields : code and label.

Good luck.

kbonnet
Champ in-the-making
Champ in-the-making
Hi Acis,

Thanks for your reply! I have it running since 10 minutes Smiley Happy

It wasnt clear to me that i needed to put my class in the WEB-INF/lib directory. I thought that putting in somewhere in the classpath would be sufficient. When i tried WEB-INF/lib, it started working.

I also, in the process of troubleshooting, made a .jar file of it. And since i dont like to tamper in the deflated alfresco.war code, i create an AMP-file for it, so that i can install it nicely.

Now i have everything to deploy new classes, i can start customizing your java code.  Smiley Tongue

Thanks a lot!

Kind regards,

Koen Bonnet

msvoren
Champ in-the-making
Champ in-the-making
Thanks! That was helpful!

and 1 more question.. Is it possible to, somehow, reload this list from database, each time I access this dropdown?

Because now, this list is static, in order to apply changes from database, i have to restart alfresco.

Any chance of making this more dynamical?

acis
Champ in-the-making
Champ in-the-making
I didn't try but I think it's possible by overriding the getAllowedValues() method like :

public List getAllowedValues() {
      this.loadDB();
      super.getAllowedValues();
}

robain
Champ in-the-making
Champ in-the-making
my question is how does "allowedLabels" in this code comes into the picture. I see that only allowed values are displayed on the properties page drop down list and the same after selection. do generators and converters come into play if i want to show both of them "label" and "values". Can anyone post a sample of the generator or describe how it is going to look like.

Thanks
Robain

robain
Champ in-the-making
Champ in-the-making
To add to my previous comment where does getSelectItemList() method come into play. I dont see it in any of the extended classes or the interface. but where or how is this method utilized.

Thanks
Robain.

acis
Champ in-the-making
Champ in-the-making
Hi robain !

Here is how it works :

Your defined a constraint : in that case we extended ListOfValuesConstraint to select the values from a database or anywhere else.

In your web-client-config-custom.xml you should have define that you want to display the property with the constraint.
<property-sheet>
<show-property name="tc:codestr" component-generator="ListOfValuesQueryGenerator" />
</property-sheet>

If you don't put the additional attribut "component-generator", Alfresco will use a list with the values as values and labels of the drop down list. But here we want to have values as values and labels as labels. So we have to create a component generator.

As you can see, I added an additional attribute "component-generator". This attribute means that when alfresco will display the property, it'll use ListOfValuesQueryGenerator to create the component. The Generator is a subclass of org.alfresco.web.bean.generator.TextFieldGenerator. You must override the createComponent and generate methods.
This componentGenerator will call getSelectedItems.

public UIComponent generate(FacesContext context, String id) {

     UIOutput component = (UIOutput)context.getApplication().createComponent(
      ComponentConstants.JAVAX_FACES_OUTPUT);
      component.setRendererType(ComponentConstants.JAVAX_FACES_TEXT);
      FacesHelper.setupComponentId(context, component, id);
      return component;
   }

   protected UIComponent createComponent(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem item) {
     
      UIComponent component = null;
     
      if (propertySheet.inEditMode()) {
        
        ListOfValuesQueryConstraint constraint=this.getListOfValuesQuery(context, propertySheet, item);
        
         PropertyDefinition propDef = this.getPropertyDefinition(context, propertySheet.getNode(), item.getName());
        
         if (constraint != null && item.isReadOnly() == false && propDef != null && propDef.isProtected() == false) {
            component = context.getApplication().createComponent(UISelectOne.COMPONENT_TYPE);
            FacesHelper.setupComponentId(context, component, item.getName());
           
            UISelectItems itemsComponent = (UISelectItems)context.getApplication().createComponent("javax.faces.SelectItems");
           
            itemsComponent.setValue(constraint.getSelectItemList());
           
            component.getChildren().add(itemsComponent);
         }
      }
      else {
         component = generate(context, item.getName());
      }
     
      return component;
   }

protected ListOfValuesQueryConstraint getListOfValuesQuery(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem item) {
    
     ListOfValuesQueryConstraint lovqConstraint = null;
      PropertyDefinition propertyDef = getPropertyDefinition(context, propertySheet.getNode(), item.getName());
     
      if (propertyDef != null) {

         List<ConstraintDefinition> constraints = propertyDef.getConstraints();
         for (ConstraintDefinition constraintDef : constraints) {
           
            Constraint constraint = constraintDef.getConstraint();
            if (constraint instanceof ListOfValuesQueryConstraint) {
               lovqConstraint = (ListOfValuesQueryConstraint)constraint;
               break;
            }
         }
      }
     
      return lovqConstraint;
   }

Ok now the Edit mode will work perfectly. But the problem is to display the field in view mode. Alfresco will show the value and not the label. So here you have to use a converter.

In your generator add this :
protected void setupConverter(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem property, PropertyDefinition propertyDef, UIComponent component) {
        
         super.setupConverter(context, propertySheet, property, propertyDef, component);
        
         if(!propertySheet.inEditMode() && propertyDef != null && component instanceof UIOutput) {
            ((UIOutput)component).setConverter(new ListOfValuesQueryConverter(this.getListOfValuesQuery(context, propertySheet, property)));
         }
      }

and create the converter

public class ListOfValuesQueryConverter implements Converter,StateHolder {
   
   private ListOfValuesQueryConstraint constraint;
   
   public ListOfValuesQueryConverter(ListOfValuesQueryConstraint constraint) {
      this.constraint=constraint;
   }
   
   public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException {
      return new Object();
   }
  
   public String getAsString(FacesContext context, UIComponent component, Object value) throws ConverterException {
     
      if(value instanceof String) {
         List<String> l = this.constraint.getAllowedValues();
            for(int i=0;i<l.size();i++) {
               if(l.get(i).equals((String)value)) {
                  return this.constraint.getAllowedLabels().get(i);
               }
            }
      }
      else if(value instanceof List) {
         String result="";
         List<String> l = this.constraint.getAllowedValues();
         for(String val : (List<String>)value) {
               for(int i=0;i<l.size();i++) {
                  if(l.get(i).equals(val)) {
                     result+=this.constraint.getAllowedLabels().get(i)+" ";
                     break;
                  }
               }
         }
         return result;
      }
     
      return new String();
   }
  
   public ListOfValuesQueryConverter() {}
   public Object saveState(FacesContext context) { return (Object)this.constraint; }
   public void restoreState(FacesContext context, Object state) { this.constraint=(ListOfValuesQueryConstraint)state; }
   public boolean isTransient() { return false; }
   public void setTransient(boolean newTransientValue) {}
}


we could have use the "converter=" attribute in the web-client-config-custom.xml but good luck to do it. I tried hard and it didn't work out.

alex--70
Champ in-the-making
Champ in-the-making
Escuse me for my english, when i compile the 'customConstraints' i need to import the following :

import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint;
import org.alfresco.web.bean.generator.BaseComponentGenerator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

where do i find these ?

Thanks
Alex–70

acis
Champ in-the-making
Champ in-the-making
Download Alfresco SDK to get the libs.