Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Create database table to be used by plugin

Michael Danielsson
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 21, 2012

I want to create a table in the JIRA database. This table shall contain data to be used by a plugin.

In the book "JIRA Development Cookbook" I found the chapter "Extending JIRA DB with custom schema" and I have tried to follow the examples but the table was not created.

I have added

<entity-group group="default" entity="Employee"/>

to the entitygroup.xml and

<entity entity-name="Employee" table-name="employee" packagename="">
		<field name="id" type="numeric"/>
		<field name="name" type="long-varchar"/>
		<field name="address" col-name="empaddress" type="longvarchar"/>
		<field name="company" type="long-varchar"/>
		<prim-key field="id"/>
		<index name="emp_entity_name">
			<index-field name="name"/>
		</index>
	</entity>

to the entitymodel.xml file.

According to the book Once JIRA recognizes that there is no table corresponding to the new entity name employee in the database; it will create one.

I have restarted JIRA but no new table has be created.

Any suggestions?

5 answers

1 accepted

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

0 votes
Answer accepted
Jobin Kuruvilla [Adaptavist]
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 21, 2012

<field name="address" col-name="empaddress" type="longvarchar"/>

long-varchar is misspelt. Type should be long-varchar.

1 vote
crf
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
June 7, 2014

The answers given here are *extremely* low-level hackery. This is dangerous and should be avoided unless you really have no other option. Most plugins' needs are adequately met by one of the simple storage mechanisms provided by the API, such as PropertySet, PluginSettings, or Active Objects.

One of the advantages of playing by the rules is that your plugin's data is expected and dealt with gracefully. JIRA can know that it's there and care for its export and import. Although I haven't tried fiddling with the OfBiz Entity Engine guts at the level suggested here, I suspect it is very unlikely that backup/restore will pay any attention to your plugin's data.

Jobin Kuruvilla [Adaptavist]
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
June 9, 2014

Most Answers questions like this are outdated because there are better ways to do the same things in JIRA now and/or JIRA deprecated the old ways. This is one such example ;)

Backups will pay attention to the above method but of course ActiveObjects should be used, now that it is the best option available!

0 votes
Michael Danielsson
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 21, 2012

5.1.6

Postgres

2012-10-22 15:07:36,484 main ERROR [ofbiz.core.util.UtilXml] XmlFileLoader: File null process error. Line: 11. Error message: Attribute "package-name" is required and must be specified for element type "entity".

2012-10-22 15:07:36,484 main ERROR [ofbiz.core.util.UtilXml] XmlFileLoader: File null process error. Line: 11. Error message: Attribute "packagename" must be declared for element type "entity".

2012-10-22 15:07:37,481 main WARN [core.entity.jdbc.DatabaseUtil] Entity "Employee" has no table in the database

2012-10-22 15:07:37,481 main ERROR [core.entity.jdbc.DatabaseUtil] Could not create table "public.employee"

2012-10-22 15:07:37,481 main ERROR [core.entity.jdbc.DatabaseUtil] Field type [null] not found for field [address] of entity [Employee], not creating table.

0 votes
Jobin Kuruvilla [Adaptavist]
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 21, 2012

Which version of JIRA is it? And what database? Are you seeing any error in the startup?

0 votes
Radu Dumitriu
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 21, 2012

This is what we do on the activator of a plugin:

.............

        LOG.debug("Loading standard default resources");
        //we need for sure the config resources. Therefore, we must add them
        GenericDelegatorLoader ufg = new GenericDelegatorLoader("default",
                                                                AbstractPluginLifecycleActivator.class);
		ufg.loadXMLFiles(CFG_LOADER_ENT, CFG_LOADER_ENT_LOCATION,
                         CFG_LOADER_GRP, CFG_LOADER_GRP_LOCATION);
        LOG.debug("Done loading standard default resources.");

..........................

private static final String CFG_LOADER_ENT = "maincp";
	private static final
    String CFG_LOADER_ENT_LOCATION =
            "/ofbiz/entities/entitymodelKPlg.xml";
    
	private static final String CFG_LOADER_GRP = "maincp";
	private static final
    String CFG_LOADER_GRP_LOCATION =
            "/ofbiz/entities/entitygroupKPlg.xml";

====

/*
 * Created at Jul 19, 2010, 5:45:44 PM
 *
 * File: GenericDelegatorLoader.java
 */
package com.keplerrominfo.jira.commons.ofbiz;

import java.util.*;
import java.io.*;

import org.ofbiz.core.config.GenericConfigException;
import org.ofbiz.core.config.ResourceHandler;
import org.ofbiz.core.entity.*;
import org.ofbiz.core.entity.config.EntityConfigUtil;
import org.ofbiz.core.entity.model.*;
import org.ofbiz.core.util.*;
import org.w3c.dom.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.keplerrominfo.jira.commons.util.ResourceUtils;

/**
 * Class for adding extra functionality to generic delegator. Given a generic
 * delegator name, it has a method that adds entities and groups from 2 xml
 * files to the generic delegator represented by the name.
 * 
 * @author Maria Cirtog (mcirtog@kepler-rominfo.com)
 * @author Radu Dumitriu (rdumitriu@gmail.com)
 * @since 1.0
 */
public class GenericDelegatorLoader {
	private static final
    Log LOG = LogFactory.getLog(GenericDelegatorLoader.class);

	private String delegatorName;
    private Class&lt;?&gt; callerClazz;

	/**
	 * Constructs a new instance of &lt;code&gt;GenericDelegatorLoader&lt;/code&gt; with the
	 * specified delegator name.
	 * 
	 * @param delegatorName The name of the generic delegator.
     * @param caller the caller class
	 */
	public GenericDelegatorLoader(String delegatorName, Class&lt;?&gt; caller) {
	    this.delegatorName = delegatorName != null ? delegatorName : "default";
        callerClazz = caller != null ? caller : GenericDelegatorLoader.class;
	}

    /**
	 * Constructs a new instance of &lt;code&gt;GenericDelegatorLoader&lt;/code&gt; with the
	 * specified delegator name.
	 *
	 * @param delegatorName The name of the generic delegator.
	 */
	public GenericDelegatorLoader(String delegatorName) {
        this(delegatorName, null);
    }

    /**
     * Default constructor, uses the 'default' delegator
     */
    public GenericDelegatorLoader() {
        this(null, null);
    }

	/**
	 * Load additional entitymodel and entitygroup xml files and join
	 * informations to the delegator represented by delegator name instance
	 * variable. It is used instead of declaring an entitymodel.xml and an
	 * entitygroup.xml files in entityengine.xml. 
	 * The declaration in entityengine.xml would be like: 
	 * &lt;entity-model-reader name="main"&gt;
	 * 		&lt;resource loader="..." location="..."/&gt; 
	 * &lt;/entity-model-reader&gt;
	 * &lt;entity-group-reader name="main" loader="..." location="..." /&gt;
	 * 
	 * @param loaderEnt The loader name for the xml for entities(Ex: maincp).
	 *        Corresponds to loader from &lt;entity-model-reader&gt; tag.
	 * @param locationEnt The location for the xml file for entities(Ex:
	 *        com/keplerrominfo/jira/commons/ofbiz/entities/entitymodel.xml).
	 *        Corresponds to location from &lt;entity-model-reader&gt; tag.
	 * @param loaderGrp The loader name for the xml for groups(Ex: maincp).
	 *        Corresponds to loader from &lt;entity-group-reader&gt; tag.
	 * @param locationGrp The location for the xml file for groups(Ex:
	 *        com/keplerrominfo/jira/commons/ofbiz/entities/entitygroup.xml).
	 *        Corresponds to location from &lt;entity-group-reader&gt; tag
	 * @throws OfbizDataException if an error occurs when loading the 2 xml
	 *         files
	 */
	public void loadXMLFiles(String loaderEnt, final String locationEnt,
		                     String loaderGrp, final String locationGrp) throws OfbizDataException {
		GenericDelegator delegator =
			GenericDelegator.getGenericDelegator(delegatorName);
		if (delegator == null) {
			LOG.warn("Null delegator in loadXMLFiles() method.");
			return;
		}
		try {
			ModelReader modelR = delegator.getModelReader();

			ResourceHandler rhEntities =
                                    new ResourceHandler(
                                        EntityConfigUtil.ENTITY_ENGINE_XML_FILENAME,
                                        loaderEnt,
                                        locationEnt) {
                @Override
                public InputStream getStream() throws GenericConfigException {
                    if(!locationEnt.startsWith("/")) {
                        return super.getStream();
                    }
                    try {
                        return ResourceUtils.getAsInputStream(locationEnt,
                                                              callerClazz);
                    } catch(Exception e) {
                        throw new GenericConfigException("I/O error", e);
                    }
                }
            };
			addEntitiesToEntityCache(modelR, rhEntities, loaderEnt, locationEnt);

			ModelGroupReader modelGroupR = delegator.getModelGroupReader();
			ResourceHandler rhEntityGroup =
                                    new ResourceHandler(
                                        EntityConfigUtil.ENTITY_ENGINE_XML_FILENAME,
                                        loaderGrp,
                                        locationGrp) {
                @Override
                public InputStream getStream() throws GenericConfigException {
                    if(!locationGrp.startsWith("/")) {
                        return super.getStream();
                    }
                    try {
                        return ResourceUtils.getAsInputStream(locationGrp,
                                                              callerClazz);
                    } catch(Exception e) {
                        throw new GenericConfigException("I/O error", e);
                    }
                }
            };
			addGroupsToGroupCache(modelGroupR, rhEntityGroup);

			initializeHelpersAndDatasourceCheck();

		} catch (Exception ex) {
			String msg  = "GOT exception ";
			LOG.error(msg, ex);
			throw new OfbizDataException(msg, ex);
		}
	}

	/**
	 * Add the associations "entity name"-"group name" from the xml file
	 * represented by the ResourceHandler rh to the group cache from the model
	 * reader modelGroupR. The code is a part from getGroupCache() method from
	 * ModelGroupReader class.
	 * 
	 * @param modelGroupReader The model group reader.
	 * @param resourceHandler The resource handler for entity groups associations.
	 * @throws OfbizDataException if an error occurs when adding groups to
	 * group cache
	 */
	private void addGroupsToGroupCache(ModelGroupReader modelGroupReader,
		                               ResourceHandler resourceHandler)
                                                    throws OfbizDataException {
		if(modelGroupReader == null || resourceHandler == null) {
			LOG.warn(String.format("Null timereport group reader or resource " +
					               "handler in addGroupsToGroupCache() method." +
					               " Model reader: %s resource handler: %s",
					               modelGroupReader, resourceHandler));
			return;
		}
		try {
			Map&lt;?, ?&gt; groupCache =  modelGroupReader.getGroupCache();
            Document document;

			synchronized (ModelGroupReader.class) {

				try {
					document = resourceHandler.getDocument();
				} catch (GenericConfigException e) {
					String msg = "Error loading entity group timereport";
					LOG.error(msg, e);
					throw new OfbizDataException(msg, e);
				}
				if (document == null) {
					String msg = String.format("Could not get document for %s",
                                               resourceHandler);
					LOG.error(msg);
					throw new OfbizDataException(msg);
				}

				Element docElement = document.getDocumentElement();

				if (docElement == null) {
                    String msg = "NULL doc element.";
					LOG.error(msg);
					throw new OfbizDataException(msg);
				}
				docElement.normalize();
				Node curChild = docElement.getFirstChild();

				if (curChild != null) {
					do {
						if (curChild.getNodeType() == Node.ELEMENT_NODE &amp;&amp;
							"entity-group".equals(curChild.getNodeName())) {
							Element curEntity = (Element) curChild;
							String entityName =
                                    UtilXml.checkEmpty(curEntity.getAttribute("entity"));
							String groupName =
                                    UtilXml.checkEmpty(curEntity.getAttribute("group"));

							if(groupName == null ||
							   entityName == null) {
							    continue;
                            }
                            safelyMapAdd(groupCache, entityName, groupName);
						}
					} while ((curChild = curChild.getNextSibling()) != null);
				} else {
					LOG.warn("[addGroupsToGroupCache()] No child nodes found.");
				}
			}
		} catch (Exception ex) {
			String msg = String.format("Got exception when adding groups " +
					"from resource handler: %s", resourceHandler);
			LOG.error(msg, ex);
			throw new OfbizDataException(msg, ex);
		}
	}

	/**
	 * Add the model entities from the xml file represented by the
	 * ResourceHandler rhEntity to the entity cache from the model reader
	 * modelR. The code is(with little changes) a part from getEntityCache()
	 * method in ModelReader class.
	 * 
	 * @param modelReader The model reader.
	 * @param resourceHandler The resource handler.
	 * @param loaderEnt The loader name for the xml for entities.
	 * @param locationEnt The location for the xml file for entities.
	 * @throws OfbizDataException if an error occurs when adding entities to
	 * entity cache
	 */
	private void addEntitiesToEntityCache(ModelReader modelReader,
		                                  ResourceHandler resourceHandler,
                                          String loaderEnt,
                                          String locationEnt) throws OfbizDataException {
		if (modelReader == null || resourceHandler == null) {
			LOG.warn(String.format("Null timereport reader or resource handler" +
					               " in addEntitiesToEntityCache() method. Model" +
                                   " reader: %s Resource handler: %s",
                                   modelReader, resourceHandler));
			return;
		}
		try {
            Document document;
			Map&lt;String, ModelEntity&gt; entityCache = modelReader.getEntityCache();
            List&lt;ModelViewEntity&gt; tempViewEntityList
                                            = new LinkedList&lt;ModelViewEntity&gt;();
            Hashtable&lt;String, String&gt; docElementValues
                                            = new Hashtable&lt;String, String&gt;();

			synchronized (ModelReader.class) {
				try {
					document = resourceHandler.getDocument();
				} catch (GenericConfigException e) {
                    String msg = "Error getting document from resource handler";
                    LOG.error(msg);
					throw new GenericEntityConfException(msg, e);
				}
				if(document == null) {
					String msg = String.format("Could not get document for %s",
                                               resourceHandler);
					LOG.error(msg);
					throw new OfbizDataException(msg);
				}

				Element docElement = document.getDocumentElement();

				if (docElement == null) {
					String msg = "NULL doc element.";
					LOG.error(msg);
					throw new OfbizDataException(msg);
				}
				docElement.normalize();
				Node curChild = docElement.getFirstChild();

				int i = 0;

				if (curChild != null) {
					do {
						boolean isEntity = "entity".equals(curChild.getNodeName());
						boolean isViewEntity = "view-entity".equals(curChild.getNodeName());

						if ((isEntity || isViewEntity) &amp;&amp;
							curChild.getNodeType() == Node.ELEMENT_NODE) {
							i++;
							Element curEntity = (Element) curChild;
							String entityName =
								UtilXml.checkEmpty(curEntity.getAttribute("entity-name"));

							// check to see if entity with same name has already
							// been read
							if (entityCache.containsKey(entityName)) {
                                if(LOG.isDebugEnabled()) {
                                    LOG.debug(String.format(
                                        "Entity %s is defined more than once, most " +
                                        "recent will overwrite previous definition",
                                        entityName));
                                }
							}

							// add entity to entityResourceHandlerMap map
							modelReader.addEntityToResourceHandler(entityName,
								                                   loaderEnt,
                                                                   locationEnt);

							ModelEntity entity;

							if (isEntity) {
								entity = createModelEntity(modelReader,
                                                           curEntity,
										                   docElement,
                                                           null,
                                                           docElementValues);
                                if(LOG.isDebugEnabled()){
                                    LOG.debug(String.format("[Entity]: # %d: %s",
                                                            i, entityName));
                                }
							} else {
								entity = createModelViewEntity(modelReader,
                                                               curEntity,
										                       docElement,
                                                               null,
                                                               docElementValues);
								// put the view entity in a list to get ready
								// for the second pass to populate fields...
								tempViewEntityList.add((ModelViewEntity)entity);
                                if(LOG.isDebugEnabled()){
                                    String msg = String.format("[ViewEntity]: # %d: %s",
                                                               i, entityName);
                                    LOG.debug(msg);
                                }
							}

							if (entity != null) {
                                safelyMapAdd(entityCache, entityName, entity);
							} else {
								LOG.warn(String.format("Could not create entity " +
                                                       "for entityName: %s",
										               entityName));
							}
						}
					} while ((curChild = curChild.getNextSibling()) != null);
				} else {
					LOG.warn("No child nodes found.");
				}

				modelReader.rebuildResourceHandlerEntities();

				// do a pass on all of the view entities now that all of the
				// entities have loaded and populate the fields
                for (ModelViewEntity me : tempViewEntityList) {
                    me.populateFields(entityCache);
                }
				LOG.debug(String.format("FINISHED LOADING ENTITIES."));
			}
		} catch (Exception ex) {
			String msg = String.format("Got exception when adding entities " +
					"from resource handler: %s", resourceHandler);
			LOG.error(msg, ex);
			throw new OfbizDataException(msg, ex);
		}
	}

    @SuppressWarnings("unchecked")
    private void safelyMapAdd(Map map, String name, Object entity) {
        map.put(name, entity);
    }

    /**
	 * For the delegator instance, initialize helpers by group and do the data
	 * source check. The code is a part from GenericDelegator(String
	 * delegatorName) constructor.
	 * @throws OfbizDataException if an error occurs
	 */
	private void initializeHelpersAndDatasourceCheck()
		throws OfbizDataException {
		GenericDelegator delegator =
			GenericDelegator.getGenericDelegator(delegatorName);
		if (delegator == null) {
			LOG.warn("Null delegator in initializeHelpersAndDatasourceCheck().");
			return;
		}
		// initialize helpers by group
		Iterator&lt;?&gt; groups =
			UtilMisc.toIterator(delegator.getModelGroupReader().getGroupNames());

		while (groups != null &amp;&amp;
			groups.hasNext()) {
			String groupName = (String)groups.next();
			String helperName = delegator.getGroupHelperName(groupName);
            if(LOG.isDebugEnabled()) {
                LOG.debug(String.format("Delegator %s initializing helper %s " +
                                        "for entity group %s ",
                                        delegator.getDelegatorName(),
                                        helperName,
                                        groupName));
            }
			TreeSet&lt;String&gt; helpersDone = new TreeSet&lt;String&gt;();

			if (helperName != null &amp;&amp;
				helperName.length() &gt; 0) {
				// make sure each helper is only loaded once
				if (helpersDone.contains(helperName)) {
                    if(LOG.isDebugEnabled()) {
					    LOG.debug(String.format("Helper %s already initialized," +
						                        " not re-initializing.",
                                                helperName));
                    }
					continue;
				}
				helpersDone.add(helperName);
				// pre-load field type defs, the return value is ignored
				ModelFieldTypeReader.getModelFieldTypeReader(helperName);
				// get the helper and if configured, do the datasource check
				GenericHelper helper =
					GenericHelperFactory.getHelper(helperName);

				try {
					helper.checkDataSource(delegator
						.getModelEntityMapByGroup(groupName), null, true);
				} catch (GenericEntityException e) {
					LOG.warn(e);
				}
			}
		}
	}


	private ModelEntity createModelEntity(ModelReader modelR,
		                                  Element entityElement,
                                          Element docElement,
                                          UtilTimer utilTimer,
		                                  Hashtable&lt;String, String&gt; docElementValues) {
		if (entityElement == null) {
			return null;
        }

		return new ModelEntity(modelR, entityElement,
                               docElement, utilTimer,
				               docElementValues);
	}

	
	private ModelViewEntity createModelViewEntity(ModelReader modelR,
                                                  Element entityElement,
                                                  Element docElement,
                                                  UtilTimer utilTimer,
                                                  Hashtable&lt;String, String&gt; docElementValues) {
		if (entityElement == null) {
			return null;
        }
		return new ModelViewEntity(modelR, entityElement,
                                   docElement, utilTimer,
				                   docElementValues);
	}

}

Radu Dumitriu
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 21, 2012

To make it clear: entitymodel and entitygroup XMLs are resources of the plugin.

However, I forgot to add the GenericDelegatorLoader code. Here it is, see above.

Michael Danielsson
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 21, 2012

According to the book you shall not have to do anything more than adding stuff in the xml files and restart JIRA. The table shall automatically be created.

Has this changed since the book was written or is some information missing?

Radu Dumitriu
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 21, 2012

Using this, you can add info dynamically, in your plugin. Modifying the original xml is bad :)

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

TAGS
AUG Leaders

Atlassian Community Events